Github鏈接,給個(gè)Star鼓勵(lì)我寫更多好庫

ezgif-1-4516d51ebf.gif
事先說明:我在demo中一進(jìn)入Activity就立刻觸發(fā)下拉刷新,所以你看到幀率可能掉到了40,是因?yàn)橄到y(tǒng)的startActivity本身就掉幀非常厲害。想真實(shí)測出幀率,需要進(jìn)入Activity后等幀率穩(wěn)定在60了,再手動下拉刷新
包含功能:
- 9張圖。如果只有一張圖,那么單張圖的寬高根據(jù)圖片原始寬高等比例縮放
- 只有一張圖的時(shí)候,這個(gè)圖可能是視頻,圖中間有播放按鈕
- 內(nèi)容支持表情。[微笑]要顯示為圖片??
- 內(nèi)容有@人功能,@人有點(diǎn)擊事件
- 每個(gè)Item帶有評論,XXX回復(fù)XXX:你好[微笑]
傳統(tǒng)做法的效果:
- 首次進(jìn)入Activity后觸發(fā)下拉刷新,請求成功后setAdpater,這時(shí)候幀率會掉到
49幀左右。丟失11幀 - 手指往下滾動,滾動過程中,幀率在
57幀 - 60幀徘徊 - 退出Activity再次進(jìn)入,由于java底層的代碼優(yōu)化,執(zhí)行效率會上升。首次setAdpater幀率為
53幀左右
1659104674017.jpg
我優(yōu)化后的效果:
- 首次進(jìn)入Activity后觸發(fā)下拉刷新,請求成功后setAdpater,這時(shí)候幀率會掉到
57幀左右。丟失3幀左右 - 手指往下滾動,滾動過程中,全程60幀
1659104746498.jpg
我的基礎(chǔ)優(yōu)化方案(別人帖子也會講的):
- ? 1、每個(gè)item中眾多元素的點(diǎn)擊事件不要每次都new。應(yīng)該是
onCreateViewHolder中imageView.setOnClickListener(this)。然后在onBindViewHolder中imageView.setTag。避免滾動過程中頻繁new ClickListener() - ? 2、SpanText做好緩存,避免每次滾動都解析??傊瑱z查Adpater中所有new Object()的代碼,能不new就不new
- ? 3、手寫
DrawableCenterTextView,解決系統(tǒng)的Button的DrawableLeft會貼邊問題。否則要多一層LinearLayout包裹 - ? 4、需要重復(fù)計(jì)算的size要設(shè)置為全局變量static,避免每次計(jì)算。比如每張圖片的寬高,表情圖片的15sp大小
- ? 5、底部加載更多:使用notifyItemRangeInserted代替notifyDataSetChanged。后者會觸發(fā)2、3次onCreateViewHolder
我的進(jìn)階優(yōu)化方案(你在別的帖子看不到的):
- ? 1、glide首次加載圖片會創(chuàng)建線程池,耗時(shí)約50ms,可以移到App打開時(shí)的歡迎界面就創(chuàng)建好。節(jié)省50ms
- ? 2、首次setAdpater前先不著急結(jié)束下拉刷新狀態(tài)。先開啟Thread,在Thread中解析文字的表情和@人解析,組成SpanText并緩存到model中,節(jié)省約12ms
- ? 3、采用
LruCache緩存最新的32個(gè)表情的drawable,這樣可以加快常見表情的解析速度 - ? 4、在Thread中按List<Model>.count() 解析item的xml布局,并存放在LinkedList<View> 中(為了節(jié)省內(nèi)存,我最多限制8條)。在onCreateViewHolder中進(jìn)行 .poll,節(jié)省150ms左右
- ? 5、在Thread中按List<Model>.count() 預(yù)創(chuàng)建圖片和評論的
緩存池:LinkedList<ImageView> 和 LinkedList<評論TextView>。在item顯示的時(shí)候。從緩沖池中取,而不是new。節(jié)省100ms左右 - ? 6、在Adpater的
onViewRecycled中把圖片和評論remove后存入緩存池。這一步主要為了滾動流暢 - ? 7、九張圖采用
GridLayout而不是 UnScrollView或者GridManager。后者會太重量級且會帶來更多的內(nèi)存消耗。這里其實(shí)最好自己寫一個(gè)ViewGroup
本方案用到的基礎(chǔ)常識:
- 1、ListView或RecyclerView如果當(dāng)前是不可見狀態(tài),你去setAdpater不會起到任何效果,代碼不會走onCreateViewHolder等回調(diào)。當(dāng)你設(shè)置為VISIBLE的時(shí)候,才會觸發(fā)Adapter里的回調(diào)。
- 2、RecyclerView的第3級緩存
ViewCacheExtension,用起來還要去反射ViewHolder的Layout.Params,挺不方便的,所以我沒用他。 - 3、
RecyclerViewPool緩存池每種type的最大值默認(rèn)為5。如果item是固定高度,那么緩存池的size總是在0和1之間徘徊,因?yàn)榈谝粋€(gè)item剛被回收,底部item就進(jìn)來了。 - 4、所以
RecyclerViewPool緩存池只有在非常極限情況下才會size == 5 (即:頂部的幾個(gè)item的height非常小,但是底部的item高度非常大)。 - 5、
onViewRecycled(holder: RecyclerView.ViewHolder)是Item進(jìn)入RecyclerViewPool的回調(diào),進(jìn)入之后,vh的data就等于無用了 - 6、
LinkedList要比ArrayList在增刪的時(shí)候更快,尤其是增刪第0個(gè)和最后一個(gè)object。且由于ArrayList底層用的是int[10], 所以存在內(nèi)存浪費(fèi)。所以用LinkedList做緩存池優(yōu)于ArrayList
本方案部分技術(shù)細(xì)節(jié):
Activity:
//在子線程中進(jìn)行預(yù)加載
preloadCacheViewsInThread(beanList) {
//預(yù)加載完畢,回到主線程處理UI
runOnUiThread {
handleRefreshSuccess(beanList)
}
}
private fun preloadCacheViewsInThread(topicBeanList: MutableList<TopicBean>, success: () -> Unit) {
Thread {
var pictureCountInFirstPage = 0 //第一頁有多少個(gè)圖片
var commentCountInFirstPage = 0 //第一頁有多少條小評論
var nowTime = System.currentTimeMillis()
for (topicBean in topicBeanList){
topicBean.findTotalSpanText(this@FriendActivity2, this@FriendActivity2)
topicBean.pictures?.let { pictureCountInFirstPage += it.count() }
}
Log.e(
"dq",
"預(yù)處理Model耗時(shí)為:" + (System.currentTimeMillis() - nowTime) + "毫秒"
) //方法運(yùn)行時(shí)間為:12毫秒
val layoutInflater = LayoutInflater.from(this@FriendActivity2)
//開始預(yù)加載每個(gè)Item的xml布局
nowTime = System.currentTimeMillis()
var i = 0
while (i < 8 && i < topicBeanList.count()) {
//這個(gè)8是預(yù)估的數(shù)字,也就是屏幕中的 + mCacheView(size == 2)+ pool里的(max是5,一般就是1)
val itemView: View = layoutInflater.inflate(R.layout.listitem_topic, null)
//緩存起來,放到onCreateHolder中使用
cachedItemViewList!!.add(itemView)
i++
}
Log.e(
"dq",
"預(yù)加載耗時(shí)為:" + (System.currentTimeMillis() - nowTime) + "毫秒"
) //方法運(yùn)行時(shí)間為:150毫秒
//開始預(yù)加載每個(gè)Item中的圖片的imageview
i = 0
while (i < 10 && i < pictureCountInFirstPage) {
val pictureImageView = ImageView(this@FriendActivity2)
cachedImageViewList.add(pictureImageView)
i++
}
Log.e(
"dq",
"預(yù)Image和Text耗時(shí)為:" + (System.currentTimeMillis() - nowTime) + "毫秒"
) //方法運(yùn)行時(shí)間為:120毫秒
success()
}.start()
}
Adpater:
//創(chuàng)建ViewHolder并綁定上itemview
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
var view: View? = null
cachedItemViewList?.let {
view = it.poll()
Log.e("dq", "onCreateViewHolder = 用上緩存")
}
if (view == null){
view = mInflater.inflate(R.layout.listitem_topic, parent, false)
Log.e("dq", "onCreateViewHolder = 沒用上緩存")
}
val viewHolder = TopicViewHolder(view!!)
if (this::cachedImageViewList.isInitialized) {
//是用了預(yù)加載
viewHolder.pictureGridLayout.cachedImageViewList = cachedImageViewList
viewHolder.linearLayoutForListView.cachedTextViewList = cachedTextViewList
}
return viewHolder
}
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
if (holder is TopicViewHolder) {
for (view in holder.pictureGridLayout.children) {
if (view is ImageView){
//必須要移除,不然會:You must call removeView()
cachedImageViewList.add(view);
view.setImageDrawable(null)
Log.e("dq","緩沖池回收圖片 " + cachedImageViewList.size);
}
}
holder.pictureGridLayout.removeAllViews()
for (view in holder.linearLayoutForListView.children) {
if (view is QMUILinkTextView){
//必須要移除,不然會:The specified child already has a parent. You must call removeView()
cachedTextViewList.add(view);
Log.e("dq","緩沖池回收評論 " + cachedTextViewList.size);
}
}
//必須要移除,不然會: You must call removeView()
holder.linearLayoutForListView.removeAllViews()
}
}
Author:DQ
我的其他開源庫,給個(gè)Star鼓勵(lì)我寫更多好庫:
Android 朋友圈列表Feed流的最優(yōu)化方案,讓你的RecyclerView從49幀 -> 57幀
Android 仿大眾點(diǎn)評、仿小紅書 下拉拖拽關(guān)閉Activity
Android 直播間聊天消息列表RecyclerView。一秒內(nèi)收到幾百條消息依然不卡頓
Android 仿快手直播界面加載中,頂部的滾動條狀LoadingView
Android Kotlin MVVM框架,全世界最優(yōu)化的分頁加載接口、最接地氣的封裝

