75 votes

Fuite de mémoire dans WebView

J'ai une activité à l'aide d'un xml disposition où une WebView est intégré. Je ne suis pas à l'aide de la WebView dans mon code d'activité à tous les, tous les il n'est assis là, dans mon xml de mise en page et d'être visible.

Maintenant, quand j'ai fini de l'activité, je trouve que mon activité n'est pas effacé de la mémoire. (J'ai vérifier, via hprof dump). L'activité est entièrement effacé mais si je retire la WebView de la mise en page xml.

J'ai déjà essayé un

webView.destroy();
webView = null;

dans onDestroy() de mon activité, mais qui ne l'aide pas beaucoup.

Dans mon hprof de vidage, de mon activité (appelé "Navigateur") a la suite reste GC racines (après les avoir appelés 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]  

J'ai trouvé un autre développeur a connu chose de similaire, voir la réponse de Filipe Abrantes: http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/

En effet, un post très intéressant. Récemment, j'ai eu un moment très difficile dépannage d'une fuite de mémoire sur mon Android app. En fin de compte il s'est avéré que mon xml de mise en page inclus une WebView composant qui, même si pas utilisé, a été la prévention de la mémoire de g-recueillies après l'écran de rotations/app redémarrer... est-ce un bug de l'actuel la mise en œuvre, ou est-il quelque chose spécifiques que l'on doit faire quand à l'aide de WebViews

Maintenant, malheureusement, il n'y a pas eu de réponse sur le blog ou la liste de diffusion à propos de cette question. Donc je me demandais, est-ce un bug dans le kit de développement (peut-être similaire à la MapView bug comme indiqué http://code.google.com/p/android/issues/detail?id=2181) ou comment faire pour obtenir l'activité entièrement hors de la mémoire avec une webview intégré?

57voto

Mathias Lin Points 16035

Je en conclure commentaires ci-dessus, et d'autres tests, que le problème est du à un bug dans le SDK: lors de la création d'une WebView via XML, mise en page, l'activité est passée dans le contexte de la WebView, pas le contexte de l'application. Lors de la finalisation de l'activité, la WebView conserve encore des références à l'activité, donc de l'activité n'est pas supprimé de la mémoire. J'ai déposé un rapport de bug pour cela , voir le lien dans le commentaire ci-dessus.

webView = new WebView(getApplicationContext());

Notez que cette solution de contournement ne fonctionne que pour certains cas d'utilisation, c'est à dire si vous avez juste besoin d'afficher du html dans une webview, sans aucune href liens, ni de liens vers des boîtes de dialogue, etc. Voir les commentaires ci-dessous.

35voto

caller9 Points 474

J'ai eu un peu de chance avec cette méthode:

Mettre un FrameLayout dans votre xml comme un conteneur, permet de l'appeler web_container. Puis par programmation ad la WebView comme mentionné ci-dessus. onDestroy, le retirer de la FrameLayout.

Dire que c'est quelque part dans votre xml fichier de mise en page par exemple layout/your_layout.xml

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

Puis, après que vous gonflez les afficher, ajouter la WebView instancié avec le contexte de l'application à votre FrameLayout. onDestroy, appelez la webview la méthode destroy et le supprimer de la vue hiérarchie ou de fuite.

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();
    }
}

Aussi FrameLayout ainsi que la layout_width et layout_height ont été arbitrairement copié à partir d'un projet existant où elle travaille. Je suppose que l'autre ViewGroup serait de travailler et je suis certaine disposition d'autres dimensions du travail.

Cette solution fonctionne aussi avec RelativeLayout en place de FrameLayout.

10voto

emmby Points 35359

Voici une sous-classe de WebView qui utilise le ci-dessus hack facilement éviter les fuites de mémoire:

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

Pour l'utiliser, il suffit de remplacer WebView avec NonLeakingWebView dans vos mises en page

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

Ensuite, assurez-vous d'appeler NonLeakingWebView.destroy() de votre activité de la méthode onDestroy.

Notez que ce service doit gérer la plupart des cas, mais il peut ne pas être aussi complet régulier webclient. Je n'ai pas testé pour des choses comme le flash, par exemple.

9voto

Tiago Points 1234

Basé sur user1668939 de réponse sur ce post (http://stackoverflow.com/a/12408703/1369016), c'est comment je fixe mes WebView fuite à l'intérieur d'un fragment:

@Override
public void onDetach(){

    super.onDetach();

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

La différence de user1668939 la réponse est que je n'ai pas utilisé l'un des espaces réservés. Juste appeler removeAllViews() sur le WebvView de référence lui-même a fait le tour.

## Mise à JOUR ##

Si vous êtes comme moi et que vous avez WebViews à l'intérieur de plusieurs fragments (et vous ne voulez pas répéter le code ci-dessus sur l'ensemble de vos fragments de), vous pouvez utiliser la réflexion pour le résoudre. Faites votre Fragments de prolonger celui-ci:

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();
        }
    }
}

Je suis en supposant que vous appelez votre WebView champ "webView" (et oui, votre WebView de référence doit être un champ malheureusement). Je n'ai pas trouvé une autre façon de faire qui serait indépendant du nom de domaine (à moins que je boucle à travers tous les champs et de vérifier si chacun d'eux est à partir d'une WebView classe, que je ne veux pas le faire pour les problèmes de performances).

3voto

raijintatsu Points 31

Après la lecture de http://code.google.com/p/android/issues/detail?id=9375peut-être qu'on pourrait utiliser la réflexion pour définir ConfigCallback.mWindowManager null sur l'Activité.onDestroy et de le restaurer sur l'Activité.onCreate. Je n'en suis pas sûr mais si il nécessite certaines autorisations ou qui enfreint la politique. Cela dépend de android.webkit mise en œuvre et il peut échouer sur les dernières versions d'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) {
    }
}

L'appel de la méthode ci-dessus dans l'Activité

public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setConfigCallback((WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
}

public void onDestroy() {
    setConfigCallback(null);
    super.onDestroy();
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X