ListView recycler機制詳解

 最近轉戰android平臺,支持一個兄弟團隊的無線項目。該項目遇到一個問題,有個ListView頁面,相似新浪微博的首頁,可是item複雜性遠超新浪微博:有些有圖片,有些沒有圖片。有些有一張圖片,有些有多張圖片。多張圖片佈局又有不一樣。android

 

 

如此複雜的界面效果,同時開發同窗進度很緊張,編碼時沒有考慮性能問題,因此結果是該頁面下拉上拉滾動時很卡,而且很快就會OOM掛掉.性能優化

 

 

 

網上有不少資料,介紹如何利用recycler機制優化ListView,而且也有說起利用getItemViewType支持2種類型item。但關於recycler機制並無給出詳細的分析介紹,咱們的同窗對可否知足咱們的優化需求充滿疑慮。app

 

 

因而我寫了一個demo,demo構建了擁有5種不一樣類型item的ListView,adapter的實現以下:ide

 

 
public class AppTranficListAdapter extends BaseAdapter {
private List<packageTranfic> data;
protected LayoutInflater inflater;
int layout_list[] = { R.layout.item1, R.layout.item2, R.layout.item3,
R.layout.item4, R.layout.item5 };
 
public AppTranficListAdapter(Context context, List<packageTranfic> lists) {
data = lists;
inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 
}
 
public int getCount() {
return data.size();
}
 
public long getItemId(int position) {
return position;
}
 
@Override
public int getViewTypeCount() {
return layout_list.length;
}
 
@Override
public int getItemViewType(int position) {
return data.get(position).mtype;
}
 
@Override
public Object getItem(int arg0) {
return null;
}
 
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (position >= data.size())
return convertView;
 
packageTranfic tranfic = data.get(position);
System.out.println('convertView: ' + convertView + 'cur type = '
+ tranfic.mtype);
ViewHolder holder;
if (null == convertView) {
holder = new ViewHolder();
convertView = inflater.inflate(layout_list[tranfic.mtype], null);
holder.icon = (ImageView) convertView.findViewById(R.id.icon);
holder.name = (TextView) convertView.findViewById(R.id.name);
holder.in = (TextView) convertView.findViewById(R.id.in);
holder.out = (TextView) convertView.findViewById(R.id.out);
holder.type = tranfic.mtype;
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
 
}
 
holder.icon.setImageDrawable(tranfic.icon);
holder.name.setText(tranfic.name);
holder.in.setText(tranfic.getInStr());
holder.out.setText(tranfic.getOutStr());
 
return convertView;
}
 
public final class ViewHolder {
public int type;
public ImageView icon;
public TextView name;
public TextView out;
public TextView in;
}
 
}

 

