当前位置:   article > 正文

【Android 性能优化:内存篇】——WebView 内存泄露治理_android webview 内存泄露

android webview 内存泄露

背景:笔者在公司项目中优化内存泄露时发现WebView 相关的内存泄露问题非常经典,一个 Fragment 页面使用的 WebView 有多条泄露路径,故记录下。

Fragment、Activity 使用WebView不释放

项目中一个Fragment 使用 Webview,在 Fragment onDestroyView 时候却没有释放,释放 WebView 还不简单嘛,于是笔者在 Fragment 的 onDestroyView 补充了如下代码:

  1. if (webView != null) {
  2. ViewGroup parent = (ViewGroup) webView.getParent();
  3. if (parent != null) {
  4. parent.removeView(webView);
  5. }
  6. webView.destroy();
  7. webview = null;
  8. }

然而,这样其实释放不全,还是抓到其他的泄露路径

如图GC 引用链:AwContents->WebVIew->View.LinsenerInfo->WebViewFragment

原因是使用 WebView的时候,注册了OnFocusChangeListener

  1. webView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
  2. @Override
  3. public void onFocusChange(View v, boolean hasFocus) {
  4. //省略
  5. }
  6. });

因此,释放 WebView的时候,还需要把注册的一些Listener 释放

WebView 释放不全

上面介绍了释放 WebView 资源的时候释放不全的例子,那么怎样才能将用到的WebView 资源释放完全呢?

笔者封装了一个接口如下:

  1. public void destroyWebView(WebView webView) {
  2. try {
  3. if (webView != null) {
  4. ViewGroup parent = (ViewGroup) webView.getParent();
  5. if (parent != null) {
  6. parent.removeView(webView);
  7. }
  8. webView.setOnTouchListener(null);
  9. webView.setOnKeyListener(null);
  10. webView.setOnFocusChangeListener(null);
  11. webView.setWebChromeClient(null);
  12. webView.setWebViewClient(null);
  13. webView.loadUrl("about:blank");
  14. webView.onPause();
  15. webView.removeAllViews();
  16. webView.destroyDrawingCache();
  17. webView.destroy();
  18. webView = null;
  19. }
  20. } catch (Throwable e) {
  21. e.printStackTrace();
  22. }
  23. }

这样释放真的释放完全了?如果你使用的WebView 还注册了其他的Listener,记得也需要释放

网上,还有说需要调用

  1. webView.pauseTimers();
  2. webView.clearHistory();

上面的接口慎用,因为它们是对全局生效的,不只当前WebView!

按上面两个步骤解决完,笔者以为不会再发生泄漏,谁知道还是抓到第三条泄露路径!!

GC 引用链:AwContents->BannerView->Banner->CardView->Container->AdView->匿名内部类AdListener->WebViewFragment

匿名内部类导致 WebView泄露

按上面描述的引用链,匿名内部类隐式持有外部类 Fragment 的引用,而这个匿名内部类AdShowListener 刚好是 AdView 持有的, AdView 本质上是一个 WebView.

解法很常规:把匿名内部类改为静态内部类,然后静态内部类里使用的 Fragment 改为弱引用,并且 Fragment 销毁的时候,AdShowListener 置空。

到此,笔者以为不会再发生内存泄露了,怎知,还是抓到了,这次抓的是包裹 Fragment 的Activity 作为 Context 被 webview 持有

意不意外,惊不惊喜?

GC 引用链:AwContents->WebView->WebViewActivity, WebViewActivity 作为 Conext 被 WebView 持有

因为 Fragment 初始化 WebView 的时候 使用了 getActivity(),context 一直被 WebView 内核持有,笔者猜测部分系统会有这种问题。这种问题是否无解了?山重水复疑无路,柳暗花明又一寸,笔者意外发现有个类 MutableContextWrapper 可以使用。

MutableContextWrapper 切换 Context

初始化 WebView 的时候使用AppContext,在 Activity 使用 Webview 的时候切换为 Activity,最后销毁 WebView 之前再切换回 AppContext

为什么在Activity 使用WebView的时候切换到Activity 呢?因为WebView 中的可能有些场景依赖 Activity 如:弹窗Dialog,Context 为AppContext 会发生崩溃。

  1. private WebView webview;
  2. //初始化Webview
  3. MutableContextWrapper contextWrapper = new MutableContextWrapper(getAppContext());
  4. webview = new WebView(contextWrapper);
  5. //在Activity中使用
  6. private WebView acquireWebView(Activity activity) {
  7. //缓存中的webview
  8. MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
  9. contextWrapper.setBaseContext(activity);
  10. return webView;
  11. }
  12. //销毁之前
  13. public void recycleWebView(WebView webView) {
  14. if (webView == null) {
  15. return;
  16. }
  17. MutableContextWrapper contextWrapper = (MutableContextWrapper) webView.getContext();
  18. contextWrapper.setBaseContext(getAppContext());
  19. destroyWebView(webview);
  20. }
  21. //销毁 webview 的接口
  22. public void destroyWebView(WebView webView) {
  23. try {
  24. if (webView != null) {
  25. ViewGroup parent = (ViewGroup) webView.getParent();
  26. if (parent != null) {
  27. parent.removeView(webView);
  28. }
  29. webView.setOnTouchListener(null);
  30. webView.setOnKeyListener(null);
  31. webView.setOnFocusChangeListener(null);
  32. webView.setWebChromeClient(null);
  33. webView.setWebViewClient(null);
  34. webView.loadUrl("about:blank");
  35. webView.onPause();
  36. webView.removeAllViews();
  37. webView.destroyDrawingCache();
  38. webView.destroy();
  39. webView = null;
  40. }
  41. } catch (Throwable e) {
  42. e.printStackTrace();
  43. }
  44. }

至此,没有再抓到泄露路径。

总结

本文列举了项目中治理 WebView 内存泄露的手段:

1)Fragment、Activity 销毁时释放WebView。

2)释放WebView 需要释放完全,WebView 注册的各种监听器都需要释放。

3)同时要考虑Fragment、Activity 有没用到匿名内部类,如果有要改成静态内部类,并且要静态内部类有使用Fragment、Activity的话要使用弱引用。

4)初始化 WebView 的时候使用AppContext,在 Activity 使用 Webview 的时候切换为 Activity,最后销毁 WebView 之前再切换回 AppContext。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/264020
推荐阅读
相关标签
  

闽ICP备14008679号