Android AdapterView View的复用机制 分析
2016-02-29 18:04
169 查看
前言
在平时开发当中,这中问题多简单简单,多容易容易,但我们平时都直接settag,很少能遇见,今天就做一个详细分析。开题
设想1:拿ListView为例,如果ListView的ItemView复用机制,所有的ItemView复用同一个,如果在多线程下载图片的情况下,可能最终只有最后一个View显示图片吧,因为你前面的设置setTag(url),后面马上就会将你的Tag的值覆盖掉,最终findViewByTag找到的都是最后一个。由此可见ListView缓存的不是一个,至少是一屏幕可显示的数量。也就是说ListView维护着一个ItemView的池子。跟大家解释下,为啥缓存了一个屏幕的可显示最大的ItemView数量的池子,我们可能上千个ItemView,仅依靠Tag就能实现不混乱呢。
情景:屏幕每次显示7个Item,ListView一共1000个Item,每个Item上显示一张从网络下载的图片
public View getView(int position, View convertView, ViewGroup parent) { final String url = getItem(position); View view; if (convertView == null) { view = LayoutInflater.from(getContext()).inflate(R.layout.photo_layout, null); } else { view = convertView; } final ImageView photo = (ImageView) view.findViewById(R.id.photo); // 给ImageView设置一个Tag,保证异步加载图片时不会乱序 photo.setTag(url); new LoadImgTask(photo).execute(url); return view; }
下载完成图片,进行photo.getTag().equals(url)来防止图片显示的混乱。如果我们打开界面,开启了7个线程去下载,此时缓存了这7个ItemView,现在滑动屏幕显示另外下一屏,此时7个ItemView都会复用,会把第一屏设置的Tag全部覆盖掉,没错就是覆盖掉了,又开启7个线程去下载图片,当第一屏的ItemView的图片下载完成后,如果直接findViewByTag然后设置图片会显示在第二屏上,就混乱了,所以一般在显示前都会判断photo.getTag().equals(url);确定了再显示,也就是说第一屏的ItemView图片下载完了,但是Tag被覆盖了,所以即使下载完成了,也不会有任何显示。这就解释了为什么我们防止混乱的代码需要那样去写。
研究
注释的意思上用一个数据集来存储应当在下一个布局重用的View,避免重新创建新的布局。这个对象应该就是对我们缓存管理的核心类了。继续看这个类,这是一个内部类:大概意思:这个类是用来帮助在滑动布局时重用View的,RecycleBin包含了两个级别的存储,ActiveViews和ScrapViews,ActiveViews存储的是第一次显示在屏幕上的View;所有的ActiveViews最终都会被移到ScrapViews,ScrapViews存储的是有可能被adapter复用的View。
现在很明确了AbsListView缓存依赖于两个数组,一个数组存储屏幕上当前现实的ItemView,一个显示从屏幕下移除的且可能会被复用的ItemView。下面看ListView里面的代码:
protected void layoutChildren() { if (dataChanged) { for (int i = 0; i < childCount; i++) { recycleBin.addScrapView(getChildAt(i)); } } else { recycleBin.fillActiveViews(childCount, firstPosition); } .... }
/** * Fill ActiveViews with all of the children of the AbsListView. * * @param childCount The minimum number of views mActiveViews should hold * @param firstActivePosition The position of the first view that will be stored in * mActiveViews */ 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); activeViews[i] = child; } }
可以看出,如果数据发生变化则把当前的ItemView放入ScrapViews中,否则把当前显示的ItemView放入ActiveViews中。那么咱们关键的getView方法到底是在哪调用呢,下面看RecycleBin中的方法:
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); } else { isScrap[0] = true; child.dispatchFinishTemporaryDetach(); } } else { child = mAdapter.getView(position, null, this); } return child; }
可以看到,这个方法就是返回当前一个布局用户当前Item的显示,首先根据position去ScrapView中找,找到后调用我们的getView,此时getView里面的convertView!=null了,然后getView如果返回的View发生变化,缓存下来,否则convertView==null了。
后续会继续研究跟进。
相关文章推荐
- Android 三大图片加载框架比较
- Android图片加载框架Glide使用总结
- Compile a native C Android application
- Android的IPC机制(六)—— BroadcastReceiver的使用
- 详解Android应用开发中Scroller类的屏幕滑动功能运用
- 如何保证Android Service在后台不被Kill,或者被Kill后重启
- Android 中 Environment.getExternalStorageDirectory()无效
- 动画效果Animation-android
- Android 第二天重置版
- 解决Android EditText多行输入,内容或边框显示的问题
- AnimationDrawable资源的使用
- Android中ActivityManagerService与应用程序通信模型分析
- Android shape使用
- Android Studio 使用遇到的各种问题
- android创建多线程的方法
- Android Support 23.2 BottomSheetBehavior的使用
- Android 开发一些有用的链接地址(持续更新中)
- Android中Handler的用法
- Android-将RGB彩色图转换为灰度图
- Android分享中,如何过滤指定的应用,并且对不同的分享方式发送不同的内容?