Thursday, August 30, 2012

[android-developers] Messenger from Remote Service Causing Memory Leak

I have an application that communicates with a Service in a remote process using the Messengerinterface. 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 a Messenger used as a callback receive the response data back from the Service
  • When the operation executes, it wraps its Messenger into an Intent and calls startService() 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 a Message to the Messenger 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 basic snippet of how the service is structured:

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 anOutOfMemoryError. 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 theMessenger. 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.

MAT Trace Example

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 a better way to implement the IPC to the Servicein the same fashion, so that multiple disparate objects can make a request and get a result asynchronously?  I'm not convinced that just switching to using a bound service implementation will solve this issue as I would will need the Messenger to process the callback.

--
You received this message because you are subscribed to the Google
Groups "Android Developers" group.
To post to this group, send email to android-developers@googlegroups.com
To unsubscribe from this group, send email to
android-developers+unsubscribe@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/android-developers?hl=en

No comments:

Post a Comment