WebView造成内存泄露

关于WebView的内存泄露,因为WebView在加载网页后会长期占用内存而不能被释放,因此我们在Activity销毁后要调用它的destory()方法来销毁它以释放内存。

另外在查阅WebView内存泄露相关资料时看到这种情况:

Webview下面的Callback持有Activity引用,造成Webview内存无法释放,即使是调用了Webview.destory()等方法都无法解决问题(Android5.1之后)。

最终的解决方案是:在销毁WebView之前需要先将WebView从父容器中移除,然后在销毁WebView。详细分析过程请参考这篇文章:WebView内存泄漏解决方法

@Override
protected void onDestroy() {
super.onDestroy();
// 先从父控件中移除WebView
mWebViewContainer.removeView(mWebView);
mWebView.stopLoading();
mWebView.getSettings().setJavaScriptEnabled(false);
mWebView.clearHistory();
mWebView.removeAllViews();
mWebView.destroy();
}
前言:在项目的开发过程中,由于对内存要求较高,最近对应用的内存分析比较在意,前段时间监控图片内存,对Bitmap造成的内存泄漏进行了分析,并解决了问题。但是在图片内存泄漏之后,发现在访问网页的时候,webview竟然也会有内存泄漏,虽然内存占用很小,但是用户多次访问还是存在隐患。

于是,开始对webview进行内存分析,发现webview下面的callback持有activity引用,造成webview内存无法释放,在网上也找了很多方法,但是webview.destory()等方法大都无法解决问题。

最后看到一篇文章,才算明了出现这个bug的原因,按照作者的做法,确实解决了问题,安卓5.1和6.0系统都不存在内存泄漏问题。

文章附下:

销毁webview的方式

mWebView.removeAllViews();
/**、
* 这里内存泄漏了,因为它的父容器在退出前没有被销毁,所以就会持有引用,内存泄漏
* */
// mWebView.destroy();

改为

在 Android 5.1 系统上,在项目中遇到一个WebView引起的问题,每打开一个带webview的界面,退出后,这个activity都不会被释放,activity的实例会被持有,由于我们项目中经常会用到浏览web页面的地方,可能引起内存积压,导致内存溢出的现象,所以这个问题还是比较严重的。

问题分析

使用Android Studio的内存monitor,得到了以下的内存分析,我打开了三个BookDetailActivity界面(都有webview),检查结果显示有3个activity泄漏,如下图所示:

这个问题还是比较严重的,那么进一步看详细的信息,找出到底是哪里引起的内存泄漏,详情的reference tree如下图所示:

从上图中可以看出,在第1层中的 TBReaderApplication 中的 mComponentCallbacks 成员变量,它是一个array list,它里面会持有住activity,引导关系是 mComponentCallbacks->AwContents->BaseWebView->BookDetailActivity, 代码在 Application 类里面,代码如下所示:

public void registerComponentCallbacks(ComponentCallbacks callback) {
synchronized (mComponentCallbacks) {
mComponentCallbacks.add(callback);
}
} public void unregisterComponentCallbacks(ComponentCallbacks callback) {
synchronized (mComponentCallbacks) {
mComponentCallbacks.remove(callback);
}
}

上面两个方法,会在 Context 基类中被调用,代码如下:

/**
* Add a new {@link ComponentCallbacks} to the base application of the
* Context, which will be called at the same times as the ComponentCallbacks
* methods of activities and other components are called. Note that you
* <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when
* appropriate in the future; this will not be removed for you.
*
* @param callback The interface to call. This can be either a
* {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface.
*/
public void registerComponentCallbacks(ComponentCallbacks callback) {
getApplicationContext().registerComponentCallbacks(callback);
} /**
* Remove a {@link ComponentCallbacks} object that was previously registered
* with {@link #registerComponentCallbacks(ComponentCallbacks)}.
*/
public void unregisterComponentCallbacks(ComponentCallbacks callback) {
getApplicationContext().unregisterComponentCallbacks(callback);
}

