Android-内存优化-首页内存占用优化

来源:互联网 发布:php中array keys 编辑:程序博客网 时间:2024/04/29 07:16

Android-内存优化-首页内存占用优化

平时开发大家都会遇到OOM问题,今天介绍下我最近如何优化首页内存占用问题的。

描述

  1. 应用OutOfMemory Crash很多。
  2. 首页4个Tab都展示过后,占用内存达到110MB左右(小米4)。
  3. 首页4个Tab里都是图片。
  4. 首页展示的框架是ListView多ItemType方式实现的。
  5. 显示图片的View都是使用NetworkImageView。
  6. 我们使用图片库是Glide。

分析

  1. 首页内存占用主要是Bitmap,并且内存一直占用着,给二三级页面申请内存带来压力(GC),减少了首页占用的内存,手机OOM问题就会好很多。
  2. 要想内存占用小,必须减少与Bitmap的引用。
  3. 是否可以把其他未显示的Tab内存回收或断开与Bitmap的引用。
  4. ListView的多ItemType时,未显示的部分,其实还是在内存中的,所以其中的ImageView还是引用着Bitmap的,Bitmap内存空间是无法释放的。
  5. 手机一个屏幕能显示的内容有限,每个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消失与显示的回调,那么我们就可以干一个很简单事情了。大体思路如下:

  1. 我们的应用中图片展示都是使用的NetworkImageView。
  2. NetworkImageView实现onStartTemporaryDetach和onFinishTemporaryDetach方法。
  3. 收集未显示的NetworkImageView。
  4. 在合适时机把未显示的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会闪现下默认图,个人觉得这样的用户体验不好,老板看到应该也会觉得体验不好。不过二三级页面可以做成这种方式。

个人总结

  1. 多看android源码才是王道。
  2. 优化无止境,这才刚开始,后续会接着介绍关于内存方面的优化。
  3. 后续会介绍Fresco来优化内存。
1 0
原创粉丝点击