How is the performance optimization for Listview loading implemented

  • 2021-01-19 22:23:31
  • OfStack

Listview in the android development is a very important component, it according to the data in the form of a list of the adaptive display specific content, users can freely define listview per 1 column layout, but when listview have large amounts of data need to be loaded, will occupy a lot of memory and impact performance, at that time according to need to fill and will need to use view to reduce the number of objects to create.

The core of listview loading is its adapter. The performance optimization for listview loading in this paper is the optimization of adpter, which is divided into four levels:

0. The original load

1. Use convertView

2. Use ViewHolder

3. Realize local refresh

� and most primitive loading

Here is ES32en without any optimizations. For the sake of convenience, the data from ES33en is passed directly to ES34en in the constructor as follows:


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;
}
}

1. The use of convertView

Line 27 of the above code is already warning in Eclipse:

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

This means that the view removed from the visible area is recyclable. It is already passed in as the second parameter of getview, so it is not necessary to take the inflate from xml every time.

The optimized code is as follows:


@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;
}

If the convertView passed in is not null, it will be reused directly, otherwise it will be from xml inflate.

According to the above code, if one screen of the mobile phone displays up to 5 listitem at the same time, the maximum number of inflate from xml will be 5 times, which is obviously more efficient than each listitem in AdapterOptmL0.

The above usage, while more efficient, introduces a pitfall: if you reuse convertView, you need to reset all properties that might have been modified.

Here's an example:

If textview in view is set to INVISIBLE in getview, and now the first view is out of view during scrolling, and it is assumed that it is passed as a parameter to getview in view and reused

So, in the getview of the 10th view, not only setText, but also setVisibility, because the reused view is currently in INVISIBLE state!

2. Use ViewHolder

From the warning on line 27 of AdapterOptmL0, we can also see that the compiler recommends a model called 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;
}
}

As you can see from the code, the optimization done in this step is to use a class ViewHolder to hold all the child controls found in listitem, so that you don't have to go through the time-consuming findViewById operation every time.

This 1 step optimization, in the listitem layout is more complex when the effect is more obvious.

3. Realize local refresh

OK. So far, we have done a good deal of the general tuning requirements for listview, so it is time to consider the tuning requirements for real usage scenarios.

The actual use of listview usually involves updating listview data in the background and then calling notifyDataSetChanged methods on Adatper to update UI on listview.

In most cases, only one or several listview data will be updated at a time. However, calling notifyDataSetChanged method will refresh all the listitem data within the visual range once. This is not scientific!

Therefore, the further optimization space lies in the local refresh listview, without saying much, see the code:


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 {
// Not in view listitem There is no need to refresh manually, it will pass when it is visible getView Automatically refresh 
}
}
} 

public void notifyDataSetChanged(int position) It is possible to update only the specified listitem according to position.

Refresh part of the article

In the interface that partially refreshes data, there is actually one more thing you can do: listview does not refresh while scrolling.

The idea is that if you are currently scrolling, remember 1 pending task and wait until listview stops scrolling before scrolling, so as not to cause scrolling refresh confusion. The code is as follows:


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) {
// Refresh immediately if not currently scrolling 
Log.d("Snser", "notifyDataSetChanged position=" + position + " update now");
updateView((ViewHolder)mListView.getChildAt(relativePosition).getTag(), (Integer)getItem(position));
} else {
synchronized (mPendingNotify) {
// Currently scrolling, wait for scrolling to stop and then refresh 
Log.d("Snser", "notifyDataSetChanged position=" + position + " update pending");
mPendingNotify.add(this);
}
}
} else {
// Not in view listitem There is no need to refresh manually, it will pass when it is visible getView Automatically refresh 
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) {
// Scrolling has stopped, put the need to refresh listitem Refresh every 1 Under the 
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) {
}
}

The above is a complete description of how the performance optimization for Listview loading is implemented. I hope it will help you.


Related articles: