日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > Android >内容正文

Android

android跨进程读写内存,Android 跨进程内存泄露

發(fā)布時(shí)間:2025/10/17 Android 24 豆豆
生活随笔 收集整理的這篇文章主要介紹了 android跨进程读写内存,Android 跨进程内存泄露 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

內(nèi)存泄露的檢測和修復(fù)一直是每個(gè)APP的重點(diǎn)和難點(diǎn),也有很多文章講述了如何檢測和修復(fù)。本篇文章

結(jié)合最近開發(fā)的項(xiàng)目遇到的實(shí)例,講述下Android Binder導(dǎo)致的內(nèi)存泄露的一個(gè)案例。

發(fā)現(xiàn)問題

參與的項(xiàng)目在最近的版本接入了一個(gè)開源的內(nèi)存檢測工具LeakCanary,在提交給QA測試驗(yàn)證后。

瞬間檢測出來N多的內(nèi)存泄露,XXXActivity泄露,XXXActivity泄露…坑爹的是,這種泄露還不是必現(xiàn)的。好在堆棧都基本一樣,隨便拉一個(gè)出來分享吧

* 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的匿名類沒有被釋放。通過函數(shù)名就可以知道INetworkCallbackStub是Android自動(dòng)生成的用于跨進(jìn)程通信的框架,到對(duì)應(yīng)的NetSceneBase查看對(duì)應(yīng)的代碼:

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的引用發(fā)現(xiàn),除了跨進(jìn)程傳遞給網(wǎng)絡(luò)進(jìn)程外沒有其他任何地方引用了networkCallback。

而網(wǎng)絡(luò)進(jìn)程在完成相應(yīng)的網(wǎng)絡(luò)請(qǐng)求后,便將networkCallback置null,那這里的GC ROOT又是怎么回事呢?

繼續(xù)看代碼,networkCallback是跨進(jìn)程傳遞給網(wǎng)絡(luò)進(jìn)程的,所以查看AIDL自動(dòng)生成的代碼:

@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;

}

跨進(jìn)程傳輸必須用到Parcel,在這段代碼里有這句_data.writeStrongBinder((((callback!=null))?(callback.asBinder()):(null)));

而這個(gè)_data就是Java層的Parcel對(duì)象。PS:這里的callback其實(shí)是一個(gè)Binder對(duì)象,而Binder對(duì)象構(gòu)造函數(shù)里面有如下這段代碼

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對(duì)象是匿名類、內(nèi)部成員類或者是局部類就有可能出現(xiàn)內(nèi)存泄露。

接著往下看

public final void writeStrongBinder(IBinder val) {

//調(diào)用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就是對(duì)應(yīng)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對(duì)象,走下面這部分邏輯。最后調(diào)用jbh->get獲得native層的IBinder對(duì)象指針。

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,創(chuàng)建了一個(gè)全局引用,如不主動(dòng)調(diào)用env->DeleteGlobalRef(object),Java層的對(duì)象也就是networkCallback就不會(huì)被釋放。

{

ALOGV("Creating JavaBBinder %p\n", this);

android_atomic_inc(&gNumLocalRefs);

incRefsCreated(env);

}

解決問題

定位到問題之后就好辦,這里networkCallback是由于nativ層引用了導(dǎo)致無法釋放,那系統(tǒng)什么時(shí)候才能釋放這部分內(nèi)存呢。

結(jié)論是當(dāng)網(wǎng)絡(luò)進(jìn)程的netwCallback執(zhí)行finalize(),也就是網(wǎng)絡(luò)進(jìn)程對(duì)其進(jìn)行垃圾回收的時(shí)候,native層才不會(huì)引用到主進(jìn)程的networkCallback。所以,主進(jìn)程也不是每次檢測都會(huì)泄露,過段時(shí)間網(wǎng)絡(luò)進(jìn)程進(jìn)行GC后,對(duì)應(yīng)的Activity也就被回收了。但其實(shí)網(wǎng)絡(luò)進(jìn)程用到的內(nèi)存資源是很少的也是比較穩(wěn)妥,網(wǎng)絡(luò)進(jìn)程可能會(huì)很長一段時(shí)間不進(jìn)行GC。那么我們能做的就是,在網(wǎng)絡(luò)請(qǐng)求完成后切斷networkCall與上層的引用,避免Activity的泄露。查看上面的引用鏈,networkCall是網(wǎng)絡(luò)進(jìn)程和主進(jìn)程通訊的接口,imageLoadInterface是業(yè)務(wù)層和UI的接口。切斷這兩個(gè)引用的任何一個(gè)都可以避免底層的內(nèi)存泄露進(jìn)一步導(dǎo)致Activity的泄露,從這里也是看出RoundedImageView這個(gè)控件編碼也有問題。

最后解決方法是,networkCallback不再以匿名內(nèi)部類實(shí)現(xiàn),而是單獨(dú)以一個(gè)類實(shí)現(xiàn)然后將NetSceneBase以參數(shù)的形式傳遞給NetworkCallback,在網(wǎng)絡(luò)請(qǐng)求結(jié)束后將netSceneBase置null。

總結(jié)

以上就是這個(gè)case從發(fā)現(xiàn)到解決的全部過程,可以看出導(dǎo)致內(nèi)存泄露的原因有兩個(gè) 1.忽略了Android底層組件的工作機(jī)制以及各個(gè)對(duì)象的生命周期。 2.上層邏輯編碼問題,imageLoadInterface接口沒有及時(shí)注銷。 PS:文章中使用的工具LeakCanary,DDMS和MAT還是很強(qiáng)大的,具體用法google即可。

總結(jié)

以上是生活随笔為你收集整理的android跨进程读写内存,Android 跨进程内存泄露的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。