Android Frequent Examination Question-ListView and Its Reuse

Keywords: Android

ListView has almost been replaced by RecycleView, but the core idea of reuse is great, and it is often questioned in entry-level interviews. When you look at RecycleView, clear up ListView first. The reason for this is a serious bug in RecycleView reuse. In fact, I tried to reuse ListView a long time ago. Now it's amazing to look back. How did I create a multi-tier nested ListView and solve the reuse problem at the same time? I was so amazing that adapter didn't even know what it was that brought out these guys. It's a long way off.

All in all, this time I just learned the two pieces of source code together. Start with a simple ListView.

I have previously recorded the use of ListView control, adapter related content is relatively simple, not to mention.

First, you need to know what ListView is. android is often OOM or crash. The reason is memory overflow. In a list item, there may be hundreds or thousands of list items. If all of them are put into memory and a new list unit is loaded for each list item, the memory will not be able to eat and the program apes will not be able to eat. So there's ListView. Its main purpose is to continuously reuse and reduce the memory occupied by the same unit. ListView can display content in the form of a list, and content beyond the screen can be moved to the screen by simply sliding through the fingers.

The biggest advantage of ListView is the reuse process. That's RecycleBin. Several main methods in this class:

fillActiveViews(int childCount, int firstActivePosition)
The first parameter is the number of views, and the second parameter is the position of the initial view. This method is used to place the element at the specified position in the ListView.

void fillActiveViews(int childCount, int firstActivePosition) {
		if (mActiveViews.length < childCount) {
			mActiveViews = new View[childCount];
		}
		mFirstActivePosition = firstActivePosition;
		final View[] activeViews = mActiveViews;
		for (int i = 0; i < childCount; i++) {
			View child = getChildAt(i);
			AbsListView.LayoutParams lp = (AbsListView.LayoutParams) child.getLayoutParams();
			// Don't put header or footer views into the scrap heap
			if (lp != null && lp.viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
				// Note: We do place AdapterView.ITEM_VIEW_TYPE_IGNORE in
				// active views.
				// However, we will NOT place them into scrap views.
				activeViews[i] = child;
			}
		}
	}

getActiveView(int position) 
This method corresponds to the previous one, which is used to get the element of the position position, and then remove the acquired element.

View getActiveView(int position) {
		int index = position - mFirstActivePosition;
		final View[] activeViews = mActiveViews;
		if (index >= 0 && index < activeViews.length) {
			final View match = activeViews[index];
			activeViews[index] = null;
			return match;
		}
		return null;
	}

addScrapView(View scrap)
When a View is determined to be discarded (such as scrolling out of the screen), this method takes the View to the cache.

void addScrapView(View scrap) {
		AbsListView.LayoutParams lp = (AbsListView.LayoutParams) scrap.getLayoutParams();
		if (lp == null) {
			return;
		}
		// Don't put header or footer views or views that should be ignored
		// into the scrap heap
		int viewType = lp.viewType;
		if (!shouldRecycleViewType(viewType)) {
			if (viewType != ITEM_VIEW_TYPE_HEADER_OR_FOOTER) {
				removeDetachedView(scrap, false);
			}
			return;
		}
		if (mViewTypeCount == 1) {
			dispatchFinishTemporaryDetach(scrap);
			mCurrentScrap.add(scrap);
		} else {
			dispatchFinishTemporaryDetach(scrap);
			mScrapViews[viewType].add(scrap);
		}
 
		if (mRecyclerListener != null) {
			mRecyclerListener.onMovedToScrapHeap(scrap);
		}
	}

getScrapView(int position) 
When you take out the abandoned view, the view in the abandoned cache is out of order, so the getScrapView() method takes a scrap view at the end directly and returns it.

View getScrapView(int position) {
		ArrayList<View> scrapViews;
		if (mViewTypeCount == 1) {
			scrapViews = mCurrentScrap;
			int size = scrapViews.size();
			if (size > 0) {
				return scrapViews.remove(size - 1);
			} else {
				return null;
			}
		} else {
			int whichScrap = mAdapter.getItemViewType(position);
			if (whichScrap >= 0 && whichScrap < mScrapViews.length) {
				scrapViews = mScrapViews[whichScrap];
				int size = scrapViews.size();
				if (size > 0) {
					return scrapViews.remove(size - 1);
				}
			}
		}
		return null;
	}

setViewTypeCount()
The purpose of this method is to enable a RecycleBin caching mechanism for each type of data item. After rewriting, there is an opportunity to solve the reuse problem.

