问题背景

在排查项目内存泄漏过程中发现了一些由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内存泄漏问题及快速解决方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
# android
# webview内存泄漏
# Android webview 内存泄露的解决方法
# Android 内存溢出和内存泄漏的问题
# Android webView加载数据时内存溢出问题及解决
# 是在
# 什么时候
# 就会
# 这两个
# 给大家
# 无法正常
# 发现了
# 也有
# 都不
# 有个
# 第一个
# 是有
# 那就
# 也就
# 出现在
# 会在
# 很容易
# 要把
# 我们可以
# 这也
相关文章:
广州网站制作公司哪家好一点,广州欧莱雅百库网络科技有限公司官网?
义乌企业网站制作公司,请问义乌比较好的批发小商品的网站是什么?
已有域名如何免费搭建网站?
如何快速打造个性化非模板自助建站?
大连网站制作公司哪家好一点,大连买房网站哪个好?
如何通过NAT技术实现内网高效建站?
安徽网站建设与外贸建站服务专业定制方案
建站主机系统SEO优化与智能配置核心关键词操作指南
Thinkphp 中 distinct 的用法解析
国美网站制作流程,国美电器蒸汽鍋怎么用官方网站?
攀枝花网站建设,攀枝花营业执照网上怎么年审?
建站之星下载版如何获取与安装?
如何快速查询网址的建站时间与历史轨迹?
如何在云主机上快速搭建网站?
如何规划企业建站流程的关键步骤?
如何通过商城免费建站系统源码自定义网站主题?
建站之星官网登录失败?如何快速解决?
如何通过PHP快速构建高效问答网站功能?
如何在企业微信快速生成手机电脑官网?
如何在建站之星绑定自定义域名?
电影网站制作价格表,那些提供免费电影的网站,他们是怎么盈利的?
详解jQuery中基本的动画方法
如何在七牛云存储上搭建网站并设置自定义域名?
制作网站建设的公司有哪些,网站建设比较好的公司都有哪些?
建站VPS能否同时实现高效与安全翻墙?
在线制作视频网站免费,都有哪些好的动漫网站?
青岛网站建设如何选择本地服务器?
如何挑选优质建站一级代理提升网站排名?
如何选择适合PHP云建站的开源框架?
深圳网站制作培训,深圳哪些招聘网站比较好?
如何使用Golang table-driven基准测试_多组数据测量函数效率
定制建站是什么?如何实现个性化需求?
官网自助建站系统:SEO优化+多语言支持,快速搭建专业网站
如何在阿里云域名上完成建站全流程?
移动端手机网站制作软件,掌上时代,移动端网站的谷歌SEO该如何做?
微信推文制作网站有哪些,怎么做微信推文,急?
深圳网站制作平台,深圳市做网站好的公司有哪些?
微网站制作教程,我微信里的网站怎么才能复制到浏览器里?
网站专业制作公司,网站编辑是做什么的?好做吗?工作前景如何?
简单实现Android验证码
建站之星如何防范黑客攻击与数据泄露?
电商平台网站制作流程,电商网站如何制作?
如何制作新型网站程序文件,新型止水鱼鳞网要拆除吗?
太平洋网站制作公司,网络用语太平洋是什么意思?
linux top下的 minerd 木马清除方法
如何快速辨别茅台真假?关键步骤解析
如何在云指建站中生成FTP站点?
如何在香港服务器上快速搭建免备案网站?
如何用虚拟主机快速搭建网站?详细步骤解析
建站之星免费模板:自助建站系统与智能响应式一键生成
*请认真填写需求信息,我们会在24小时内与您取得联系。