2010-06-28 79 views
69

我有一個使用嵌入了WebView的xml佈局的活動。我沒有在我的活動代碼中使用WebView,它只是坐在我的xml佈局中並且可見。WebView中的內存泄漏

現在,當我完成活動時,我發現我的活動沒有從內存中清除。 (我通過hprof轉儲檢查)。如果我從xml佈局中刪除WebView,該活動完全清除。

我已經在自己的活動的onDestroy()嘗試了

webView.destroy(); 
webView = null; 

,但不會有什麼幫助。

在我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佈局包括網頁視圖 組件,即使不使用,是 防止存儲器被 G-收集......這是當前 的錯誤實現,或者是有一些具體的事情 一個需要做的時候 使用網頁視圖

現在,不幸的是出現了關於這個問題的博客或郵件列表沒有答覆呢。因此,我想知道,是SDK中的錯誤(可能類似於報告的http://code.google.com/p/android/issues/detail?id=2181的MapView錯誤)或如何使用webview嵌入式內存完全關閉內存活動

+0

如果您動態創建WebView,會發生這種情況嗎? – ktingle 2010-06-28 08:40:50

+1

我剛剛測試過,但沒有什麼區別。 – 2010-06-28 10:55:55

+1

同時,我在http://code.google.com/p/android/issues/detail?id=9375上提交了一個錯誤報告,但也許有人對此有一個解決方法;那麼請張貼它。 – 2010-06-28 11:03:48

回答

51

我從上面的評論和進一步測試得出結論,問題是SDK中存在一個錯誤:當通過XML佈局創建WebView時,活動作爲WebView的上下文而不是應用程序上下文傳遞。完成活動後,WebView仍然保持對活動的引用,因此活動不會從內存中移除。 我爲此提交了一個錯誤報告,請參閱上面評論中的鏈接。

webView = new WebView(getApplicationContext()); 

注意,這種解決方法只適用於特定的使用情況,也就是說,如果你只需要在網頁視圖中顯示HTML,沒有任何的href鏈接,也不鏈接對話框等請參見下面的註釋。

+1

在創建WebView時,getApplicationContext()實際上對我的內存泄漏有效。但是當我將webview添加到另一個ViewGroup時,內存泄漏再次出現。我瘋狂的猜測是採用父級的baseContext。有沒有任何工作?我也用getApplicationContext()創建了父類...所以我想我的理論已經出來 – weakwire 2011-09-20 01:34:55

+25

請注意,使用應用程序上下文意味着你將無法點擊你的web視圖中的鏈接,因爲這樣做會導致崩潰:「從Activity上下文外調用startActivity()需要FLAG_ACTIVITY_NEW_TASK標誌,這真的是你想要的嗎?」 – emmby 2012-01-20 23:47:54

+0

@emmby感謝提示,並沒有想到這一點,因爲我只是需要webview來顯示更復雜的HTML(超出了你可以用一個TextView做什麼),但沒有鏈接。因此,我沒有遇到你提到的問題,但謝謝你再次指出。 +1 – 2012-06-18 15:27:05

30

我有一些運氣使用這種方法:

將一個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的銷燬方法並將其從視圖層次結構中刪除,否則會泄漏。

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。

+1

這對我來說絕對有效,非常感謝!我所做的唯一改進是使用活動上下文而不是應用程序上下文,我期望它能夠將我從其他地方拯救出來 - 上面提到的崩潰發生時Flash或對話框出現在webview中 – SilithCrowe 2012-11-05 21:11:30

+0

+1這似乎是一個更適合我的解決方法。 – 2013-01-02 10:02:44

+0

太糟糕了,它不會似乎沒有爲我工作.. sConfigCallback仍然存在參考:/ – 2013-01-10 12:19:18

10

這裏的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; 
     } 
    } 
} 

要使用它,只是NonLeakingWebView取代的WebView在佈局

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

然後確保請從您活動的onDestroy方法中調用NonLeakingWebView.destroy()

請注意,這個web客戶端應該處理常見的情況,但它可能不像普通的webclient那樣功能全面。例如,我沒有爲諸如閃光之類的東西測試它。

+0

找到這種實現方式只有一個問題:在DialogFragment中有WebView我在構造函數中接收ContextThemeWrapper而不是Activity和ClassCastException。 – sandrstar 2012-03-22 10:29:29

+0

如果您在Web視圖中使用Flash內容,您至少會在Kindle Fire上從com.adobe.flashplayer.FlashPaintSurface中獲得ClassCastException。 – 2012-08-16 18:27:49

+0

2013年2月1日更新,以解決BrowserFrame.sConfigCallback – emmby 2013-02-02 02:40:17

1

