WebView造成的記憶體洩露
今天在檢測記憶體洩露的時候,發現有一個activity的洩露是這樣的:
Browser是繼承自Application的類,在自己的這個類裡面看了下,沒有mComponentCallbacks這個成員變數,那麼猜想可能是在父類Application中,看了下原始碼,結果真的是
這個類裡面還有註冊和反註冊:
從上面記憶體洩露的呼叫棧來看,就是application裡的成員變數,持有了一個activity例項,而這個成員變數,實際上就關聯到了webview的例項,這個成員變數有註冊和反註冊功能,也就是說我們可能在某些地方沒有進行反註冊。
那麼註冊和反註冊是在什麼地方呼叫的呢?
這就要看這個類了org.chromium.android_webview.AwContents
在5.1上,核心中的webview實際上已經從webkit變更為chrome的了。也就是上面記憶體洩露呼叫棧中的AwContents。這個類的兩個方法
看看這兩個方法 onAttachedToWindow
和 onDetachedFromWindow
:
@Override
public void onAttachedToWindow() {
if (isDestroyed()) return;
if (mIsAttachedToWindow) {
Log.w(TAG, "onAttachedToWindow called when already attached. Ignoring" );
return;
}
mIsAttachedToWindow = true;
mContentViewCore.onAttachedToWindow();
nativeOnAttachedToWindow(mNativeAwContents, mContainerView.getWidth(),
mContainerView.getHeight());
updateHardwareAcceleratedFeaturesToggle();
if (mComponentCallbacks != null) return;
mComponentCallbacks = new AwComponentCallbacks();
mContext.registerComponentCallbacks(mComponentCallbacks);
}
@Override
public void onDetachedFromWindow() {
if (isDestroyed()) return;
if (!mIsAttachedToWindow) {
Log.w(TAG, "onDetachedFromWindow called when already detached. Ignoring");
return;
}
mIsAttachedToWindow = false;
hideAutofillPopup();
nativeOnDetachedFromWindow(mNativeAwContents);
mContentViewCore.onDetachedFromWindow();
updateHardwareAcceleratedFeaturesToggle();
if (mComponentCallbacks != null) {
mContext.unregisterComponentCallbacks(mComponentCallbacks);
mComponentCallbacks = null;
}
mScrollAccessibilityHelper.removePostedCallbacks();
}
呼叫了mContext的註冊和反註冊方法。實際上context的registerComponentCallbacks 方法是在基類Context中實現的,它具體的是呼叫了Application的registerComponent方法:
在上面的onDetachedFromWindow 中,一旦我們這個if(isDestroyed())return; 檢測為true,就返回了不會去執行反註冊方法。
而這個isDestroyed又是在我們執行webview的destroy的時候被賦值的。所以如果我們在webview的onDetachedFromWindow前先執行了webview的destroy方法, 那麼就可能存在洩露。所以正確的做法就是:
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.destroy();
完整的程式碼如下:
public void destroy() {
if (mWebView != null) {
// 如果先呼叫destroy()方法,則會命中if (isDestroyed()) return;這一行程式碼,需要先onDetachedFromWindow(),再
// destory()
ViewParent parent = mWebView.getParent();
if (parent != null) {
((ViewGroup) parent).removeView(mWebView);
}
mWebView.stopLoading();
// 退出時呼叫此方法,移除繫結的服務,否則某些特定系統會報錯
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.clearView();
mWebView.removeAllViews();
try {
mWebView.destroy();
} catch (Throwable ex) {
}
}
}
,ps:其實我們在開發過程中,使用的是sdk的提供的WebView這個類,這個類是
package android.webkit;
包下的, 我們在這個類裡面沒有看到相關的onDetachedFromWindow方法。
在高版本的系統中,我們依舊還是使用WebView這個api,但實際系統為我們關聯到了AwContents這個類了,實際上真正執行的是這個類裡的方法。
最後,附上完整的分析文章連結: