国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 系統 > Android > 正文

Android 5.1 WebView內存泄漏問題及快速解決方法

2019-10-23 18:29:14
字體:
來源:轉載
供稿:網友

問題背景

在排查項目內存泄漏過程中發現了一些由WebView引起的內存泄漏,經過測試發現該部分泄漏只會出現在android 5.1及以上的機型。雖然項目使用WebView的場景并不多,但秉承著一個泄漏都不放過的精神,我們肯定要把它給解決了。

遇到的問題

項目中使用WebView的頁面主要在FAQ頁面,問題也出現在多次進入退出時,發現內存占用大,GC頻繁。使用LeakCanary觀察發現有兩個內存泄漏很頻繁:

android,webview,內存泄漏

android,webview,內存泄漏

我們分析一下這兩個泄漏:

從圖一我們可以發現是WebView的ContentViewCore中的成員變量mContainerView引用著AccessibilityManager的mAccessibilityStateChangeListeners導致activity不能被回收造成了泄漏。

引用關系:mAccessibilityStateChangeListeners->ContentViewCore->WebView->SettingHelpActivity

從圖二可以發現引用關系是: mComponentCallbacks->AwContents->WebView->SettingHelpActivity

問題分析

我們找找mAccessibilityStateChangeListeners 與 mComponentCallbacks是在什么時候注冊的,我們先看看mAccessibilityStateChangeListeners

AccessibilityManager.java

