内存泄漏在WebView中

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

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

我已经试过了

webView.destroy(); webView = null; 

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

在我的hprof转储中,我的活动(名为'Browser')具有以下剩余的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应用程序内存泄漏。 最后,事实certificate,我的xml布局包括一个WebView组件,即使不使用,也会阻止内存在屏幕旋转/应用程序重新启动后被g收集…这是当前实现的缺陷,还是具体说明使用WebView时需要做的事情

现在,不幸的是,这个问题还没有在博客或邮件列表上得到答复。 因此,我想知道,SDK中是否存在一个错误(可能类似于http://code.google.com/p/android/issues/detail?id=2181报告的MapView错误)或者如何完全执行这个活动closures内存与WebViewembedded

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

 webView = new WebView(getApplicationContext()); 

请注意,这种解决方法只适用于某些使用情况,即如果您只需要在web视图中显示html,没有任何href链接或链接到对话框等,请参阅下面的注释。

这个方法我有一些运气:

把一个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的销毁方法,并从视图层次结构中删除它,否则你会泄漏。

 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将工作,我敢肯定其他布局维度将工作。

此解决scheme也适用于使用RelativeLayout来代替FrameLayout。

下面是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; } } } 

要使用它,只需在布局中用NonLeakingWebViewreplaceWebView即可

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

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

请注意,这个web客户端应该处理常见的情况,但是它可能不像普通的web客户端那样全function。 例如,我没有testing过像flash那样的东西。

基于user1668939在这个post( https://stackoverflow.com/a/12408703/1369016 )的答案,这是我如何解决我的WebView泄漏片段内:

 @Override public void onDetach(){ super.onDetach(); webView.removeAllViews(); webView.destroy(); } 

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

##更新##

如果你像我一样,并且在几个片段中都有WebViews(并且你不想在所有的片段中重复上面的代码),你可以使用reflection来解决它。 只要让你的碎片扩展这一个:

 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引用必须是一个字段不幸的)。 我还没有find另一种方法来独立于字段的名称(除非我循环遍历所有字段,并检查每个字段是否来自一个WebView类,我不想做性能问题)。

阅读http://code.google.com/p/android/issues/detail?id=9375之后; ,也许我们可以使用reflection来设置ConfigCallback.mWindowManager在Activity.onDestroy上为空,并在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(); } 

我修正了Webview令人沮丧的内存泄漏问题:

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

基础知识

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

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

转折点:

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

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

解决scheme:如果为一个活动创build了一个不同的进程,则可以使用它的上下文创build一个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是使用embedded式视图创build的。

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

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

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

并带走了引用它的webview

注:使用此kill命令时要格外小心。 (由于显而易见的原因,不推荐)。 不要在活动中实现任何静态方法(在这种情况下,活动B)。 不要使用任何其他任何引用此活动(因为它将被杀死,不再可用)。

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

“应用程序上下文”解决方法存在一个问题: WebView尝试显示任何对话框时发生崩溃。 例如,在login/传递表单子表单(任何其他情况?)上的“记住密码”对话框。

可以使用WebView设置的setSavePassword(false)来修复“记住密码”的情况。

在调用WebView.destroy()之前,需要从父视图中删除WebView。

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