我有一个使用xml布局的活动,其中嵌入了WebView.我不是在我的活动代码中使用的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]
我发现,另一个开发经历了类似的事情,看到菲利佩阿布兰特什的回答上: http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/
确实是一个非常有趣的帖子 最近我在Android应用程序上对内存泄漏进行故障排除非常困难.最终事实证明,我的XML布局包括,即使不使用的WebView组件,是防止内存被G-收集屏幕转/应用程序重新启动后,...这是当前实现的一个bug,或者是有什么特定于使用WebView时需要执行的操作
现在,遗憾的是,博客或邮件列表中尚未对此问题做出回复.因此,我想知道,是在SDK中的错误(也许类似的MapView错误报道http://code.google.com/p/android/issues/detail?id=2181)或如何完全得到活动嵌入了webview的内存?
我从上面的评论和进一步的测试中得出结论,问题是SDK中的一个错误:当通过XML布局创建WebView时,活动作为WebView的上下文传递,而不是应用程序上下文.完成活动后,WebView仍会保留对活动的引用,因此活动不会从内存中删除.我提交了一份错误报告,请参阅上面评论中的链接.
webView = new WebView(getApplicationContext());
请注意,此解决方法仅适用于某些用例,即如果您只需要在Webview中显示html,没有任何href-links或指向对话框的链接等.请参阅下面的注释.
我对这个方法运气不错:
将FrameLayout作为容器放在xml中,我们称之为web_container.然后以编程方式广告WebView,如上所述.onDestroy,将其从FrameLayout中删除.
假设这是你的xml布局文件中的某个地方,例如layout/your_layout.xml
然后在向视图膨胀之后,将使用应用程序上下文实例化的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可以工作,我确信其他布局尺寸也可以.
此解决方案也适用于RelativeLayout而不是FrameLayout.
这是WebView的一个子类,它使用上面的hack来无缝地避免内存泄漏:
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 WeakReferenceactivityRef; public MyWebViewClient( Activity activity ) { this.activityRef = new WeakReference (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; } } }
要使用它,只需在布局中使用NonLeakingWebView替换WebView
然后确保NonLeakingWebView.destroy()
从您的activity的onDestroy方法调用.
请注意,此webclient应处理常见情况,但可能不像常规Web客户端那样功能齐全.例如,我没有测试过像flash这样的东西.
基于user1668939在这篇文章(/sf/ask/17360801/)上的答案,这就是我在片段中修复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类,我不想为性能问题做).