[WebView] - WebView leaked

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixiao1999/article/details/62430488

在 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;

扫描二维码关注公众号,回复: 2936863 查看本文章
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

Line 298-301
http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/2.3.5_r1/android/view/ViewRoot.java#ViewRoot.addConfigCallback%28android.content.ComponentCallbacks%29

这两处的源码阐述了,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();
  }

猜你喜欢

转载自blog.csdn.net/weixiao1999/article/details/62430488