最近转战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机制,进行性能优化也应该得心应手了。