Android异步加载全解析之引入一级缓存

Android异步加载全解析之引入缓存

为啥要缓存

通过对图像的缩放,我们做到了对大图的异步加载优化,但是现在的App不仅是高清大图,更是高清多图,动不动就是图文混排,以图代文,如果这些图片都加载到内存中,必定会OOM。因此,在用户浏览完图像后,应当立即将这些废弃的图像回收,但是,这又带来了另一个问题,也就是当用户在浏览完一次图片后,如果还要返回去再进行重新浏览,那么这些回收掉的图像又要重新进行加载,保不准就要那些无聊到蛋疼的人在那一边看你回收GC,一边看你重新加载。这两件事情,肯定是互相矛盾的,也是影响性能的一个很重要的原因。

内存缓存

针对这样一个非常需要找到一个彼此平衡点的问题,Google提供了一套内存缓存技术。内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。LruCache 是在support-v4中才引入的,在引入LruCache 之前,Google建议的是使用软引用或弱引用 (SoftReference or WeakReference)来进行内存缓存。但是从Android 2.3开始,GC算法修改,软引用与弱引用同样会优先被GC回收,所以这种方法也就没有太高的使用价值了,现在网上很多还在继续使用SoftReference 和WeakReference的文章,大多都是过时的文章,建议大家跟上党的步伐,与时俱进。

LruCache使用

内存缓存LruCache所使用的内存缓存大小是由开发者决定的,开发者需要根据图像的使用率、分辨率、访问频率、设备性能等很多因素进行考虑。这个平衡点经常需要很多经验和测试来决定。使用LruCache非常简单:

private LruCache<String, Bitmap> mMemoryCaches;// 获取应用内存int maxMemory = (int) Runtime.getRuntime().maxMemory();// 分配cacheint cacheSize = maxMemory / 10;mMemoryCaches = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getByteCount();}};// 从LruCache获取中获取缓存对象public Bitmap getBitmapFromMemoryCaches(String url) {return mMemoryCaches.get(url);}// 增加缓存对象到LruCachepublic void addBitmapToMemoryCaches(String url,Bitmap bitmap) {if (getBitmapFromMemoryCaches(url) == null) {mMemoryCaches.put(url, bitmap);}}首先,我们需要声明LruCache,接着,通过LruCache的构造方法创建缓存对象,并为其分配cacheSize,这个cacheSize通常我们需要通过Runtime来获取,获取当前系统分给App的可用内存,并将这些内存的一部分用做LruCache缓存。LruCache中必须重写sizeOf方法,通过这个方法,LruCache可以获取每个缓存对象的大小,子类必须重写,因为默认的LruCache获取的是缓存的个数。。。尼玛。

最后,我们提供两个方法getBitmapFromMemoryCaches和addBitmapToMemoryCaches分别用来获取和增加内存缓存到LruCache。

等等,我们好像还没写释放内存的方法,对,不用你写了,Lru算法可以保证cacheSize不会OOM,一旦超过这个大小,GC就会回收时间最长的对象,释放空间。

为异步处理加入一级缓存

OK,在了解了关于缓存的基础信息后,我们回到现在这个例子,想想怎么利用缓存来进行异步处理的优化。首先,ListView、GridView这些娇生惯养的玩意儿,碰不得摔不得,更不能在它滚的开心的时候,你还在后面拼命玩加载。所以,第一个重点,滚的时候就让它开心的滚,滚完了再开始加载。

滚完再加载

要实现这一点,我们可以通过给Adapter增加AbsListView.OnScrollListener接口来实现。

当然,还有一点需要注意,第一次初始化的时候,一定要手动来加载图片,不然系统判断你没滚,只能调用onScroll方法,不会调用onScrollStateChanged方法。而且我们也需要在onScroll方法中来不断获取可见的Item。特别要注意的是visibleItemCount,只要大于0的时候,才认为是开始显示图片了。

@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {if (scrollState == SCROLL_STATE_IDLE) {mImageLoader.loadImages(mStart, mEnd);} else {mImageLoader.cancelAllTasks();}}@Overridepublic void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {mStart = firstVisibleItem;mEnd = firstVisibleItem + visibleItemCount;if (mFirstFlag && visibleItemCount > 0) {mImageLoader.loadImages(mStart, mEnd);mFirstFlag = false;}}

加载显示的项目

加载数据的时候,获取第一个能显示的Item和最后一个可见的Item,只加载这一部分。所以我们创建一个方法——loadImages(int start, int end)。这个方法用来加载从start到end之间的Item数据。

加载的时候,先从内存缓存中去取,如果有,那说明最近已经加载过了,那直接加载就好了,如果没有取到,那就开启synctask去下载。

public void loadImages(int start, int end) {for (int i = start; i < end; i++) {String url = Images.IMAGE_URLS[i];Bitmap bitmap = getBitmapFromMemoryCaches(url);if (bitmap == null) {ASyncDownloadImage task = new ASyncDownloadImage(url);mTasks.add(task);task.execute(url);} else {ImageView imageView = (ImageView) mListView.findViewWithTag(url);imageView.setImageBitmap(bitmap);}}}这里我们在设置图片的时候,,直接通过findViewWithTag,通过url来找到相应的Imageview,这里与之前不同是因为我们这里是按照start到end来进行加载,直接从ListView对象中获取对应的Imageview比较简单。

下载与Asynctask

下载依然是使用老方法:

private static Bitmap getBitmapFromUrl(String urlString) {Bitmap bitmap;InputStream is = null;try {URL url = new URL(urlString);HttpURLConnection conn = (HttpURLConnection) url.openConnection();is = new BufferedInputStream(conn.getInputStream());bitmap = BitmapFactory.decodeStream(is);conn.disconnect();return bitmap;} catch (Exception e) {e.printStackTrace();} finally {try {if (is != null)is.close();} catch (IOException e) {}}return null;}Asynctask也与之前基本类似:

做对的事情比把事情做对重要。

Android异步加载全解析之引入一级缓存

相关文章:

你感兴趣的文章:

标签云: