Ich habe eine Aktivität mit einem XML-Layout, in das eine WebView eingebettet ist. Ich verwende das WebView überhaupt nicht in meinem Aktivitätscode. Es befindet sich lediglich in meinem XML-Layout und ist sichtbar.
Wenn ich 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 das WebView aus dem XML-Layout entferne.
Ich habe es schon versucht
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 (nachdem ich sie aufgerufen destroy()
habe):
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]
Ich habe festgestellt, dass ein anderer Entwickler ähnliche Erfahrungen gemacht hat. Siehe die Antwort von Filipe Abrantes unter: http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/
In der Tat ein sehr interessanter Beitrag. Vor kurzem fiel es mir sehr schwer, einen Speicherverlust 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 Bildschirmrotationen / App-Neustart gesammelt wird. Ist dies ein Fehler der aktuellen Implementierung oder gibt es etwas? spezifisch, dass man tun muss, wenn man WebViews verwendet
Leider gibt es im Blog oder in der Mailingliste noch keine Antwort auf diese Frage. Daher frage ich mich, ob dies ein Fehler im SDK ist (möglicherweise ähnlich dem MapView-Fehler, wie unter http://code.google.com/p/android/issues/detail?id=2181 gemeldet ) oder wie die Aktivität vollständig abgerufen werden kann aus dem Speicher mit einer eingebetteten Webansicht ?
quelle
Antworten:
Ich schließe aus den obigen Kommentaren und weiteren Tests, dass das Problem ein Fehler im SDK ist: Beim Erstellen einer WebView über ein XML-Layout wird die Aktivität als Kontext für die WebView übergeben, nicht als Anwendungskontext. Nach Abschluss der Aktivität behält das WebView weiterhin Verweise auf die Aktivität bei, sodass die Aktivität nicht aus dem Speicher entfernt wird. 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 ohne href-Links oder Links zu Dialogen usw. anzeigen müssen. Siehe die folgenden Kommentare.
quelle
Ich hatte etwas Glück mit dieser Methode:
Fügen Sie ein FrameLayout als Container in Ihre XML-Datei ein und nennen Sie es web_container. Fügen Sie dann programmgesteuert das WebView wie oben erwähnt hinzu. onDestroy, entferne es aus dem FrameLayout.
Angenommen, dies befindet sich irgendwo in Ihrer XML-Layoutdatei, z. B. layout / your_layout.xml
<FrameLayout android:id="@+id/web_container" android:layout_width="fill_parent" android:layout_height="wrap_content"/>
Fügen Sie dann nach dem Aufblasen der Ansicht die mit dem Anwendungskontext instanziierte WebView zu Ihrem FrameLayout hinzu. Rufen Sie bei Destroy die Zerstörungsmethode der Webansicht auf und entfernen Sie sie aus der Ansichtshierarchie. Andernfalls tritt ein Leck auf.
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(); } }
Auch FrameLayout sowie layout_width und layout_height wurden willkürlich aus einem vorhandenen Projekt kopiert, in dem es funktioniert. Ich gehe davon aus, dass eine andere ViewGroup funktionieren würde, und ich bin sicher, dass andere Layoutabmessungen funktionieren werden.
Diese Lösung funktioniert auch mit RelativeLayout anstelle von FrameLayout.
quelle
Hier ist eine Unterklasse von WebView, die den oben genannten 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 WebView in Ihren Layouts einfach durch NonLeakingWebView
<com.mycompany.view.NonLeakingWebView android:layout_width="fill_parent" android:layout_height="wrap_content" ... />
NonLeakingWebView.destroy()
Stellen Sie dann sicher, dass Sie die onDestroy-Methode Ihrer Aktivität aufrufen.Beachten Sie, dass dieser Webclient die häufigsten Fälle behandeln sollte, jedoch möglicherweise nicht so umfassend ist wie ein normaler Webclient. Ich habe es zum Beispiel nicht für Dinge wie Flash getestet.
quelle
Basierend auf der Antwort von user1668939 in diesem 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. Das Aufrufen von removeAllViews () in der WebvView-Referenz selbst hat den Trick getan.
## UPDATE ##
Wenn Sie wie ich WebViews in mehreren Fragmenten haben (und den obigen Code nicht für alle Ihre Fragmente wiederholen möchten), können Sie ihn mithilfe von Reflection lösen. Lassen Sie einfach Ihre Fragmente dieses erweitern:
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(); } } }
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 Feldes ist (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).
quelle
Nachdem wir http://code.google.com/p/android/issues/detail?id=9375 gelesen haben , können wir ConfigCallback.mWindowManager mithilfe von Reflection in Activity.onDestroy auf null setzen und in Activity.onCreate wiederherstellen. 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 in 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) { } }
Aufruf der obigen Methode in Aktivität
public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setConfigCallback((WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE)); } public void onDestroy() { setConfigCallback(null); super.onDestroy(); }
quelle
Ich habe das Problem mit dem Speicherverlust von frustrierendem Webview wie folgt behoben:
(Ich hoffe das kann vielen helfen)
Grundlagen :
android.os.Process.killProcess(android.os.Process.myPid());
kann aufgerufen werden.Wendepunkt:
Standardmäßig werden alle Aktivitäten in einer Anwendung in demselben Prozess ausgeführt. (Der Prozess wird durch den Paketnamen definiert). Aber:
Lösung: Wenn für eine Aktivität ein anderer Prozess erstellt wird, kann dessen Kontext zum Erstellen einer Webansicht verwendet werden. Wenn dieser Prozess abgebrochen wird, werden alle Komponenten mit Verweisen auf diese Aktivität (in diesem Fall die Webansicht) beendet, und der wichtigste wünschenswerte Teil ist:
Code für Hilfe: (ein einfacher Fall)
Insgesamt zwei Aktivitäten: sagen 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>
Starten Sie A und dann B.
B wird mit eingebetteter Webansicht erstellt.
Wenn bei Aktivität B der BackKey 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
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 keine Verweise auf diese Aktivität von anderen (da diese getötet werden und nicht mehr verfügbar sind).
quelle
Sie können versuchen, die Webaktivität in einen separaten Prozess zu versetzen und zu beenden, wenn die Aktivität zerstört wird, wenn die Verarbeitung mehrerer Prozesse für Sie keine große Anstrengung darstellt.
quelle
Sie müssen die WebView aus der übergeordneten Ansicht entfernen, bevor Sie sie aufrufen
WebView.destroy()
.Destroy () -Kommentar von WebView - "Diese Methode sollte aufgerufen werden, nachdem dieses WebView aus dem Ansichtssystem entfernt wurde."
quelle
Es gibt ein Problem mit der Problemumgehung "App-Kontext": Absturz, wenn
WebView
versucht wird, einen Dialog anzuzeigen. Zum Beispiel das Dialogfeld "Passwort speichern" beim Einreichen von Anmelde- / Passformularen (in anderen Fällen?).Es könnte mit
WebView
EinstellungensetSavePassword(false)
für den Fall "Passwort merken" behoben werden.quelle