「應用程序上下文」解決方法存在問題:當WebView嘗試顯示任何對話框時發生崩潰。例如,在登錄/傳遞表單子表單(任何其他情況?)中「記住密碼」對話框。

可以使用WebView設置「setSavePassword(false)」修復「記住密碼」的情況。

+0

另一種情況(在Galaxy Nexus,HTC Hero上覆制,但不在Galaxy Ace上):從下拉列表中選擇(這也觸發WebView顯示對話框) 。 – 2012-09-27 05:36:54

3

閱讀http://code.google.com/p/android/issues/detail?id=9375之後,也許我們可以使用反射來將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) { 
    } 
} 

在調用活動

public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setConfigCallback((WindowManager)getApplicationContext().getSystemService(Context.WINDOW_SERVICE)); 
} 

public void onDestroy() { 
    setConfigCallback(null); 
    super.onDestroy(); 
} 
+0

有沒有人對此進行過徹底測試,並確認其可靠工作? – jenzz 2012-12-18 13:44:12

+0

沒有違反的政策(關於隱藏的API或什麼),你只是沒有任何保證底層系統不會在更新中改變。潛在地,您可以將這些代碼封裝在API級別檢查中,並且只有在對它們進行測試後才允許它進行新的SDK修訂。 – powerj1984 2013-05-20 21:24:18

+0

我認爲這是一個很好的方法來解決它時,第一個創建的活動與WebView實例將被銷燬。這個問題讓我受到了很大的影響。但是當您嘗試使用WebView啓動另一個活動時,它可能會造成麻煩。 – qiuping345 2013-11-19 07:34:59

2

你可以嘗試把網絡活動在一個單獨的進程,當活動被銷燬,如果多進程處理是不是一個很大的努力,你退出上述方法。

7

基於user1668939的這個帖子上(https://stackoverflow.com/a/12408703/1369016)的答案,這是我的固定片段裏面我的WebView泄漏:

@Override 
public void onDetach(){ 

    super.onDetach(); 

    webView.removeAllViews(); 
    webView.destroy(); 
} 

從user1668939的回答不同的是,我沒有使用任何佔位符。只要在WebvView引用上調用removeAllViews()就可以了。

## UPDATE ##

如果你和我一樣,有網頁視圖內幾個片段(你不想重複在所有片段上面的代碼),你可以使用反射來解決它。只是讓你的碎片延長此一:

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場「web視圖」(是的,你的WebView參考必須是場不幸)。我還沒有找到另一種方式來做到這一點,將獨立於字段的名稱(除非我循環遍歷所有字段,並檢查每個字段是否來自WebView類,我不想爲性能問題做)。

+0

其實我發現這是最好的解決方案(至少對我來說)。我正在使用Xamarin Android,並且每次關閉WebView的活動時都會損失〜1 MB。 – Tobias81 2014-10-10 12:41:16

3

我沮喪網頁視圖像這樣的固定的內存泄漏問題:

(我希望這可以幫助到很多)

基礎

  • 要創建一個web視圖,參考(比方說一項活動)是必要的。
  • 殺死一個進程:

android.os.Process.killProcess(android.os.Process.myPid());可以被調用。

轉折點:

默認情況下,所有活動在同一個進程中的一個應用程序運行。 (該過程由軟件包名稱定義)。但是:

可以在同一個應用程序中創建不同的進程。

解決方案: 如果一個活動被創建一個不同的過程,它的上下文可以被用來創建一個網頁視圖。而當這一過程被殺死了,有這個活動(的WebView在這種情況下)引用所有組件都死亡,主要有利的部分是:

GC被強制稱爲收集這種垃圾(web視圖)。

代碼求助:(一個簡單的例子)

共有兩個活動:說一個&乙

清單文件:

<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是使用嵌入式視圖創建的。

當backKey壓在活動B,的onDestroy叫做:

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

和此殺死當前進程即com.processkill。P3

,並帶走它引用

注意web視圖:在使用本kill命令要格外小心。 (由於顯而易見的原因,不推薦)。不要在活動中實現任何靜態方法(在這種情況下,活動B)。不要使用任何其他任何對此活動的引用(因爲它會被殺死並且不再可用)。

+0

如何使用此方法處理webview中的導航(即重定向,登錄,鏈接,表單等)? – Megakoresh 2016-08-17 09:32:37

1

在調用WebView.destroy()之前,您需要從父視圖中刪除WebView。

WebView的destroy()註釋 - 「應在從視圖系統中刪除此WebView後調用此方法。」

+0

這是爲我解決了這個問題。將其添加到FrameLayout中,然後從該活動的onDestroy()中的FrameLayout中移除Web視圖。 – 2017-01-30 18:04:19