最近很多小伙伴都在问Androidwebview内存泄漏源码分析及处理办法和androidwebview内存泄露这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展Android5.1
最近很多小伙伴都在问Android webview 内存泄漏源码分析及处理办法和android webview 内存泄露这两个问题,那么本篇文章就来给大家详细解答一下,同时本文还将给你拓展Android 5.1 WebView内存泄漏问题及快速解决方法、android listview的item中有webview,但是点击webview会没有反应?、Android ViewFlipper滑动切换webview,webview里的网页布局变得混乱、Android ViewPager内存泄漏等相关知识,下面开始了哦!
本文目录一览:- Android webview 内存泄漏源码分析及处理办法(android webview 内存泄露)
- Android 5.1 WebView内存泄漏问题及快速解决方法
- android listview的item中有webview,但是点击webview会没有反应?
- Android ViewFlipper滑动切换webview,webview里的网页布局变得混乱
- Android ViewPager内存泄漏
Android webview 内存泄漏源码分析及处理办法(android webview 内存泄露)
问题背景
在排查项目内存泄漏过程中发现了一些由WebView引起的内存泄漏。
问题描述
项目中使用WebView的页面出现在多次进入退出时,发现内存占用大,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;
}
}
Android 5.1 WebView内存泄漏问题及快速解决方法
问题背景
在排查项目内存泄漏过程中发现了一些由WebView引起的内存泄漏,经过测试发现该部分泄漏只会出现在android 5.1及以上的机型。虽然项目使用WebView的场景并不多,但秉承着一个泄漏都不放过的精神,我们肯定要把它给解决了。
遇到的问题
项目中使用WebView的页面主要在FAQ页面,问题也出现在多次进入退出时,发现内存占用大,GC频繁。使用LeakCanary观察发现有两个内存泄漏很频繁:
@H_301_21@
@H_301_21@
我们分析一下这两个泄漏:
从图一我们可以发现是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.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 listview的item中有webview,但是点击webview会没有反应?
listview的item中嵌套有webview,给listview设置了onclickItem的点击事件,但是当点到webview的位置时没有响应,其他位置却可以,应该怎么设置才能在点到webview的位置时也有响应?Android ViewFlipper滑动切换webview,webview里的网页布局变得混乱
@红猎人 你好,想跟你请教个问题:师兄啊急求一个问题。
网页是自己写的,用div又嵌套了几个网页。若比较快的滑到该屏不出现该问题,若等一会儿滑到该屏就有此问题,请问该怎么解决。
Android ViewPager内存泄漏
private static final int[] images = {R.drawable.tutorial_step_01,R.drawable.tutorial_step_02,R.drawable.tutorial_step_03,R.drawable.tutorial_step_04,R.drawable.tutorial_step_05,R.drawable.tutorial_step_06};
然后我创建适配器:
@Override public Object instantiateItem(ViewGroup container,int position) { LinearLayout tv = (LinearLayout) inflater.inflate(R.layout.tut_slide,null); TextView title = (TextView) tv.findViewById(R.id.tut_title); title.setText(getResources().getText(titles[position])); TextView content = (TextView) tv.findViewById(R.id.tut_content); ImageView image = (ImageView) tv.findViewById(R.id.tut_image); slide_image = BitmapFactory.decodeResource(getResources(),images[position]); image.setimageBitmap(slide_image); content.setText(getResources().getText(contents[position])); ((ViewPager) container).addView(tv,0); return tv; } @Override public void destroyItem(ViewGroup container,int position,Object object) { ((ViewPager) container).removeView((LinearLayout) object);
//
}
麻烦的是,在我选择另一个页面之后,android不想收集图像.因此,在10-15次更改后,它会出现OutOfMemory异常.然后我添加到initializung行
if (slide_image!= null) { slide_image.recycle(); System.gc(); }
它工作得很好!但除了一件事:我有黑色屏幕而不是第一张图像,在几次翻转之后被真正的图像取代.所以我不知道如何处理这种内存泄漏
解决方法
PagerAdapter不仅应该在超出offLimitScreenPageLimit时调用destroyItem方法,而且还应该在屏幕旋转时调用,但它不会,因此必须强制执行它…为了实现它,你只需要设置为null活动的onStop或onDestroy方法上的适配器.
@Override protected void onDestroy(){ pager.setAdapter(null); }
干杯!
今天关于Android webview 内存泄漏源码分析及处理办法和android webview 内存泄露的介绍到此结束,谢谢您的阅读,有关Android 5.1 WebView内存泄漏问题及快速解决方法、android listview的item中有webview,但是点击webview会没有反应?、Android ViewFlipper滑动切换webview,webview里的网页布局变得混乱、Android ViewPager内存泄漏等更多相关知识的信息可以在本站进行查询。
本文标签: