android跨进程读写内存,Android 跨进程内存泄露
内存泄露的检测和修复一直是每个APP的重点和难点,也有很多文章讲述了如何检测和修复。本篇文章
结合最近开发的项目遇到的实例,讲述下Android Binder导致的内存泄露的一个案例。
发现问题
参与的项目在最近的版本接入了一个开源的内存检测工具LeakCanary,在提交给QA测试验证后。
瞬间检测出来N多的内存泄露,XXXActivity泄露,XXXActivity泄露…坑爹的是,这种泄露还不是必现的。好在堆栈都基本一样,随便拉一个出来分享吧
* com.ui.theme.ThemeListActivity has leaked:
* GC ROOT com.business.netscene.NetSceneBase1.this0 (anonymous class extends com.data.network.framework.INetworkCallbackStub)∗referencescom.common.util.image.SceneBitmapDownload.info∗referencescom.common.util.image.BitmapDownloadInfo.imageLoadInterface∗referencescom.ui.common.RoundedImageView4.this$0 (anonymous class implements com.common.util.image.ImageLoadInterface)
* references com.ui.common.RoundedImageView.mContext
* leaks com..ui.theme.ThemeListActivity instance
定位问题
通过堆栈信息可以清楚的看到Activity到GCRoot的完整引用链,最终泄露是由于继承INetworkCallbackStub的匿名类没有被释放。通过函数名就可以知道INetworkCallbackStub是Android自动生成的用于跨进程通信的框架,到对应的NetSceneBase查看对应的代码:
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(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 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 get(JNIEnv* env, jobject obj)
{
AutoMutex _l(mLock);
sp 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跨进程读写内存,Android 跨进程内存泄露相关推荐
- android u盘读写权限,Android 外部SD卡/U盘无法写入解决方法(需要root)
但今天我遇到一个问题,就是我买了只TF卡装上去以后发现:一般程序无法写入TF卡,而系统自带的文件工具能够写入. 什么原因呢? 好在这个平板已经是root的,马上调出rootexplorer文件管理器查 ...
- android zip文件读写,【Android】Zip文件解压方法
android中zip文件解压 public class ZipUtil { /** * 解压到指定路径 * * @param inputStream * @param outPathString * ...
- Windows进程与线程学习笔记(九)—— 线程优先级/进程挂靠/跨进程读写
Windows进程与线程学习笔记(九)-- 线程优先级/进程挂靠/跨进程读写 要点回顾 线程优先级 调度链表 分析 KiFindReadyThread 分析 KiSwapThread 总结 进程挂靠 ...
- linux 跨进程读取内存,Android之Linux跨进程通信的方式
As we all know,Android是基于Linux内核开发的,而市面上几乎所有的App都离开跨进程通信.可能你会说Android是通过Binder完成进程之间的通信的.但是Binder是怎么 ...
- android listview 滑动黑屏,Android 跨进程启动Activity黑屏(白屏)的三种解决方案
当Android跨进程启动Activity时,过程界面很黑屏(白屏)短暂时间(几百毫秒?).当然从桌面Lunacher启动一个App时也会出现相同情况,那是因为App冷启动也属于跨进程启动Activi ...
- 进程线程007 进程挂靠与跨进程读写内存
文章目录 进程挂靠 进程与线程的关系 线程与进程如何关联 为什么需要ApcState.Process CR3的值可以随便改吗 分析NtReadVirtualMemory函数 总结 跨进程读写内存 跨进 ...
- Android之使用AIDL时的跨进程回调—Server回调Client
首先建立在server端建立两个aidl文件 ITaskCallback.aidl 用于存放要回调client端的方法 package com.cmcc.demo.server; interface ...
- 13.跨进程读写内存
跨进程的本质是"进程挂靠"正常情况下, A进程的线程只能访问A进程的地址空间,如果A进程的线程想访问B进程的地址空间,就要修改当前的Cr3的值为B进程的页目录表基值(KPROCES ...
- Android中获取系统内存信息以及进程信息-----ActivityManager的使用(一)
本节内容主要是讲解ActivityManager的使用,通过ActivityManager我们可以获得系统里正在运行的activities,包括 进程(Process)等.应用程序/包.服务(Serv ...
最新文章
- iOS中你可能没有完全弄清楚的(一)synthesize
- 包浆网图分分钟变高清,伪影去除、细节恢复更胜前辈AI,下载可玩|腾讯ARC实验室出品...
- [概念型] 区块链包含术语概念【27术语整理汇总】
- Java 洛谷 P1307 数字反转
- bootstrap学习(五)代码
- 什么是 lnmp 实现原理。
- Oracle中Merge into的用法实例讲解
- ubuntu16.04下面使用graphviz
- 常用的正则表达式的运用--学习笔记(二)
- think-in-java(9)接口
- PHP 实现简单的 倒计时 时分秒
- mysql安装使用--2 用户管理
- 构造avl树_浅谈AVL树,B-树,B+树
- Android Studio 如何添加悬浮提示
- CTP: 为什么报网络原因发送失败,但连接却成功?
- js-YDUI 移动端解决方案
- java下载https的网络图片,添加安全证书方式
- jwplayer播放器初探
- 【IoT】产品设计之市场概念:市场定位、产品定位、市场需求、产品需求
- Md5码的生成及变种Md5码的生成
热门文章
- 网络编程学习笔记(gethostbyname2函数与IPv6支持)
- TabHost的使用(二):实现TabHost.TabContentFactory接口
- cad 关键字被保留了?选择集关键字保留了? N S W E关键字无法用?
- JS、CSS中的相对路径
- python3之subprocess常见方法使用
- 生成4位验证码(后台)
- 电商总结(八)如何打造一个小而精的电商网站架构
- QueryList的使用
- js 宽窄屏切换效果代码优化
- gdb InnoDB Redundant Row Format