This is the code for reusing some classes. Next is the drawing process of ListView, which is mainly different from other view s in layout, so its onLayout() method is crucial. Its layout method inherits the parent class AbsListView.

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
	super.onLayout(changed, l, t, r, b);
	mInLayout = true;
	if (changed) {
		int childCount = getChildCount();
		for (int i = 0; i < childCount; i++) {
			getChildAt(i).forceLayout();
		}
		mRecycler.markChildrenDirty();
	}
	layoutChildren();
	mInLayout = false;
}

Logically, it's a judgment that if the ListView changes, change becomes true, and all the sub-layouts are forced to be redrawn. There is a layoutChildren() method below, which is then implemented in ListView. The main work of layoutChildren() method is to draw sub-layout. Then draw it from the top down in the setupChild() method, and stop the layout when the layout exceeds the scope of ListView. So there's not much code to explain. The most critical piece of code is in the obtainView() method.

/**
 * Get a view and have it show the data associated with the specified
 * position. This is called when we have already discovered that the view is
 * not available for reuse in the recycle bin. The only choices left are
 * converting an old view or making a new one.
 * 
 * @param position
 *            The position to display
 * @param isScrap
 *            Array of at least 1 boolean, the first entry will become true
 *            if the returned view was taken from the scrap heap, false if
 *            otherwise.
 * 
 * @return A view displaying the data associated with the specified position
 */
View obtainView(int position, boolean[] isScrap) {
	isScrap[0] = false;
	View scrapView;
	scrapView = mRecycler.getScrapView(position);
	View child;
	if (scrapView != null) {
		child = mAdapter.getView(position, scrapView, this);
		if (child != scrapView) {
			mRecycler.addScrapView(scrapView);
			if (mCacheColorHint != 0) {
				child.setDrawingCacheBackgroundColor(mCacheColorHint);
			}
		} else {
			isScrap[0] = true;
			dispatchFinishTemporaryDetach(child);
		}
	} else {
		child = mAdapter.getView(position, null, this);
		if (mCacheColorHint != 0) {
			child.setDrawingCacheBackgroundColor(mCacheColorHint);
		}
	}

You can see that you first get a View from the getScrapView() method. There must be no abandoned view when initializing the entire ListView, so you get a null value here. So we call the content in mAdapter, our adapter's getView() method.

public View getView(int position, View convertView, ViewGroup parent){
    View view;
	if (convertView == null) {
		view = LayoutInflater.from(getContext()).inflate(resourceId, null);
	} else {
		view = convertView;
	}
}

Our getView method has three parameters: position, view and parent control. The second parameter of the getView() method above is null, which means there is no layout, so call the inflate() method of LayoutInflater to load a layout, and then return to view. This means that every item is initially loaded when it first enters.

Next comes the reuse logic.

The reuse process is not much different from the previous process. One is that when loading, it will start from the current location, then load the other item s above and below it, and the other is that it will not call the inflate method to layout again. This time a true is returned to the setupChild() method, telling the setupChild() method that the view has been laid out, and then attach ing a previously detach ed view to the ViewGroup.

So the reuse process is over. (Forget it, I didn't make it clear this time. I'm afraid I don't even understand myself.)

In the graph, it is found that when the element 0 item drops out the data and loads the data of element 6, it becomes the process of reuse.

At this point, the problem of reuse arises. Suppose there is a checkBox in the element, I check element 0, then slide down, and find that element 6 is ticked? Because checkBox of element 0 is used here, element 6 is also checked. This is a common problem. I remember being confused by it a few years ago. There are several solutions:

1. Rewrite your adapter, which is the most troublesome, but also the most practical and stable way to bind checkBox attributes to viewHolder instead of viewHolder, so that checkBox attributes can be changed when data is changed. Typically, the setTag() method is used to bind.

2. Never reuse. Don't laugh, it's really a way, because there aren't many item s in the list and they're no longer reusable at all. It's basically a fix for bug s by losing performance. Of course, it's too risky not to do it or not.

3. Multiple backgrounds. This checkBox is not suitable for this method. For example, when there are three types of item s, three views are mistakenly made. Each time the situation is judged in adapter, different views are selected to deal with, and the others are hidden.

Incidentally, Listview's optimization method: Optimize one: the use of convertView, mainly to optimize the loading layout. From the above analysis, we know that more convertView is one of the key to reuse. Optimize 2. Use of viewHolder to cache control instances. Optimize 3: Slide does not load pictures (personal feeling is not good).

Posted by Anzeo on Wed, 28 Aug 2019 04:55:50 -0700