private final CopyOnWriteArrayList<AccessibilityStateChangeListener>    mAccessibilityStateChangeListeners = new CopyOnWriteArrayList<>();/** * Registers an {@link AccessibilityStateChangeListener} for changes in * the global accessibility state of the system. * * @param listener The listener. * @return True if successfully registered. */public boolean addAccessibilityStateChangeListener(    @NonNull AccessibilityStateChangeListener listener) {  // Final CopyOnWriteArrayList - no lock needed.  return mAccessibilityStateChangeListeners.add(listener);}/** * Unregisters an {@link AccessibilityStateChangeListener}. * * @param listener The listener. * @return True if successfully unregistered. */public boolean removeAccessibilityStateChangeListener(    @NonNull AccessibilityStateChangeListener listener) {  // Final CopyOnWriteArrayList - no lock needed.  return mAccessibilityStateChangeListeners.remove(listener);}

上面這幾個方法是在AccessibilityManager.class中定義的,根據方法調用可以發現在ViewRootImpl初始化會調用addAccessibilityStateChangeListener 添加一個listener,然后會在dispatchDetachedFromWindow的時候remove這個listener。

既然是有remove的,那為什么會一直引用著呢?我們稍后再分析。

我們再看看mComponentCallbacks是在什么時候注冊的

Application.java

public void registerComponentCallbacks(ComponentCallbacks callback) {  synchronized (mComponentCallbacks) {    mComponentCallbacks.add(callback);  }}public void unregisterComponentCallbacks(ComponentCallbacks callback) {  synchronized (mComponentCallbacks) {    mComponentCallbacks.remove(callback);  }}

上面這兩個方法是在Application中定義的,根據方法調用可以發現是在Context 基類中被調用

/** * Add a new {@link ComponentCallbacks} to the base application of the * Context, which will be called at the same times as the ComponentCallbacks * methods of activities and other components are called. Note that you * <em>must</em> be sure to use {@link #unregisterComponentCallbacks} when * appropriate in the future; this will not be removed for you. * * @param callback The interface to call. This can be either a * {@link ComponentCallbacks} or {@link ComponentCallbacks2} interface. */public void registerComponentCallbacks(ComponentCallbacks callback) {  getApplicationContext().registerComponentCallbacks(callback);}/** * Remove a {@link ComponentCallbacks} object that was previously registered * with {@link #registerComponentCallbacks(ComponentCallbacks)}. */public void unregisterComponentCallbacks(ComponentCallbacks callback) {  getApplicationContext().unregisterComponentCallbacks(callback);}

根據泄漏路徑,難道是AwContents中注冊了mComponentCallbacks未反注冊么?

只有看chromium源碼才能知道真正的原因了,好在chromium是開源的,我們在android 5.1 Chromium源碼中找到我們需要的AwContents(自備梯子),看下在什么時候注冊了

AwContents.java

@Override    public void onAttachedToWindow() {      if (isDestroyed()) return;      if (mIsAttachedToWindow) {        Log.w(TAG, "onAttachedToWindow called when already attached. Ignoring");        return;      }      mIsAttachedToWindow = true;      mContentViewCore.onAttachedToWindow();      nativeOnAttachedToWindow(mNativeAwContents, mContainerView.getWidth(),          mContainerView.getHeight());      updateHardwareAcceleratedFeaturesToggle();      if (mComponentCallbacks != null) return;      mComponentCallbacks = new AwComponentCallbacks();      mContext.registerComponentCallbacks(mComponentCallbacks);    }    @Override    public void onDetachedFromWindow() {      if (isDestroyed()) return;      if (!mIsAttachedToWindow) {        Log.w(TAG, "onDetachedFromWindow called when already detached. Ignoring");        return;      }      mIsAttachedToWindow = false;      hideAutofillPopup();      nativeOnDetachedFromWindow(mNativeAwContents);      mContentViewCore.onDetachedFromWindow();      updateHardwareAcceleratedFeaturesToggle();      if (mComponentCallbacks != null) {        mContext.unregisterComponentCallbacks(mComponentCallbacks);        mComponentCallbacks = null;      }      mScrollAccessibilityHelper.removePostedCallbacks();      mNativeGLDelegate.detachGLFunctor();    }

在以上兩個方法中我們發現了mComponentCallbacks的蹤影,

在onAttachedToWindow的時候調用mContext.registerComponentCallbacks(mComponentCallbacks)進行注冊,

在onDetachedFromWindow中反注冊。

我們仔細看看onDetachedFromWindow中的代碼會發現

如果在onDetachedFromWindow的時候isDestroyed條件成立會直接return,這有可能導致無法執行mContext.unregisterComponentCallbacks(mComponentCallbacks);

也就會導致我們第一個泄漏,因為onDetachedFromWindow無法正常流程執行完也就不會調用ViewRootImp的dispatchDetachedFromWindow方法,那我們找下這個條件什么時候會為true

/**   * Destroys this object and deletes its native counterpart.   */  public void destroy() {    mIsDestroyed = true;    destroyNatives();  }

發現是在destroy中設置為true的,也就是說執行了destroy()就會導致無法反注冊。我們一般在activity中使用webview時會在onDestroy方法中調用mWebView.destroy();來釋放webview。根據源碼可以知道如果在onDetachedFromWindow之前調用了destroy那就肯定會無法正常反注冊了,也就會導致內存泄漏。

問題的解決

我們知道了原因后,解決就比較容易了,就是在銷毀webview前一定要onDetachedFromWindow,我們先將webview從它的父view中移除再調用destroy方法,代碼如下:

@Overrideprotected void onDestroy() {  super.onDestroy();  if (mWebView != null) {   ViewParent parent = mWebView.getParent();   if (parent != null) {     ((ViewGroup) parent).removeView(mWebView);   }   mWebView.removeAllViews();   mWebView.destroy();   mWebView = null;  }}

還有個問題,就是為什么在5.1以下的機型不會內存泄漏呢,我們看下4.4的源碼AwContents

/** * @see android.view.View#onAttachedToWindow() * * Note that this is also called from receivePopupContents. */public void onAttachedToWindow() {  if (mNativeAwContents == 0) return;  mIsAttachedToWindow = true;  mContentViewCore.onAttachedToWindow();  nativeOnAttachedToWindow(mNativeAwContents, mContainerView.getWidth(),      mContainerView.getHeight());  updateHardwareAcceleratedFeaturesToggle();  if (mComponentCallbacks != null) return;  mComponentCallbacks = new AwComponentCallbacks();  mContainerView.getContext().registerComponentCallbacks(mComponentCallbacks);}/** * @see android.view.View#onDetachedFromWindow() */public void onDetachedFromWindow() {  mIsAttachedToWindow = false;  hideAutofillPopup();  if (mNativeAwContents != 0) {    nativeOnDetachedFromWindow(mNativeAwContents);  }  mContentViewCore.onDetachedFromWindow();  updateHardwareAcceleratedFeaturesToggle();  if (mComponentCallbacks != null) {    mContainerView.getContext().unregisterComponentCallbacks(mComponentCallbacks);    mComponentCallbacks = null;  }  mScrollAccessibilityHelper.removePostedCallbacks();  if (mPendingDetachCleanupReferences != null) {    for (int i = 0; i < mPendingDetachCleanupReferences.size(); ++i) {      mPendingDetachCleanupReferences.get(i).cleanupNow();    }    mPendingDetachCleanupReferences = null;  }}

我們可以看到在onDetachedFromWindow方法上是沒有isDestroyed這個判斷條件的,這也證明了就是這個原因造成的內存泄漏。

問題的總結

使用webview容易造成內存泄漏,如果使用沒有正確的去釋放銷毀很容易造成oom。webview使用也有很多的坑,需多多測試。

以上這篇Android 5.1 WebView內存泄漏問題及快速解決方法就是小編分享給大家的全部內容了,希望能給大家一個參考,也希望大家多多支持VEVB武林網。


注:相關教程知識閱讀請移步到Android開發頻道。
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 泌阳县| 陈巴尔虎旗| 前郭尔| 慈溪市| 六枝特区| 广水市| 泗洪县| 四会市| 太仆寺旗| 九江市| 象州县| 曲松县| 梅河口市| 高碑店市| 常宁市| 洮南市| 汾阳市| 额济纳旗| 秦安县| 青神县| 界首市| 余干县| 枣强县| 峨边| 江西省| 会宁县| 九江县| 吉安市| 徐闻县| 通辽市| 梅河口市| 武乡县| 大埔区| 中阳县| 巴中市| 涿鹿县| 嘉祥县| 泾阳县| 军事| 保定市| 津南区|