最近转战android平台,支持一个兄弟团队的无线项目。该项目遇到一个问题,有个ListView页面,类似新浪微博的首页,但是item复杂性远超新浪微博:有些有图片,有些没有图片。有些有一张图片,有些有多张图片。多张图片布局又有不同。
如此复杂的界面效果,同时开发同学进度很紧张,编码时没有考虑性能问题,所以结果是该页面下拉上拉滚动时很卡,并且很快就会OOM挂掉.
网上有很多资料,介绍如何利用recycler机制优化ListView,并且也有提及利用getItemViewType支持2种类型item。但关于recycler机制并没有给出详细的分析介绍,我们的同学对能否满足我们的优化需求充满疑虑。
于是我写了一个demo,demo构建了拥有5种不同类型item的ListView,adapter的实现如下:
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 = 0
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机制,进行性能优化也应该得心应手了。