Ich habe eine Aktivität, die ein XML-Layout verwendet, in das eine WebView eingebettet ist. Ich verwende die WebView überhaupt nicht in meinem Aktivitätscode, sie sitzt nur dort in meinem XML-Layout und ist sichtbar.
Wenn ich jetzt die Aktivität beende, stelle ich fest, dass meine Aktivität nicht aus dem Speicher gelöscht wird. (Ich überprüfe über hprof dump). Die Aktivität wird jedoch vollständig gelöscht, wenn ich die WebView aus dem XML-Layout entferne.
Ich habe schon versucht, a
webView.destroy();
webView = null;
in onDestroy() meiner Aktivität, aber das hilft nicht viel.
In meinem hprof-Dump hat meine Aktivität (mit dem Namen ‘Browser’) die folgenden verbleibenden GC-Wurzeln (nach dem Aufruf von destroy() drauf):
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]
In der Tat ein sehr interessanter Beitrag. Kürzlich hatte ich eine sehr harte Zeit, ein Speicherleck in meiner Android-App zu beheben. Am Ende stellte sich heraus, dass mein XML-Layout eine WebView-Komponente enthielt, die, selbst wenn sie nicht verwendet wurde, verhinderte, dass der Speicher nach Bildschirmdrehungen/App-Neustarts g-gesammelt wurde … ist dies ein Fehler der aktuellen Implementierung oder gibt es etwas Spezifisch, was man tun muss, wenn man WebViews verwendet
Nun, leider gibt es noch keine Antwort auf dem Blog oder der Mailingliste auf diese Frage. Daher frage ich mich, ob es sich um einen Fehler im SDK handelt (vielleicht ähnlich dem gemeldeten MapView-Fehler http://code.google.com/p/android/issues/detail?id=2181) oder wie Sie die Aktivität mit einer eingebetteten Webansicht vollständig aus dem Speicher entfernen?
Tritt dies auf, wenn Sie die WebView dynamisch erstellen?
– Kügelchen
28. Juni 10 um 8:40 Uhr
Das habe ich gerade getestet, macht aber keinen Unterschied.
In Ordnung. Ein weiterer Test mit geringfügiger Änderung: Wenn ich die WebView programmgesteuert erstelle und „this“ (die Aktivität) als Kontext festlege, bleibt die Aktivität weiterhin im Speicher. Wenn ich jedoch getApplicationContext() verwende, ist es in Ordnung und die Aktivität wird ohne beibehaltene Referenzen entfernt.
– Mathias Conradt
28. Juni 10 um 12:54 Uhr
Matthias Conradt
Ich schließe aus den obigen Kommentaren und weiteren Tests, dass das Problem ein Fehler im SDK ist: Beim Erstellen einer WebView über das XML-Layout wird die Aktivität als Kontext für die WebView übergeben, nicht der Anwendungskontext. Beim Beenden der Aktivität behält die WebView immer noch Verweise auf die Aktivität, daher wird die Aktivität nicht aus dem Speicher entfernt. Ich habe dafür einen Fehlerbericht eingereicht, siehe den Link im Kommentar oben.
webView = new WebView(getApplicationContext());
Beachten Sie, dass diese Problemumgehung nur für bestimmte Anwendungsfälle funktioniert, dh wenn Sie nur HTML in einer Webansicht anzeigen müssen, ohne Href-Links oder Links zu Dialogen usw. Siehe die Kommentare unten.
ty für dieses getApplicationContext() hat tatsächlich an meinem Speicherleck gearbeitet, als ich die WebView erstellt habe. Aber wenn ich die Webansicht zu einer anderen ViewGroup hinzufüge, erscheint das Speicherleck erneut. Meine wilde Vermutung ist, dass der baseContext des Elternteils übernommen wird. Gibt es dafür einen WorkAround? Ich erstelle den Elternteil auch mit getApplicationContext() … also habe ich wohl keine Theorie mehr
– Schwachdraht
20. September 11 um 1:34 Uhr
Beachten Sie, dass die Verwendung des Anwendungskontexts bedeutet, dass Sie in Ihrer Webansicht nicht auf Links klicken können, da dies zu einem Absturz führen würde: “Der Aufruf von startActivity() von außerhalb eines Aktivitätskontexts erfordert das FLAG_ACTIVITY_NEW_TASK-Flag. Ist das wirklich so was willst du?”
– Emma
20. Januar 12 um 23:47 Uhr
Außerdem stürzt die Webansicht jedes Mal ab, wenn sie versucht, einen Dialog zu erstellen (z. B. um sich ein Passwort zu merken usw.), da sie einen Aktivitätskontext erwartet.
– markschiz
30. August 2012 um 18:13 Uhr
Es zerquetscht die App, wenn WebView einen Dialog anzeigen möchte, zum Beispiel mit der Frage “Möchten Sie das Passwort speichern”, dann wird es zerquetscht 🙁
– David
30. Dezember 2012 um 20:52 Uhr
Dieser Fehler ist in Android 5.0 lilpops immer noch aktiv. Wir müssen also bedenken, dass wir dieses Fehlerproblem noch umgehen müssen.
– GFxJamal
18. Mai ’15 um 9:40 Uhr
Anrufer9
Ich hatte etwas Glück mit dieser Methode:
Fügen Sie ein FrameLayout als Container in Ihre XML-Datei ein, nennen wir es web_container. Fügen Sie dann die WebView wie oben erwähnt programmgesteuert hinzu. onDestroy, entfernen Sie es aus dem FrameLayout.
Angenommen, dies befindet sich irgendwo in Ihrer XML-Layoutdatei, z. B. layout/your_layout.xml
Nachdem Sie die Ansicht vergrößert haben, fügen Sie Ihrem FrameLayout die mit dem Anwendungskontext instanziierte WebView hinzu. onDestroy, rufen Sie die Destroy-Methode der Webansicht auf und entfernen Sie sie aus der Ansichtshierarchie, sonst werden Sie lecken.
Auch FrameLayout sowie layout_width und layout_height wurden willkürlich aus einem bestehenden Projekt kopiert, wo es funktioniert. Ich gehe davon aus, dass eine andere ViewGroup funktionieren würde, und ich bin mir sicher, dass andere Layoutdimensionen funktionieren werden.
Diese Lösung funktioniert auch mit RelativeLayout anstelle von FrameLayout.
Das hat bei mir absolut funktioniert. Vielen Dank! Die einzige Verbesserung, die ich vorgenommen habe, war die Verwendung des Aktivitätskontexts anstelle des Anwendungskontexts, von dem ich erwarte, dass er mich vor den an anderer Stelle erwähnten Abstürzen bewahren wird, die auftreten, wenn Flash oder Dialoge in der Webansicht erscheinen.
– SilithCrowe
5. November 12 um 21:11 Uhr
Schade, dass es bei mir nicht zu funktionieren scheint. Der sConfigCallback ist immer noch da und enthält die Referenz :/
– Surya Wijaya Madjid
10. Januar 13 um 12:19 Uhr
Emma
Hier ist eine Unterklasse von WebView, die den obigen Hack verwendet, um Speicherlecks nahtlos zu vermeiden:
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;
}
}
}
Um es zu verwenden, ersetzen Sie einfach WebView in Ihren Layouts durch NonLeakingWebView
Dann unbedingt anrufen NonLeakingWebView.destroy() aus der onDestroy-Methode Ihrer Aktivität.
Beachten Sie, dass dieser Webclient die üblichen Fälle verarbeiten sollte, aber möglicherweise nicht so umfassend wie ein normaler Webclient ist. Ich habe es zum Beispiel nicht auf Dinge wie Flash getestet.
Bei dieser Art der Implementierung wurde nur ein Problem gefunden: Mit WebView in DialogFragment habe ich ContextThemeWrapper im Konstruktor anstelle von Activity und damit ClassCastException erhalten.
– Sandstern
22. März 2012 um 10:29 Uhr
Wenn Sie Flash-Inhalte in der Webansicht haben, erhalten Sie eine ClassCastException von com.adobe.flashplayer.FlashPaintSurface … zumindest auf dem Kindle Fire.
– Christoph Perry
16. August 2012 um 18:27 Uhr
Am 1. Februar 2013 aktualisiert, um das zusätzliche Leck in BrowserFrame.sConfigCallback zu umgehen
– Emma
2. Februar 13 um 2:40 Uhr
Für mich löst sich die Erinnerung immer noch nicht auf
– Tarun Tak
8. Februar 13 um 12:17 Uhr
Stellen Sie sicher, dass Sie NonLeakingWebView.destroy() in onDestroy() Ihrer Aktivität aufrufen
– Emma
8. Februar 13 um 22:54 Uhr
Bitcoin Cash – ADA-Enthusiast
Basierend auf der Antwort von Benutzer1668939 auf diesen Beitrag (https://stackoverflow.com/a/12408703/1369016) habe ich mein WebView-Leck in einem Fragment folgendermaßen behoben:
@Override
public void onDetach(){
super.onDetach();
webView.removeAllViews();
webView.destroy();
}
Der Unterschied zur Antwort von user1668939 besteht darin, dass ich keine Platzhalter verwendet habe. Der Aufruf von removeAllViews() in der WebvView-Referenz selbst hat den Zweck erfüllt.
## AKTUALISIEREN ##
Wenn Sie wie ich sind und WebViews in mehreren Fragmenten haben (und Sie den obigen Code nicht über alle Ihre Fragmente hinweg wiederholen möchten), können Sie die Reflektion verwenden, um das Problem zu lösen. Lassen Sie einfach Ihre Fragmente dieses erweitern:
Ich gehe davon aus, dass Sie Ihr WebView-Feld “webView” nennen (und ja, Ihre WebView-Referenz muss leider ein Feld sein). Ich habe keinen anderen Weg gefunden, der unabhängig vom Namen des Felds wäre (es sei denn, ich durchlaufe alle Felder und überprüfe, ob jedes aus einer WebView-Klasse stammt, was ich aus Leistungsgründen nicht tun möchte).
Nach dem Lesen http://code.google.com/p/android/issues/detail?id=9375, vielleicht könnten wir Reflektion verwenden, um ConfigCallback.mWindowManager auf Activity.onDestroy auf null zu setzen und es auf Activity.onCreate wiederherzustellen. Ich bin mir jedoch nicht sicher, ob es einige Berechtigungen erfordert oder gegen eine Richtlinie verstößt. Dies hängt von der Implementierung von android.webkit ab und kann bei späteren Versionen von Android fehlschlagen.
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) {
}
}
Aufrufen der obigen Methode in Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setConfigCallback((WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE));
}
public void onDestroy() {
setConfigCallback(null);
super.onDestroy();
}
Es gibt keine Richtlinie, die verletzt werden könnte (in Bezug auf versteckte APIs oder so weiter), Sie haben nur keine Garantie dafür, dass sich das zugrunde liegende System bei einem Update nicht ändert. Möglicherweise könnten Sie diesen Code in eine Überprüfung auf API-Ebene einschließen und ihn nur für neue SDK-Revisionen zulassen, nachdem Sie mit ihnen getestet haben.
– powerj1984
20. Mai ’13 um 21:24 Uhr
Ich denke, dies ist eine gute Möglichkeit, das Problem zu beheben, wenn die erste erstellte Activity with WebView-Instanz zerstört wird. Ich habe sehr unter diesem Problem gelitten. Aber es KANN zu Problemen führen, wenn Sie versuchen, eine andere Aktivität mit WebView zu starten.
– qiuping345
19. November 13 um 7:34 Uhr
Ich habe das Problem mit Speicherlecks bei frustrierenden Webviews wie folgt behoben:
(ich hoffe das hilft vielen)
Grundlagen:
Um eine Webansicht zu erstellen, wird eine Referenz (z. B. eine Aktivität) benötigt.
So beenden Sie einen Prozess:
android.os.Process.killProcess(android.os.Process.myPid()); kann angerufen werden.
Wendepunkt:
Standardmäßig werden alle Aktivitäten im selben Prozess in einer Anwendung ausgeführt. (Der Prozess wird durch den Paketnamen definiert). Aber:
Innerhalb derselben Anwendung können verschiedene Prozesse erstellt werden.
Lösung:
Wenn für eine Aktivität ein anderer Prozess erstellt wird, kann sein Kontext verwendet werden, um eine Webansicht zu erstellen. Und wenn dieser Prozess beendet wird, werden alle Komponenten mit Verweisen auf diese Aktivität (in diesem Fall Webview) beendet und der wichtigste wünschenswerte Teil ist:
GC wird nachdrücklich aufgerufen, diesen Müll einzusammeln (Webview).
Code für Hilfe: (ein einfacher Fall)
Insgesamt zwei Aktivitäten: Sagen Sie A & B
Manifestdatei:
<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>
Beginnen Sie mit A, dann mit B
A > B
B wird mit eingebetteter Webansicht erstellt.
Wenn backKey bei Aktivität B gedrückt wird, wird onDestroy aufgerufen:
@Override
public void onDestroy() {
android.os.Process.killProcess(android.os.Process.myPid());
super.onDestroy();
}
und dies beendet den aktuellen Prozess, dh com.processkill.p3
und nimmt die darauf referenzierte Webansicht weg
HINWEIS: Seien Sie besonders vorsichtig, wenn Sie diesen Kill-Befehl verwenden. (aus offensichtlichen Gründen nicht empfohlen). Implementieren Sie keine statische Methode in der Aktivität (in diesem Fall Aktivität B). Verwenden Sie keinen Verweis auf diese Aktivität von einer anderen (da sie gelöscht und nicht mehr verfügbar ist).
Es gibt keine Richtlinie, die verletzt werden könnte (in Bezug auf versteckte APIs oder so weiter), Sie haben nur keine Garantie dafür, dass sich das zugrunde liegende System bei einem Update nicht ändert. Möglicherweise könnten Sie diesen Code in eine Überprüfung auf API-Ebene einschließen und ihn nur für neue SDK-Revisionen zulassen, nachdem Sie mit ihnen getestet haben.
– powerj1984
20. Mai ’13 um 21:24 Uhr
Ich denke, dies ist eine gute Möglichkeit, das Problem zu beheben, wenn die erste erstellte Activity with WebView-Instanz zerstört wird. Ich habe sehr unter diesem Problem gelitten. Aber es KANN zu Problemen führen, wenn Sie versuchen, eine andere Aktivität mit WebView zu starten.
– qiuping345
19. November 13 um 7:34 Uhr
Fanny
Sie können versuchen, die Webaktivität in einen separaten Prozess zu stecken und zu beenden, wenn die Aktivität zerstört ist, wenn die Handhabung mehrerer Prozesse für Sie kein großer Aufwand ist.
Tritt dies auf, wenn Sie die WebView dynamisch erstellen?
– Kügelchen
28. Juni 10 um 8:40 Uhr
Das habe ich gerade getestet, macht aber keinen Unterschied.
– Mathias Conradt
28. Juni 10 um 10:55 Uhr
Inzwischen habe ich einen Fehlerbericht unter eingereicht code.google.com/p/android/issues/detail?id=9375 aber vielleicht hat jemand einen Workaround dafür; dann bitte posten.
– Mathias Conradt
28. Juni 10 um 11:03 Uhr
In Ordnung. Ein weiterer Test mit geringfügiger Änderung: Wenn ich die WebView programmgesteuert erstelle und „this“ (die Aktivität) als Kontext festlege, bleibt die Aktivität weiterhin im Speicher. Wenn ich jedoch getApplicationContext() verwende, ist es in Ordnung und die Aktivität wird ohne beibehaltene Referenzen entfernt.
– Mathias Conradt
28. Juni 10 um 12:54 Uhr