从Glide.with(View)开始优化RecyclerView掉帧问题

最近在项目中发现有个recyclerview滑动掉帧现象很严重,刚好最近要更新一个版本,就准备把这个问题优化了


一、Glide.with(View) 是低效的

Android 中对于图片的处理 无非就是 Glide Picasso Fresco 这几种 而在 imageView 直接显示图片 Glide 无疑是更好的选择 Glide 的实现这里就不赘述 Glide with 方法有很多种 例如

@NonNull
  public static RequestManager with(@NonNull Context context) {
    return getRetriever(context).get(context);
  }
 @NonNull
  @Deprecated
  public static RequestManager with(@NonNull Activity activity) {
    return with(activity.getApplicationContext());
  }
 @NonNull
  public static RequestManager with(@NonNull Fragment fragment) {
    return getRetriever(fragment.getContext()).get(fragment);
  }
....

recycler V iew item 如果要显示图片 我们一般拿到数据后 会将数据直接放入 adapter 然后在进行图片处理 而在 adapter 中又不应该持有 activity fragment context 所以就会调用 Glide.with(View)

  @NonNull
  public static RequestManager with(@NonNull View view) {
    return getRetriever(view.getContext()).get(view);
  }

Glide 中关于这个方法的解释是

其中有一句 This method may be inefficient aways and is definitely inefficient for large hierarchies. Consider memoizing the result after the View is attached or again, prefer the Activity and Fragment variants whenever possible. ”大致的意思就是 这个方法是低效的 除非万不得已的情况下 ( 自定义 view ) 不然还是建议使用其他的 with () 方法

Glide 的求生欲望真的很强大 让我们看看他的真实面目

1) 先是通过 findActivity 方法递归获取 activity


2) 然后还要获取 fragment 通过 findAllSupportFragmentsWithViews 方法递归调用获取 Activity 中所有的 Fragment ,并以 fragment.getView() 作为键, fragment 作为值,以键值对的形式存入 map

总结起来就是 获取 view activity 然后如果找到 view fragment 就使用 fragment 如果没有找到 fragment 但是找到了 activity 就使用 activity 如果都没找到就是用 application 同时生命周期也绑定在 application

 

其实如果一个 item 中的图片资源少的话 影响倒是也不大 但是我们的那个 recycler V iew 中每个 item 都要加载好几个 imageView 量变引发质变了属于是

所以采用回调的方式将 Adapter 中的 Glide 的操作放在 A ctivity / Fragment 中进行 对于性能来说就好了很多

二、简化布局的层级

其实首先排查的是布局的层级 因为 ui 比较复杂 所以布局这块嵌套的层级比较多 这里把该省的都省下来了 例如 LinearLayout 中嵌套了 Fr amlayout Fr amlayout 中又嵌套了 ConstraintLayout

三、 尽量使用LinearLayout FrameLayout

这当然是因为 LinearLayout FrameLayout 的性能足够优秀 在性能对比如下

RelativeLayout 举例 RelativeLayout 会让子 View 调用 2 onMeasure LinearLayout 在有 weight 时, 会调用子View2 onMeasure RelativeLayout 的子 View 如果高度和 RelativeLayout 不同,会导致 RelativeLayout onMeasure() 方法中做横向测量时,纵向的测量结果尚未完成,只好暂时使用自己的高度传入子 View 系统。而父 View 给子 View 传入的值也没有变化就不会做无谓的测量的优化会失效


四、 滑动时不加载,停止时加载

其实到上面那一步之后 掉帧问题已经优化了很多 用户正常上下滑动是没有感觉到上面卡顿 但是 如果快速的较长时间的滑动依旧会出现卡顿 所以还需要继续优化。 这个时候就准备从每个 item加载多个图片这块入手

主要思路是 当用户滑动时停止加载图片资源

Glide.with(this).pauseRequests()

在用户停止滑动时加载图片

Glide.with(this).resumeRequests()

体验起来确实爽多了,不管怎么快速滑动都是OK 但是又有新的问题 用户的体验就很差 因为滑动时看到的都是未加载的空白图片 还得接着优化

前面说到 到第三步时 我们正常滑动其实已经不会卡顿了 无非就是要避免用户快速滑动的问题 那我们只要判断用户滑动时速度 当速度过快时 停止加载 速度慢下来恢复加载 就可以完美解决问题 参考代码如下

mBinding.rvList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
   override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
       super.onScrollStateChanged(recyclerView, newState)
       if (newState != SCROLL_STATE_IDLE && mScrolled) {
           Glide.with(this).pauseRequests()
       } else {
           Glide.with(this).resumeRequests()
       }
   }
   override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
       super.onScrolled(recyclerView, dx, dy)
       mScrolled = dy > 80 || dy < -55
   }
})

五、 其他优化操作

参考了下别人的方案 如果后续还需要优化应该是还有优化的空间

1、 加大RecyclerView 的缓存,用空间换时间,来提高滚动的流畅性。

mBinding.rvList.setItemViewCacheSize(20);
mBinding.rvList.setDrawingCacheEnabled(true);
mBinding.rvList.setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH);

2、 增加RecyclerView 预留的额外空间

object : LinearLayoutManager(this) {
   override fun getExtraLayoutSpace(state: RecyclerView.State): Int {
       return size
   }
}

3、 设置固定高度

如果item 高度是固定的话,可以使用 RecyclerView.setHasFixedSize(true); 来避免 requestLayout 浪费资源。

 

4 极端情况下可以采用减少 xml 文件 inflate 时间的方式

xml 文件包括: layout drawable xml xml 文件 inflate ItemView 是通过耗时的 IO 操作。可以使用代码去生成布局,即 newView() 的方式。这种方式是比较麻烦,但是在布局太过复杂,或对性能要求比较高的时候可以使用。


请使用浏览器的分享功能分享到微信等