Android 5.1 WebView内存泄漏问题及快速解决方法

问题背景

在排查项目内存泄漏过程中发现了一些由WebView引起的内存泄漏,经过测试发现该部分泄漏只会出现在android 5.1及以上的机型。虽然项目使用WebView的场景并不多,但秉承着一个泄漏都不放过的精神,我们肯定要把它给解决了。

遇到的问题

项目中使用WebView的页面主要在FAQ页面,问题也出现在多次进入退出时,发现内存占用大,GC频繁。使用LeakCanary观察发现有两个内存泄漏很频繁:

我们分析一下这两个泄漏:

从图一我们可以发现是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方法,代码如下:

@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以下的机型不会内存泄漏呢,我们看下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内存泄漏问题及快速解决方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

时间: 2024-10-06 15:40:40

Android 5.1 WebView内存泄漏问题及快速解决方法的相关文章

Android 5.1 WebView内存泄漏分析

背景 问题分析 解决方案 Android 5.1之前的代码 结束语 背景 在 Android 5.1 系统上,在项目中遇到一个WebView引起的问题,每打开一个带webview的界面,退出后,这个activity都不会被释放,activity的实例会被持有,由于我们项目中经常会用到浏览web页面的地方,可能引起内存积压,导致内存溢出的现象,所以这个问题还是比较严重的. 问题分析 使用Android Studio的内存monitor,得到了以下的内存分析,我打开了三个BookDetailActi

关于php内存不够用的快速解决方法_php实例

有时候我们再运行php程序时,会出现 Allowed memory size of 8388608 bytes exhausted (tried to allocate 1298358 bytes) 出现该错误的原因:在确保不是程序产生的原因(例如死循环),是由于php页面消耗的最大内存默认是为 8M (在PHP的ini件里可以看到) , 如果文件太大 或图片太大 在读取的时候 会发生上述错误. 解决办法: 1.修改 php.ini将memory_limit由 8M 改成 16M(或更大),重启

Android编程实现webview将网页打包成apk的方法

本文实例讲述了Android编程实现webview将网页打包成apk的方法.分享给大家供大家参考,具体如下: 功能非常简单,而且乍一看没什么特别大的用处,因为实际上就是浏览器而已...但如果说网页一开始就是针对手机开发的呢?是不是可以将android的开发转变为网页的开发了?有待研究,不过据说也可以用这种方法将html5打包哦,先记录一下可能以后也可以赶下潮流. public class MainActivity extends Activity { private WebView webvie

win7系统出现内存不足的原因和解决方法

  原因解析及解决方法: 原因一:同时运行过多的应用程序.系统内存大小是固定的,在同一时间运行程序过多会造成内存占用率大,从而使计算机内存不足. 解决方法: 1.在系统的任务栏空白处点击鼠标右键,然后选择"任务管理器"选项 2.打开"任务管理器"之后,点击"性能"选项就可以看到当前内存的使用情况 3.在"任务管理器"中点击"进程"选项卡,然后点击"内存"一栏,就可以看见每个程序使用内存的

win7系统使用过程中总提示内存不足的原因及解决方法

  在使用win7系统的过程中,有时候会遇到一些常见的故障问题,比如有的用户反映在操作使用win7系统的时候,系统总弹出"计算机的内存不足"的提示.大部分用户遇到这种情况往往不懂得如何处理.其实只要我们了解系统提示内存不足的原因所在,就能找出解决该问题的方法.下面小编就跟大家分享关于win7系统使用过程中总提示内存不足的原因及解决方法! 一.系统提示"计算机内存不足"的原因: 1.系统运行太多的应用程序; 2.硬盘剩余空间太少; 3.系统"虚拟内存&quo

Android实现QQ新用户注册界面遇到问题及解决方法_Android

在上篇文章给大家介绍了Android实现QQ登录界面遇到问题及解决方法,本篇文章继续给大家介绍有关android qq界面知识. 先给大家展示下效果图: 问题: 1.下拉列表(因为还没看到这里...) 2.标题栏显示问题 3.按钮的 Enable 设置 以下是代码: 布局 fragment_main(问题1) <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools

mongodb 对内存的严重占用以及解决方法【转载】

mongodb 对内存的严重占用以及解决方法[转载]  刚开始使用mongodb的时候,不太注意mongodb的内存使用,但通过查资料发现mongodb对内存的占用是巨大的,在本地测试服务器中,8G的内存居然被占用了45%.汗呀. 本文就来剖析一下mongodb对内存的具体使用方法,以及生产环境针对mongodb占大量内存的问题的解决. 先看一个MongoDB服务器的top命令结果 shell> top -p $(pidof mongod) Mem:  32872124k total, 3006

Android Force Close 出现的异常原因分析及解决方法_Android

一.原因: forceclose,意为强行关闭,当前应用程序发生了冲突. NullPointExection(空指针),IndexOutOfBoundsException(下标越界),就连Android API使用的顺序错误也可能导致(比如setContentView()之前进行了findViewById()操作)等等一系列未捕获异常 二.如何避免 如何避免弹出Force Close窗口 ,可以实现Thread.UncaughtExceptionHandler接口的uncaughtExcepti

Android程序启动时出现黑屏问题的解决方法_Android

本文实例讲述了Android程序启动时出现黑屏问题的解决方法.分享给大家供大家参考,具体如下: 关于黑屏: 默认的情况下,程序启动时,会有一个黑屏的时期,原因是,首个activity会加载一些数据,比如初始化列表数据.向服务器发送请求获取数据等等. 去除方法: 1.在style里面添加一个style: <style name="ContentOverlay"parent="@android:style/Theme.Light"> <itemname