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

Dev*_*red 2 service android memory-leaks

我有一个应用Service程序使用该Messenger接口与远程进程通信.以下是设置方式的基本架构:

  • 应用程序生成几个需要访问服务的"操作"对象.
  • 每个"操作"包含一个用于从中接收响应数据的Handler包装MessengerService
  • 当操作执行时,它将其包装MessengerIntent并调用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);
}
Run Code Online (Sandbox Code Playgroud)

以及如何构建服务的片段(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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

MAT跟踪示例

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

提前致谢!

Dev*_*red 11

感谢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