可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I'm currently developing an Android application that has the following needs:
A worker thread started in a Service. This thread does some processing and needs to be invoked from the main Activity and provide some asynchronous answers to the same Activity.
Invoking the Service from the Activity is easy (IBinder stuff)
My question is now about the proper implementation of the service callback.
I was first going to add an android.os.Handler in the Activity and handle the thread's anwers in MyActivity.handleMessage(Message) but this requires that I give this handler's reference to the service. So what happens when the Android OS decides to destroy/recreate my Activity due to an orientation change for example ? Does my activity stay alive as it is referenced (indirectly) in the service ? If the Activity destroyed/rebuilt anyway, what happens to my Handler reference in the Service ?
I guess I'm not using the right method to callback an Activity from a Service thread, so I wanted to know if someone can point me the correct way of doing.
TIA
回答1:
I prefer using LocalBroadcastManager
Here's an example for the code in your Activity
:
BroadcastReceiver localBroadcastReceiver = new BroadcastReceiver()
{
@Override
public void onReceive(Context context, Intent intent)
{
Log.d("BroadcastReceiver", "Message received " + intent.getAction());
Log.d("BroadcaseReceiver", "Received data " + intent.getStringExtra("com.my.package.intent.EXTRA_DATA"));
}
};
@Override
protected void onStart()
{
super.onStart();
final LocalBroadcastManager localBroadcastManager =
LocalBroadcastManager.getInstance(this);
final IntentFilter localFilter = new IntentFilter();
localFilter.addAction("com.my.package.intent.ACTION_NAME_HERE");
localBroadcastManager.registerReceiver(localBroadcastReceiver, localFilter);
}
@Override
protected void onStop()
{
super.onStop();
final LocalBroadcastManager localBroadcastManager =
LocalBroadcastManager.getInstance(this);
// Make sure to unregister!!
localBroadcastManager.unregisterReceiver(localBroadcastReceiver);
}
Anywhere else in your codebase (such as in the completion of your background thread):
final LocalBroadcastManager localBroadcastManager =
LocalBroadcastManager.getInstance(context);
final Intent intent = new Intent("com.my.package.intent.ACTION_NAME_HERE")
intent.putExtra("com.my.package.intent.EXTRA_DATA", yourBackgroundData);
localBroadcastManager.sendBroadcast(intent);
You can, of course, use intent.putExtra
to add any additional data or use multiple actions to differentiate broadcast messages.
回答2:
We've done this by centralizing all communication between activities and service in the Application
class. We extend the Application
class and then have methods there that bind to the service and accept the callbacks. There is no direct connection between an Activity
and the Service
in this architecture.
The advantage of this mechanism is that you don't have to worry about unbinding/rebinding to the service during activity transitions and activity death and recreations. The Application
class manages all this and it isn't affected by what activities do. The Application
class receives all the callbacks, and you will need to have code there to determine what to do with the callbacks. Probably you will want to store some data in the Application
class and then notify the activities that there is new data available, or something similar.
Another approach is to have the Service
broadcast the callbacks. In this case the coupling between the service and the activities is loose, so you wouldn't need to create a Handler
in the activity and then pass it to the Service
. Activities can just register BroadcastReceiver
s for the callbacks they are interested in, or you can manage this in the manifest.
回答3:
One solution, as you say, is to use MyActivity.handleMessage(Message). Whenever your activity starts (or is restarted) you will probably attempt to start the service (the "onBind" stuff you mention). If the service is already running, no harm is done. Once the bind stuff is done you tell the service the Messenger to send replies to.
To ensure restarts are handled, in onStop you need to tell the service to remove that Messenger from it's "list of places to send replies to" so that it doesn't accidentally send a message to a now non-existent Messenger. When onStart is called as part of the restart, it will send the now-correct Messenger.
Obviously this requires that the service handle this and somehow manage the scenario in which it has a response to send while there is no Messenger to send it to. Either it holds to information until such time as a Messenger is available or it drops the information, and the Activity gets all the state information explicitly as a follow on to the bind stuff it has done in onStart.
Another approach is to have the Activity poll the Service every so often (10 seconds?) when it knows that there is processing going on to see if the results are available, and then to stop polling once all the information is back.
回答4:
For asynchronous service calls, one would pass a callback reference upon initiating it, I'd say.
The callback is going to be executed by the binder thread, which is standard for this approach.
Of course, the Activity which initiates the call can rely on the fact that the binder thread runs in its own process. So it's pretty easy to get back to the main/UI thread from the callback.
Hence, if the callback needs to process something in the main/UI thread, it just uses
(new Handler(Looper.getMainLooper()).post()
which has the advantage that it finds the main/UI thread dynamically at the point in time when the code is executed. On the other hand, that's not even necessary because the main/UI thread does not change, so finding it via a View reference or anything else you may have at hand in the callback would also work.