开发Android应用经常需要处理图片的加载问题。因为图片一般都是存放在服务器端,需要联网去加载,而这又是一个比较耗时的过程,所以Android中都是通过开启一个异步线程去加载。为了增加用户体验,给用户省流量,一般把加载完的图片先缓存下来,下次加载的时候就不需要再联网去服务器端加载。图片缓存一般分为一级缓存(即内存缓存)和二级缓存(即磁盘缓存)。这里只讲一级缓存。
内存缓存就是把加载完的图片先放在手机内存中,等下次加载的时候再从内存中取出来。
优点是速度快,缺点是不能长久保存,用户退出应用程序之后内存缓存就被回收了。而且加载太多会抛出Java.lang.OutOfMemory异常。
磁盘缓存就是把加载完的图片放到手机内置存储卡或SD卡中。下次加载的时候在从里面取出来。
优点是能够长期保存,缺点是速度较慢,为了不影响用户体验,也一般是通过异步线程去取磁盘中的缓存图片;还有一个就是用户卸载应用程序后这些缓存文件不会随着删除,浪费了用户的磁盘空间,需要用户手动删除。
图片缓存很多人都是用软引用SoftRReference来处理,但是Android官方并不推荐这么用。
Android官方给出的理由是:Note: In the past, a popular memory cache implementation was a SoftReference
or WeakReference
bitmap cache, however this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.
所以我们一般使用LruCache这个类,这个类可以这样理解:它就是一个内存缓存对象,你的图片就缓存在这个类里面。(Java中什么都是对象)这个类是在API 12中才有的,不过幸运的是Android官方把它加入了v4包中,所以1.6以后的都可以使用这个类。这个类用起来也比较简单(官方文档上也讲的很详细)。
下面讲一下我是怎样用的:
首先是给ListView 自定义一个Adapter(继承自BaseAdapter),然后在这个类中申明一个成员变量LruCache ,而不能在getView()方法中申明。因为一个ListView所有的Item项只能维护这一个LruCache 。LruCache有一个构造方法LruCache(int maxSize) ,参数就是这个缓存空间最大的容量(多少字节)。申明一个LruCache对象一般需要复写它的sizeOf(K key, V value),用这个方法改变每个缓存条目计算大小的方式。Android源码中默认是返回1,说默认是以个数来计算的。(其实这里我也不太理解,有懂的朋友还望不吝赐教。)
Android sizeof()的源码:
-
-
-
-
-
-
-
- protected int sizeOf(K key, V value) {
- return 1;
- }
不过我是参照官方文档写的。
- private final int maxMemory = (int) Runtime.getRuntime().maxMemory();
- private final int cacheSize = maxMemory / 5;
- private LruCache<String, Bitmap> mLruCache = new LruCache<String, Bitmap>(
- cacheSize) {
- @Override
- protected int sizeOf(String key, Bitmap bitmap) {
-
- return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
- }
- };
然后写一个异步加载图片的类AsyncImageLoader ; 这个类采用AsyncTask来异步加载图片。
- package com.folyd.tuan.util;
-
- import java.io.IOException;
-
- import android.graphics.Bitmap;
- import android.os.AsyncTask;
- import android.support.v4.util.LruCache;
- import android.widget.ImageView;
-
- import com.folyd.tuan.util.SimpleImageLoader;
-
-
-
-
-
-
-
- public class AsyncImageLoader extends AsyncTask<String, Void, Bitmap> {
- private ImageView image;
- private LruCache<String, Bitmap> lruCache;
-
-
-
-
-
-
- public AsyncImageLoader(ImageView image, LruCache<String, Bitmap> lruCache) {
- super();
- this.image = image;
- this.lruCache = lruCache;
- }
-
- @Override
- protected Bitmap doInBackground(String... params) {
- Bitmap bitmap = null;
- try {
- bitmap = SimpleImageLoader.getBitmap(params[0]);
- } catch (IOException e) {
- e.printStackTrace();
- }
- addBitmapToMemoryCache(params[0], bitmap);
- return bitmap;
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- image.setImageBitmap(bitmap);
- }
-
- private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
- if (getBitmapFromMemoryCache(key) == null) {
- lruCache.put(key, bitmap);
- }
- }
-
- public Bitmap getBitmapFromMemoryCache(String key) {
- return lruCache.get(key);
- }
- }
SimpleImageLoader 的getBitmap方法是一个简单的从网上获取图片的方法。
- package com.folyd.tuan.util;
-
- import java.io.IOException;
- import java.io.InputStream;
- import java.net.HttpURLConnection;
- import java.net.URL;
-
- import android.graphics.Bitmap;
- import android.graphics.BitmapFactory;
-
-
-
-
-
-
- public class SimpleImageLoader {
-
- public static Bitmap getBitmap(String urlStr) throws IOException{
- Bitmap bitmap;
- URL url = new URL(urlStr);
- HttpURLConnection conn = (HttpURLConnection) url.openConnection();
- conn.setRequestMethod("GET");
- conn.setReadTimeout(5*1000);
- conn.setDoInput(true);
- conn.connect();
- InputStream is = conn.getInputStream();
- bitmap = BitmapFactory.decodeStream(is);
- is.close();
- return bitmap;
- }
- }
然后在自定义的Adapter中封装一个加载图片的方法。这里涉及到AsyncTask 的一些知识,可以查看官方文档学习。
-
-
-
-
-
- private void loadBitmap(String urlStr, ImageView image) {
- AsyncImageLoader asyncLoader = new AsyncImageLoader(image, mLruCache);
- Bitmap bitmap = asyncLoader.getBitmapFromMemoryCache(urlStr);
- if (bitmap != null) {
- image.setImageBitmap(bitmap);
- } else {
- image.setImageResource(R.drawable.thum);
- asyncLoader.execute(urlStr);
- }
- }
然后在自定义Adapter的getView()方法方法中调用上面的loadBitmap()方法。
这样就实习了图片异步加载和内存缓存了。
看一下效果图:
当然没有磁盘缓存的ListView 绝对是不行的。ListView还有很多需要优化的技巧需要我们去不断学习。比如说图片懒加载。