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 跨进程内存泄露的全部內容,希望文章能夠幫你解決所遇到的問題。

- 上一篇: c语言 宏 变长参数,科学网—C/C++
- 下一篇: android 动态修改 selecto