我有一个应用程序使用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”对象无限期地保存在内存中。
有没有人知道当“操作”结束时是否有办法清除或禁用Messenger
所以它不会造成内存泄漏?是否有另一种方法可以以相同的方式将IPC实现到Service
,以便多个不同的对象可以发出请求并异步获得结果?
提前致谢!
答案 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。它遵循这些方针。