【白話Android】為什麼ListView能夠實現成百上千條資料都不會OOM?
由於AbsListView有個內部類RecycleBin,實現了RecycleBin機制,裡面有維護兩個列表,一個是正在使用的view,也就是在螢幕上能看見的view,一個是已經被遺棄的view,也就是螢幕上看不到的view。ListView有一個特點,就是所有子view的佈局都是一樣的,因此,就給了一個思路,就是不是每次都要生成一個view,而是對於需要生成view的時候,可以考慮複用看不見的view。
方案一:每次都重新生成view,由於ListView的子View的數量是由應用決定的,因此這個方案是不可控的,如果view的數量超級多,那麼一不小心就會出現OOM。
方案二:目前Android採用的方案是使用RecycleBin機制。
(1)初始化的時候,只加載螢幕可見的幾個View。
(2)在滑動的時候,將滑出去螢幕看不見的部分的View從可見View列表裡面移除並放到遺棄列表中,同時對於新劃入的View,需要先去遺棄View列表中獲取View,如果當前遺棄列表為空,那麼就重新載入;如果不為空,則直接從遺棄列表裡面拿。
有一個比較直觀的圖如下:

RecycleBin.png
由於螢幕大小有限,因此一個螢幕下容納的View是有限的,因此,即使無數個數據,需要的View的空間也只有可見螢幕那個多個。
擴充套件
這個也是我們在自定義ListView介面卡的時候應該進行優化的地方。可以參考原始碼ArrayAdapter.java的getView方法。在getView的時候,不能每次都重新載入一個新的View,而是先看下能不能複用原來的View。而getView的引數裡面的convertView就是這樣一個快取。
Android中的原始碼如下:
@Override public @NonNull View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { return createViewFromResource(mInflater, position, convertView, parent, mResource); } private @NonNull View createViewFromResource(@NonNull LayoutInflater inflater, int position, @Nullable View convertView, @NonNull ViewGroup parent, int resource) { final View view; final TextView text; if (convertView == null) { view = inflater.inflate(resource, parent, false); } else { view = convertView; } ... }
同時,在ListView優化方面,還有一個優化點,就是不要每次都去呼叫findViewById去獲取控制元件的例項,而是可以藉助自定義的ViewHolder將快取存下來。
完整的示例程式碼如下:
public class PersonInfoAdapter extends ArrayAdapter<PersonInfo> { private int mResourceId; public PersonInfoAdapter(@NonNull Context context, int resource, @NonNull List<PersonInfo> objects) { super(context, resource, objects); mResourceId = resource; } @NonNull @Override public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { View view; ViewHolder viewHolder; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(mResourceId, parent, false); viewHolder = new ViewHolder(); viewHolder.imageView = (ImageView)view.findViewById(R.id.image); viewHolder.textView = (TextView)view.findViewById(R.id.text); view.setTag(viewHolder); } else { view = convertView; viewHolder = (ViewHolder)view.getTag(); } PersonInfo personInfo = getItem(position); viewHolder.imageView.setImageResource(personInfo.getmImageId()); viewHolder.textView.setText(personInfo.getmName()); return view; } class ViewHolder { ImageView imageView; TextView textView; } }