欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android 5.1 WebView內(nèi)存泄漏問題及快速解決方法

 更新時間:2017年05月09日 11:39:39   投稿:jingxian  
下面小編就為大家?guī)硪黄狝ndroid 5.1 WebView內(nèi)存泄漏問題及快速解決方法。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

問題背景

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

遇到的問題

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

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

從圖一我們可以發(fā)現(xiàn)是WebView的ContentViewCore中的成員變量mContainerView引用著AccessibilityManager的mAccessibilityStateChangeListeners導(dǎo)致activity不能被回收造成了泄漏。

引用關(guān)系:mAccessibilityStateChangeListeners->ContentViewCore->WebView->SettingHelpActivity

從圖二可以發(fā)現(xiàn)引用關(guān)系是: 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中定義的,根據(jù)方法調(diào)用可以發(fā)現(xiàn)在ViewRootImpl初始化會調(diào)用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中定義的,根據(jù)方法調(diào)用可以發(fā)現(xiàn)是在Context 基類中被調(diào)用

/**
 * 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);
}

根據(jù)泄漏路徑,難道是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();
    }

在以上兩個方法中我們發(fā)現(xiàn)了mComponentCallbacks的蹤影,

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

在onDetachedFromWindow中反注冊。

我們仔細看看onDetachedFromWindow中的代碼會發(fā)現(xiàn)

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

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

/**

   * Destroys this object and deletes its native counterpart.

   */

  public void destroy() {

    mIsDestroyed = true;

    destroyNatives();

  }

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

問題的解決

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

@Override
protected 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以下的機型不會內(nèi)存泄漏呢,我們看下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這個判斷條件的,這也證明了就是這個原因造成的內(nèi)存泄漏。

問題的總結(jié)

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

以上這篇Android 5.1 WebView內(nèi)存泄漏問題及快速解決方法就是小編分享給大家的全部內(nèi)容了,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

最新評論