今天在检测内存泄露的时候,发现有一个activity的泄露是这样的:
Browser是继承自Application的类,在自己的这个类里面看了下,没有mComponentCallbacks这个成员变量,那么猜想可能是在父类Application中,看了下源码,结果真的是
这个类里面还有注册和反注册:
从上面内存泄露的调用栈来看,就是application里的成员变量,持有了一个activity实例,而这个成员变量,实际上就关联到了webview的实例,这个成员变量有注册和反注册功能,也就是说我们可能在某些地方没有进行反注册。
那么注册和反注册是在什么地方调用的呢?
这就要看这个类了org.chromium.android_webview.AwContents
在5.1上(实际上是4.4就开始了http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2014/1106/1921.html),内核中的webview实际上已经从webkit变更为chrome的了。(但是实际上对开发者来说用的还是webview这个接口,在源码的位置是:frameworks/base/core/java/android/webkit,webview的实际功能交给WebViewProvider来做,而新的AwContents这个类是在:frameworks/webview/chromium/java/com/android/webview/chromium/)
也就是上面内存泄露调用栈中的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这个类了,实际上真正执行的是这个类里的方法。
最后,附上完整的分析文章链接: