Android RecyclerView 性能优化总结
轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/111240529
本文出自【趙彥軍的博客】
目錄
- 相關文章
- recyclerView.setHasFixedSize(true);
- LinearLayoutManager.setInitialItemPrefetchCount()
- 局部刷新
- DiffUtil
- AsyncListDiff
- getExtraLayoutSpace
- 避免創建過多 OnClickListener 對象
- 復用RecycledViewPool
- 減少過度繪制
相關文章
https://blog.csdn.net/smileiam/article/details/88396546
https://zhuanlan.zhihu.com/p/49338922
https://www.jianshu.com/p/29352def27e6
SetHasStableIds(true) + getItemId 探究
https://www.jianshu.com/p/66d0feab2b5b
Android AsyncListDiffer-RecyclerView最好的伙伴
recyclerView.setHasFixedSize(true);
當Item的高度如是固定的,設置這個屬性為true可以提高性能,尤其是當RecyclerView有條目插入、刪除時性能提升更明顯。
RecyclerView在條目數量改變,會重新測量、布局各個item,如果設置了 setHasFixedSize(true),由于item的寬高都是固定的,adapter的內容改變時,RecyclerView不會整個布局都重繪。具體可用以下偽代碼表示:
void onItemsInsertedOrRemoved() {if (hasFixedSize) layoutChildren();else requestLayout();LinearLayoutManager.setInitialItemPrefetchCount()
RecyclerView 嵌套 RecyclerView 實現橫向滑動,設置 LinearLayoutManager.setInitialItemPrefetchCount() 設置 橫向 滾動布局預加載個數,避免UI 卡頓
- 只有 LinearLayoutManager 有這個api,其他 LayoutManager 沒有
- 只有 RecyclerView 嵌套 RecyclerView 才有效
局部刷新
notifyItemChanged(int position) notifyItemInserted(int position) notifyItemRemoved(int position) notifyItemMoved(int fromPosition, int toPosition) notifyItemRangeChanged(int positionStart, int itemCount) notifyItemRangeInserted(int positionStart, int itemCount) notifyItemRangeRemoved(int positionStart, int itemCount)DiffUtil
https://blog.csdn.net/zxt0601/article/details/52562770
DiffUtil.Callback 是一個抽象類
public abstract static class Callback {public abstract int getOldListSize();//老數據集sizepublic abstract int getNewListSize();//新數據集sizepublic abstract boolean areItemsTheSame(int oldItemPosition, int newItemPosition);//新老數據集在同一個postion的Item是否是一個對象?(可能內容不同,如果這里返回true,會調用下面的方法)public abstract boolean areContentsTheSame(int oldItemPosition, int newItemPosition);//這個方法僅僅是上面方法返回ture才會調用,我的理解是只有notifyItemRangeChanged()才會調用,判斷item的內容是否有變化//該方法在DiffUtil高級用法中用到 ,暫且不提@Nullablepublic Object getChangePayload(int oldItemPosition, int newItemPosition) {return null;}}1、繼承自DiffUtil.Callback的類,實現它的四個abstract方法
/*** 介紹:核心類 用來判斷 新舊Item是否相等* 作者:zhangxutong* 郵箱:zhangxutong@imcoming.com* 時間: 2016/9/12.*/public class DiffCallBack extends DiffUtil.Callback {private List<TestBean> mOldDatas, mNewDatas;//看名字public DiffCallBack(List<TestBean> mOldDatas, List<TestBean> mNewDatas) {this.mOldDatas = mOldDatas;this.mNewDatas = mNewDatas;}//老數據集size@Overridepublic int getOldListSize() {return mOldDatas != null ? mOldDatas.size() : 0;}//新數據集size@Overridepublic int getNewListSize() {return mNewDatas != null ? mNewDatas.size() : 0;}/*** Called by the DiffUtil to decide whether two object represent the same Item.* 被DiffUtil調用,用來判斷 兩個對象是否是相同的Item。* For example, if your items have unique ids, this method should check their id equality.* 例如,如果你的Item有唯一的id字段,這個方法就 判斷id是否相等。* 本例判斷name字段是否一致** @param oldItemPosition The position of the item in the old list* @param newItemPosition The position of the item in the new list* @return True if the two items represent the same object or false if they are different.*/@Overridepublic boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {return mOldDatas.get(oldItemPosition).getName().equals(mNewDatas.get(newItemPosition).getName());}/*** Called by the DiffUtil when it wants to check whether two items have the same data.* 被DiffUtil調用,用來檢查 兩個item是否含有相同的數據* DiffUtil uses this information to detect if the contents of an item has changed.* DiffUtil用返回的信息(true false)來檢測當前item的內容是否發生了變化* DiffUtil uses this method to check equality instead of {@link Object#equals(Object)}* DiffUtil 用這個方法替代equals方法去檢查是否相等。* so that you can change its behavior depending on your UI.* 所以你可以根據你的UI去改變它的返回值* For example, if you are using DiffUtil with a* {@link android.support.v7.widget.RecyclerView.Adapter RecyclerView.Adapter}, you should* return whether the items' visual representations are the same.* 例如,如果你用RecyclerView.Adapter 配合DiffUtil使用,你需要返回Item的視覺表現是否相同。* This method is called only if {@link #areItemsTheSame(int, int)} returns* {@code true} for these items.* 這個方法僅僅在areItemsTheSame()返回true時,才調用。* @param oldItemPosition The position of the item in the old list* @param newItemPosition The position of the item in the new list which replaces the* oldItem* @return True if the contents of the items are the same or false if they are different.*/@Overridepublic boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {TestBean beanOld = mOldDatas.get(oldItemPosition);TestBean beanNew = mNewDatas.get(newItemPosition);if (!beanOld.getDesc().equals(beanNew.getDesc())) {return false;//如果有內容不同,就返回false}if (beanOld.getPic() != beanNew.getPic()) {return false;//如果有內容不同,就返回false}return true; //默認兩個data內容是相同的}梳理流程圖如下
2、使用方法
注釋掉你以前寫的notifyDatasetChanged()方法吧
在將newDatas 設置給Adapter之前,先調用DiffUtil.calculateDiff()方法,計算出新老數據集轉化的最小更新集,就是DiffUtil.DiffResult對象。
//第一個參數是DiffUtil.Callback對象, //第二個參數代表是否檢測Item的移動,改為false算法效率更高,按需設置,我們這里是true。 DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffCallBack(mDatas, newDatas), true);3、DiffUtil.DiffResult 對象的 dispatchUpdatesTo()方法,傳入RecyclerView的Adapter,替代原來的mAdapter.notifyDataSetChanged()方法。
diffResult.dispatchUpdatesTo(mAdapter);AsyncListDiff
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserViewHodler> {private AsyncListDiffer<User> mDiffer;private DiffUtil.ItemCallback<User> diffCallback = new DiffUtil.ItemCallback<User>() {@Overridepublic boolean areItemsTheSame(User oldItem, User newItem) {return TextUtils.equals(oldItem.getId(), newItem.getId());}@Overridepublic boolean areContentsTheSame(User oldItem, User newItem) {return oldItem.getAge() == newItem.getAge();}};public UserAdapter() {mDiffer = new AsyncListDiffer<>(this, diffCallback);}@Overridepublic int getItemCount() {return mDiffer.getCurrentList().size();}public void submitList(List<User> data) {mDiffer.submitList(data);}public User getItem(int position) {return mDiffer.getCurrentList().get(position);}@NonNull@Overridepublic UserAdapter.UserViewHodler onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user_list, parent, false);return new UserViewHodler(itemView);}@Overridepublic void onBindViewHolder(@NonNull UserAdapter.UserViewHodler holder, int position) {holder.setData(getItem(position));}class UserViewHodler extends RecyclerView.ViewHolder {//此處省略一段public UserViewHodler(View itemView) {super(itemView);}public void setData(User data) {tvName.setText(data.getName());tvAge.setText(String.valueOf(data.getAge()));}} }使用方法:
List<User> users = new ArrayList<>(); for (int i = 0; i < 10; i++) {users.add(new User(String.valueOf(i), "用戶" + i, i + 20)); } mAdapter.submitList(users);getExtraLayoutSpace
在RecyclerView的元素比較高,一屏只能顯示一個元素的時候,第一次滑動到第二個元素會卡頓。
RecyclerView (以及其他基于adapter的view,比如ListView、GridView等)使用了緩存機制重用子 view(即系統只將屏幕可見范圍之內的元素保存在內存中,在滾動的時候不斷的重用這些內存中已經存在的view,而不是新建view)。
這個機制會導致一個問題,啟動應用之后,在屏幕可見范圍內,如果只有一張卡片可見,當滾動的時 候,RecyclerView找不到可以重用的view了,它將創建一個新的,因此在滑動到第二個feed的時候就會有一定的延時,但是第二個feed之 后的滾動是流暢的,因為這個時候RecyclerView已經有能重用的view了。
如何解決這個問題呢,其實只需重寫getExtraLayoutSpace()方法。根據官方文檔的描述 getExtraLayoutSpace將返回LayoutManager應該預留的額外空間(顯示范圍之外,應該額外緩存的空間)。
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this) {@Overrideprotected int getExtraLayoutSpace(RecyclerView.State state) {return 300;} };避免創建過多 OnClickListener 對象
onCreateViewHolder 和 onBindViewHolder 對時間都比較敏感,盡量避免繁瑣的操作和循環創建對象。例如創建 OnClickListener,可以全局創建一個。
同時onBindViewHolder調用次數會多于onCreateViewHolder的次數,如從RecyclerViewPool緩存池中取到的View都需要重新bindView,所以我們可以把監聽放到CreateView中進行。
優化前:
@Override public void onBindViewHolder(ViewHolder holder, int position) {holder.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//do something}}); }優化后:
private class XXXHolder extends RecyclerView.ViewHolder {private EditText mEt;EditHolder(View itemView) {super(itemView);mEt = (EditText) itemView;mEt.setOnClickListener(mOnClickListener);}}private View.OnClickListener mOnClickListener = new View.OnClickListener() {@Overridepublic void onClick(View v) {//do something} }復用RecycledViewPool
在TabLayout+ViewPager+RecyclerView的場景中,當多個RecyclerView有相同的item布局結構時,多個RecyclerView共用一個RecycledViewPool可以避免創建ViewHolder的開銷,避免GC。RecycledViewPool對象可通過RecyclerView對象獲取,也可以自己實現。
RecycledViewPool mPool = mRecyclerView1.getRecycledViewPool(); //下一個RecyclerView可直接進行setRecycledViewPoolmRecyclerView2.setRecycledViewPool(mPool);mRecyclerView3.setRecycledViewPool(mPool);減少過度繪制
減少布局層級
總結
以上是生活随笔為你收集整理的Android RecyclerView 性能优化总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android Bitmap 研究与思考
- 下一篇: Android 跨进程通信大总结