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 レイアウトには、使用されていなくても、画面の回転/アプリの再起動後にメモリが g-collected されるのを妨げていた WebView コンポーネントが含まれていることが判明しました...これは現在の実装のバグですか、それとも何かありますか? WebViews を使用するときに行う必要がある特定の

残念ながら、この質問についてブログやメーリング リストにはまだ回答がありません。したがって、SDK のバグ (報告されたhttp://code.google.com/p/android/issues/detail?id=2181の MapView バグに似ている可能性があります) か、アクティビティを完全に取得する方法を考えています。ウェブビューが埋め込まれたメモリをオフにしますか?

4

9 に答える 9

57

上記のコメントとさらなるテストから、問題は SDK のバグであると結論付けました。XML レイアウトを介して WebView を作成する場合、アクティビティはアプリケーション コンテキストではなく、WebView のコンテキストとして渡されます。アクティビティを終了しても、WebView は引き続きアクティビティへの参照を保持するため、アクティビティはメモリから削除されません。そのためのバグレポートを提出しました。上記のコメントのリンクを参照してください。

webView = new WebView(getApplicationContext());

この回避策は、特定のユース ケースでのみ機能することに注意してください。つまり、href リンクやダイアログへのリンクなどを使用せずに、webview に html を表示する必要がある場合などです。以下のコメントを参照してください。

于 2010-06-28T12:56:27.807 に答える
35

私はこの方法で運が良かった:

xml に FrameLayout をコンテナーとして配置し、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 が機能すると仮定し、他のレイアウト ディメンションも機能すると確信しています。

このソリューションは、FrameLayout の代わりに RelativeLayout でも機能します。

于 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

この投稿 ( https://stackoverflow.com/a/12408703/1369016 )に対する user1668939 の回答に基づいて、フラグメント内の WebView リークを修正した方法を次に示します。

@Override
public void onDetach(){

    super.onDetach();

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

user1668939 の回答との違いは、プレースホルダーを使用していないことです。WebvView 参照自体で removeAllViews() を呼び出すだけでうまくいきました。

## アップデート ##

あなたが私のようで、複数のフラグメント内に WebView がある場合 (そして、すべてのフラグメントで上記のコードを繰り返したくない場合)、リフレクションを使用して解決できます。フラグメントにこれを拡張させるだけです:

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) {
    }
}

アクティビティで上記のメソッドを呼び出す

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 のメモリ リークの問題を修正しました。

(これが多くの人に役立つことを願っています)

基本:

  • Web ビューを作成するには、参照 (アクティビティなど) が必要です。
  • プロセスを強制終了するには:

android.os.Process.killProcess(android.os.Process.myPid());呼び出すことができます。

ターニングポイント:

デフォルトでは、すべてのアクティビティが 1 つのアプリケーションの同じプロセスで実行されます。(プロセスはパッケージ名で定義されます)。しかし:

同じアプリケーション内で異なるプロセスを作成できます。

解決策: アクティビティに対して別のプロセスが作成された場合、そのコンテキストを使用して Web ビューを作成できます。そして、このプロセスが強制終了されると、このアクティビティ (この場合は webview) への参照を持つすべてのコンポーネントが強制終了され、主な望ましい部分は次のとおりです。

このガベージ (webview) を収集するために GC が強制的に呼び出されます。

ヘルプのコード: (1 つの単純なケース)

合計 2 つのアクティビティ: 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 が強制終了されます

それに参照されているWebビューを取り除きます

注:この kill コマンドを使用するときは、特に注意してください。(明白な理由により推奨されません)。アクティビティ (この場合はアクティビティ B) に静的メソッドを実装しないでください。他のアクティビティからこのアクティビティへの参照を使用しないでください (強制終了されて利用できなくなるため)。

于 2015-02-06T09:53:05.657 に答える
2

マルチプロセスの処理が大きな労力ではない場合は、Web アクティビティを別のプロセスに入れて、アクティビティが破棄されたときに終了することができます。

于 2013-03-11T09:05:00.947 に答える
2

を呼び出す前に、親ビューから WebView を削除する必要がありますWebView.destroy()

WebView の destroy() コメント - 「この WebView がビュー システムから削除された後に、このメソッドを呼び出す必要があります。」

于 2016-05-17T20:07:47.473 に答える
1

「アプリ コンテキスト」の回避策には問題がありますWebView。ダイアログを表示しようとするとクラッシュします。たとえば、ログイン/パスフォーム送信時の「パスワードを記憶する」ダイアログ (他のケースはありますか?)。

「パスワードを記憶する」場合のWebView設定で修正できます。setSavePassword(false)

于 2012-09-27T04:37:04.130 に答える