Android-内存优化-首页内存占用优化
来源:互联网 发布:php中array keys 编辑:程序博客网 时间:2024/04/29 07:16
Android-内存优化-首页内存占用优化
平时开发大家都会遇到OOM问题,今天介绍下我最近如何优化首页内存占用问题的。
描述
- 应用OutOfMemory Crash很多。
- 首页4个Tab都展示过后,占用内存达到110MB左右(小米4)。
- 首页4个Tab里都是图片。
- 首页展示的框架是ListView多ItemType方式实现的。
- 显示图片的View都是使用NetworkImageView。
- 我们使用图片库是Glide。
分析
- 首页内存占用主要是Bitmap,并且内存一直占用着,给二三级页面申请内存带来压力(GC),减少了首页占用的内存,手机OOM问题就会好很多。
- 要想内存占用小,必须减少与Bitmap的引用。
- 是否可以把其他未显示的Tab内存回收或断开与Bitmap的引用。
- ListView的多ItemType时,未显示的部分,其实还是在内存中的,所以其中的ImageView还是引用着Bitmap的,Bitmap内存空间是无法释放的。
- 手机一个屏幕能显示的内容有限,每个Tab中内容无法全部显示,是否可以在合适的时机(首页Tab切换或onStop被触发的时候)把ListView中未显示ItemType中ImageView与Bitmap引用断开。
如何回收掉ListView中未显示的Item占用的内存???
验证
- 大家都知道ListView会把未显示的View添加到RecycleBin中,等待下次的重用。child.dispatchFinishTemporaryDetach()通知View被重新加入到ListView中展示给用户。
// ListView.java View obtainView(int position, boolean[] isScrap) { ... // 从Recycler中获取View,用于重用 final View scrapView = mRecycler.getScrapView(position); // 大家经常见到的adapter.getView final View child = mAdapter.getView(position, scrapView, this); if (scrapView != null) { if (child != scrapView) { // Failed to re-bind the data, return scrap to the heap. mRecycler.addScrapView(scrapView, position); } else { isScrap[0] = true; //告诉Child你重新被添加到ListView,再次进入ListView显示区域中,再次被用户看到。 child.dispatchFinishTemporaryDetach(); } } ... return child; }
- ListView滑动时调用trackMotionScroll函数,处理滑出去的View。
// ListView.java, listView滑动的时候调用的函数 boolean trackMotionScroll(int deltaY, int incrementalDeltaY) { if (down) { for (int i = 0; i < childCount; i++) { final View child = getChildAt(i); if (child.getBottom() >= top) { break; } else { count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. if (child.isAccessibilityFocused()) { child.clearAccessibilityFocus(); } //把滑出ListView区域的View添加到RecycleBin中 mRecycler.addScrapView(child, position); } } } } else { for (int i = childCount - 1; i >= 0; i--) { final View child = getChildAt(i); if (child.getTop() <= bottom) { break; } else { start = i; count++; int position = firstPosition + i; if (position >= headerViewsCount && position < footerViewsStart) { // The view will be rebound to new data, clear any // system-managed transient state. if (child.isAccessibilityFocused()) { child.clearAccessibilityFocus(); } //把滑出ListView区域的View添加到RecycleBin中 mRecycler.addScrapView(child, position); } } } } return false; }
- RecycleBin把滑出ListView区域的View添加到数组中,带后续重用。scrap.dispatchStartTemporaryDetach()通知View滑出ListView的显示区域,用户看不到了。
// ListView.java, RecycleBin中addScrapView方法,把View添加到缓存数组中。 void addScrapView(View scrap, int position) { ... // 通知View你已经滑出ListView的区域了 scrap.dispatchStartTemporaryDetach(); ... // Don't scrap views that have transient state. final boolean scrapHasTransientState = scrap.hasTransientState(); if (scrapHasTransientState) { ... } else { ... // 回调通知scrap从ListView区域中滑出去了。 if (mRecyclerListener != null) { mRecyclerListener.onMovedToScrapHeap(scrap); } } }
- View.dispatchFinishTemporaryDetach和View.dispatchStartTemporaryDetach正好满足我们的需求,让我们再看看这两个函数。
/** * @hide */ public void dispatchStartTemporaryDetach() { onStartTemporaryDetach(); } /** * This is called when a container is going to temporarily detach a child, with * {@link ViewGroup#detachViewFromParent(View) ViewGroup.detachViewFromParent}. * It will either be followed by {@link #onFinishTemporaryDetach()} or * {@link #onDetachedFromWindow()} when the container is done. */ public void onStartTemporaryDetach() { // View消失时回调 removeUnsetPressCallback(); mPrivateFlags |= PFLAG_CANCEL_NEXT_UP_EVENT; } /** * @hide */ public void dispatchFinishTemporaryDetach() { onFinishTemporaryDetach(); } /** * Called after {@link #onStartTemporaryDetach} when the container is done * changing the view. */ public void onFinishTemporaryDetach() { // View再次显示时回调 }
View的onStartTemporaryDetach和onFinishTemporaryDetach方法完全满足我们的需求,而且不需要去改动业务代码。如何实现呢?大家接着看…
解决
既然我们知道ListView中的Item消失与显示的回调,那么我们就可以干一个很简单事情了。大体思路如下:
- 我们的应用中图片展示都是使用的NetworkImageView。
- NetworkImageView实现onStartTemporaryDetach和onFinishTemporaryDetach方法。
- 收集未显示的NetworkImageView。
- 在合适时机把未显示的NetworkImageView与Bitmap引用断开。
代码实现如下:
- NetworkImageView实现
public class NetworkImageView extends ImageView { // list view reuse view @Override public void onFinishTemporaryDetach() { super.onFinishTemporaryDetach(); GlideRecycledHelper.getInstance().detachView(this); } // list view item no display @Override public void onStartTemporaryDetach() { super.onStartTemporaryDetach(); GlideRecycledHelper.getInstance().attachView(this); } // add view @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); GlideRecycledHelper.getInstance().detachView(this); } // remove view @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); GlideRecycledHelper.getInstance().detachView(this); } }
- GlideRecycledHelper实现收集未显示的NetworkImageView。
public class GlideRecycledHelper { private LinkedList<WeakReference<View>> mRecycledViews = new LinkedList<>(); private static GlideRecycledHelper sInstance = new GlideRecycledHelper(); /** * GlideRecycledHelper * * @return GlideRecycledHelper instance */ public static GlideRecycledHelper getInstance() { return sInstance; } private GlideRecycledHelper() { } /** * attachView * * @param view view */ public void attachView(View view) { if (view == null) { return; } if (checkThread()) { return; } if (!findView(view)) { mRecycledViews.add(new WeakReference<View>(view)); // 把未显示的View添加到队列中 } } /** * detachView * * @param view view */ public void detachView(View view) { if (view == null) { return; } if (checkThread()) { return; } removeView(view); // 从队列中删除再显示的View } private boolean findView(View view) { Iterator<WeakReference<View>> iterator = mRecycledViews.iterator(); while (iterator.hasNext()) { WeakReference<View> weakView = iterator.next(); View v = weakView.get(); if (v == null) { iterator.remove(); } else if (v == view) { return true; } } return false; } private void removeView(View view) { Iterator<WeakReference<View>> iterator = mRecycledViews.iterator(); while (iterator.hasNext()) { WeakReference<View> weakView = iterator.next(); View v = weakView.get(); if (v == null) { iterator.remove(); } else if (v == view) { iterator.remove(); } } } /** * clear recycled view memory */ public void clearMemory() { if (checkThread()) { return; } try { Iterator<WeakReference<View>> iterator = mRecycledViews.iterator(); while (iterator.hasNext()) { WeakReference<View> weakView = iterator.next(); View v = weakView.get(); if (v == null) { iterator.remove(); } else { clear(v); } } } catch (StackOverflowError error) { } } /** * clear view reference to glide request * * @param view view */ public static void clear(View view) { if (checkThread()) { return; } try { clearInternal(view); } catch (StackOverflowError e) { } } // 断开NetworkImageView与Bitmap之间的引用 private static void clearInternal(View view) { if (view == null) { return; } if (view instanceof NetworkImageView) { try { Glide.clear(view); } catch (Exception e) { e.printStackTrace(); } ((ImageView) view).setImageDrawable(null); return; } if (view instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) view; int childCount = viewGroup.getChildCount(); for (int i = 0; i < childCount; i++) { clearInternal(viewGroup.getChildAt(i)); } } } private static boolean checkThread() { if (Looper.getMainLooper().getThread() != Thread.currentThread()) { return true; } return false; } }
总结
有人可能会问,为什么不直接使用ListView的setRecyclerListener
/** * Sets the recycler listener to be notified whenever a View is set aside in * the recycler for later reuse. This listener can be used to free resources * associated to the View. * * @param listener The recycler listener to be notified of views set aside * in the recycler. * * @see android.widget.AbsListView.RecycleBin * @see android.widget.AbsListView.RecyclerListener */ public void setRecyclerListener(RecyclerListener listener) { mRecycler.mRecyclerListener = listener; }
首页如果ListView中ItemView未显示时立马就断开与View的引用,这样在上下滑动ListView的时候,ItemView会闪现下默认图,个人觉得这样的用户体验不好,老板看到应该也会觉得体验不好。不过二三级页面可以做成这种方式。
个人总结
- 多看android源码才是王道。
- 优化无止境,这才刚开始,后续会接着介绍关于内存方面的优化。
- 后续会介绍Fresco来优化内存。
1 0
- Android-内存优化-首页内存占用优化
- App内存占用优化
- 优化图片内存占用
- Android资源图片内存占用及优化
- Android资源图片内存占用及优化
- Android资源图片内存占用及优化
- Android资源图片内存占用及优化
- Android资源图片内存占用及优化
- Android资源图片内存占用及优化
- Android资源图片内存占用及优化
- Android资源图片内存占用及优化
- [Unity优化]减少内存占用:贴图优化
- 关于Android应用内存占用查看及优化
- Android优化图片加载所占用的内存
- 【Android】 Android 内存优化
- android 内存优化 性能优化
- android 内存优化 性能优化 .
- Android 性能优化、内存优化
- 六年级数学期中考试, 我只考了88分, 但试卷最后被老师写下: Good, very good, very very good!!!
- 框架演进之路(一)--从复杂调用到逻辑分层
- 栈的两种实现方式
- 构造函数,密封类密封方法,子类调用父类的构造方法, Projector,类的实例化声明
- Android性能优化一些方法
- Android-内存优化-首页内存占用优化
- 该重新认识一下APNS推送了
- 2016苹果WWDC大会说了啥?
- ISLR读书笔记(3)分类
- Hexo+github搭建个人博客--实践笔记
- RxJava 使用debounce操作符 优化app搜索功能
- 上班时间统计
- openfire源码分析(一)
- 经典消除游戏——Unity 祖玛游戏