啓動app,運行後向下拖動,log以下:函數

 

 09:46:56.472: convertView: null  cur type = 3佈局

 

 09:46:56.502: convertView: null  cur type = 1性能

 

 09:46:56.522: convertView: null  cur type = 0優化

 

 09:46:56.552: convertView: android.widget.LinearLayout@42570d70  cur type = 0this

 

 09:46:56.552: convertView: null  cur type = 4編碼

 

 09:46:56.582: convertView: android.widget.LinearLayout@42528a60  cur type = 3

 

 09:46:56.592: convertView: android.widget.LinearLayout@4252afb0  cur type = 1

 

 09:46:56.592: convertView: android.widget.LinearLayout@42570d70  cur type = 0

 

 09:46:56.602: convertView: android.widget.LinearLayout@42570d70  cur type = 0

 

 09:46:56.622: convertView: android.widget.LinearLayout@425732a0  cur type = 4

 

 09:46:56.642: convertView: android.widget.LinearLayout@42528a60  cur type = 3

 

 09:46:56.682: convertView: android.widget.LinearLayout@4252afb0  cur type = 1

 

 09:46:56.692: convertView: android.widget.LinearLayout@42570d70  cur type = 0

 

 09:46:56.712: convertView: null  cur type = 0

 

 09:46:56.742: convertView: android.widget.LinearLayout@425732a0  cur type = 4

 

 09:47:10.296: convertView: null  cur type = 0

 

 09:47:10.587: convertView: null  cur type = 0

 

 09:47:12.929: convertView: android.widget.LinearLayout@4252afb0  cur type = 1

 

 09:47:13.200: convertView: android.widget.LinearLayout@42528a60  cur type = 3

 

 09:48:20.321: convertView: null  cur type = 3

 

 09:48:20.391: convertView: null  cur type = 3

 

 09:48:20.672: convertView: null  cur type = 2

 

 09:48:21.112: convertView: android.widget.LinearLayout@425732a0  cur type = 4

 

 09:48:21.192: convertView: null  cur type = 4

 

 09:48:21.372: convertView: android.widget.LinearLayout@4252afb0  cur type = 1

 

 09:48:21.813: convertView: null  cur type = 1

 

 09:48:21.883: convertView: android.widget.LinearLayout@4252a6e8  cur type = 3

 

 09:48:21.993: convertView: android.widget.LinearLayout@41e73a08  cur type = 0

 

 09:48:22.443: convertView: android.widget.LinearLayout@41e70dc0  cur type = 4

 

 09:48:22.514: convertView: android.widget.LinearLayout@42520030  cur type = 2

 

 09:48:22.644: convertView: android.widget.LinearLayout@4252afb0  cur type = 1

 

 09:48:23.054: convertView: null  cur type = 2

 

 09:48:23.104: convertView: android.widget.LinearLayout@41eeffd0  cur type = 1

 

 09:48:23.154: convertView: android.widget.LinearLayout@4252a6e8  cur type = 3

 

 09:48:23.675: convertView: android.widget.LinearLayout@42520030  cur type = 2

 

 09:48:23.735: convertView: android.widget.LinearLayout@41e70dc0  cur type = 4

 

 09:48:23.785: convertView: android.widget.LinearLayout@4252afb0  cur type = 1

 

 09:48:24.235: convertView: android.widget.LinearLayout@41e73678  cur type = 3

 

 09:48:24.285: convertView: android.widget.LinearLayout@41f06040  cur type = 2

 

 

 

優化已經生效。經過demo咱們知道ListView  recycler機制同樣能夠做用於擁有不一樣item的ListView。但不光要知其然,還要知其因此然。咱們再去看一下ListView以及RecycleBin的源碼具體來分析其item view重用機制。

 

 

 

首先咱們來看一下ListView的實現,添加新item的入口函數,都會調用obtainView:

 

 

 private View addViewAbove(View theView, int position) {

        int abovePosition = position - 1;
        View view =  obtainView(abovePosition, mIsScrap);
        int edgeOfNewChild = theView.getTop() - mDividerHeight;
        setupChild(view, abovePosition, edgeOfNewChild, false, mListPadding.left,
                false, mIsScrap[0]);
        return view;
    }
 
    private View addViewBelow(View theView, int position) {
        int belowPosition = position + 1;
        View view =  obtainView(belowPosition, mIsScrap);
        int edgeOfNewChild = theView.getBottom() + mDividerHeight;
        setupChild(view, belowPosition, edgeOfNewChild, true, mListPadding.left,
                false, mIsScrap[0]);
        return view;
    }
 
 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        View child;
 
 
        if (!mDataChanged) {
            // Try to use an existing view for this position
            child = mRecycler.getActiveView(position);
            if (child != null) {
                // Found it -- we're using an existing child
                // This just needs to be positioned
                setupChild(child, position, y, flow, childrenLeft, selected, true);
 
                return child;
            }
        }
 
        // Make a new view for this position, or convert an unused view if possible
        child = obtainView(position, mIsScrap);
 
        // This needs to be positioned and measured
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);
 
        return child;
    }
 

ListView派生自AbsListView,obtainView由AbsListView實現:

 View obtainView(int position, boolean[] isScrap) {

        isScrap[0] = false;
        View scrapView;
 
        scrapView = mRecycler.getTransientStateView(position);
        if (scrapView != null) {
            return scrapView;
        }
 
        scrapView = mRecycler.getScrapView(position);
 
        View child;
        if (scrapView != null) {
            child = mAdapter.getView(position, scrapView, this);
 
            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
            }
 
            if (child != scrapView) {
                mRecycler.addScrapView(scrapView, position);
                if (mCacheColorHint != 0) {
                    child.setDrawingCacheBackgroundColor(mCacheColorHint);
                }
            } else {
                isScrap[0] = true;
                child.dispatchFinishTemporaryDetach();
            }
        } else {
            child = mAdapter.getView(position, null, this);
 
            if (child.getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                child.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES);
            }
 
            if (mCacheColorHint != 0) {
                child.setDrawingCacheBackgroundColor(mCacheColorHint);
            }
        }
 
        if (mAdapterHasStableIds) {
            final ViewGroup.LayoutParams vlp = child.getLayoutParams();
            LayoutParams lp;
            if (vlp == null) {
                lp = (LayoutParams) generateDefaultLayoutParams();
            } else if (!checkLayoutParams(vlp)) {
                lp = (LayoutParams) generateLayoutParams(vlp);
            } else {
                lp = (LayoutParams) vlp;
            }
            lp.itemId = mAdapter.getItemId(position);
            child.setLayoutParams(lp);
        }
 
        if (AccessibilityManager.getInstance(mContext).isEnabled()) {
            if (mAccessibilityDelegate == null) {
                mAccessibilityDelegate = new ListItemAccessibilityDelegate();
            }
            if (child.getAccessibilityDelegate() == null) {
                child.setAccessibilityDelegate(mAccessibilityDelegate);
            }
        }
 
        return child;
    }

在obtainView函數中,咱們看到了recycler,recycler對應的類爲AbsListView中的RecycleBin。當ListView中的items有多種類型時,不在當前視圖中的item view會有條件的放入到對應類型的mScrapViews中。具體是否放入mScrapViews咱們能夠看如下代碼:

 

  void addScrapView(View scrap, int position) {
            AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
            if (lp == null) {
                return;
            }
 
            lp.scrappedFromPosition = position;
 
            // Don't put header or footer views or views that should be ignored
            // into the scrap heap
            int viewType = lp.viewType;
            final boolean scrapHasTransientState = scrap.hasTransientState();
            if (!shouldRecycleViewType(viewType) || scrapHasTransientState) {
                if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER || scrapHasTransientState) {
                    if (mSkippedScrap == null) {
                        mSkippedScrap = new ArrayList<View>();
                    }
                    mSkippedScrap.add(scrap);
                }
                if (scrapHasTransientState) {
                    if (mTransientStateViews == null) {
                        mTransientStateViews = new SparseArray<View>();
                    }
                    scrap.dispatchStartTemporaryDetach();
                    mTransientStateViews.put(position, scrap);
                }
                return;
            }
 
            scrap.dispatchStartTemporaryDetach();
            if (mViewTypeCount == 1) {
                mCurrentScrap.add(scrap);
            } else {
                mScrapViews[viewType].add(scrap);
            }
 
            scrap.setAccessibilityDelegate(null);
            if (mRecyclerListener != null) {
                mRecyclerListener.onMovedToScrapHeap(scrap);
            }
        }

 

再根據item的viewType去mScrapViews尋找可重用的view:

    View getScrapView(int position) {

            if (mViewTypeCount == 1) {
                return retrieveFromScrap(mCurrentScrap, position);
            } else {
                int whichScrap = mAdapter.getItemViewType(position);
                if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
                    return retrieveFromScrap(mScrapViews[whichScrap], position);
                }
            }
            return null;
        }
 
 static View retrieveFromScrap(ArrayList<View> scrapViews, int position) {
        int size = scrapViews.size();
        if (size > 0) {
            // See if we still have a view for this position.
            for (int i=0; i<size; i++) {
                View view = scrapViews.get(i);
                if (((AbsListView.LayoutParams)view.getLayoutParams())
                        .scrappedFromPosition == position) {
                    scrapViews.remove(i);
                    return view;
                }
            }
            return scrapViews.remove(size - 1);
        } else {
            return null;
        }
    }
 

至此,咱們已經比較瞭解ListView recycler機制,進行性能優化也應該駕輕就熟了。