28

In my Android application, I have a simple list view with adapter. There's a heavy query which is to fill the list view with data. So I put it to an IntentService that runs in another thread.

The IntentService is normally running separately, on its own, just to query some data and insert it into the SQLite database.

But now I would like to have the following possibility:

  1. The activity starts the IntentService with startService().
  2. The IntentService does its heavy work.
  3. When the IntentService is finished, it should inform the activity about the result so that the activity can be refreshed to show the new data.

Is this possible? I read a lot of questions here on Stack Overflow on this topic. But in every question, there was another solution. So I want to ask you all: Which solution is the best for my purpose?

  • Binding the IntentService to the Activity does not seem to be the best solution as there might be conflicts with configuration changes of the activity etc. Correct?
  • This blog post suggests using AIDL with Parcelables - which sounds very complex to me. There is an easier way, isn't it?
  • One could set up a broadcast receiver in the activity and fire this broadcast in the IntentService when it is finished.
  • Some people say you should use createPendingResult() to pass a PendingIntent to the IntentService. If the IntentService finds that PendingIntent in its extras, it uses this to trigger off onActivityResult() in the Activity. Is this the way to choose?
Community
  • 1
  • 1
caw
  • 30,999
  • 61
  • 181
  • 291

5 Answers5

24

As an example, I use a ResultReceiver to call notifyDataSetChanged() on the adapter of my Activity (which extends ListActivity). It can be adapted to do whatever you need.

ResultReceiver code:

public class MyResultReceiver extends ResultReceiver {

    private Context context = null;

    protected void setParentContext (Context context) {
        this.context = context;
    }

    public MyResultReceiver(Handler handler) {
        super(handler);
    }

    @Override
    protected void onReceiveResult (int resultCode, Bundle resultData) {

        // Code to process resultData here

        ((BaseAdapter) ((ListActivity)context).getListAdapter()).notifyDataSetChanged();
    }
}

MyActivity code:

public class MyActivity extends ListActivity {

    private MyResultReceiver theReceiver = null;

    ...

    private void callService () {
        theReceiver = new MyResultReceiver(new Handler());
        theReceiver.setParentContext(this);
        Intent i = new Intent("com.mycompany.ACTION_DO_SOMETHING");

        // Code to define and initialize myData here

        i.putExtra("someData", myData);
        i.putExtra("resReceiver", theReceiver);
        startService(i);

    }
}

IntentService code:

Bundle resultBundle = new Bundle();
ResultReceiver resRec = intent.getParcelableExtra("resReceiver");

// Do some work then put some stuff in resultBundle here

resRec.send(12345, resultBundle);
Squonk
  • 48,735
  • 19
  • 103
  • 135
  • Thank you very much for this solution and the detailed description! Do I have to register the ResultReceiver or the custom Intent in the Android manifest file? And can I put stuff like an ArrayList or a Cursor into the result Bundle? Do I need parcelables there? – caw Feb 01 '12 at 09:43
  • The ResultReceiver doesn't need to be registered in the manifest but I do have the custom Intent registered for my purposes. You can call the IntentService in any way you want as long as there is an Intent to pass the ResultReceiver. As for resultBundle you can put anything in there that a Bundle can handle. – Squonk Feb 01 '12 at 10:22
  • It is interesting that two similar threads both got the accepted solution of using ResultReceiver, yet the official way seems to be LocalBroadcastManager. I think the reason that LocalBroadcastManager is preferrable because the ResultReceiver may be destroyed if the activity gets destroyed and then restarted? – Gordon Liang Dec 23 '15 at 02:53
  • @GordonLiang : If you look at the date of this answer it was nearly 4 years ago and I'm fairly sure `LocalBroadcastManager` wasn't available then. Bearing in mind it is part of the v4 support library (which has had additions over the years) I'm not even sure if that library was available or, if it was, if it included that class. To be honest I never liked `ResultReceiver` for the reason you state. Another alternative which I used was 'sticky' broadcasts with standard `BroadcastReceiver` but `LocalBroadcastManager` just does a similar thing locally and 'sticky' broadcasts are deprecated now. – Squonk Dec 23 '15 at 15:59
  • @GordonLiang : OK, A bit of research shows Rev 1 of the v4 support library was released in March 2011 with `LocalBroadcastReceiver` being introduced in Rev 4 in October 2011 (so ~4 months before this question / answer). I'd been developing my main app (which would have used `ResultReceiver`) since before Rev 1 of the library so wouldn't have had `LocalBroadcastManager` available to me. Also, back then the documentation for the dev pages was sketchy to say the least and we all basically just had to use whatever we came across that worked - there were no 'official recommendations' back then. – Squonk Dec 23 '15 at 16:16
  • @ Squonk Thanks for pointing this out. I am pretty new to Android, so it is good to know. The SDK evolves and becoming better, that's how Android grows. – Gordon Liang Dec 23 '15 at 16:33
14

When the IntentService completes, it should use LocalBroadcastManager to send an intent to any registered activity.

The IntentService will contain code like this:

private void sendBroadcast() {
    Intent intent = new Intent("myBroadcastIntent");
    intent.putExtra("someName", someValue);
    LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
}

The activity receiving the notification will contain code like this:

@Override
protected void onCreate(Bundle savedInstanceState) {
    // ...
    BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String someValue = intent.getStringExtra("someName");
            // ... do something ...
        }
    };
    LocalBroadcastManager.getInstance(this)
        .registerReceiver(receiver, new IntentFilter("myBroadcastIntent"));
}

For more depth, see the blog post Using LocalBroadcastManager In Service To Activity Communications.

Edward Brey
  • 40,302
  • 20
  • 199
  • 253
14

None of the other answers references the official android documentation

https://developer.android.com/training/run-background-service/report-status.html

that states clearly that for the Activity-IntentService communication "The recommended way to send and receive status is to use a LocalBroadcastManager, which limits broadcast Intent objects to components in your own app"!

DSoldo
  • 959
  • 10
  • 19
3

I think the event bus is the way to go. Simple and effective interprocess communication.

Eugene
  • 59,186
  • 91
  • 226
  • 333
3

I would suggest using a Broadcast Receiver in the The Activity waiting for the result. Your Service would just use sendBroadcast with a custom Intent.

rui.araujo
  • 9,597
  • 1
  • 18
  • 24
  • Thank you! But isn't this also a bit overdone as the broadcast is available system-wide (for all applications)? – caw Jan 31 '12 at 23:44
  • I think that there are ways of not broadcasting for the entire system. And if you wanted to run the service in a different process it would be a simple way of communication. – rui.araujo Feb 04 '12 at 14:34
  • 2
    [LocalBroadcastManager](http://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html) is the object that you need to make a broadcast inside of your app. – AXSM Mar 07 '13 at 16:11