从第二张图我们已经知道,是webview引起的内存泄漏,而且能看到是在 org.chromium.android_webview.AwContents 类中,难道是这个类注册了component callbacks,但是未反注册?一般按系统设计,都会反注册的,最有可能的原因就是某些情况下导致不能正常反注册,不多说,read the fucking source。基于这个思路,我把chromium的源码下载下来,代码在这里 chromium_org(https://android.googlesource.com/platform/external/chromium_org/?spm=5176.100239.blogcont61612.7.j9EPtE

然后找到 org.chromium.android_webview.AwContents 类,看看这两个方法 onAttachedToWindow 和 onDetachedFromWindow:

@Override
public void onAttachedToWindow() {
if (isDestroyed()) return;
if (mIsAttachedToWindow) {
Log.w(TAG, "onAttachedToWindow called when already attached. Ignoring");
return;
}
mIsAttachedToWindow = true; mContentViewCore.onAttachedToWindow();
nativeOnAttachedToWindow(mNativeAwContents, mContainerView.getWidth(),
mContainerView.getHeight());
updateHardwareAcceleratedFeaturesToggle(); if (mComponentCallbacks != null) return;
mComponentCallbacks = new AwComponentCallbacks();
mContext.registerComponentCallbacks(mComponentCallbacks);
} @Override
public void onDetachedFromWindow() {
if (isDestroyed()) return;
if (!mIsAttachedToWindow) {
Log.w(TAG, "onDetachedFromWindow called when already detached. Ignoring");
return;
}
mIsAttachedToWindow = false;
hideAutofillPopup();
nativeOnDetachedFromWindow(mNativeAwContents); mContentViewCore.onDetachedFromWindow();
updateHardwareAcceleratedFeaturesToggle(); if (mComponentCallbacks != null) {
mContext.unregisterComponentCallbacks(mComponentCallbacks);
mComponentCallbacks = null;
} mScrollAccessibilityHelper.removePostedCallbacks();
}

系统会在attach处detach进行注册和反注册component callback,注意到 onDetachedFromWindow() 方法的第一行,if (isDestroyed()) return;, 如果 isDestroyed() 返回 true 的话,那么后续的逻辑就不能正常走到,所以就不会执行unregister的操作,通过看代码,可以得到,调用主动调用 destroy()方法,会导致 isDestroyed() 返回 true。

/**
* Destroys this object and deletes its native counterpart.
*/
public void destroy() {
if (isDestroyed()) return;
// If we are attached, we have to call native detach to clean up
// hardware resources.
if (mIsAttachedToWindow) {
nativeOnDetachedFromWindow(mNativeAwContents);
}
mIsDestroyed = true;
new Handler().post(new Runnable() {
@Override
public void run() {
destroyNatives();
}
});
}

一般情况下,我们的activity退出的时候,都会主动调用 WebView.destroy() 方法,经过分析,destroy()的执行时间在onDetachedFromWindow之前,所以就会导致不能正常进行unregister()。

解决方案

找到了原因后,解决方案也比较简单,核心思路就是让onDetachedFromWindow先走,那么在主动调用之前destroy(),把webview从它的parent上面移除掉。

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

完整的代码如下:

public void destroy() {
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(); try {
mWebView.destroy();
} catch (Throwable ex) { }
}
}

Android 5.1之前的代码

对比了5.1之前的代码,它是不会存在这样的问题的,以下是kitkat的代码,它少了一行 if (isDestroyed()) return;,有点不明白,为什么google在高版本把这一行代码加上。

/**
* @see android.view.View#onDetachedFromWindow()
*/
public void onDetachedFromWindow() {
mIsAttachedToWindow = false;
hideAutofillPopup();
if (mNativeAwContents != 0) {
nativeOnDetachedFromWindow(mNativeAwContents);
} mContentViewCore.onDetachedFromWindow(); if (mComponentCallbacks != null) {
mContainerView.getContext().unregisterComponentCallbacks(mComponentCallbacks);
mComponentCallbacks = null;
} if (mPendingDetachCleanupReferences != null) {
for (int i = 0; i < mPendingDetachCleanupReferences.size(); ++i) {
mPendingDetachCleanupReferences.get(i).cleanupNow();
}
mPendingDetachCleanupReferences = null;
}
}

结束

在开发过程中,还发现一个支付宝SDK的内存问题,也是因为这个原因,具体的类是 com.alipay.sdk.app.H5PayActivity,我们没办法,也想了一个不是办法的办法,在每个activity destroy时,去主动把 H5PayActivity 中的webview从它的parent中移除,但这个问题限制太多,不是特别好,但的确也能解决问题,方案如下:

/**
* 解决支付宝的 com.alipay.sdk.app.H5PayActivity 类引起的内存泄漏。
*
* <p>
* 说明:<br>
* 这个方法是通过监听H5PayActivity生命周期,获得实例后,通过反射将webview拿出来,从
* 它的parent中移除。如果后续支付宝SDK官方修复了该问题,则我们不需要再做什么了,不管怎么
* 说,这个方案都是非常恶心的解决方案,非常不推荐。同时,如果更新了支付宝SDK后,那么内部被混淆
* 的字段名可能更改,所以该方案也无效了。
* </p>
*
* @param activity
*/
public static void resolveMemoryLeak(Activity activity) {
if (activity == null) {
return;
} String className = activity.getClass().getCanonicalName();
if (TextUtils.equals(className, "com.alipay.sdk.app.H5PayActivity")) {
Object object = Reflect.on(activity).get("a"); if (DEBUG) {
LogUtils.e(TAG, "AlipayMemoryLeak.resolveMemoryLeak activity = " + className
+ ", field = " + object);
} if (object instanceof WebView) {
WebView webView = (WebView) object;
ViewParent parent = webView.getParent();
if (parent instanceof ViewGroup) {
((ViewGroup) parent).removeView(webView);
}
}
}
}

