背景:筆者在公司項(xiàng)目中優(yōu)化內(nèi)存泄露時(shí)發(fā)現(xiàn)WebView 相關(guān)的內(nèi)存泄露問題非常經(jīng)典,一個 Fragment 頁面使用的 WebView 有多條泄露路徑,故記錄下。
Fragment、Activity 使用WebView不釋放
項(xiàng)目中一個Fragment 使用 Webview,在 Fragment onDestroyView 時(shí)候卻沒有釋放,釋放 WebView 還不簡單嘛,于是筆者在 Fragment 的 onDestroyView 補(bǔ)充了如下代碼:
if (webView != null) {
ViewGroup parent = (ViewGroup) webView.getParent();
if (parent != null) {
parent.removeView(webView);
}
webView.destroy();
webview = null;
}
然而,這樣其實(shí)釋放不全,還是抓到其他的泄露路徑
如圖GC 引用鏈:AwContents->WebVIew->View.LinsenerInfo->WebViewFragment
原因是使用 WebView的時(shí)候,注冊了OnFocusChangeListener
webView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
//省略
}
});
因此,釋放 WebView的時(shí)候,還需要把注冊的一些Listener 釋放
WebView 釋放不全
上面介紹了釋放 WebView 資源的時(shí)候釋放不全的例子,那么怎樣才能將用到的WebView 資源釋放完全呢?
筆者封裝了一個接口如下:
public void destroyWebView(WebView webView) {
try {
if (webView != null) {
ViewGroup parent = (ViewGroup) webView.getParent();
if (parent != null) {
parent.removeView(webView);
}
webView.setOnTouchListener(null);
webView.setOnKeyListener(null);
webView.setOnFocusChangeListener(null);
webView.setWebChromeClient(null);
webView.setWebViewClient(null);
webView.loadUrl("about:blank");
webView.onPause();
webView.removeAllViews();
webView.destroyDrawingCache();
webView.destroy();
webView = null;
}
} catch (Throwable e) {
e.printStackTrace();
}
}
這樣釋放真的釋放完全了?如果你使用的WebView 還注冊了其他的Listener,記得也需要釋放
網(wǎng)上,還有說需要調(diào)用
webView.pauseTimers();
webView.clearHistory();
上面的接口慎用,因?yàn)樗鼈兪菍θ稚У?,不只?dāng)前WebView!
按上面兩個步驟解決完,筆者以為不會再發(fā)生泄漏,誰知道還是抓到第三條泄露路徑??!
GC 引用鏈:AwContents->BannerView->Banner->CardView->Container->AdView->匿名內(nèi)部類AdListener->WebViewFragment
匿名內(nèi)部類導(dǎo)致 WebView泄露
按上面描述的引用鏈,匿名內(nèi)部類隱式持有外部類 Fragment 的引用,而這個匿名內(nèi)部類AdShowListener 剛好是 AdView 持有的, AdView 本質(zhì)上是一個 WebView.
解法很常規(guī):把匿名內(nèi)部類改為靜態(tài)內(nèi)部類,然后靜態(tài)內(nèi)部類里使用的 Fragment 改為弱引用,并且 Fragment 銷毀的時(shí)候,AdShowListener 置空。
到此,筆者以為不會再發(fā)生內(nèi)存泄露了,怎知,還是抓到了,這次抓的是包裹 Fragment 的Activity 作為 Context 被 webview 持有
意不意外,驚不驚喜?
GC 引用鏈:AwContents->WebView->WebViewActivity, WebViewActivity 作為 Conext 被 WebView 持有
因?yàn)?Fragment 初始化 WebView 的時(shí)候 使用了 getActivity(),context 一直被 WebView 內(nèi)核持有,筆者猜測部分系統(tǒng)會有這種問題。這種問題是否無解了?山重水復(fù)疑無路,柳暗花明又一寸,筆者意外發(fā)現(xiàn)有個類 MutableContextWrapper 可以使用。
MutableContextWrapper 切換 Context
初始化 WebView 的時(shí)候使用AppContext,在 Activity 使用 Webview 的時(shí)候切換為 Activity,最后銷毀 WebView 之前再切換回 AppContext
為什么在Activity 使用WebView的時(shí)候切換到Activity 呢?因?yàn)閃ebView 中的可能有些場景依賴 Activity 如:彈窗Dialog,Context 為AppContext 會發(fā)生崩潰。
private WebView webview;
//初始化Webview
MutableContextWrapper contextWrapper = new MutableContextWrapper(getAppContext());
webview = new WebView(contextWrapper);
//在Activity中使用
private WebView acquireWebView(Activity activity) {
//緩存中的webview
MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
contextWrapper.setBaseContext(activity);
return webView;
}
//銷毀之前
public void recycleWebView(WebView webView) {
if (webView == null) {
return;
}
MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
contextWrapper.setBaseContext(getAppContext());
destroyWebView(webview);
}
//銷毀 webview 的接口
public void destroyWebView(WebView webView) {
try {
if (webView != null) {
ViewGroup parent = (ViewGroup) webView.getParent();
if (parent != null) {
parent.removeView(webView);
}
webView.setOnTouchListener(null);
webView.setOnKeyListener(null);
webView.setOnFocusChangeListener(null);
webView.setWebChromeClient(null);
webView.setWebViewClient(null);
webView.loadUrl("about:blank");
webView.onPause();
webView.removeAllViews();
webView.destroyDrawingCache();
webView.destroy();
webView = null;
}
} catch (Throwable e) {
e.printStackTrace();
}
}
至此,沒有再抓到泄露路徑。
總結(jié)
本文列舉了項(xiàng)目中治理 WebView 內(nèi)存泄露的手段:
1)Fragment、Activity 銷毀時(shí)釋放WebView。
2)釋放WebView 需要釋放完全,WebView 注冊的各種監(jiān)聽器都需要釋放。
3)同時(shí)要考慮Fragment、Activity 有沒用到匿名內(nèi)部類,如果有要改成靜態(tài)內(nèi)部類,并且要靜態(tài)內(nèi)部類有使用Fragment、Activity的話要使用弱引用。文章來源:http://www.zghlxwxcb.cn/news/detail-765057.html
4)初始化 WebView 的時(shí)候使用AppContext,在 Activity 使用 Webview 的時(shí)候切換為 Activity,最后銷毀 WebView 之前再切換回 AppContext。文章來源地址http://www.zghlxwxcb.cn/news/detail-765057.html
到了這里,關(guān)于【Android 性能優(yōu)化:內(nèi)存篇】——WebView 內(nèi)存泄露治理的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!