有些东西还是记下来比较好,记记随笔,欢迎批评建议。
前段时间在项目中就用到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(); }
这个方法亲测有效。