Android内存优化14 内存泄漏常见情况5 特殊对象造成的内存泄漏 WebView内存泄漏的更多相关文章

  1. Android内存优化10 内存泄漏常见情况1 静态泄漏

    1,内存泄漏到本质是该释放的对象被持久化的对象引用了,造成持久化的常见情况有1,静态持久化 2,线程持久化 线程持久化 因为存活的线程是有dvk虚拟久直接持有,所以存活的线程都是持久化的 内存泄漏1: ...

  2. Android内存优化12 内存泄漏常见情况3 注册泄漏

    android 中有很多注册和反注册,由于在注册后,上下文自身会被持久化的观察者列表所持有,如果不进行反注册,就会造成内存泄漏 内存泄漏1:Sensor Manager 代码如下: MainActiv ...

  3. Android内存优化11 内存泄漏常见情况2 内部类泄漏

    线程持久化 Java中的Thread有一个特点就是她们都是直接被GC Root所引用,也就是说Dalvik虚拟机对所有被激活状态的线程都是持有强引用,导致GC永远都无法回收掉这些线程对象,除非线程被手 ...

  4. Android内存优化13 内存泄漏常见情况4 资源泄漏

    资源未关闭或释放导致内存泄露 在使用IO.File流或者Sqlite.Cursor等资源时要及时关闭.这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放, ...

  5. Android性能优化:手把手带你全面了解 内存泄露 & 解决方案

    . 简介 即 ML (Memory Leak)指 程序在申请内存后,当该内存不需再使用 但 却无法被释放 & 归还给 程序的现象2. 对应用程序的影响 容易使得应用程序发生内存溢出,即 OOM ...

  6. ANDROID内存优化——大汇总(转)

    原文作者博客:转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! ANDROID内存优化(大汇总——上) 写在最前: 本文的思路主要借鉴了20 ...

  7. ANDROID内存优化(大汇总——中)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

  8. Android 性能优化 - 详解内存优化的来龙去脉

    前言 APP内存的使用,是评价一款应用性能高低的一个重要指标.虽然现在智能手机的内存越来越大,但是一个好的应用应该将效率发挥到极致,精益求精. 这一篇中我们将着重介绍Android的内存优化.本文的篇 ...

  9. Android内存优化大全(中)

    转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 写在最前: 本文的思路主要借鉴了2014年AnDevCon开发者大会的一个演讲PPT,加上 ...

随机推荐

  1. [How to]如何通过xib来自定义UIViewController

    代码:https://github.com/xufeng79x/CreateControllerByXib 1.简介 UIViewController实例可以通过代码.storyborad或者xib方 ...

  2. tornado 模版

    tornado 模版语法 取消转义 : 取消项目转义 :autoescape = None 取消模版转义:{% autoescape None %} 取消行转义   :{% raw bd %} 强制转 ...

  3. spring mvc注解文件上传下载

    需要两个包: 包如何导入就不介绍了,前端代码如下(一定要加enctype="multipart/form-data"让服务器知道是文件上传): <form action=&q ...

  4. CountDownLatch源码浅析

    Cmd Markdown链接 CountDownLatch源码浅析 参考好文: JDK1.8源码分析之CountDownLatch(五) Java并发之CountDownLatch源码分析 Count ...

  5. python 函数内置方法short_desc

    1. 给函数设置一个文本 def action_checked(self, request): pass action_checked.short_desc = "签到" # sh ...

  6. python基础(3)---流程控制

    流程控制 与C语言不通的是python的流程控制代码块不是用{}花括号表示的,而是强制缩进来控制的:而且缩进必须一致,官方推荐是使用4个空格,不建议使用tab(制表符)做缩进,一是不同的系统tab所占 ...

  7. C#发送Post请求,带参数,不带参数,指定参数

    1.不带参数发送Post请求 /// <summary> /// 指定Post地址使用Get 方式获取全部字符串 /// </summary> /// <param na ...

  8. string char * const char *之间的互相转换

    string  ->   const char * 用str的c_str()方法或者data()方法均可,这个两个方法返回值为cong char * string str = "hel ...

  9. 【初探移动前端开发04】jQuery Mobile 一

    网格布局 jquery mobile提供一种多列布局功能,由于移动设备的屏幕大小原因,一般情况还是不要使用多列布局啦. jquery mobile提供一种css样式规则来定义多列布局,对应css为ui ...

  10. codeforces 某套题s : surf(贪心 || 动态规划)

    题目: Now that you’ve come to Florida and taken up surfing, you love it! Of course, you’ve realized th ...