<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          RecyclerView性能優(yōu)化的最后一公里

          共 14684字,需瀏覽 30分鐘

           ·

          2021-08-22 23:16

          1. 前言

          時(shí)至今日相信大部分的Android開(kāi)發(fā)者對(duì)RecyclerView的緩存機(jī)制如數(shù)家珍。相關(guān)教程也是數(shù)不勝數(shù)。如果你想詳細(xì)了解這些不同緩存的作用以及實(shí)現(xiàn)原理??梢詤⒖嘉抑皩戇^(guò)的兩篇文章。聊聊RecyclerView緩存機(jī)制詳細(xì)聊聊RecyclerView緩存機(jī)制,前者主要是介紹各個(gè)層級(jí)緩存的作用以及它們之間的區(qū)別,后者主要是從源碼的角度講解緩存是怎么實(shí)現(xiàn)的。緩存架構(gòu)圖如下:

          「今天我們重點(diǎn)來(lái)講解一下ViewCacheExtension緩存」
          public abstract static class ViewCacheExtension {

              public abstract View getViewForPositionAndType(
                  Recycler recycler,
                  int position,
                  int type
              );
          }

          ViewCacheExtension是RecyclerView框架預(yù)留給開(kāi)發(fā)者實(shí)現(xiàn)自己的緩存邏輯的一個(gè)接口。很詭異的是,就算是到2021年的秋天,無(wú)論你怎么搜索,還是很難找到正確使用ViewCacheExtension的方法。網(wǎng)上的教程,對(duì)它的定性都很一致,由于ViewCacheExtension只提供了getView而沒(méi)有提供putView方法,所以它的用處不大「當(dāng)然這是錯(cuò)誤的,本文就是為ViewCacheExtension翻案的?!?/strong> 當(dāng)我們窮盡所有方法,把RecyclerView調(diào)優(yōu)方案都用盡了的時(shí)候,用好ViewCacheExtension就成了將RecyclerView性能優(yōu)化到極致的最后一公里。

          曾經(jīng)我也是Too young too simple,說(shuō)ViewCacheExtension沒(méi)什么軟用。下圖引用自我寫的聊聊RecyclerView緩存機(jī)制

          2. ViewCacheExtension能為性能優(yōu)化做什么?

          "減少ItemView的嵌套層級(jí),讓布局盡量輕量級(jí)"或者減少ItemView的inflate時(shí)長(zhǎng)會(huì)是RecyclerView性能優(yōu)化的眾多Tips中的其二。這樣的方案當(dāng)然沒(méi)問(wèn)題。但是現(xiàn)實(shí)有可能是,ItemView本身就是很復(fù)雜,將它的布局優(yōu)化之后inflate還是很耗時(shí) 或者ItemView是前輩寫的,太復(fù)雜了,后繼的開(kāi)發(fā)者無(wú)能為力或者不愿意去修改它。 這種情況下如何進(jìn)一步優(yōu)化到極致。當(dāng)然你可能會(huì)說(shuō),我用ConstraintLayout將布局優(yōu)化到極致,我能力強(qiáng)而且能吃苦耐勞,前輩寫的復(fù)雜且低效的布局我有信心有能力優(yōu)化好。退一步講,這些你都做的很好了。RecyclerView剛初始化的時(shí)候ItemView inflate終歸要耗時(shí),而且是會(huì)阻塞線程。假設(shè)有個(gè)10個(gè)ItemView,每個(gè)耗時(shí)20ms,那也會(huì)阻塞主線程200ms,有沒(méi)有辦法優(yōu)化呢?

          ?

          答案當(dāng)然是有。用ViewCacheExtension來(lái)優(yōu)化。用它來(lái)優(yōu)化RecyclerView初始化時(shí)創(chuàng)建View對(duì)主線程阻塞的時(shí)長(zhǎng)。

          ?

          3. 從一個(gè)案例說(shuō)起

          首先模擬復(fù)雜View的場(chǎng)景。TextView的構(gòu)造方法中休眠100ms。

          class HeavyTextView @JvmOverloads constructor(
              context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
          ) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
              init {
                  println("heavy view init")
                  Thread.sleep(100L)
              }
          }

          RecyclerView的界面很簡(jiǎn)單,就是幾個(gè)TextView。itemView布局文件代碼如下:

          <androidx.cardview.widget.CardView 
              xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:layout_marginLeft="5dp"
              android:layout_marginTop="5dp"
              android:layout_marginRight="5dp"
              android:layout_marginBottom="5dp">
              <com.peter.viewgrouptutorial.recyclerview.HeavyTextView
                  android:id="@+id/heavy.text"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:background="@drawable/white_touch"
                  android:clickable="true"
                  android:orientation="horizontal"
                  android:padding="@dimen/small"
                  android:textSize="14sp" />
          </androidx.cardview.widget.CardView>

          程序運(yùn)行結(jié)果如下:

          我們通過(guò)Systrace來(lái)看下RecyclerView性能表現(xiàn)

          通過(guò)上圖我們可以看到。初始化HeavyTextView總共花費(fèi)了639ms。我們知道Android每幀的耗時(shí)超過(guò)16ms就要掉幀了。所以相對(duì)來(lái)說(shuō)比較卡頓。實(shí)際運(yùn)行程序,也會(huì)發(fā)現(xiàn)跳轉(zhuǎn)到該Activity明顯不流暢。

          對(duì)比下優(yōu)化后的效果。前提是不修改HeavyTextView,仍然休眠100ms

          對(duì)比RV OnLayout事件,優(yōu)化后的效果只需要76ms。將近10倍的優(yōu)化空間。實(shí)際效果是,跳轉(zhuǎn)Activity很順滑很流暢。

          4. 優(yōu)化方案

          程序UI模型圖如下,從AActivity跳轉(zhuǎn)到BActivity,它有一個(gè)RecyclerView列表。

          AActivity代碼如下:

          圖片版本代碼:

          Kotlin版本代碼 方便復(fù)制

          class AActivity : AppCompatActivity() {
              companion object {
                  //靜態(tài)變量,ArrayList保存開(kāi)發(fā)者緩存View
                  var sCustomViewCaches: ArrayList<View> = arrayListOf()
              }

              override fun onCreate(savedInstanceState: Bundle?) {
                  super.onCreate(savedInstanceState)
                  //當(dāng)AActivity MessageQueue有空閑的時(shí)候,創(chuàng)建10個(gè)HeavyText布局ItemView
                  Looper.myQueue().addIdleHandler {
                      thread {
                          repeat(10) {
                              val linearLayout = LinearLayout(this@AActivity).apply {
                                  orientation = LinearLayout.VERTICAL
                              }

                              //將itemView add到linearLayout上,后有remove掉,為了正確的將item布局中padding顯示出來(lái)
                              val itemView = LayoutInflater.from(this@AActivity)
                                  .inflate(R.layout.custom_cache_view_item, linearLayout)
                              linearLayout.removeView(itemView)

                              //背景設(shè)置成紅色為了更好的測(cè)試是否用到了正確緩存中的View
                              itemView.setBackgroundColor(Color.RED)

                              itemView.layoutParams = RecyclerView.LayoutParams(
                                  ViewGroup.LayoutParams.MATCH_PARENT,
                                  ViewGroup.LayoutParams.WRAP_CONTENT
                              )

                              // 反射設(shè)置RecyclerView.LayoutParams的mViewHolder屬性
                              val viewHolderField =
                                  RecyclerView.LayoutParams::class.java.getDeclaredField("mViewHolder")
                                      .apply {
                                          isAccessible = true
                                      }
                              
                               //等效于Adapter中的onCreateViewHolder方法,創(chuàng)建ViewHolder
                              val viewHolder = object : RecyclerView.ViewHolder(itemView) {}
                              
                              //將ViewHolder的mItemViewType設(shè)置成0。具體業(yè)務(wù)具體實(shí)現(xiàn)。主要是為了復(fù)用
                              with(
                                  RecyclerView.ViewHolder::class.java.getDeclaredField("mItemViewType")
                                      .apply {
                                          isAccessible = true
                                      }) {
                                  set(viewHolder, 0)
                              }
                              
                              viewHolderField.set(itemView.layoutParams, viewHolder)
                              
                              //將ItemView保存到緩存中
                              sCustomViewCaches.add(itemView)
                          }

                          println("custom  view cache ok")

                      }
                      false
                  }
              }
          }

          BActivity實(shí)現(xiàn)如下

          圖片版本代碼:

          Kotlin版本代碼 方便復(fù)制

          class BActivity : AppCompatActivity() {
              private lateinit var mRecyclerView: RecyclerView
              
              override fun onCreate(savedInstanceState: Bundle?) {
                  super.onCreate(savedInstanceState)
                  setContentView(R.layout.activity_recycler_view_custom_cache)
                  mRecyclerView = findViewById(R.id.recyclerview)
                  //省略很多RecyclerView的常規(guī)操作比如setAdapter和LayoutManager
                  mRecyclerView.setViewCacheExtension(object : RecyclerView.ViewCacheExtension() {
                      override fun getViewForPositionAndType(
                          recycler: RecyclerView.Recycler,
                          position: Int,
                          type: Int
                      ): View? {
                          //從AActivity的緩存中拿View,Demo實(shí)例,實(shí)際業(yè)務(wù)可以寫的更優(yōu)雅
                          if (AActivity.sCustomViewCaches.size != 0) {
                              val view = DashboardActivity.sCustomViewCaches.removeFirst()
                              println("custom cache view remove $position $view")
                              if (position == 0) {
                                  println("attention $position $view")
                              }
                              return view
                          }
                          return null
                      }
                  })
              }
          }

          5.遇到的坑

          1. 空指針異常。解決方案:為itemView設(shè)置RecyclerView.LayoutParems。

          2. ViewHolder不能為空。解決方案:反射設(shè)置ViewHolder。

          3. 布局間距不正確。解決方案:先將itemView add到臨時(shí)viewGroup上,然后remove掉。

          4. 緩存復(fù)用不正確。解決方案:反射設(shè)置ViewHolder的itemViewType。

          5. 緩存不夠用。原因RecyclerView的layout_height="wrap_content",解決方案:"設(shè)置成match_parent"。與測(cè)量機(jī)制有關(guān)。

          「以上坑,本案例全部解決過(guò)了,期待并感謝您的素質(zhì)三連-> 點(diǎn)贊、在看、分享」


          技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。

          推薦閱讀:

          音視頻面試基礎(chǔ)題

          OpenGL ES 學(xué)習(xí)資源分享

          開(kāi)通專輯 | 細(xì)數(shù)那些年寫過(guò)的技術(shù)文章專輯

          NDK 學(xué)習(xí)進(jìn)階免費(fèi)視頻來(lái)了

          推薦幾個(gè)堪稱教科書(shū)級(jí)別的 Android 音視頻入門項(xiàng)目

          覺(jué)得不錯(cuò),點(diǎn)個(gè)在看唄~

          瀏覽 123
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  香蕉日逼网 | 黄色片黄色片一级片不卡片 | 国产成人777777精品综合 | 大香蕉久操国产 | 日韩插毕视频 |