0

I have a service listening for UDP packets that is bound to my MainActivity (which is the only activity in the app). The service runs on its own thread and I can see the UDP messages as well as the parsed messages in logcat. I created a setParsedMessage() and a public getParsedMessage() in order to get the parsed string and send it to my main activity in order to change a TextView and an ImageView depending on what the parsed message is, however it does not appear to be retrieving the String for some reason. I read about this method on the Developer.Android website, however I've also seen something about using Handler to do this instead. Here is my code:

MainActivity:

public class MainActivity extends Activity {

AlertAssignments mAlertAssignments;



Button startListeningButton;

boolean started;
int counter;
boolean mBound = false;
Context context;
ListenerService mListenerService;

TextView mTextView;
TextView mBlinkView;
ImageView mImageView;


private StartListening _StartListeningTask;
String messageFromService = "";




@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    //start listener service
    Intent listenerServiceIntent = new Intent(MainActivity.this, ListenerService.class);
    this.bindService(listenerServiceIntent, mConnection, Context.BIND_AUTO_CREATE);

    mImageView = (ImageView) findViewById(R.id.image_view);
    mTextView = (TextView) findViewById(R.id.alert_text);
    mBlinkView = (TextView) findViewById(R.id.blinking_text);
    Animation mAnimation = new AlphaAnimation(0.0f, 1.0f);
    mAnimation.setDuration(50);
    mAnimation.setStartOffset(20);
    mAnimation.setRepeatCount(Animation.INFINITE);
    mAnimation.setRepeatMode(Animation.REVERSE);
    mBlinkView.startAnimation(mAnimation); //animation value
    mAlertAssignments = new AlertAssignments();

}
private ServiceConnection mConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName className, IBinder service) {
        ListenerService.LocalBinder binder = (ListenerService.LocalBinder) service;
        mListenerService = binder.getService();
        mBound = true;
        if(mBound) {
            Log.e("UDP", "Service has been bound successfully");
        }
        else {
            Log.e("UDP", "Service has not been bound");
        }

        readFromService();
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        mBound = false;
    }
};

@Override
protected void onStart() {
    super.onStart();

}


@Override
protected void onStop() {
    super.onStop();
    //unbind from service
    if(mBound) {
        this.unbindService(mConnection);
        mBound = false;
    }

}

private void readFromService() {
    try {
        Integer parsedMessage = Integer.valueOf(mListenerService.getParsedMessage());
        mImageView.setImageResource(mAlertAssignments.alarmImages[parsedMessage]);

        if(parsedMessage >= 10 && parsedMessage <= 19 && parsedMessage != 0) {
            mTextView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
        } else {
            mBlinkView.setText(mAlertAssignments.alertTextMessages[parsedMessage]);
        }
    } catch(NumberFormatException e) {
        e.printStackTrace();
    }

}


}

I had read that using the public getter like this:
Integer parsedMessage = Integer.valueOf(mListenerService.getParsedMessage());
would allow me to access the string value of mListenerService.getParsedMessage, however I'm guessing that may only work for started services, not bound services.

AlertAssignments is a simple enumeration that uses ordinal arrays to bind images and Strings to values, so mImageView.setImageResource(mAlertAssignments.alarmImages[parsedMessage]) would set the ImageView to an image. Finally, here is the Service:

public class ListenerService extends Service{
public String the_alarm_S;
public String parsedMessage = "";
private final IBinder mBinder = new LocalBinder();


public class LocalBinder extends Binder {
    ListenerService getService() {
        return ListenerService.this;
    }
}

@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}

DatagramSocket socket;

Thread UDPBroadcastThread;

void startListenForUDPBroadcast() {
    UDPBroadcastThread = new Thread(new Runnable() {
        public void run() {
            try {
                while (shouldRestartSocketListen) {
                    try {
                        socket = new DatagramSocket(12001);
                        socket.setReuseAddress(true);
                        String message = "";

                        byte[] recvBuf = new byte[1024];
                        DatagramPacket packet = new DatagramPacket(recvBuf, 1024);
                        Log.e("UDP", "Waiting for UDP broadcast");
                        try {
                            socket.receive(packet);
                            Log.e("UDP", "Received Packet");
                        } catch (IOException e) {
                            e.printStackTrace();

                        }
                        message = new String(packet.getData());

                        Log.e("UDP", "Got UDB broadcast message: " + message);

                        setParsedMessage(message);

                        if(socket != null) {
                            socket.close();
                        }
                    } catch (SocketException e) {
                        e.printStackTrace();
                    }
                }
                //if (!shouldListenForUDPBroadcast) throw new ThreadDeath();
            } catch (Exception e) {
                Log.i("UDP", "no longer listening for UDP broadcasts cause of error " + e.getMessage());
            }
        }
    });
    UDPBroadcastThread.start();
}

private Boolean shouldRestartSocketListen = true;

