android跨进程读写内存,Android 跨进程内存泄露
內(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)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言 宏 变长参数,科学网—C/C++
- 下一篇: android 动态修改 selecto