在 Android H 版本之前,WebView存在一个令人恶心的BUG。
这个BUG的描述是这样子的:
1,如果传递给WebView的Context引用是 Activity’s Context,当退出这个Activity时,Activity不能被回收导致内存泄漏
2,如果传递给WebView的Context引用是Appliaction’s Context,这个WebView不能被显示dialog并开启其他的Activity,这明显不是我们 want 的
要解决这个问题,产生了一系列的解决办法:
第1种,在单独的进程中启动WebView,一旦WebView完成了使命,就可以杀死进程,以避免内存泄漏,如果WebView进错需要与主进程交换数据,则需要实现一些AIDL接口,下面是实现思路和举例
基本步骤
[1]. Basics基本步骤:
- To create a webview, a reference (say an activity) is needed.
- To kill a process:
`android.os.Process.killProcess(android.os.Process.myPid());
can be called.`
[2]. Turning point 重点思想:
默认情况下,所有活动在一个应用程序中在同一进程中运行 (该过程由包名称定义)。 但:
Different processes can be created within same application.
所以,如果为活动创建了不同的进程,则其上下文可用于创建WebView。 当这个进程被杀死时,所有引用这个活动的组件(在这种情况下是webview)被杀死,最主要的部分是:
GC is called forcefully to collect this garbage (webview).
[3]. Code for help: (one simple case)
Total two activities: say A & B
Manifest file:
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:process="com.processkill.p1" // can be given any name
android:theme="@style/AppTheme" >
<activity
android:name="com.processkill.A"
android:process="com.processkill.p2"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="com.processkill.B"
android:process="com.processkill.p3"
android:label="@string/app_name" >
</activity>
</application>
Start A then B
A > B
B 是带有WebView的
当在活动B上按backKey时,调用onDestroy:
@Override
public void onDestroy() {
android.os.Process.killProcess(android.os.Process.myPid());
super.onDestroy();
}
and this kills the current process i.e. com.processkill.p3
并删除引用它的webview
需要注意的是:
使用此kill命令时要格外小心。 不要在Activity(在这种情况下为ActivityB)中实现任何静态方法。 不要使用任何其他任何引用此Activity(因为它将被杀死,不再可用)。
第2种,重写WebView,代码如下
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.util.AttributeSet;
import android.webkit.WebView;
import android.webkit.WebViewClient;
public class NonLeakingWebView extends WebView {
private static Field sConfigCallback;
static {
try {
sConfigCallback = Class.forName("android.webkit.BrowserFrame")
.getDeclaredField("sConfigCallback");
sConfigCallback.setAccessible(true);
} catch (Exception e) {
// ignored
}
}
public NonLeakingWebView(Context context) {
super(context.getApplicationContext());
setWebViewClient(new MyWebViewClient((Activity) context));
}
public NonLeakingWebView(Context context, AttributeSet attrs) {
super(context.getApplicationContext(), attrs);
setWebViewClient(new MyWebViewClient((Activity) context));
}
public NonLeakingWebView(Context context, AttributeSet attrs, int defStyle) {
super(context.getApplicationContext(), attrs, defStyle);
setWebViewClient(new MyWebViewClient((Activity) context));
}
@Override
public void destroy() {
super.destroy();
try {
if (sConfigCallback != null)
sConfigCallback.set(null, null);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected static class MyWebViewClient extends WebViewClient {
protected WeakReference<Activity> activityRef;
public MyWebViewClient(Activity activity) {
this.activityRef = new WeakReference<Activity>(activity);
}
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
try {
final Activity activity = activityRef.get();
if (activity != null)
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri
.parse(url)));
} catch (RuntimeException ignored) {
// ignore any url parsing exceptions
}
return true;
}
}
}
///OnActivity finish or destroy call
@Override
public void finish() {
webView.destroy();
super.finish();
}
但是这种方法被认为:如果你的APP只有一个WebView,这种方法可能会很有效的规避内存泄漏,否则,每一个带有WebView的Activity都会出现leak,并且举出的原因
Please check the code:
Line 209-215
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.5_r1/android/webkit/BrowserFrame.java#BrowserFrame.ConfigCallback
这两处的源码阐述了,sConfigCallbacks的声明是一个静态的ArrayList并且用来保存WebView实例中BrowserFrame.ConfigCallback的引用。
当每次点击Back返回键并且onDestroy()在WebviewActivity中被调用,则BrowserFrame.sConfigCallback设置为null,然后,如果我们开启另外一个WebViewActivity,那么就会有一个WebView被创建,这时候如果BrowserFrame.sConfigCallback为Null,那么新的一个BrowserFrame.sConfigCallback实例被创建并被添加到静态ArrayList中,其中包括成员变量mWindowManger,mWindowManger间接的引用了WebViewActivity,所以内存泄漏发生了。
在Android L 5.1版本中,还存在一个WebView内存泄漏的问题,该问题的描述是这样的:
每新打开一次WebViewActivity,就会发生就会发生一次改Webview实例无法释放,新增一个对象。
该问题的分析可以点开连接:
https://coolpers.github.io/webview/memory/leak/2015/07/16/android-5.1-webview-memory-leak.html
总结这个泄漏的原因就是,webView的onDestroy 发生在 onDetach 之前。
在Android 5.1之前,不存在判断onDestroy,可以正常调用执行
if (mComponentCallbacks != null) {
mContext.unregisterComponentCallbacks(mComponentCallbacks);
mComponentCallbacks = null;
}
在Android 5.1时,会提前判断
f (isDestroyed()) return;
不能unregist,导致内存泄漏。
这个新的内存泄漏的规避方法如下:
在destroy之前,把webview 从 parent 中 remove 掉,同样可以提前detach。
protected void onDestroy() {
if (mWebView != null) {
((ViewGroup) mWebView.getParent()).removeView(mWebView);
mWebView.destroy();
mWebView = null;
}
super.onDestroy();
}