最近轉戰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機制,進行性能優化也應該駕輕就熟了。