信使到远程服务导致内存泄漏

时间:2012-08-30 20:11:51

标签: android service memory-leaks

我有一个应用程序使用Service接口与远程进程中的Messenger进行通信。以下是设置内容的基本架构:

  • 应用程序生成多个需要访问服务的“操作”对象。
  • 每个“操作”都包含一个Handler,其中包含Messenger,用于从Service
  • 接收回复数据
  • 当操作执行时,它将Messenger包装成Intent并调用startService()将消息传递给远程服务
  • 远程服务根据Intent的参数执行某些操作,然后通过向Message发送Messenger来返回响应。

以下是操作中的基本代码:

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);
}

这是一个如何构建服务的片段(它基本上是一个IntentService,在队列为空时不会关闭):

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);
    }
}

这一切都非常有效。我可以将来自多个不同应用程序的大量操作发送到同一个服务,他们都会处理并将响应发送到正确的位置。然而...

我注意到如果应用程序运行得足够长并且活动足够,那么它会因OutOfMemoryError而崩溃。在MAT中查看HPROF数据时,我注意到所有这些操作都停留在内存中,并且由于Messenger而被垃圾收集器中的人质挟持。显然,Messenger实例正在创建一个与Binder的长期本机连接,它被视为GC Root,它将每个“Operation”对象无限期地保存在内存中。

MAT Trace Example

有没有人知道当“操作”结束时是否有办法清除或禁用Messenger所以它不会造成内存泄漏?是否有另一种方法可以以相同的方式将IPC实现到Service,以便多个不同的对象可以发出请求并异步获得结果?

提前致谢!

2 个答案:

答案 0 :(得分:10)

感谢Dianne Hackborn在Android团队中提供的一些非常有用的见解,问题是因为远程服务进程还没有Garbage收集它的Messenger实例,实际上,它将应用程序进程中的实例保留为人质,直到时间。

这是她的回复:

  

确实,跨进程发送信使需要在其上持有GREF,以便其他进程与之通信。除了错误(已发生但我不确定是否在任何已发布的平台版本中),当其他进程本身不再对此进行引用时,GREF将被释放。当我们在Dalvik谈论事情时“不再持有引用”通常意味着“另一方有垃圾收集了Java代理对象。”

     

这意味着当您将Messenger(或任何IBinder对象)转移到另一个进程时,您自己进程中的Dalvik VM无法再管理该对象本身的内存,并且依赖于释放它的所有远程对象直到它可以在本地发布。这将包括IBinder所引用的所有对象。

     

处理此问题的常见模式是在IBinder / Messenger中使用WeakReference,其中包含对将要访问的其他对象的引用。这允许您的本地垃圾收集器清理所有其他对象(可能非常重,包含诸如位图之类的大事件),即使远程进程仍然在您的IBinder上有引用。当然,如果你这样做,除了不再需要之外,还需要有其他东西对这些其他对象进行引用,否则垃圾收集器可以在之前清除它们,之后不再需要它们。

     

我建议的其他方法是不进行为每个IPC实例化Messenger对象的设计。创建一个传递给每个IPC调用的Messenger。否则,由于其他进程继续保持引用,您可以生成大量远程保留的远程对象,因为另一方没有积极地进行垃圾回收,因为由于这些调用而创建的所有对象都很小。

更多信息: https://groups.google.com/d/msg/android-developers/aK2o1W2xrMU/Z0-QujnU3wUJ

答案 1 :(得分:0)

我不确定这是否是最佳方式,因为即使Activity在后​​台,您也会从message获得Service

我认为您应该绑定到service并在连接服务后立即向服务注册messenger。然后在断开连接时取消注册messenger

检查AOSP中的ExportVcardActivity。它遵循这些方针。