1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 14684字,需瀏覽 30分鐘

         ·

        2021-08-22 23:16

        1. 前言

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

        「今天我們重點來講解一下ViewCacheExtension緩存」
        public abstract static class ViewCacheExtension {

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

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

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

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

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

        ?

        答案當然是有。用ViewCacheExtension來優(yōu)化。用它來優(yōu)化RecyclerView初始化時創(chuàng)建View對主線程阻塞的時長。

        ?

        3. 從一個案例說起

        首先模擬復雜View的場景。TextView的構造方法中休眠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的界面很簡單,就是幾個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>

        程序運行結果如下:

        我們通過Systrace來看下RecyclerView性能表現(xiàn)

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

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

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

        4. 優(yōu)化方案

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

        AActivity代碼如下:

        圖片版本代碼:

        Kotlin版本代碼 方便復制

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

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

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

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

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

                            // 反射設置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設置成0。具體業(yè)務具體實現(xiàn)。主要是為了復用
                            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實現(xiàn)如下

        圖片版本代碼:

        Kotlin版本代碼 方便復制

        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實例,實際業(yè)務可以寫的更優(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設置RecyclerView.LayoutParems。

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

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

        4. 緩存復用不正確。解決方案:反射設置ViewHolder的itemViewType。

        5. 緩存不夠用。原因RecyclerView的layout_height="wrap_content",解決方案:"設置成match_parent"。與測量機制有關。

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


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

        推薦閱讀:

        音視頻面試基礎題

        OpenGL ES 學習資源分享

        開通專輯 | 細數(shù)那些年寫過的技術文章專輯

        NDK 學習進階免費視頻來了

        推薦幾個堪稱教科書級別的 Android 音視頻入門項目

        覺得不錯,點個在看唄~

        瀏覽 123
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

          <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            男的和女的搞鸡 | 天天综合—永久入口 | 香蕉视频在线观看免费 | 欧美精品成人在线视频 | 在工地里被弄得好爽 | 日韩性爱小视频 | AAA女郎写真视频在线 | 成人午夜视频一区 | 黄色录像网址 | 国产精品爱久久久久久久威尼斯 |