我有一个使用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]
我发现其他开发者经历过类似的事情,请参阅Filipe Abrantes的回复: http://www.curious-creature.org/2008/12/18/avoid-memory-leaks-on-android/
确实是一个非常有趣的帖子。 最近我度过了一段非常艰难的时光 解决我的内存泄漏 Android应用。事实证明最终结果 我的xml布局包含一个WebView 即使没有使用,也是的组件 防止记忆 在屏幕旋转/ app后收集g 重启......这是当前的一个错误 实施,或者有什么 具体是一个人需要做的时候 使用WebViews
现在,遗憾的是,博客或邮件列表上还没有关于此问题的回复。因此我想知道,这是SDK中的一个错误(可能类似于报告http://code.google.com/p/android/issues/detail?id=2181的MapView错误)或如何通过嵌入的webview将活动完全从内存中取出?< / p>
答案 0 :(得分:53)
我从上面的评论和进一步的测试中得出结论,问题是SDK中的一个错误:当通过XML布局创建WebView时,活动作为WebView的上下文传递,而不是应用程序上下文。完成活动后,WebView仍会保留对活动的引用,因此活动不会从内存中删除。 我提交了一份错误报告,请参阅上面评论中的链接。
webView = new WebView(getApplicationContext());
请注意,此解决方法仅适用于某些用例,即如果您只需要在webview中显示html,没有任何href-links或指向对话框的链接等。请参阅下面的评论。
答案 1 :(得分:32)
我对这种方法运气不错:
将一个FrameLayout作为容器放在xml中,我们称之为web_container。然后以编程方式广告WebView,如上所述。 onDestroy,将其从FrameLayout中删除。
说这是你的xml布局文件中的某个地方,例如布局/ your_layout.xml
<FrameLayout
android:id="@+id/web_container"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
然后在扩充视图后,将使用应用程序上下文实例化的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。
答案 2 :(得分:10)
这是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 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;
}
}
}
要使用它,只需在布局中用NonLeakingWebView替换WebView
<com.mycompany.view.NonLeakingWebView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
...
/>
然后确保从您的活动的onDestroy方法中调用NonLeakingWebView.destroy()
。
请注意,此webclient应处理常见情况,但可能不像常规Web客户端那样功能齐全。例如,我没有对像flash这样的东西进行测试。
答案 3 :(得分:7)
基于user1668939对这篇文章(https://stackoverflow.com/a/12408703/1369016)的回答,这就是我在片段中修复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类,我不想为性能问题做)。
答案 4 :(得分:3)
在阅读http://code.google.com/p/android/issues/detail?id=9375之后,也许我们可以使用反射在Activity.onDestroy上将ConfigCallback.mWindowManager设置为null并在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();
}
答案 5 :(得分:3)
我修复了像这样令人沮丧的Webview的内存泄漏问题:
(我希望这对许多人有帮助)
<强>基本强>:
android.os.Process.killProcess(android.os.Process.myPid());
可以被调用。
转折点:
默认情况下,所有活动在一个应用程序中的同一进程中运行。 (该过程由包名称定义)。但是:
可以在同一个应用程序中创建不同的进程。
<强>解决方案:强> 如果为活动创建了不同的流程,则可以使用其上下文创建Web视图。当这个过程被杀死时,所有引用了这个活动的组件(在这种情况下都是webview)都被杀掉了,主要的好处是:
强制调用GC来收集垃圾(webview)。
帮助代码:(一个简单的案例)
总共两项活动:说A&amp;乙
清单文件:
<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&gt;乙
B是使用嵌入式webview创建的。
当在活动B上按下backKey时,会调用onDestroy:
@Override
public void onDestroy() {
android.os.Process.killProcess(android.os.Process.myPid());
super.onDestroy();
}
这会杀死当前进程,即com.processkill.p3
并删除引用它的webview
注意:使用此kill命令时要格外小心。 (由于显而易见的原因不推荐)。不要在活动中实现任何静态方法(在这种情况下为活动B)。不要使用任何其他参考此活动(因为它将被杀死,不再可用)。
答案 6 :(得分:2)
您可以尝试将Web活动放在单独的进程中,并在活动被销毁时退出,如果多进程处理对您来说不是很大的努力。
答案 7 :(得分:2)
在调用WebView.destroy()
之前,您需要从父视图中删除WebView。
WebView的destroy()注释 - “在从视图系统中删除此WebView后,应调用此方法。”
答案 8 :(得分:1)
“app context”解决方法存在问题:WebView
尝试显示任何对话框时崩溃。例如,在登录/传递表单子目录中记住“记住密码”对话框(任何其他情况?)。
可以使用WebView
设置'setSavePassword(false)
修复“记住密码”的情况。