WebView内存泄漏--解决方法小结

2018-11-22 21:20:04

有些东西还是记下来比较好,记记随笔,欢迎批评建议。


前段时间在项目中就用到webview展示大量的新闻资讯页面,然后就惊喜的出现内存泄漏了,于是乎我在网上查了一些资料然后在这里总结一下解决方法,欢迎拍砖。
(方法4划重点)。
Android混合开发时经常用到WebView加载html等页面,而WebView的内存泄漏就是最经常遇到的问题,尤其是当项目中需要用webview加载的页面比较多时。

即使当我退出页面时在我的BrowserActivity的onDestroy()方法中进行内存占用回收(如下图)但并没有效果:

mWebView.removeAllViews();
mWebView.destroy();
mWebView=null;

当我点开了多少条新闻内存中就存在多少个BrowserActivity的实例,说明我退出时这个BrowserActivity没有被回收,这样的话当我浏览的新闻比较多时,内存就会累积存在一定的OOM风险,而且新闻界面一般存在大量图片,所以这个问题是必须要解决的。

1. new一个而不是在.xml中定义webview节点

attention:最初在写这篇的时候这一小节可能写的不够严谨,要是造成误解真是抱歉;写这篇小结时的目的也是想把知道的一些解决方法记下来方便自己查看,没想到能收到评论和质疑,我还是很开心的,但是通过这个我也发现,发出来的东西还是要写的严谨一些,会慢慢改进的。所以这一小节重新说明了一下,要是有不对的地方还是欢迎大家拍砖。

不要在布局文件中定义webview的节点,而是在需要的时候动态生成。你可以在需要webview的布局位置放一个LinearLayout,需要时在代码中动态生成webview并add进去:

//mWebView=new WebView(this);mWebView=new WebView(getApplicationContext());
LinearLayout linearLayout  = findViewById(R.id.xxx);
linearLayout.addView(mWebView);

然后在onDestroy()方法中调用:

@Overrideprotected void onDestroy() {    if( mWebView!=null) {
       mWebView.setVisibility(View.GONE);
       mWebView.removeAllViews();
       mWebView.destroy();
    }    super.onDestroy();
}

tips: 关于创建webview时new WebView(...);到底是传入ApplicationContext还是Activity的context,说法不一,但是网上较为一致的观点是采用application的context。
传ApplicationContext貌似可以防止webview对activity的引用而造成的内存泄漏;但是在很多情况下会报错,但是这个出错应该是webview的某些特殊动作产生由Application到Activity的类型转换错误;
采用activity的context细想来貌似和在xml中直接定义没有什么区别;

2. 手动删除引用

这个方法在我的项目中没有效果,但原文博主说在他的项目中效果很好,也许对其他人的情况有效,在这里也记下来。

public void setConfigCallback(WindowManager windowManager) {    try {
        Field field = WebView.class.getDeclaredField("mWebViewCore");
        field = field.getType().getDeclaredField("mBrowserFrame");
        field = field.getType().getDeclaredField("sConfigCallback");
        field.setAccessible(true);
        Object configCallback = field.get(null);        if (null == configCallback) {            return;
        }

        field = field.getType().getDeclaredField("mWindowManager");
        field.setAccessible(true);
        field.set(configCallback, windowManager);
    } catch(Exception e) {
    }
}

然后在activity中调用:

   public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);
        setConfigCallback(WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
    }    public void onDestroy() {
        setConfigCallback(null);        super.onDestroy();
    }

3. 进程

为加载WebView的界面开启新进程,在该页面退出之后关闭这个进程。
这个方法我没有测试,不知道应用和效果如何,有兴趣的可以试试。

4. 从根源解决(划重点)

前面的方法都没有解决我内存泄漏的问题,然后我看到了一篇文章是从源码角度分析了webview内存泄漏的原因,最后按作者的方法解决了问题,后面会贴上原文地址。这里简单说一下:
原文里说的webview引起的内存泄漏主要是因为org.chromium.android_webview.AwContents 类中注册了component callbacks,但是未正常反注册而导致的。

org.chromium.android_webview.AwContents 类中有这两个方法 onAttachedToWindow 和 onDetachedFromWindow;系统会在attach和detach处进行注册和反注册component callback;
在onDetachedFromWindow() 方法的第一行中:

if (isDestroyed()) return;,

如果 isDestroyed() 返回 true 的话,那么后续的逻辑就不能正常走到,所以就不会执行unregister的操作;我们的activity退出的时候,都会主动调用 WebView.destroy() 方法,这会导致 isDestroyed() 返回 true;destroy()的执行时间又在onDetachedFromWindow之前,所以就会导致不能正常进行unregister()。
然后解决方法就是:让onDetachedFromWindow先走,在主动调用destroy()之前,把webview从它的parent上面移除掉。

ViewParent parent = mWebView.getParent();if (parent != null) {
    ((ViewGroup) parent).removeView(mWebView);
}

mWebView.destroy();

完整的activity的onDestroy()方法:

@Overrideprotected void onDestroy() {    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();
        mWebView.destroy();

    }    super.on Destroy();
}

这个方法亲测有效。



  • 2021-01-18 15:12:57

    flex和inline-flex区别

    flex: 将对象作为弹性伸缩盒显示 inline-flex:将对象作为内联块级弹性伸缩盒显示

  • 2021-01-21 13:52:36

    node.js使用Nodemailer发送邮件

    常常看到一些网站有邮箱获取验证码验证注册或者修改密码等,今天也来了解一下在nodejs + express怎么发送电子邮件。使用模块Nodemailer。这里以qq邮箱举例子。

  • 2021-01-21 13:55:53

    Mongodb字段更新$unset操作符

    当使用$操作符匹配任何数组元素,$unset替换指定的元素为null而不是删除掉指定的元素,此行为保持数组大小和位置一直;

  • 2021-01-22 08:30:02

    Android IO简化之Okio库

    如果之前有使用过Okhttp,那么你一定知道底层的IO读取是由square公司开发的Okio库。它补充了Java.io和java.nio的不足,以便能够更加方便,快速的访问、存储和处理你的数据。而在一般的开发中,我们也可以使用Okio来做IO读写,非常方便深得我心

  • 2021-01-22 21:56:48

    emcc生成wasm,wast,bc文件的方法

    Emscripten实现把C/C++文件转成wasm,wast(wasm的可读形式),llvm字节码(bc格式),ll格式(llvm字节码的可读形式)的步骤。

  • 2021-01-22 21:59:34

    emcc编译与部分重要参数选取

    C/C++代码通过emcc编译为字节码,然后根据不同的目标编译为asm.js或wasm。emcc和gcc编译选项类似,例如-s OPTIONS=VALUE、-O等。另外为了适应Web环境,emcc增加了一些特有的选项,如–pre-js 、–post-js 等。