private void setParsedMessage(String messageContents) {
    the_alarm_S = messageContents;
    String parseMessage[] = the_alarm_S.split("!!!");
    Log.e("UDP", "Parsed message with value " + parseMessage[1]);
    parsedMessage = parseMessage[1];

}
public String getParsedMessage() {
    return parsedMessage;
}

private void stopListen() {
    shouldRestartSocketListen = false;
    if(socket != null) {
        socket.close();
    }
}

@Override
public void onCreate() {
    startListenForUDPBroadcast();
}

@Override
public void onDestroy() {
    stopListen();
}


@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    shouldRestartSocketListen = true;
    startListenForUDPBroadcast();
    Log.i("UDP", "Service started");
    return START_STICKY;
}
}

Can someone give me the simplest method of getting the String from the service to the main activity, or if I already have it, where I am going wrong in using it? I would like to avoid having to rewrite my Service as an IntentService unless it's absolutely necessary to do so since this is a relatively simple object to pass to MainActivity

Thanks

Tadhg
  • 474
  • 7
  • 19

2 Answers2

1

You could try subscribing to the service. What I mean is pass some interface that the service calls to notify the activity about changes, here's an example I just tested:

A Subscriber interface

public interface ServiceSubscriber {
  void messageCallback(String message);
}

Subscribe to the service using the Subscriber

public class TestService extends Service {

    ArrayList<ServiceSubscriber> subscribers = new ArrayList<>();
    private TestBinder testBinder = new TestBinder();

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        new Thread(){
            @Override
            public void run() {
                while(true){
                    //this is where you are receiving UDP packets
                    doStuff();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        return super.onStartCommand(intent, flags, startId);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return testBinder;
    }

    private void doStuff() {
        System.out.println("Service is doing stuff!");
        //loop through your subscribers and notify them of your changes
        //a loop here isn't very costly, if there aren't many subscribers
        for (ServiceSubscriber subscriber : subscribers) {
            subscriber.messageCallback("I'm doing stuff");
        }

    }

    public class TestBinder extends Binder {

        public TestService getService() {
            return TestService.this;
        }
    }

    public void subscribeToMessages(ServiceSubscriber subscriber) {
        subscribers.add(subscriber);
    }


    public void unSubscribeToMessages(ServiceSubscriber subscriber) {
        subscribers.remove(subscriber);
    }
}

Now for the usual Binding Activity, where you define what you need to do with the Message Callback:

public class MainActivity extends AppCompatActivity {
    private TestService testService;
    private Subscriber subscriber;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onStart() {
        super.onStart();

        bindService(new Intent(this, TestService.class),serviceConnection, Context.BIND_AUTO_CREATE);
    }

    private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            testService = ((TestService.TestBinder)service).getService();
            subscriber = new ServiceSubscriber() {
                @Override
                public void messageCallback(String message) {
                    //I'm just printing out the message received 
                    //Be careful if you need to do UI stuff to use a 
                    //Handler
                    System.out.println(message);
                }
            }
            testService.subscribeToMessages(subscriber );
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };

}

Of course don't forget to unsubscribe on destroy.

Updating UI often doesn't break your app if you do it by using a handler

 //activity fields
    Handler handler



//in activity constructor
handler = new Handler();

//update UI by calling
handler.post(new Runnable(){
  @Override
  public void run(){ 
    //update the UI here
  }

EDIT: I forgot to keep a reference of the subscriber, to unsubscribe later. Changed from anonymous instance to a field.

vlatkozelka
  • 909
  • 1
  • 12
  • 27
0

Make below method to your sevice class:

private void sendMessage() {
   Intent intent = new Intent("message");
   intent.putExtra("message", your_message);
   LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

And put the below code in your activity class:

 @Override
public void onResume() {
  super.onResume();

  LocalBroadcastManager.getInstance(this)
                       .registerReceiver(mMessageReceiver,
                                         new IntentFilter("message"));
}

private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
  @Override
  public void onReceive(Context context, Intent intent) {
        String yourMessage = intent.getIntExtra("message",-1);
  }
};

@Override
protected void onPause() {
  LocalBroadcastManager.getInstance(this)
                       .unregisterReceiver(mMessageReceiver);
  super.onPause();
}

Note: -1 is for default value

huk
  • 220
  • 3
  • 11
  • That's why I asked in comments about how often he receives the message. A broadcast every 1/100 isn't going to work . (As far as I know) – vlatkozelka Jul 19 '17 at 18:16
  • Will updating the UI that often cause an issue or should I write a delta function that only changes the UI when the value of the message changes? – Tadhg Jul 19 '17 at 18:24
  • i guess you should use a bound service which enables the Activity to get a direct reference to the Service, thus allowing direct calls on it, rather than using Intents so when there is a message you can update the UI according to it. – huk Jul 19 '17 at 18:31
  • The service is bound to the activity in the `onCreate()` method, and I'm using a direct call to it with `Integer parsedMessage = Integer.valueOf(mListenerService.getParsedMessage());` but it doesnt seem to be updating the UI. I'll add a check to check and see if it's being passed to the activity but my guess is its value is `" "` – Tadhg Jul 19 '17 at 18:34