Contents
  1. 1. 用代码New一个WebView而不是在XML中静态写入
    1. 1.1. 在XML文件中用layout占位
    2. 1.2. 在对应的Activity中通过代码添加
  2. 2. 销毁时的动作

这几年H5的快速发展,使得Hybrid混合开发越来越流行,而webview也成为了开发中必备的元素。但是我们知道WebView在加载页面时,会占用非常大的内存,无论是iOS还是Android系统上,加载一个Web页面一般需要用到100M左右,而如果我们不及时清理WebView的内存,那最后可能会随着内存消耗的不断增加而发生OOM(Out Of Memory)导致程序崩溃。那今天我就总结一下前一阵子在开发中解决WebView内存消耗过大问题吧,当然,这个过程也是参考了很多前人的经验总结,我会照例在文章的末尾列出来表示感谢。
这里先交代下关于查看程序的内存消耗,笔者每次都是在程序运行起来以后,通过手机上的系统工具来查看每个应用实时消耗内存的大小。因为AndroidStudio的Monitor中的内存消耗并没有记录WebView,而手机上工具显示的一定是准确有效的,所以,不要说我看AndroidStudio里面的内存并没有增加啊就误以为你的内存控制策略真的有效。
好了,我们来看一下一个有效的WebView内存控制策略是如何的。

用代码New一个WebView而不是在XML中静态写入

这个步骤是之后所有步骤的前提,如果你的WebView是在XML中静态存在的,那么本篇博文后面的方法将不会起到效果。
那么如何通过代码New一个WebView呢?

在XML文件中用layout占位

我们可以通过建立一个layout(无论是RelativeLayout亦或是LinearLayout)来作为WebView的容器(Container)。
一个简单的XML代码片段如下:

1
2
3
4
5
<!-- webViewContainer-->
<RelativeLayout
android:id="@+id/base_web_view_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />

在对应的Activity中通过代码添加

接下来只需要在Activity中New一个WebView并且添加到我们的容器中就ok了。
同样奉上代码片段:

1
2
3
4
5
6
RelativeLayout webview_container = (RelativeLayout) findViewById(R.id.base_web_view_container);
web_view_ = new WebView(yourApplicationContext);
web_view_.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
web_view_.setOnWebCallback(yourWebCallback);
webview_container.addView(web_view_);

这里特别说明几点:
关于WebView的context应该用Activity还是Application的context,这里网上较为一致的观点都是采用Application的,理由是这样不会造成Activity的context的内存泄漏。
不过我用LearCanary检测并没有发现因为WebView引用了Activity的context而导致Activity的内存泄漏。
而网上说的引用Application的context会在WebView的某些特殊动作是产生由Application到Activity的类型转换错误,关于这个错误我也并没有遇到过,而我的WebView也是负责了相当多的操作,例如视频播放,弹窗处理等等,所以,不要一味地听信别人的结论,一定要实实在在地动手写一遍。
但是我这里依旧使用的是Application的Context,因为经过我的实际测试,在反复New和销毁WebView时,采用Application要比采用Activity的context要稳定地少用20~30M左右的内存,虽然他们的内存都维持在一个稳定的消耗水平,但总体看来,Application要比Activity少那么一点。

销毁时的动作

当我们使用完了一个WebView,要销毁时,需要做的事情其实也很简单,我们直接来看代码吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Description: release the memory of web view, otherwise it's resource will not be recycle.
* Created by Michael Lee on 7/18/16 20:38
*/

public void clearWebViewResource() {
if (web_view_ != null) {
LogUtils.d(TAG,"Clear webview's resources");
web_view_.removeAllViews();
// in android 5.1(sdk:21) we should invoke this to avoid memory leak
// see (https://coolpers.github.io/webview/memory/leak/2015/07/16/
// android-5.1-webview-memory-leak.html)
((ViewGroup) web_view_.getParent()).removeView(web_view_);
web_view_.setTag(null);
web_view_.clearHistory();
web_view_.destroy();
web_view_ = null;
}
}

在你要退出Activity或者单纯地想要销毁一个webview时,调用这个方法就好了。

大家一定有注意到我的代码中有一行代码前面加了很多的注释。这里面其实是一段辛酸的故事:
笔者的程序在大部分机型上都测试的没有问题,但是唯独在小米的几个机型(小米4和红米)上,总是无缘无故出现内存泄漏而导致闪退,最后定位确实是因为WebView的某些原因导致的,但是又苦于找不到是什么原因,因为在别的手机上无法重现,唯独小米的手机上,在WebView之间切换时,不定期就会闪退。
但是即便在程序中安装了LeakCanary,也没有检测到哪里发生了内存泄漏。
直到第二天,我在其他手机上运行时,突然检测到一个mComponentCallbacks导致的内存泄漏,于是就在google上搜到了这篇文章,里面提到是由于Android5.1中的一个bug引起的新的内存泄漏点,正好我的手机也是5.1系统的,于是就出现了这个内存泄漏,于是我就按照帖子里给出方法,由webview的父view手动把它remove出去。
结果,这一行简单的代码((ViewGroup) web_view_.getParent()).removeView(web_view_);竟然顺便修好了小米手机上闪退的问题,虽然那几台小米手机都不是Android5.1版本的,但是问题就这么随之修复了。。。


好了,这就是在我的工程中切实有效的优化WebView的所有代码了。当然,我也在网上找到了微信开创的单独启用一个线程来进行webview的相关操作,然后退出时直接杀死这个线程来解决webview的内存泄漏的问题,但是和我的工程不太相符,因为这种方法要求你的webview在单独的Activity中,这样你可以在finish activity时杀死进程;而我的程序是因为在一个activity中要显示多个webview,所以,那种方法显然不好使了。
总之,选择何种解法还是要根据你自己的工程来的。
还有就是,LearCanary确实是个很好用的工具,强烈推荐大家使用。

最后奉上参考文章:

Contents
  1. 1. 用代码New一个WebView而不是在XML中静态写入
    1. 1.1. 在XML文件中用layout占位
    2. 1.2. 在对应的Activity中通过代码添加
  2. 2. 销毁时的动作