76

我有一个使用嵌入 WebView 的 xml 布局的活动。我根本没有在我的活动代码中使用 WebView,它所做的只是坐在我的 xml 布局中并且可见。

现在,当我完成活动时,我发现我的活动没有从内存中清除。(我通过 hprof 转储检查)。如果我从 xml 布局中删除 WebView,则该活动将完全清除。

我已经尝试过

webView.destroy();
webView = null;

在我的活动的 onDestroy() 中,但这并没有多大帮助。

在我的 hprof 转储中,我的活动(名为“浏览器”)具有以下剩余的 GC 根(在调用destroy()它之后):

com.myapp.android.activity.browser.Browser
  - mContext of android.webkit.JWebCoreJavaBridge
    - sJavaBridge of android.webkit.BrowserFrame [Class]
  - mContext of android.webkit.PluginManager
    - mInstance of android.webkit.PluginManager [Class]  

我发现另一位开发人员也经历过类似的事情,请参阅 Filipe Abrantes 的回复: http ://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/

确实是一个非常有趣的帖子。最近,我很难解决我的 Android 应用程序上的内存泄漏问题。最后事实证明,我的 xml 布局包含一个 WebView 组件,即使没有使用,它也会阻止在屏幕旋转/应用重启后内存被 g-collection ......这是当前实现的错误,还是有什么具体到使用 WebViews 时需要做的事情

现在,不幸的是,博客或邮件列表上还没有关于这个问题的回复。因此我想知道,是 SDK 中的错误(可能类似于报告的http://code.google.com/p/android/issues/detail?id=2181中的 MapView 错误)还是如何完全获取活动使用嵌入的 webview 关闭内存

4

9 回答 9

57

我从上面的评论和进一步的测试中得出结论,问题是 SDK 中的一个错误:通过 XML 布局创建 WebView 时,活动作为 WebView 的上下文传递,而不是应用程序上下文。完成活动后,WebView 仍然保留对活动的引用,因此活动不会从内存中删除。我为此提交了一个错误报告,请参阅上面评论中的链接。

webView = new WebView(getApplicationContext());

请注意,此解决方法仅适用于某些用例,即如果您只需要在 web 视图中显示 html,没有任何 href 链接或对话框链接等。请参阅下面的评论。

于 2010-06-28T12:56:27.807 回答
35

我对这种方法有一些运气:

将 FrameLayout 作为容器放入您的 xml 中,我们将其称为 web_container。然后以编程方式对 WebView 进行广告,如上所述。onDestroy,将其从 FrameLayout 中移除。

假设这是您的 xml 布局文件中的某个位置,例如 layout/your_layout.xml

<FrameLayout
    android:id="@+id/web_container"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"/>

然后在膨胀视图后,将使用应用程序上下文实例化的 WebView 添加到您的 FrameLayout。onDestroy,调用 webview 的 destroy 方法并将其从视图层次结构中删除,否则您将泄漏。

public class TestActivity extends Activity {
    private FrameLayout mWebContainer;
    private WebView mWebView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.your_layout);

        mWebContainer = (FrameLayout) findViewById(R.id.web_container);
        mWebView = new WebView(getApplicationContext());
        mWebContainer.addView(mWebView);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mWebContainer.removeAllViews();
        mWebView.destroy();
    }
}

此外,FrameLayout 以及 layout_width 和 layout_height 都是从其工作的现有项目中任意复制的。我假设另一个 ViewGroup 可以工作,并且我确信其他布局尺寸也可以工作。

此解决方案还可以使用 RelativeLayout 代替 FrameLayout。

于 2011-11-04T14:46:02.360 回答
10

这是 WebView 的一个子类,它使用上述技巧来无缝避免内存泄漏:

package com.mycompany.view;

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;

/**
 * see http://stackoverflow.com/questions/3130654/memory-leak-in-webview and http://code.google.com/p/android/issues/detail?id=9375
 * Note that the bug does NOT appear to be fixed in android 2.2 as romain claims
 *
 * Also, you must call {@link #destroy()} from your activity's onDestroy method.
 */
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;
        }
    }
}

要使用它,只需在布局中将 WebView 替换为 NonLeakingWebView

                    <com.mycompany.view.NonLeakingWebView
                            android:layout_width="fill_parent"
                            android:layout_height="wrap_content"
                            ...
                            />

然后确保NonLeakingWebView.destroy()从您的活动的 onDestroy 方法调用。

