Paolo Rovelli
Paolo Rovelli

Reputation: 9694

Android Activity and Service communication: how to keep the UI up-to-date

I have an Activity A (not the main Activity) that launches a Service S that does some stuff in the background and, in the meanwhile, should made some changes to the UI of A.

Let's just say that S count from 0 to 100 and A should display this count in Real-Time. Since the real job of S is quite more complicated and CPU-consuming, I do not want to use AsyncTask for it (indeed "AsyncTasks should ideally be used for short operations (a few seconds at the most.) [...]") but just a regular Service started in a new Thread (an IntentService would be fine as well).

This is my Activity A:

public class A extends Activity {
    private static final String TAG = "Activity";
    private TextView countTextView;    // TextView that shows the number
    Button startButton;                // Button to start the count
    BResultReceiver resultReceiver;


    /**
     * Receive the result from the Service B.
     */
    class BResultReceiver extends ResultReceiver {
        public BResultReceiver(Handler handler) {
            super(handler);
        }

        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            switch ( resultCode )  {
                case B.RESULT_CODE_COUNT:
                        String curCount = resultData.getString(B.RESULT_KEY_COUNT);
                        Log.d(TAG, "ResultReceived: " + curCount + "\n");
                        runOnUiThread( new UpdateUI(curCount) );  // NOT WORKING AFTER onResume()!!!
                   break;
            }
        }
    }


    /**
     * Runnable class to update the UI.
     */
    class UpdateUI implements Runnable {
        String updateString;

        public UpdateUI(String updateString) {
            this.updateString = updateString;
        }

        public void run() {
            countTextView.setText(updateString);
        }
    }


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

        countTextView = (TextView) findViewById(R.id.countTextView);
        startButton = (Button) findViewById(R.id.startButton);

        resultReceiver = new BResultReceiver(null);
    }


    public void startCounting(View view) {
        startButton.setEnabled(false);

        //Start the B Service:
        Intent intent = new Intent(this, B.class);
        intent.putExtra("receiver", resultReceiver);
        startService(intent);
    }
}

And this is my Service B:

public class B extends Service {
    private static final String TAG = "Service";
    private Looper serviceLooper;
    private ServiceHandler serviceHandler;
    private ResultReceiver resultReceiver;
    private Integer count;

    static final int RESULT_CODE_COUNT = 100;
    static final String RESULT_KEY_COUNT = "Count";


    /**
     * Handler that receives messages from the thread.
     */
    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            while ( count < 100 ) {
                count++;
                //Sleep...
                sendMessageToActivity(RESULT_CODE_COUNT, RESULT_KEY_COUNT, count.toString());
            }

            //Stop the service (using the startId to avoid stopping the service in the middle of handling another job...):
            stopSelf(msg.arg1);
        }
    }


    @Override
    public void onCreate() {
        //Start up the thread running the service:
        HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND);
        thread.start();

        this.count = 0;

        //Get the HandlerThread's Looper and use it for our Handler
        serviceLooper = thread.getLooper();
        serviceHandler = new ServiceHandler(serviceLooper);
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        this.resultReceiver = intent.getParcelableExtra("receiver");

        //For each start request, send a message to start a job and deliver the start ID so we know which request we're stopping when we finish the job:
        Message msg = serviceHandler.obtainMessage();
        msg.arg1 = startId;
        serviceHandler.sendMessage(msg);

        //If we get killed, after returning from here, restart:
        return START_REDELIVER_INTENT;
    }


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


    /**
     * Send a message from to the activity.
     */
    protected void sendMessageToActivity(Integer code, String name, String text) {
        Bundle bundle = new Bundle();
        bundle.putString(name, text);

        //Send the message:
        resultReceiver.send(code, bundle);
    }
}

Everything works fine but if I click the back button (or the home button) and then I re-open the Activity A then the UI of A is not updated anymore (it just shows the initial configuration of A - i.e. the "startButton" is still clickable and the count is not showed - it seems runOnUiThread(...) is not working anymore). However, the Service B is still running in the background and the I can see the correct count is passed to the Activity A in the Log.d(...). Finally, if I click on the "startButton" again, the counting does not start from the beginning (0) but from where B has been arrived (I've double checked it by showing it in the notification bar).

How can I fix this behaviour? I would like that, when I re-open the Activity A, it automatically continues to receive and update the data from the Service B. Or, in other words, that the Service keeps the UI of the Activity A up-to-date.

Please give me some hints, links or piece of code. Thanks!

Upvotes: 3

Views: 3473

Answers (1)

Erik
Erik

Reputation: 5119

When you click back button your Activity is destroyed. When you start the Activity again you get a new Activity. That also happen when you rotate the device. This is Android lifecycle event

The Activity is not good for heavy Business Logic only to show stuff/control stuf. What you have to do is create a simple MVC, Model View Controller. The view (Activity) should only be used for showing results and controlling the eventflow.

The Service can hold an Array of the count and when your Activity start it will onBind() your Service that is running (or if not running will start the Service since you bind to it) Let the Activity(View) get the Array of results and show it. This simple setup exclude the (M)Model Business Logic.

Update
Following up a bit read this it's Android official docs and perfect start since it do kind of what you asking. As you see in the example in the onStart() the Activity establish a connection with the service and in the onStop() the connection is removed. There's no point having a connection after on onStop(). Just like you asking for. I would go with this setup and not let the Service continuously sending data because that would drain resources and the Activity is not always listening because it will stop when in the background.
Here's an activity that binds to LocalService and calls getRandomNumber() when a button is clicked:

Upvotes: 3

Related Questions