一氧腰、緩存機(jī)制對比
1.1 ListView(兩級緩存)
ListView | ||||
---|---|---|---|---|
是否需要回調(diào)createView | 是否需要回調(diào)bindView | 生命周期 | 備注 | |
mActiveViews | 否 | 否 | onLayout函數(shù)周期中 | 用于屏幕內(nèi)ItemView快速重用 |
mScrapViews | 否 | 是 | 與mAdapter一致,當(dāng)adapter被更換時(shí)闺金,mScrapViews會被清空 |
1.2 RecyclerView(四級緩存)
RecyclerView | ||||
---|---|---|---|---|
是否需要回調(diào)createView | 是否需要回調(diào)bindView | 生命周期 | 備注 | |
mAttachedScrap | 否 | 否 | onLayout函數(shù)周期內(nèi) | 用于屏幕內(nèi)ItemView快速重用 |
mCacheViews | 否 | 否 | 與Adapter一致县忌,當(dāng)mAdapter被更換時(shí)鳞骤,mCacheViews即被緩存至mRecyclerPool | 默認(rèn)上限為2,即緩存屏幕外2個(gè)ItemView |
mViewCacheExtension | 不直接使用词裤,需要用戶在定制刺洒,默認(rèn)不實(shí)現(xiàn) | |||
mRecyclerPool | 否 | 是 | 與自身生命周期一致,不再被引用時(shí)即被釋放 | 默認(rèn)上限為5吼砂,技術(shù)上可以實(shí)現(xiàn)所有RecyclerViewPool共用同一個(gè) |
1.3 ListView和RecyclerView獲取緩存流程
ListView獲取緩存原理
RecyclerView獲取緩存原理
二逆航、RecyclerView局部刷新
ListView離屏緩存實(shí)現(xiàn)機(jī)制:ListView從mScrapViews根據(jù)pos獲取相應(yīng)的緩存,但是沒有直接用渔肩,而是重新getView纸泡,bindView。RecyclerView中通過pos獲取的是viewholder赖瞒,即pos->(view, viewHolder, flag)女揭,標(biāo)志位flag的作用是判斷View是否需要重新bindView。
三栏饮、ListView開發(fā)注意事項(xiàng)
3.1 ListView加載圖片數(shù)據(jù)混亂
ListView是根據(jù)RecycleBin來實(shí)現(xiàn)ItemView的復(fù)用的吧兔,出現(xiàn)圖片數(shù)據(jù)錯(cuò)亂加載的原因是每次從mScrapViews中獲取ItemView,都會重新調(diào)用getView()方法袍嬉,但是圖片是異步加載的倦炒,但是ItemView是復(fù)用的幢竹。所以可以通過tag來區(qū)分當(dāng)前ImageView和要顯示的圖片是否一致,如果一致則不去網(wǎng)絡(luò)加載。
// 給 ImageView 設(shè)置一個(gè) tag
holder.img.setTag(imgUrl);
// 預(yù)設(shè)一個(gè)圖片
holder.img.setImageResource(R.drawable.ic_launcher);
// 通過 tag 來防止圖片錯(cuò)位
if (imageView.getTag() != null && imageView.getTag().equals(imageUrl)) {
imageView.setImageBitmap(result);
}
3.2 ListView的優(yōu)化
- 使用ViewHolder復(fù)用機(jī)制
由于ListView的實(shí)現(xiàn)機(jī)制劫笙,每次從緩存里面獲取ItemView都需要getView()推捐,重新inflate布局比較耗時(shí)梳庆,可以給ListView添加ViewHolder并設(shè)置tag贯吓,代碼如下:
ViewHolder viewHolder = null;
View view = null;//getView方法要返回的View
if(convertView == null){//如果當(dāng)前沒有可以復(fù)用的View
viewHolder = new ViewHolder();
view = LayoutInflater.from(context).inflate(resourceId,null);//那么就從XML文件生成一個(gè)View
viewHolder.resourceViewName = view.findViewById(resouceViewId);//從XML中找到對應(yīng)的View
view.setTag(viewHolder);//將ViewHolder設(shè)置在當(dāng)前ItemView的tag里面
}else{//否則
view = convertView;//就使用可以復(fù)用的View
viewHolder = (ViewHolder)convertView.getTag();//從復(fù)用的View中取出viewHoder
}
class ViewHolder {
TextView name;
}
-
對圖片的優(yōu)化
給圖片設(shè)置內(nèi)存緩存(LruCache,SoftReference)弓柱,當(dāng)使用圖片時(shí)可用直接加載好的圖片沟堡,這樣體驗(yàn)會更流暢侧但;另外當(dāng)用戶快速滑動ListView時(shí)候,可以默認(rèn)為不加載航罗。
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {//list停止?jié)L動時(shí)加載圖片
loadImage(startPos, endPos);// 異步加載圖片 ,只加載可以看到的圖片
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//設(shè)置當(dāng)前屏幕顯示的起始pos和結(jié)束pos
startPos = firstVisibleItem;
endPos = firstVisibleItem + visibleItemCount;
if (endPos >= totalItemCount) {
endPos = totalItemCount - 1;
}
}
});
- onClickListener的處理
當(dāng)contentView中有很多View需要對點(diǎn)擊事件的處理禀横,可以集中在viewHolder中處理:
class ViewHolder implements OnClickListener{
int position;
TextView name;
public void setPosition(int position){
this.position = position;
}
@Override
public void onClick(View v) {
switch (v.getId()){
//XXXX
}
}
}
ViewHolder viewHolder = null;
View view = null;//getView方法要返回的View
if(convertView == null){//如果當(dāng)前沒有可以復(fù)用的View
viewHolder = new ViewHolder();
view = LayoutInflater.from(context).inflate(resourceId,null);//那么就從XML文件生成一個(gè)View
viewHolder.resourceViewName = view.findViewById(resouceViewId);//從XML中找到對應(yīng)的View
viewHolder.setPosition(position);//設(shè)置位置
viewHolder.name.setOnClickListener(viewHolder);//設(shè)置ClickListener
view.setTag(viewHolder);//將ViewHolder設(shè)置在當(dāng)前ItemView的tag里面
}else{//否則
view = convertView;//就使用可以復(fù)用的View
viewHolder = (ViewHolder)convertView.getTag();//從復(fù)用的View中取出viewHoder
}
四、RecyclerView開發(fā)注意事項(xiàng)
4.1 RecyclerView的性能優(yōu)化
-
數(shù)據(jù)方面的處理
類似于這種對數(shù)據(jù)進(jìn)行html柵格化的操作 ( Html.fromHtml(data) )粥血, 有必要放在子線程和網(wǎng)絡(luò)加載中一起處理柏锄,可以讓用戶覺得數(shù)據(jù)加載的時(shí)間會稍微長點(diǎn);分頁加載的數(shù)據(jù)要進(jìn)行緩存起來复亏,加快加載速度趾娃。
-
布局優(yōu)化
- 減少過度繪制,可考慮自定義View來減少層級蜓耻,或更合理的設(shè)置布局來減少層級,不推薦在RecyclerView中使用ConstranintLayout械巡。
- 減少xml文件inflate時(shí)間刹淌,如果當(dāng)item的復(fù)用幾率很低的情況下,隨著type增多讥耗,可以嘗試用代碼去生成布局有勾,這樣可以加快布局的加載速度。
- 減少View對象的創(chuàng)建古程,盡量的簡化ItemView蔼卡,設(shè)計(jì)ItemType時(shí),多對ViewType能夠共用的地方設(shè)計(jì)成自定義View挣磨。
-
其他
- 25.1.0版本及以上使用Prefetch功能雇逞,RecyclerView視圖數(shù)據(jù)預(yù)取。
在RecyclerView顯示一幀的過程包括UI線程創(chuàng)建視圖和綁定視圖的操作茁裙,然后交給RenderThread發(fā)指令通知GPU進(jìn)行渲染的操作塘砸,但是在顯示一幀完成和下幀開始創(chuàng)建視圖的過程中,UI線程是無所事事的晤锥,可以在這個(gè)時(shí)候?qū)竺娴囊晥D進(jìn)行創(chuàng)建和綁定操作掉蔬。
如果Item高度是固定的話,可以使用RecyclerView.setHasFixedSize(true)矾瘾;來避免requestLayout浪費(fèi)資源女轿。
設(shè)置RecyclerView.addOnScrollListener(listener),來對滑動過程中停止加載的操作壕翩。
如果不要求動畫蛉迹,可以調(diào)用 getItemAnimator().setSupportsChangeAnimations(false),把默認(rèn)動畫關(guān)閉放妈。
對TextView使用String.toUpperCase來替代 android:textAllCaps="true"婿禽。
通過重寫RecyclerView.onViewRecycled(holder)來回收資源赏僧,當(dāng)這個(gè)方法被回調(diào)的時(shí)候,就表示表示這個(gè)Holder已經(jīng)被扔進(jìn)mRecyclerPool.mScrap里了扭倾,也就是再次取出的時(shí)候會經(jīng)過onBindViewHolder方法重新綁定數(shù)據(jù)淀零。
通過設(shè)置RecyclerView.setItemViewCacheSize(size);來加大RecyclerView的緩存膛壹,用空間來換取時(shí)間上的流暢性驾中。
如果多個(gè)RecyclerView的Adapter是一樣的,比如嵌套的RecyclerView中存在一樣的Adapter模聋,可以設(shè)置RecyclerView.setRecycledViewPool(pool)肩民;來共用一個(gè)RecycledViewPool。
對ItemView設(shè)置監(jiān)聽器链方,不要對每個(gè)Item都調(diào)用addXXListener持痰,應(yīng)該共用一個(gè)Listener,根據(jù)ID來進(jìn)行不同的操作祟蚀,優(yōu)化了對象的頻繁創(chuàng)建帶來的資源消耗工窍。
通過getExtraLayoutSpace來增加RecyclerView預(yù)留的額外空間。
//顯示范圍之外提供更大的緩存空間前酿。 new LinearLayoutManager(this) { @Override protected int getExtraLayoutSpace(RecyclerView.State state) { return size; } };
4.2 stableId 的使用
setHasStableIds用來標(biāo)識每一個(gè)itemView是否需要一個(gè)唯一標(biāo)識患雏,當(dāng)stableId設(shè)置為true的時(shí)候,每一個(gè)itemView數(shù)據(jù)就有一個(gè)唯一標(biāo)識罢维。getItemId()返回代表這個(gè)ViewHolder的唯一標(biāo)識淹仑,如果沒有設(shè)置stableId唯一性,返回NO_ID=-1肺孵。通過setHasStableIds可以使itemView的焦點(diǎn)固定匀借,從而解決RecyclerView的notify方法使得圖片加載時(shí)閃爍問題。注意:setHasStableIds()必須在 setAdapter() 方法之前調(diào)用平窘,否則會拋異常怀吻。因?yàn)镽ecyclerView.setAdapter后就設(shè)置了觀察者,設(shè)置了觀察者stateIds就不能變了初婆。
4.3 7.0新工具類DiffUtil
DiffUtil是support-v7:24.2.0中的新工具類蓬坡,它用來比較兩個(gè)數(shù)據(jù)集,尋找出舊數(shù)據(jù)集—>新數(shù)據(jù)集的最小變化量磅叛,它和mAdapter.notifyDataSetChanged()最大不同在于`它會自動計(jì)算新老數(shù)據(jù)集的差異屑咳,并根據(jù)差異情況,自動調(diào)用以下四個(gè)方法:
adapter.notifyItemRangeInserted(position, count);
adapter.notifyItemRangeRemoved(position, count);
adapter.notifyItemMoved(fromPosition, toPosition);
adapter.notifyItemRangeChanged(position, count, payload);
且調(diào)用notifyDataSetChanged()
不會觸發(fā)RecyclerView的動畫(刪除弊琴、新增兆龙、位移、change動畫),其次性能較低紫皇,它不管數(shù)據(jù)是否一樣都整個(gè)刷新了一遍整個(gè)RecyclerView 慰安,使用如下:
public abstract static class Callback {
public abstract int getOldListSize();//老數(shù)據(jù)集size
public abstract int getNewListSize();//新數(shù)據(jù)集size
//新老數(shù)據(jù)集在同一個(gè)position的Item是否是一個(gè)對象,如果給itemView設(shè)置了stableIds聪铺,則僅比較它們單獨(dú)的 //id(可能內(nèi)容不同化焕,如果這里返回true,會調(diào)用下面的方法)
public abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);
//這個(gè)方法僅僅是上面方法返回true才會調(diào)用铃剔,判斷item的內(nèi)容是否有變化撒桨,類似于Object.equals(Object)
public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);
//當(dāng)areItemsTheSame()返回true且areContentsTheSame()返回false,
//同之處用下面的方法找出兩個(gè)itemView的data不
@Nullable
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
return null;
}
}
//使用的時(shí)候?qū)崿F(xiàn)Callback接口
DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new ProductListDiffCallback(mProducts, newProducts));
diffResult.dispatchUpdatesTo(mProductAdapter);
4.4 NestedScrollView嵌套RecyclerView
-
滑動IRecyclerView列表會出現(xiàn)強(qiáng)烈的卡頓感
mRecyclerView.setNestedScrollingEnabled(false);
RecyclerView默認(rèn)是setNestedScrollingEnabled(true),是支持嵌套滾動的,也就是說當(dāng)它嵌套在NestedScrollView中時(shí),默認(rèn)會隨著NestedScrollView滾動而滾動,放棄了自己的滾動键兜。將該值置false可以讓RecyclerView不支持嵌套滑動凤类,這樣RecyclerView可以自己響應(yīng)滑動事件。
-
每次打開界面都是定位在RecyclerView在屏幕頂端,列表上面的布局都被頂上去了
RecyclerView搶占了焦點(diǎn),自動滾動導(dǎo)致的.
RecyclerView會在構(gòu)造方法中調(diào)用setFocusableInTouchMode(true), 搶占焦點(diǎn)后一定會定位到第一行的位置普气,可以在NestedScrollView中添加屬性:android:focusableInTouchMode="true"谜疤,同時(shí)在RecyclerView中添加屬性:android:descendantFocusability="blocksDescendants"或直接設(shè)置mRecyclerVIew.setFocusableInTouchMode(false)
4.5 圖片混亂和閃爍的問題
- 當(dāng)Item還在加載圖片的過程中,被移除屏幕可視范圍现诀,不需要繼續(xù)加載這張圖片了夷磕,可以在onViewRecycled中取消圖片的加載,這樣就不會造成圖片加載完設(shè)置到其他Item的ImageView中了赶盔。
- 每一個(gè)經(jīng)過屏幕可視區(qū)域的item企锌,加載的圖片都要放到緩存中榆浓,即使item離開了可視區(qū)域于未,也要加載完畢并放入緩存中,方便下次瀏覽時(shí)能夠快速加載陡鹃,每次onBind時(shí)對ImageView設(shè)置tag標(biāo)記烘浦,如果tag標(biāo)記已經(jīng)被更改了,舊線程加載好的圖片不再設(shè)置到ImageView中萍鲸。