Android Messenger到远程服务导致内存泄漏

Android Messenger到远程服务导致内存泄漏,android,service,memory-leaks,Android,Service,Memory Leaks,我有一个应用程序,它使用Messenger界面在远程进程中与服务通信。以下是如何设置事物的基本架构: 应用程序生成几个需要访问服务的“操作”对象 每个“操作”都包含一个处理程序包装在信使中,用于从服务 当操作执行时,它将其信使包装成意图,并调用startService()将消息传递给远程服务 远程服务根据意图的参数执行一些工作,然后通过向信使发送消息返回响应以进行该操作 以下是操作中的基本代码: public class SessionOperation { /* ... */

我有一个应用程序,它使用
Messenger
界面在远程进程中与
服务
通信。以下是如何设置事物的基本架构:

  • 应用程序生成几个需要访问服务的“操作”对象
  • 每个“操作”都包含一个
    处理程序
    包装在
    信使
    中,用于从
    服务
  • 当操作执行时,它将其
    信使
    包装成
    意图
    ,并调用
    startService()
    将消息传递给远程服务
  • 远程服务根据
    意图
    的参数执行一些工作,然后通过向
    信使发送
    消息
    返回响应以进行该操作
以下是操作中的基本代码:

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
,当队列为空时不会关闭):

这一切都非常有效。我可以将数吨操作从几个不同的应用程序发送到同一个服务,它们都会处理并将响应发送到正确的位置。然而

我注意到,如果应用程序运行的时间足够长,并且具有足够的活动,它将出现
OutOfMemoryError
崩溃。在查看MAT中的HPROF数据时,我注意到所有这些操作都保留在内存中,并且由于
Messenger
,它们被垃圾收集器劫持。显然,
Messenger
实例正在创建一个到Binder的长期本机连接,该连接将计为GC根,这将无限期地将每个“操作”对象保留在内存中

是否有人知道在“操作”结束时,是否有办法清除或禁用Messenger,以免造成内存泄漏?是否有其他方法可以以相同的方式将IPC实现到
服务
,以便多个不同的对象可以发出请求并异步获得结果


提前谢谢

我不确定这是否是最好的方式,因为即使
活动
在后台,您也会从
服务
收到
消息

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


签入AOSP。这是沿着这些思路进行的。

多亏了安卓团队Dianne Hackborn的一些非常有用的见解,问题在于远程服务进程尚未对Messenger的实例进行垃圾收集,实际上,Messenger在此之前一直将应用程序进程中的实例作为人质

以下是她的答复:

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

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

处理这个问题的一种常见模式是在IBinder/Messenger中使用WeakReference,它保存对它将访问的其余对象的引用。这允许您的本地垃圾收集器清理所有其他对象(可能很重,包含位图之类的大对象),即使远程进程在您的IBinder上仍然有引用。当然,如果您这样做,则需要有其他东西在这些其他对象上保留引用,直到不再需要它们,否则垃圾收集器可以在不再需要它们之前清理它们

我建议的另一件事是不要为每个IPC实例化Messenger对象。创建一个Messenger,将其传递给每个IPC呼叫。否则,您可以生成许多远程对象,这些对象由于其他进程继续持有引用而被保留,因为另一方没有积极地进行垃圾收集,因为它由于这些调用而创建的所有对象都很小

更多信息:

实际上,这些都不是在活动的上下文中发生的,它们都是完全后台操作,需要绑定到服务直到完成,而不管用户做什么。无论如何,即使我使用绑定机制访问服务而不是意图,内存泄漏仍然存在。您不应该通过意图发送messenger,而是在服务连接后注册它们。并在工作完成后注销
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);
    }
}