请注意,此 Web 客户端应处理常见情况,但它可能不如常规 Web 客户端功能全面。例如,我还没有对它进行过闪存测试。

于 2012-01-20T23:52:31.097 回答
9

根据 user1668939 在这篇文章 ( https://stackoverflow.com/a/12408703/1369016 ) 上的回答,这就是我修复片段内 WebView 泄漏的方法:

@Override
public void onDetach(){

    super.onDetach();

    webView.removeAllViews();
    webView.destroy();
}

与 user1668939 的回答不同的是我没有使用任何占位符。只需在 WebvView 引用本身上调用 removeAllViews() 就可以了。

## 更新 ##

如果你和我一样,并且在几个片段中都有 WebView(并且你不想在所有片段中重复上面的代码),你可以使用反射来解决它。只需让您的 Fragments 扩展这个:

public class FragmentWebViewLeakFree extends Fragment{

    @Override
    public void onDetach(){

        super.onDetach();

        try {
            Field fieldWebView = this.getClass().getDeclaredField("webView");
            fieldWebView.setAccessible(true);
            WebView webView = (WebView) fieldWebView.get(this);
            webView.removeAllViews();
            webView.destroy();

        }catch (NoSuchFieldException e) {
            e.printStackTrace();

        }catch (IllegalArgumentException e) {
            e.printStackTrace();

        }catch (IllegalAccessException e) {
            e.printStackTrace();

        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

我假设您将 WebView 字段称为“webView”(是的,您的 WebView 引用必须是一个字段)。我还没有找到另一种独立于字段名称的方法(除非我遍历所有字段并检查每个字段是否来自 WebView 类,因为性能问题我不想这样做)。

于 2013-10-15T21:32:56.610 回答
3

在阅读http://code.google.com/p/android/issues/detail?id=9375之后,也许我们可以使用反射在 Activity.onDestroy 上将 ConfigCallback.mWindowManager 设置为 null 并在 Activity.onCreate 上恢复它。我不确定它是否需要一些权限或违反任何政策。这取决于 android.webkit 实现,它可能会在更高版本的 Android 上失败。

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();
}
于 2012-11-29T03:37:41.330 回答
3

我修复了令人沮丧的 Webview 的内存泄漏问题,如下所示:

(我希望这可以帮助很多人)

基础知识

  • 要创建 webview,需要一个引用(比如一个活动)。
  • 要杀死一个进程:

android.os.Process.killProcess(android.os.Process.myPid());可以调用。

转折点:

默认情况下,所有活动都在一个应用程序的同一进程中运行。(过程由包名定义)。但:

可以在同一个应用程序中创建不同的进程。

解决方案: 如果为一个activity创建了不同的进程,它的上下文可以用来创建一个webview。当这个进程被杀死时,所有引用这个活动的组件(在这种情况下是 webview)都被杀死,主要的理想部分是:

强制调用 GC 来收集这些垃圾(webview)。

求助代码:(一个简单的案例)

共有两个活动:说 A 和 B

清单文件:

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

开始 A 然后 B

A > B

B 是使用嵌入的 webview 创建的。

当在活动 B 上按下 backKey 时,会调用 onDestroy:

@Override
    public void onDestroy() {
        android.os.Process.killProcess(android.os.Process.myPid());
        super.onDestroy();
    }

这会杀死当前进程,即 com.processkill.p3

并带走引用它的 webview

注意:使用此 kill 命令时要格外小心。(由于明显的原因不推荐)。不要在活动中实现任何静态方法(在本例中为活动 B)。不要使用任何其他活动对此活动的任何引用(因为它将被杀死并且不再可用)。

于 2015-02-06T09:53:05.657 回答
2

如果多进程处理对您来说不是很大的努力,您可以尝试将 Web 活动放在单独的进程中并在活动被销毁时退出。

于 2013-03-11T09:05:00.947 回答
2

您需要在调用WebView.destroy().

WebView 的 destroy() 注释 - “此方法应在此 WebView 从视图系统中删除后调用。”

于 2016-05-17T20:07:47.473 回答
1

“应用程序上下文”解决方法存在问题:WebView尝试显示任何对话框时崩溃。例如,登录/通过表单提交时的“记住密码”对话框(还有其他情况吗?)。

它可以通过“记住密码”案例的WebView设置来修复。setSavePassword(false)

于 2012-09-27T04:37:04.130 回答