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();
}

这个方法亲测有效。



  • 2019-08-27 05:43:13

    Laravel 门面自动补全工具 laravel-ide-helper

    当我们在 PhpStorm 编辑器中,开发 Laravel 框架的项目时,很多类方法都不能自动补全和定位,比如 Facade 门面的方法,DB::table()、Route::get() 等。

  • 2019-08-28 08:28:36

    Js apply,call方法详解,及其apply()方法的妙用

    在给对象参数的情况下,如果参数的形式是数组的时候,比如apply示例里面传递了参数arguments,这个参数是数组类型,并且在调用Person的时候参数的列表是对应一致的(也就是Person和Student的参数列表前两位是一致的) 就可以采用 apply , 如果我的Person的参数列表是这样的(age,name),而Student的参数列表是(name,age,grade),这样就可以用call来实现了,也就是直接指定参数列表对应值的位置(Person.call(this,age,name,grade));

  • 2019-08-28 08:49:47

    Nginx + fastcgi + php 的原理与关系

    CGI对每个请求会parse一遍对应脚本的配置文件(如php.ini), 加载配置和扩展,初始化执行环境,性能非常差,所有有了下面的流程:

  • 2019-08-28 09:23:15

    php单例模式

    单例模式,是一种常见的软件设计模式。在应用这个模式时,单例对象的类必须保证只有一个实例存在。

  • 2019-08-28 22:45:02

    彻底搞懂Gradle、Gradle Wrapper与Android Plugin for Gradle的区别和联系

    Gradle是个构建系统,能够简化你的编译、打包、测试过程。熟悉Java的同学,可以把Gradle类比成Maven。Gradle Wrapper的作用是简化Gradle本身的安装、部署。不同版本的项目可能需要不同版本的Gradle,手工部署的话比较麻烦,而且可能产生冲突,所以需要Gradle Wrapper帮你搞定这些事情。Gradle Wrapper是Gradle项目的一部分。