【问题标题】:Messenger to Remote Service Causing Memory Leak信使到远程服务导致内存泄漏
【发布时间】:2012-08-30 20:11:50
【问题描述】:

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

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

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

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,它将每个“操作”对象无限期地保存在内存中。

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

提前致谢!

【问题讨论】:

    标签: android service memory-leaks


    【解决方案1】:

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

    这是她回复的正文:

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

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

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

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

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

    【讨论】:

      【解决方案2】:

      我不确定这是否是最好的方法,因为即使Activity 在后台,您也会从Service 获得message。

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

      在 AOSP 中检查 ExportVcardActivity。它遵循这些思路。

      【讨论】:

      • 好吧,实际上这些都不会发生在 Activity 的上下文中,这些都是完全后台操作,无论用户做什么,都需要一直绑定到服务直到完成。无论如何,即使我使用绑定机制来访问服务而不是意图,内存泄漏仍然存在。
      • 你不应该通过意图发送信使,而是在服务连接后注册它们。工作完成后注销
      猜你喜欢
      • 2019-10-21
      • 1970-01-01
      • 1970-01-01
      • 2015-07-06
      • 2014-06-07
      • 2013-11-20
      • 2011-10-28
      • 2016-01-18
      • 2012-12-13
      相关资源
      最近更新 更多