15

Despite similar question was asked, I have differnet situation: My app consists mostly of a background Service. I want to start external activities and get results back.

I see several options:

  1. Create dummy Activity and keep reference to it for using its startActivityForResult. This consumes quite a lot of memory, as we know.

  2. Use Broadcast Intents instead of Android's results infrastructure: ask client activities to broadcast their results before closing. This kind of breaks the idea and not so performance-efficient.

  3. Use Instrumentation directly - try to copy code from startActivityForResult into my Service.

  4. Use Service interfaces - serialize and add AIDL connection to the Intent for starting an Activity. In this case Activity should call Service directly instead of providing result.

The third approach feels closer to Android for me, but I'm not sure if it's possible to do - Service does not have its Instrumentation, and default implementation seems to always return null.

Maybe you have any other ideas?

Community
  • 1
  • 1
Alexander Kosenkov
  • 1,597
  • 1
  • 10
  • 21
  • It can be achieved with a simple hack , by the use of SharedPreferences ,[SO](http://stackoverflow.com/a/31461941/4859873) – Sanjeev Jul 16 '15 at 18:46

2 Answers2

22

I’ve been thinking about this recently when implementing account authenticators with three-legged authorisation flows. Sending a result back to the service for processing performs better than processing it in the activity. It also provides a better separation of concerns.

It’s not that clearly documented, but Android provides an easy way to send and receive results anywhere (including services) with ResultReceiver.

I’ve found it to be a lot cleaner than passing activities around, since that always comes with the risk of leaking those activities. Additionally, calling concrete methods is less flexible.

To use ResultReceiver in a service, you’ll need to subclass it and provide a way to process the received result, usually in an inner class:

public class SomeService extends Service {

    /**
     * Code for a successful result, mirrors {@link Activity.RESULT_OK}.
     */
    public static final int RESULT_OK = -1;

    /**
     * Key used in the intent extras for the result receiver.
     */
    public static final String KEY_RECEIVER = "KEY_RECEIVER";

    /**
     * Key used in the result bundle for the message.
     */
    public static final String KEY_MESSAGE = "KEY_MESSAGE";

    // ...

    /**
     * Used by an activity to send a result back to our service.
     */
    class MessageReceiver extends ResultReceiver {

        public MessageReceiver() {
            // Pass in a handler or null if you don't care about the thread
            // on which your code is executed.
            super(null);
        }

        /**
         * Called when there's a result available.
         */
        @Override
        protected void onReceiveResult(int resultCode, Bundle resultData) {
            // Define and handle your own result codes
            if (resultCode != RESULT_OK) {
                return;
            }

            // Let's assume that a successful result includes a message.
            String message = resultData.getString(KEY_MESSAGE);

            // Now you can do something with it.
        }

    }

}

When you start an activity in the service, create a result receiver and pack it into the intent extras:

/**
 * Starts an activity for retrieving a message.
 */
private void startMessageActivity() {
    Intent intent = new Intent(this, MessageActivity.class);

    // Pack the parcelable receiver into the intent extras so the
    // activity can access it.
    intent.putExtra(KEY_RECEIVER, new MessageReceiver());

    startActivity(intent);
}

And finally, in the activity, unpack the receiver and use ResultReceiver#send(int, Bundle) to send a result back.

You can send a result at any time, but here I've chosen to do it before finishing:

public class MessageActivity extends Activity {

    // ...

    @Override
    public void finish() {
        // Unpack the receiver.
        ResultReceiver receiver =
                getIntent().getParcelableExtra(SomeService.KEY_RECEIVER);

        Bundle resultData = new Bundle();

        resultData.putString(SomeService.KEY_MESSAGE, "Hello world!");

        receiver.send(SomeService.RESULT_OK, resultData);

        super.finish();
    }

}
Leo Nikkilä
  • 1,547
  • 18
  • 29
  • thanks for the solution! one thing: RESULT_OK should be **-1** according to Activity.java – Philipp Mar 31 '16 at 21:30
  • Good point. I think in this case it’s possible to use any values, but it’s better to stay consistent with whatever the platform provides. I’ve updated the answer. You could use Activity.RESULT_OK directly as well. – Leo Nikkilä Apr 02 '16 at 21:10
  • you need to add `@SuppressLint("ParcelCreator")` before the class `MessageReceiver` otherwise it will ask you to make a CREATOR as [ResultReceiver](https://developer.android.com/reference/android/os/ResultReceiver.html) implements Parcelable. Thanks for the answer. – ArJ Jul 27 '16 at 08:24
4

I think option 2 is the most idiomatic way on android. Using startActivityForResult from an Activity is a synchronous/blocking call, i.e., the parent activity waits and does not do anything until the child is done. When working from a Service and interacting with activities your primarily doing asynchronous/non-blocking calls, i.e., the service calls out for some work to be done and then waits for a signal to tell it that it can continue.

If you are using the android local service pattern then you can have your activities acquire a reference of the Service and then call a specific function after it has performed its work. Attempting your option 3 would be counter to what the framework provides for you.

Leo Nikkilä
  • 1,547
  • 18
  • 29
Rich Schuler
  • 41,814
  • 6
  • 72
  • 59
  • 1
    Thanks for your thoughts! Now I'm choosing between 2 (easier to implement) and 4 (more secure/private and should be faster). I don't really agree that startActivityForResult is blocking (because it uses callback function, not result value), and also Instrumentation is in public API =) Thanks! – Alexander Kosenkov Jul 14 '10 at 21:16
  • 1
    I meant it's not blocking in the traditional sense (e.g., a blocking io call). It's blocking in the conceptual manner in which you use it. – Rich Schuler Jul 14 '10 at 21:53
  • @Qberticus The link you provided just links to a generic samples page. – IgorGanapolsky Aug 29 '14 at 14:58