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

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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值