Listview加載的性能優(yōu)化是如何實(shí)現(xiàn)的
在android開發(fā)中Listview是一個很重要的組件,它以列表的形式根據(jù)數(shù)據(jù)的長自適應(yīng)展示具體內(nèi)容,用戶可以自由的定義listview每一列的布局,但當(dāng)listview有大量的數(shù)據(jù)需要加載的時候,會占據(jù)大量內(nèi)存,影響性能,這時候就需要按需填充并重新使用view來減少對象的創(chuàng)建。
listview加載的核心是其adapter,本文針對listview加載的性能優(yōu)化就是對adpter的優(yōu)化,總共分四個層次:
0、最原始的加載
1、利用convertView
2、利用ViewHolder
3、實(shí)現(xiàn)局部刷新
〇、最原始的加載
這里是不經(jīng)任何優(yōu)化的adapter,為了看起來方便,把listview的數(shù)據(jù)直接在構(gòu)造函數(shù)里傳給adapter了,代碼如下:
private class AdapterOptmL extends BaseAdapter { private LayoutInflater mLayoutInflater; private ArrayList<Integer> mListData; public AdapterOptmL(Context context, ArrayList<Integer> data) { mLayoutInflater = LayoutInflater.from(context); mListData = data; } @Override public int getCount() { return mListData == null ? : mListData.size(); } @Override public Object getItem(int position) { return mListData == null ? : mListData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { View viewRoot = mLayoutInflater.inflate(R.layout.listitem, parent, false); if (viewRoot != null) { TextView txt = (TextView)viewRoot.findViewById(R.id.listitem_txt); txt.setText(getItem(position) + ""); } return viewRoot; } }
一、利用convertView
上述代碼的第27行在Eclipse中已經(jīng)提示警告:
Unconditional layout inflation from view adapter: Should use View Holder pattern (use recycled view passed into this method as the second parameter) for smoother scrolling
這個意思就是說,被移出可視區(qū)域的view是可以回收復(fù)用的,它作為getview的第二個參數(shù)已經(jīng)傳進(jìn)來了,所以沒必要每次都從xml里inflate。
經(jīng)過優(yōu)化后的代碼如下:
@Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false); } if (convertView != null) { TextView txt = (TextView)convertView.findViewById(R.id.listitem_txt); txt.setVisibility(View.VISIBLE); txt.setText(getItem(position) + ""); } return convertView; }
上述代碼加了判斷,如果傳入的convertView不為null,則直接復(fù)用,否則才會從xml里inflate。
按照上述代碼,如果手機(jī)一屏最多同時顯示5個listitem,則最多需要從xml里inflate 5 次,比AdapterOptmL0中每個listitem都需要inflate顯然效率高多了。
上述的用法雖然提高了效率,但帶來了一個陷阱,如果復(fù)用convertView,則需要重置該view所有可能被修改過的屬性。
舉個例子:
如果第一個view中的textview在getview中被設(shè)置成INVISIBLE了,而現(xiàn)在第一個view在滾動過程中出可視區(qū)域,并假設(shè)它作為參數(shù)傳入第十個view的getview而被復(fù)用
那么,在第十個view的getview里面不僅要setText,還要重新setVisibility,因?yàn)檫@個被復(fù)用的view當(dāng)前處于INVISIBLE狀態(tài)!
二、利用ViewHolder
從AdapterOptmL0第27行的警告中,我們還可以看到編譯器推薦了一種模型叫ViewHolder,這是個什么東西呢,先看代碼:
private class AdapterOptmL extends BaseAdapter {
private LayoutInflater mLayoutInflater; private ArrayList<Integer> mListData; public AdapterOptmL(Context context, ArrayList<Integer> data) { mLayoutInflater = LayoutInflater.from(context); mListData = data; } private class ViewHolder { public ViewHolder(View viewRoot) { txt = (TextView)viewRoot.findViewById(R.id.listitem_txt); } public TextView txt; } @Override public int getCount() { return mListData == null ? : mListData.size(); } @Override public Object getItem(int position) { return mListData == null ? : mListData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false); ViewHolder holder = new ViewHolder(convertView); convertView.setTag(holder); } if (convertView != null && convertView.getTag() instanceof ViewHolder) { ViewHolder holder = (ViewHolder)convertView.getTag(); holder.txt.setVisibility(View.VISIBLE); holder.txt.setText(getItem(position) + ""); } return convertView; } }
從代碼中可以看到,這一步做的優(yōu)化是用一個類ViewHolder來保存listitem里面所有找到的子控件,這樣就不用每次都通過耗時的findViewById操作了。
這一步的優(yōu)化,在listitem布局越復(fù)雜的時候效果越為明顯。
三、實(shí)現(xiàn)局部刷新
OK,到目前為止,listview普遍需要的優(yōu)化已經(jīng)做的差不多了,那就該考慮實(shí)際使用場景中的優(yōu)化需求了。
實(shí)際使用listview過程中,通常會在后臺更新listview的數(shù)據(jù),然后調(diào)用Adatper的notifyDataSetChanged方法來更新listview的UI。
那么問題來了,一般情況下,一次只會更新listview的一條/幾條數(shù)據(jù),而調(diào)用notifyDataSetChanged方法則會把所有可視范圍內(nèi)的listitem都刷新一遍,這是不科學(xué)的!
所以,進(jìn)一步優(yōu)化的空間在于,局部刷新listview,話不多說見代碼:
private class AdapterOptmL3 extends BaseAdapter { private LayoutInflater mLayoutInflater; private ListView mListView; private ArrayList<Integer> mListData; public AdapterOptmL3(Context context, ListView listview, ArrayList<Integer> data) { mLayoutInflater = LayoutInflater.from(context); mListView = listview; mListData = data; } private class ViewHolder { public ViewHolder(View viewRoot) { txt = (TextView)viewRoot.findViewById(R.id.listitem_txt); } public TextView txt; } @Override public int getCount() { return mListData == null ? 0 : mListData.size(); } @Override public Object getItem(int position) { return mListData == null ? 0 : mListData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false); ViewHolder holder = new ViewHolder(convertView); convertView.setTag(holder); } if (convertView != null && convertView.getTag() instanceof ViewHolder) { updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position)); } return convertView; } public void updateView(ViewHolder holder, Integer data) { if (holder != null && data != null) { holder.txt.setVisibility(View.VISIBLE); holder.txt.setText(data + ""); } } public void notifyDataSetChanged(int position) { final int firstVisiablePosition = mListView.getFirstVisiblePosition(); final int lastVisiablePosition = mListView.getLastVisiblePosition(); final int relativePosition = position - firstVisiablePosition; if (position >= firstVisiablePosition && position <= lastVisiablePosition) { updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position)); } else { //不在可視范圍內(nèi)的listitem不需要手動刷新,等其可見時會通過getView自動刷新 } } }
修改后的Adapter新增了一個方法 public void notifyDataSetChanged(int position) 可以根據(jù)position只更新指定的listitem。
局部刷新番外篇
在局部刷新數(shù)據(jù)的接口中,實(shí)際上還可以再干點(diǎn)事情:listview正在滾動的時候不去刷新。
具體的思路是,如果當(dāng)前正在滾動,則記住一個pending任務(wù),等listview停止?jié)L動的時候再去刷,這樣不會造成滾動的時候刷新錯亂。代碼如下:
private class AdapterOptmLPlus extends BaseAdapter implements OnScrollListener{ private LayoutInflater mLayoutInflater; private ListView mListView; private ArrayList<Integer> mListData; private int mScrollState = SCROLL_STATE_IDLE; private List<Runnable> mPendingNotify = new ArrayList<Runnable>(); public AdapterOptmLPlus(Context context, ListView listview, ArrayList<Integer> data) { mLayoutInflater = LayoutInflater.from(context); mListView = listview; mListData = data; mListView.setOnScrollListener(this); } private class ViewHolder { public ViewHolder(View viewRoot) { txt = (TextView)viewRoot.findViewById(R.id.listitem_txt); } public TextView txt; } @Override public int getCount() { return mListData == null ? : mListData.size(); } @Override public Object getItem(int position) { return mListData == null ? : mListData.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { if (convertView == null) { convertView = mLayoutInflater.inflate(R.layout.listitem, parent, false); ViewHolder holder = new ViewHolder(convertView); convertView.setTag(holder); } if (convertView != null && convertView.getTag() instanceof ViewHolder) { updateView((ViewHolder)convertView.getTag(), (Integer)getItem(position)); } return convertView; } public void updateView(ViewHolder holder, Integer data) { if (holder != null && data != null) { holder.txt.setVisibility(View.VISIBLE); holder.txt.setText(data + ""); } } public void notifyDataSetChanged(final int position) { final Runnable runnable = new Runnable() { @Override public void run() { final int firstVisiablePosition = mListView.getFirstVisiblePosition(); final int lastVisiablePosition = mListView.getLastVisiblePosition(); final int relativePosition = position - firstVisiablePosition; if (position >= firstVisiablePosition && position <= lastVisiablePosition) { if (mScrollState == SCROLL_STATE_IDLE) { //當(dāng)前不在滾動,立刻刷新 Log.d("Snser", "notifyDataSetChanged position=" + position + " update now"); updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position)); } else { synchronized (mPendingNotify) { //當(dāng)前正在滾動,等滾動停止再刷新 Log.d("Snser", "notifyDataSetChanged position=" + position + " update pending"); mPendingNotify.add(this); } } } else { //不在可視范圍內(nèi)的listitem不需要手動刷新,等其可見時會通過getView自動刷新 Log.d("Snser", "notifyDataSetChanged position=" + position + " update skip"); } } }; runnable.run(); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { mScrollState = scrollState; if (mScrollState == SCROLL_STATE_IDLE) { //滾動已停止,把需要刷新的listitem都刷新一下 synchronized (mPendingNotify) { final Iterator<Runnable> iter = mPendingNotify.iterator(); while (iter.hasNext()) { iter.next().run(); iter.remove(); } } } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }
以上所述是針對Listview加載的性能優(yōu)化是如何實(shí)現(xiàn)的全部敘述,希望對大家有所幫助。
相關(guān)文章
Android軟鍵盤的顯示隱藏功能實(shí)現(xiàn)過程
這篇文章主要介紹了Android軟鍵盤的顯示隱藏功能,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-03-03Flutter 實(shí)現(xiàn)虎牙/斗魚 彈幕功能
這篇文章主要介紹了Flutter 實(shí)現(xiàn)虎牙/斗魚 彈幕功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04Android入門之ActivityGroup+GridView實(shí)現(xiàn)Tab分頁標(biāo)簽的方法
這篇文章主要介紹了Android入門之ActivityGroup+GridView實(shí)現(xiàn)Tab分頁標(biāo)簽的方法,非常實(shí)用的功能,需要的朋友可以參考下2014-08-08