I have an application that communicates with a Service
in a remote process using the Messenger
interface. Here is the basic architecture of how things are set up:
- The application generates several "Operation" objects that require access to the service.
- Each "Operation" contains a
Handler
wrapped in aMessenger
used to receive the response data back from theService
- When the operation executes, it wraps its
Messenger
into anIntent
and callsstartService()
to pass the message to the remote service - The remote service does some work based on the parameters of the
Intent
and then returns the response by sending aMessage
to theMessenger
for that operation.
Here is the basic code present in the operation:
public class SessionOperation {
/* ... */
public void runOperation() {
Intent serviceIntent = new Intent(SERVICE_ACTION);
/* Add some other extras specific to each operation */
serviceIntent.putExtra(Intent.EXTRA_EMAIL, replyMessenger);
context.startService(serviceIntent);
}
private Handler mAckHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//Process the service's response
}
};
protected Messenger replyMessenger = new Messenger(mAckHandler);
}
And a snippet of how the Service is structured (it's basically an IntentService
that doesn't shut down when the queue is empty):
public class WorkService extends Service {
private ServiceHandler mServiceHandler;
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
}
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//If intent has a message, queue it up
Message msg = mServiceHandler.obtainMessage();
msg.obj = intent;
mServiceHandler.sendMessage(msg);
return START_STICKY;
}
private void onHandleIntent(Intent intent) {
Messenger replyTarget = intent.getParcelableExtra(Intent.EXTRA_EMAIL);
/* Do some work */
Message delivery = Message.obtain(...);
replyTarget.send(delivery);
}
}
This all works fantastically well. I can send tons of operations from several different applications to the same service and they all process and send their response to just the right place. However...
I noticed that if the application ran long enough and with enough activity it would crash with an OutOfMemoryError
. Upon looking at the HPROF data in MAT, I noticed that all these operations where staying in memory, and they were held hostage from the Garbage Collector because of the Messenger
. Apparently, the Messenger
instance is creating a long-term native connection to Binder that counts as a GC Root, which is keeping each "Operation" object in memory indefinitely.
Does anyone know if there is a way to clear or disable the Messenger
when the "Operation" is over so it doesn't create this memory leak? Is there perhaps another way to implement the IPC to the Service
in the same fashion, so that multiple disparate objects can make a request and get a result asynchronously?
Thanks in advance!
Thanks to some very helpful insight from Dianne Hackborn on the Android team, the issue is because the remote service process has not yet Garbage Collected it's instance of the Messenger which, in effect, held the instances in the application's process hostage until that time.
This is the text of her reply:
More Info: https://groups.google.com/d/msg/android-developers/aK2o1W2xrMU/Z0-QujnU3wUJ
I am not sure if this is the best way since, even if
Activity
is in the background you will get themessage
fromService
.I think you should bind to the
service
and register amessenger
to the service as soon as Service is connected. And then unregister themessenger
when you disconnect.Check ExportVcardActivity in AOSP. It is following something along these lines.