Android 跨进程内存泄露
来源:互联网 发布:学校网络整合营销策划 编辑:程序博客网 时间:2024/05/22 16:10
内存泄露的检测和修复一直是每个APP的重点和难点,也有很多文章讲述了如何检测和修复。本篇文章
结合最近开发的项目遇到的实例,讲述下Android Binder导致的内存泄露的一个案例。
发现问题
参与的项目在最近的版本接入了一个开源的内存检测工具LeakCanary,在提交给QA测试验证后。
瞬间检测出来N多的内存泄露,XXXActivity泄露,XXXActivity泄露…坑爹的是,这种泄露还不是必现的。好在堆栈都基本一样,随便拉一个出来分享吧
* com.ui.theme.ThemeListActivity has leaked:
* GC ROOT com.business.netscene.NetSceneBase
* references com.ui.common.RoundedImageView.mContext
* leaks com..ui.theme.ThemeListActivity instance
定位问题
通过堆栈信息可以清楚的看到Activity到GCRoot的完整引用链,最终泄露是由于继承INetworkCallback
private INetworkCallback.Stub networkCallback = new INetworkCallback.Stub() @Override public void onResult(int errType, int respCode, WeMusicCmdTask task) throws RemoteException { NetSceneBase.this.onResult(errType, respCode, task); } @Override public void onWorking(long progress, long total) throws RemoteException { NetSceneBase.this.onProgress( progress, total ); } };
接着再查找networkCallback的引用发现,除了跨进程传递给网络进程外没有其他任何地方引用了networkCallback。
而网络进程在完成相应的网络请求后,便将networkCallback置null,那这里的GC ROOT又是怎么回事呢?
继续看代码,networkCallback是跨进程传递给网络进程的,所以查看AIDL自动生成的代码:
@Override public boolean send(com.data.network.WeMusicCmdTask task, com.data.network.framework.INetworkCallback callback) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();boolean _result;try {_data.writeInterfaceToken(DESCRIPTOR);if ((task!=null)) {_data.writeInt(1);task.writeToParcel(_data, 0);}else {_data.writeInt(0);}_data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));mRemote.transact(Stub.TRANSACTION_send, _data, _reply, 0);_reply.readException();_result = (0!=_reply.readInt());if ((0!=_reply.readInt())) {task.readFromParcel(_reply);}}finally {_reply.recycle();_data.recycle();}return _result;}
跨进程传输必须用到Parcel,在这段代码里有这句_data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));
而这个_data就是Java层的Parcel对象。PS:这里的callback其实是一个Binder对象,而Binder对象构造函数里面有如下这段代码
public Binder() { init(); if (FIND_POTENTIAL_LEAKS) { final Class<? extends Binder> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Binder class should be static or leaks might occur: " + klass.getCanonicalName()); } } }
可以看到如果Binder对象是匿名类、内部成员类或者是局部类就有可能出现内存泄露。
接着往下看
public final void writeStrongBinder(IBinder val) { //调用native方法 nativeWriteStrongBinder(mNativePtr, val); }
static void android_os_Parcel_writeStrongBinder(JNIEnv* env, jclass clazz, jint nativePtr, jobject object) { Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr); if (parcel != NULL) { //ibinderForJavaObject,这里的object就是对应java层IBinder也就是networkCallback const status_t err = parcel->writeStrongBinder(ibinderForJavaObject(env, object)); if (err != NO_ERROR) { signalExceptionForError(env, clazz, err); } } }
sp<IBinder> ibinderForJavaObject(JNIEnv* env, jobject obj) { if (obj == NULL) return NULL; //这里obj是Java层的Binder对象,走下面这部分逻辑。最后调用jbh->get获得native层的IBinder对象指针。 if (env->IsInstanceOf(obj, gBinderOffsets.mClass)) { JavaBBinderHolder* jbh = (JavaBBinderHolder*)env->GetIntField(obj, gBinderOffsets.mObject); return jbh != NULL ? jbh->get(env, obj) : NULL; } if (env->IsInstanceOf(obj, gBinderProxyOffsets.mClass)) { return (IBinder*)env->GetIntField(obj, gBinderProxyOffsets.mObject); } ALOGW("ibinderForJavaObject: %p is not a Binder object", obj); return NULL; }
sp<JavaBBinder> get(JNIEnv* env, jobject obj) { AutoMutex _l(mLock); sp<JavaBBinder> b = mBinder.promote(); if (b == NULL) { b = new JavaBBinder(env, obj); mBinder = b; ALOGV("Creating JavaBinder %p (refs %p) for Object %p, weakCount=%d\n", b.get(), b->getWeakRefs(), obj, b->getWeakRefs()->getWeakCount()); } return b; }JavaBBinder(JNIEnv* env, jobject object) : mVM(jnienv_to_javavm(env)), mObject(env->NewGlobalRef(object)) //here,创建了一个全局引用,如不主动调用env->DeleteGlobalRef(object),Java层的对象也就是networkCallback就不会被释放。{ ALOGV("Creating JavaBBinder %p\n", this); android_atomic_inc(&gNumLocalRefs); incRefsCreated(env); }
解决问题
定位到问题之后就好办,这里networkCallback是由于nativ层引用了导致无法释放,那系统什么时候才能释放这部分内存呢。
结论是当网络进程的netwCallback执行finalize(),也就是网络进程对其进行垃圾回收的时候,native层才不会引用到主进程的networkCallback。所以,主进程也不是每次检测都会泄露,过段时间网络进程进行GC后,对应的Activity也就被回收了。但其实网络进程用到的内存资源是很少的也是比较稳妥,网络进程可能会很长一段时间不进行GC。那么我们能做的就是,在网络请求完成后切断networkCall与上层的引用,避免Activity的泄露。查看上面的引用链,networkCall是网络进程和主进程通讯的接口,imageLoadInterface是业务层和UI的接口。切断这两个引用的任何一个都可以避免底层的内存泄露进一步导致Activity的泄露,从这里也是看出RoundedImageView这个控件编码也有问题。
最后解决方法是,networkCallback不再以匿名内部类实现,而是单独以一个类实现然后将NetSceneBase以参数的形式传递给NetworkCallback,在网络请求结束后将netSceneBase置null。
总结
以上就是这个case从发现到解决的全部过程,可以看出导致内存泄露的原因有两个
1.忽略了Android底层组件的工作机制以及各个对象的生命周期。
2.上层逻辑编码问题,imageLoadInterface接口没有及时注销。
PS:文章中使用的工具LeakCanary,DDMS和MAT还是很强大的,具体用法google即可。
- Android 跨进程内存泄露
- C#跨进程共享内存
- COM跨进程内存管理
- android 跨进程通信
- Android 跨进程通信
- Android跨进程通信
- Android组件跨进程
- Android跨进程通信
- Android LocalBroadcast跨进程
- Android跨进程通讯
- Android跨进程通信
- Android跨进程通信
- Android跨进程通信
- Android跨进程通信
- Android 内存泄露
- android内存泄露 mat
- 预防Android内存泄露
- 预防Android内存泄露
- Activity的4种启动模式
- 教学中控简介
- OpenGL 入门纪录--视口变换函数glViewPort函数用法
- arduino gps
- Android中性能分析之TraceView
- Android 跨进程内存泄露
- Gradle知识巩固
- Eclipse4.5.1 Mars C++11 编译出错解决方法
- ui笔记1
- Activiti学习——Activiti与Spring集成
- Mybatis 执行批量插入 10W 4.8s
- ARM-linux汇编常用语法
- 非递归的归并排序
- C语言动态数组原理及实现