欢迎访问移动开发之家(rcyd.net),关注移动开发教程。移动开发之家  移动开发问答|  每日更新
页面位置 : > > > 内容正文

androidRecyclerView的一些优化点介绍,

来源: 开发者 投稿于  被查看 47360 次 评论:79

androidRecyclerView的一些优化点介绍,


目录
  • 1.RecycledPool的重用
  • 2.setHasFixedSize(boolean)的使用
  • 3.setHasStableIds(boolean)的使用
  • 4.ViewCacheExtension的使用
  • 5.预加载
  • 6.更新列表的方式
    • item局部更新
    • 整体列表更新
  • 总结

    1.RecycledPool的重用

    场景以及使用:

    多个RecyclerView出现,并且他们的item布局结构一致,这时候可以进行重用。

    在进行RecyclerView的初始化设置时候进行RecycledPool的设置。

     //每个单元的视频列表的RecycledPool
        private var mRecycledViewPool: RecyclerView.RecycledViewPool? = null
     
    unitVideoListContentRv.run {
                    layoutManager = GridLayoutManager(itemView.context, 3)
                    if (mRecycledViewPool != null) {
                        setRecycledViewPool(mRecycledViewPool)
                    } else {
                        mRecycledViewPool = recycledViewPool
                    }
                    ...........
                }

    重用前后的对比:

    本次展示的是长列表中的item嵌套列表,进行多item的加载然后上下滑动,同时检测内存的开销占用。

    image.png

    列表的规模是13个item,每个item中有4个视频item,规模不算特别大。

    重用RecycledPool之前:

    image.png

    数据加载完毕之后,最后上下滑动的内存趋于平稳在48.4m

    重用RecycledPool之后:

    image.png

    数据加载完毕之后,最后上下滑动的内存趋于平稳在40m。

    对比总结:

    其实很明显可以看到内存开销减少,而内存开销减少能提升列表的流畅度,效果是显而易见的。这次的列表数据规模还不算大,后续如果规模增加到很大,那么对比将会更加明显。

    2.setHasFixedSize(boolean)的使用

    方法的名字就表明了,设置是否有固定的尺寸,就是说RecyclerView是否有固定的尺寸,如果设置了true。那么会在以下的情景用到:

    onMeasure---测量

    如果设置了true,那么RecyclerView的mHasFixedSize变量为true。

    @Override
        protected void onMeasure(int widthSpec, int heightSpec) {
          if (mLayout == null) {
                defaultOnMeasure(widthSpec, heightSpec);
                return;
            }
          //是否允许自动测量
          if (mLayout.isAutoMeasureEnabled()) {
            .....
          } else {
            if (mHasFixedSize) { //是否有固定的尺寸
                    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
                    return;
                }
            .........
          }
        }
     
    // mLayout.onMeasure
     public void onMeasure(@NonNull Recycler recycler, @NonNull State state, int widthSpec,
                    int heightSpec) {
                mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
            }
     
     void defaultOnMeasure(int widthSpec, int heightSpec) {
                   //直接设置固定的宽度和高度,没有进行再次的测量
            final int width = LayoutManager.chooseSize(widthSpec,
                    getPaddingLeft() + getPaddingRight(),
                    ViewCompat.getMinimumWidth(this));
            final int height = LayoutManager.chooseSize(heightSpec,
                    getPaddingTop() + getPaddingBottom(),
                    ViewCompat.getMinimumHeight(this));
     
            setMeasuredDimension(width, height);
        }

    所以设置了这个值的时候,RecyclerView在测量的时候会有性能上的提升。

    3.setHasStableIds(boolean)的使用

    方法的名称意思是设置是否有稳定的id,设置了该值为true后,ViewHolder中的mHasStableIds就为true。

    StableId有三种模式:NO_STABLE_IDS、ISOLATED_STABLE_IDS、SHARED_STABLE_IDS

    RecyclerView在进行Item的Remove,Insert,Change的时候会调用到。

    如果设置了这个属性,那么需要在Adapter中重写getItemId(int position)方法。

    这样子在进行列表的更新时候,Adapter会根据getItemId方法返回的long类型的id进行判断,决定当前的item是否需要刷新。因此取代以往的全部刷新的情况,从而提高效率。

     
    class Album{
         String coverUrl;
         String title;
    }
     
    @Override
    public long getItemId(int position){
        Album album = mListOfAlbums.get(position);
          //如果返回的id和上次不一样,那就代表这个item发生了数据变化,则进行刷新
          //如果返回的id和上次一致,那么这个item就没有改变,就无需刷新了。
        return (album.coverUrl + album.title).hashcode();
    }

    4.ViewCacheExtension的使用

    它是一个静态抽象类,看类名就能大概知道view缓存扩展,类中包括方法:

    /**
            返回一个能绑定到适配器position位置上的view 
             * <p>
             * 此方法不应该创建新的视图。 相反,它期望返回一个已经创建的View,该View可以针对给定的类型和位置重                        新使用。 如果将视图标记为已忽略,则应先调用{@link LayoutManager#
                  stopIgnoringView(View)},然后再返回视图。
             * RecyclerView将在必要时将返回的View重新绑定到该位置
             *
             * @param recycler The Recycler that can be used to bind the View
             * @param position The adapter position
             * @param type     The type of the View, defined by adapter
             * @return 绑定到给定位置的View;如果没有可重用的View,则为NULL
             * @see LayoutManager#ignoreView(View)
             */
    public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,
                    int type);

    该缓存为RecyclerView的第二级缓存,即如果开发者设置了该缓存,那么列表从CacheView中获取不到holder,就会从ViewCacheExtension从获取。

    适用场景则为,列表有固定的数量条目和宽高,这样子,列表初始化的时候就能直接从这级缓存拿到ViewHolder,不需要再创建ViewHolder,大大节省时间,提高效率。

    5.预加载

    预加载功能在RecyclerView中是默认开启的。

    public boolean onTouchEvent(MotionEvent e) {
       switch (action) {
           case MotionEvent.ACTION_MOVE: {
                          final int x = (int) (e.getX(index) + 0.5f);
                    final int y = (int) (e.getY(index) + 0.5f);
                    int dx = mLastTouchX - x;
                    int dy = mLastTouchY - y;
                             if (mScrollState == SCROLL_STATE_DRAGGING) { //处于拖动状态
                        ........
                        if (mGapWorker != null && (dx != 0 || dy != 0)) { //滑动距离不等于0,
                            mGapWorker.postFromTraversal(this, dx, dy); //进行预取任务
                        }
                    }
           }
           break;
       }
    }
     
            /**
         * 在当前遍历之后立即安排预取。
         */
        void postFromTraversal(RecyclerView recyclerView, int prefetchDx, int prefetchDy) {
            if (recyclerView.isAttachedToWindow()) {
                ........
                //第一次触发拖动的是否将该runnable提交到Mainhandler里面,
                //等待UI thread执行完成再执行预取任务 
                if (mPostTimeNs == 0) {
                    mPostTimeNs = recyclerView.getNanoTime();//获取当前时间,记录改次任务的开始
                    recyclerView.post(this); //提交当前任务
                }
            }
                    //设置预加载的坐标
            recyclerView.mPrefetchRegistry.setPrefetchVector(prefetchDx, prefetchDy);
        }
     
             /**
         * 获取当前系统的时间,单位为纳秒
         */
            long getNanoTime() {
            if (ALLOW_THREAD_GAP_WORK) { //在Android5.0及以上的系统中
                return System.nanoTime(); //返回正在运行的Java虚拟机的高分辨率时间源的当前值,以纳秒为单位
            } else {
                return 0; //5.0以下的系统直接返回0
            }
        }
     
     
            /**
            在L +上,使用RenderThread,UI线程在将一帧传递给RenderThread之后但在下一帧开始之前具有空闲时间。我们        在此窗口中安排预取工作。
         */
        static final boolean ALLOW_THREAD_GAP_WORK = Build.VERSION.SDK_INT >= 21;

    我们可以看下预加载程序的Runnable的run方法实现了什么操作。

    @Override
        public void run() {
            try {
                TraceCompat.beginSection(RecyclerView.TRACE_PREFETCH_TAG);
                            //recyclerview嵌套的情况
                if (mRecyclerViews.isEmpty()) {
                    // abort - no work to do
                    return;
                }
     
                     //查询最新的vsync,以便于我们预测下一个
                  //绘制时间在动画和输入的回调中未生效,所以在这里进行vsync的查询是安全的
                final int size = mRecyclerViews.size();
                long latestFrameVsyncMs = 0;
                   //获取RecyclerView最近一次开始RenderThread的时间
                for (int i = 0; i < size; i++) {
                    RecyclerView view = mRecyclerViews.get(i);
                    if (view.getWindowVisibility() == View.VISIBLE) {
                        latestFrameVsyncMs = Math.max(view.getDrawingTime(), latestFrameVsyncMs);
                    }
                }
     
                if (latestFrameVsyncMs == 0) {
                    //终止,没有任何视图可见,或者无法获得最新的vsync用于估计下一个
                    return;
                }
                            //计算下一帧的时间,等于最新一帧的时间加上帧间隔的时间
                  //事实上,这是预加载工作的最后期限时间,如果不能在这个时间之前完成,那就意味着预加载失败
                long nextFrameNs = TimeUnit.MILLISECONDS.toNanos(latestFrameVsyncMs) + mFrameIntervalNs;
                            //进行预加载
                prefetch(nextFrameNs);
     
                // TODO: consider rescheduling self, if there's more work to do
            } finally {
                mPostTimeNs = 0;
                TraceCompat.endSection();
            }
        }
    void prefetch(long deadlineNs) {
                  //建立任务列表
            buildTaskList();
                  //在deadlineNs这个时间前执行并完成任务
            flushTasksWithDeadline(deadlineNs);
        }
     
    private void flushTasksWithDeadline(long deadlineNs) {
            for (int i = 0; i < mTasks.size(); i++) {
                final Task task = mTasks.get(i);
                if (task.view == null) {
                    break; // done with populated tasks
                }
                flushTaskWithDeadline(task, deadlineNs);
                task.clear();
            }
        }

    6.更新列表的方式

    item局部更新

    单项item更新

    notifyItemChanged(position)

    notifyItemInserted(position)

    notifyItemRemoved(position)

    notifyItemMoved(fromPosition, toPosition)

    整体列表更新

    notifyDataSetChanged(慎用)

    notifyItemRangeRemoved(positionStart, itemCount)

    notifyItemRangeChanged(positionStart, itemCount)

    notifyItemRangeInserted(positionStart, itemCount)

    其它的优化点

    过度绘制

    如果列表中的一个Item存在过度绘制,那么列表所有的item都过度绘制,就到存在不必要的渲染工作,消耗系统资源。

    防止过度绘制,可以打开开发者选项中的《调试GPU过度绘制》,查看页面中的颜色分区,然后进行对应的优化。

    Android 将按如下方式为界面元素着色,以确定过度绘制的次数:

    真彩色:没有过度绘制

    蓝色:过度绘制 1 次

    绿色:过度绘制 2 次

    粉色:过度绘制 3 次

    红色:过度绘制 4 次或更多次

    因为在布局中同一帧多次绘制相同的像素就会发生绘制过度,因此修复过度绘制可以减少不必要的渲染工作,以此来提高性能。特别是对于大型,多列表的布局来说。

    总结

    到此这篇关于android RecyclerView的一些优化点介绍的文章就介绍到这了,更多相关android RecyclerView内容请搜索3672js教程以前的文章或继续浏览下面的相关文章希望大家以后多多支持3672js教程!

    您可能感兴趣的文章:
    • Android入门教程之RecyclerView的具体使用详解
    • Android自定义RecyclerView Item头部悬浮吸顶
    • Android recyclerview实现纵向虚线时间轴的示例代码
    • Android实现的RecyclerView适配器
    • Android RecyclerView网格布局示例解析
    • Android RecyclerView实现滑动删除

    用户评论