Android 图片的三级缓存最佳实践与解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/pihailailou/article/details/78927223

一,写在前面

       本篇文章将从图片的缓存,图片的高效加载,异步加载图片三个点,来讲述关于缓存图片的一系列技术点。关于异步加载图片涉及使用线程池加载异步任务,对这块知识不了解可以参考文章 关于Android中常用的四种线程池的介绍   Android 源码解析AsyncTask的工作原理 。
   
         缓存,在Android中经常会用到缓存技术,图片缓存仅仅只是缓存的一种。在目前从事的开发中,有这样一类需求:开发一些接口用于管控移动设备的一些定制功能,例如:禁用音量键,设置WiFi白名单等等。每一个接口都对应一些策略值,策略值是存储在一个xml文件中,同时也会在内存即Bundle中进行缓存。这样,不用每次都解析xml文件读取数据,减少IO相关的操作的频率。这样一扯就扯远了,继续回到正题吧~
        图片缓存的使用场景:从网络加载图片时,为避免每次获取图片数据都是从网络下载,可以将图片的流数据缓存在磁盘(文件系统)中,称为磁盘缓存;最后需要将图片对应的Bitmap对象存储在内存中,称为内存缓存。
        图片缓存的好处:提高图片的加载效率会提供良好的用户体验,减少用户移动流量的消耗。

二,图片缓存     

       下面以Demo的形式展开图片缓存的学习,这样也可以更好学习如何在实际开发中自己写一个加载图片的小框架,当然缓存相关技术点也会详细介绍。
       图片缓存策略:
       在加载图片时,先在内存缓存中加载图片;若内存中没有,再从磁盘中加载图片;若磁盘中没有,则从网络下载图片。
       若从网络上下载了图片(有种说法称为"3级缓存"),需要将该图片存储在磁盘中(有种说法称为"2级缓存"),最后将图片存储内存中(有种说法称为"1级缓存")。

       从上面分析中可以知道,加载图片时需要提供三个方法,分别实现这样三个功能:从内存获取图片缓存,从磁盘获取图片缓存,从网络下载图片。下面会依次介绍这三个方法的实现,同时讲述缓存相关的技术点。

2.1,从内存获取图片缓存,使用LruCache

       在Android早期的版本中,图片的内存缓存通常是采用Bitmap的软引用SoftReference和弱引用WeakReference来实现。但在Android2.3以后,垃圾回收器倾向于回收持有软引用和弱引用的对象。这里先简单介绍下,强引用,软引用,弱引用。
       强引用:直接的对象引用,平时我们使用的对象都是强引用;
       软引用:当一个对象只持有软引用时,在内存不足时会被gc回收;
       弱引用:当一个对象只持有弱引用时,随时可能被gc回收;

       那么,该使用什么缓存技术处理图片的内存缓存呢?在Android3.1的SDK中提供了缓存类LruCache,它是一个泛型类,可以完成缓存对象的存储,查询,删除等功能。Lru是一个缓存算法,全称"Least Recently Used",意思是最近最少使用的缓存。Lru缓存算法,会在缓存总容量达到设置的最大值时,移除近期最少使用的缓存。
       值得一提的是,Android系统为了兼容低版本的设备,在support-v4兼容包中也提供了缓存类LruCache,因此建议import时导入support-v4中的LruCache类。

       查看LruCache相关的源码如下:
public class LruCache<K, V> {
    private final LinkedHashMap<K, V> map;
    
    //...

    public LruCache(int maxSize) {
        if (maxSize <= 0) {
            throw new IllegalArgumentException("maxSize <= 0");
        }
        this.maxSize = maxSize;
        this.map = new LinkedHashMap<K, V>(0, 0.75f, true);
    }

    //...

    public final V put(K key, V value) {  //... }

    public final V get(K key) {   //  ...  }

    protected int sizeOf(K key, V value) {
        return 1;
    }

    //...
}
       第1行,LruCache是一个泛型的缓存类;
       第2行,LruCache里维护了一个LinkedHashMap集合操作数据;
       第6行,构造函数参数maxSize,传入一个内存缓存的总容量的值;
       第20行,方法sizeOf由protected修饰,子类可以重写该方法,返回值表示某个缓存对象的大小;
       第16行,put方法,参数key是图片url,参数value是存入缓存对象。每个缓存对象对应唯一的url,key与value是一组映射关系。
       第18行,get方法,通过key获取value;

        首先创建LruCache对象,代码如下:
long maxMemory = Runtime.getRuntime().maxMemory();
int maxBitmapCache = (int) (maxMemory / 1024);
mLruCache = new LruCache<String, Bitmap>(maxBitmapCache){
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
	int size = bitmap.getHeight() * bitmap.getRowBytes() / 1024;
	Log.e("wcc", "size : " + size);
	return size;
    }
};
       LruCache的key是String类型,存放图片的url;value是Bitmap类型,存放BitMap对象。
       这里我们设置LruCache中缓存的总容量为当前进程可用内存,也可以设置稍微少一点,例如当前进程可用内存的1/8。
       构造函数参数值的单位是KB,因此maxMemory需要除以1024。
       bitmap.getHeight() * bitmap.getRowBytes()可以计算出Bitmap对象在内存中占用的大小,与图片文件本身的大小是有区别的。

        从内存中获取图片缓存,方法loadBitmapFromMemoryCache实现如下:
private Bitmap loadBitmapFromMemoryCache(String url) {
    String urlMd5 = makeStrToMd5(url);
    return mLruCache.get(urlMd5);
}
       代码很简单,调用LruCache$get方法获取Bitmap对象。注意key值并不是url,而是url对应的MD5值。由于url字符串中可能存在非法字符,可以转化为MD5值,一个字符串对应一个唯一的MD5值。

url转化为MD5值

       方法makeStrToMd5实现如下:
private String makeStrToMd5(String str) {
    MessageDigest md;
    StringBuilder sb = new StringBuilder();
    try {
	md = MessageDigest.getInstance("MD5");
	md.update(str.getBytes());
	byte[] byteArray = md.digest();
	
	for(int i = 0; i < byteArray.length; i++) {
	    int hexInt = byteArray[i] & 0xFF;
	    String hexString = Integer.toHexString(hexInt);
	    if (hexString.length() == 1) {
	        sb.append('0');
	    }
	    sb.append(hexString);
	}
    } catch (Exception e) {
	e.printStackTrace();
    }
    return sb.toString();
}
       通过MessageDigest得到一个字节数组byteArray,遍历字节数组,通过与运算byteArray[i] & 0xFF,将字节数转化为十进制的数。然后转化为十六进制的字符串形式,得到hexString。若字符串hexString的值为0~F,则在前面加上0;若字符串hexString的值为10~ff,则不加0。最后将处理后的字符串均放入StringBuilder容器中,return得到url对应的MD5值。

       这样,从内存中获取图片缓存就介绍完了,下面 继续介绍从磁盘中获取图片缓存

2.2,从磁盘获取图片缓存,使用DiskLruCache

       Android SDk里没有提供磁盘缓存类DiskLruCache的API,当然Android官方文档是推荐DiskLruCache实现磁盘缓存。DiskLruCache.java源码会在文章最后给出,有需要的可以点击下载。
       DiskLruCache可以实现文件的存储,读取,删除。也采用了LRU算法,当磁盘中的缓存文件达到最大允许值时,会删除近期最少使用的文件。
       DiskLruCache的使用比LruCache稍微复杂一点,基本思想却是类似的。LruCache中图片存在的形式是Bitmap对象,它对应一个url的MD5值;而DiskLruCache中图片存在的形式是流数据,一个流数据对应一个key,也就是url的MD5值。这里不清楚没关系,后面会给出详细介绍。

       创建DiskLruCache对象

       DiskLruCache的构造函数是私有的,不能new一个对象。但提供了open方法来创建DiskLruCache对象,open方法声明如下:
public static DiskLruCache open(File directory, int appVersion, int valueCount, long maxSize)
            throws IOException {
       directory:缓存文件存放的位置。若存在sd卡,缓存路径为/sdcard/Android/data/<application package>/cache;若没有sd卡,则调用getCacheDir获取缓存路径,由于各个手机厂商会有自己的定制,路径并不都一样。 需要注意的是,directory不是文件系统中任意路径都可以,否则会报异常导致创建DiskLruCache对象失败。
       appVersion:应用程序的版本号。当版本号改变时,会清理掉缓存目录里所有缓存文件。实际开发中,版本号改变也不一定会清理掉所有缓存文件。该参数用处并不大,一般设置为1即可。
       valueCount:一个key对应多少个缓存文件,一般设置为1;
       maxSize:允许的缓存总容量大小,具体大小根据需要设定。

       于是, 创建一个DiskLruCache对象的实现如下
    private static final long DISK_CACHE_MAX = 50 * 1024 * 1024;
    File diskCachePath = new File(context.getCacheDir(), "bitmap");
	if (!diskCachePath.exists()) {
	    diskCachePath.mkdir();
	}
	try {
	    mDiskLruCache = DiskLruCache.open(diskCachePath, 1, 1, DISK_CACHE_MAX);
	} catch (IOException e) {
	    e.printStackTrace();
    }

        从磁盘中获取图片缓存,方法loadBitmapFromDiskCache实现如下:
private Bitmap loadBitmapFromDiskCache(String url) {
	if (Looper.myLooper() == Looper.getMainLooper()) {
		Log.e("wcc", "do not recommend to load in main thread");
	}

	Bitmap bitmap = null;
	String key = makeStrToMd5(url);
	try {
		Snapshot snapshot = mDiskLruCache.get(key);
		Log.e("wcc", "snapshot null : " + (snapshot == null));
		if (snapshot != null) {
			FileInputStream in = (FileInputStream) snapshot
					.getInputStream(0);
			bitmap = decodeCompressBitmapFromStream(in);
		}
	} catch (Exception e) {
		e.printStackTrace();
	}

	return bitmap;
}
       第2行,通过当前线程的Looper检查代码是否在主线程中执行,对这里不理解的可以参考文章  Android中ThreadLocal的工作原理 。
       第7行,将字符串url转化为MD5值,前面已经介绍,这里不再重复阐述。
       第9~12行,调用DiskLruCache的get方法,参数为url的md5值,得到一个Snapshot对象,不同的key会得到不同的SnapShot对象。通过SnapShot可以获取磁盘中图片的输入流,有了输入流就可以对图片对应的文件流进行解析,最终得到Bitmap对象。
       第14行,解析图片的输入流,转化为Bitmap对象。下面会对高效加载Bitmap进行介绍,这是很重要的一个内容。

2.2.1,高效加载Bitmap

        BitmapFactory提供的四个方法:
       加载Bitmap是由类BitmapFactory来实现,BitmapFactory提供了四个方法decodeFile,decodeResource,decodeStream,decodeByteArray均可以加载Bitmap。分别支持从文件系统,应用资源,输入流,字节数组中加载一个Bitmap对象。
       需要注意的是:由于decodeStream不能调用两次,否则第二次返回null,可以将输入流转化为文件描述符,调用decodeFileDescriptor间接从输入流中加载Bitmap对象,后面会详细介绍。

       Android给每个应用程序提供了一定的内存大小,一般是16M。Android发展到现阶段,在测试真机时发现应用内存有384M之多,当然各个手机厂商定制也会不同。有意思的是,384/16=24倍,当然不是偶然啦。可以尝试一下低版本的模拟器,一般都是16M。需要注意的是,上面介绍的四个方法加载Bitmap对象,会占用很大的内存。 
       
        关于Bitmap占用内存大小的计算方式:
       Bitmap对象占用内存的大小,与图片本身的宽高像素大小,以及在内存中存储格式有关。
       图片的宽高大小,例如1000px * 800px 。
       存储格式是指BitmapFactory$Options里字段inPreferredConfig,若值为Bitmap.Config.ARGB_8888,表示A(透明度)的值用8bit来存储,R(红色)的值用8bit来存储,G(绿色)的值用8bit来存储,B(蓝色)的值用8bit来存储,也就是一个像素点占用32bit,即4字节。那么一张1000px * 800px的图片,解析得到的Bitmap对象占用内存大小为1000*800*4字节。

        关于inSampleSize的介绍:
       由于Android系统分配给应用的内存是有限,如果正常加载Bitmap对象,运行后往往会报OutOfMemoryError的异常信息,OOM,内存溢出。那么如何解决呢?可以在加载Bitmap的四个方法中加入参数Options对象,Options中有一个字段inSampleSize代表采样率。若设置inSampleSize为2,表示图片宽高像素大小为原来1/2,那么整张图片像素点是原来的1/4,占用内存也是原来的1/4。Android官方建议inSampleSize总为2的指数,例如2,4,8,16等等,当然根据开发需要设置其他值也可以。
       有一点需要注意,若图片宽高大小为300px * 200px,ImageView的宽高为100px * 100px时,inSampleSize是取2还是3呢?答案是取2,因此inSampleSize为3,那么图片的高<ImageView的高,拉伸填满整个控件会导致图片变模糊,而inSampleSize设置为2则不会出现这样的问题。

        从输入流中加载Bitmap对象
       方法decodeCompressBitmapFromStream代码如下:
private Bitmap decodeCompressBitmapFromStream(FileInputStream in) {
	Bitmap bitmap = null;
	try {
		FileDescriptor fd = in.getFD();
		
		BitmapFactory.Options opts = new BitmapFactory.Options();
		
		opts.inJustDecodeBounds = true;
		BitmapFactory.decodeFileDescriptor(fd, null, opts);

		opts.inJustDecodeBounds = false;
		opts.inSampleSize = getInSampleSize(500, 240, opts);
		bitmap = BitmapFactory.decodeFileDescriptor(fd, null, opts);
	} catch (Exception e) {
		e.printStackTrace();
	}
	return bitmap;
}
       第4行,将输入流转化为文件描述符FileDescriptor对象,前面已做解释。如果只需要调用一次decodeStream,则不需要转化为FileDescriptor。
       第6行,创建Options对象,有字段inSampleSize,代表采样率。
       第8行,Options的字段inJustDecodeBounds为true,表示只加载图片的宽高等基本信息,不会返回Bitmap对象。得到的宽高,根据具体需求计算出inSampleSize的值。
       第9行,很清楚了,只加载图片的宽高等基本信息。
       第11行,Options的字段inJustDecodeBounds为false,表示加载整张图片。
       第12行,调用方法getInSampleSize计算出inSampleSize的值,这里设定ImageView的宽高为500px,240px。(下面会介绍方法getInSampleSize)
       第13行,解析压缩后的图片,并返回Bitmap对象。若这里使用的是decodeStream方法,两次调用后,第二次调用decodeStream方法会返回null。

       计算inSampleSize的值,方法getInSampleSize代码如下:
private int getInSampleSize(int reqWidth, int reqHeight,
		BitmapFactory.Options opts) {
	int outWidth = opts.outWidth;
	int outHeight = opts.outHeight;

	int inSampleSize = 1;
	int tempWidth = outWidth / 2;
	int tempHeight = outHeight / 2;
	while (tempWidth >= reqWidth && tempHeight >= reqHeight) {
		tempWidth /= 2;
		tempHeight /= 2;
		inSampleSize *= 2;
	}
	return inSampleSize;
}
       遵循了两个条件,inSampleSize的值是2的指数,图片宽,高除以inSampleSize后的值>=ImageView的宽,高大小。

        小结:高效加载Bitmap需要对图片进行压缩,调用了两次decodeXXX方法。第一次只想获取图片的宽高等基本信息,设置inJustDecodeBounds为true;第二次解析整张图片,设置inJustDecodeBounds为false,同时设置采样率inSampleSize的值。

2.3,从网络下载图片

       2.1,2.2中分别讲解了如何从内存,磁盘(文件系统)加载图片,那么当上述两种方式都没有该图片的缓存时,就不得不从网络上加载图片了。这部分涉及了Android网络编程的相关知识,由于不是本篇的重点内容,不作具体阐述。发起一个网络请求,服务器会响应该请求,返回给的是一个输入流。根据这个IO流就可以将图片写入磁盘,再将Bitmap对象存入LruCache封装的LinkedHashMap集合中。
       下面会介绍图片的输入流是如何通过DiskLruCache存储在磁盘里,以及使用LruCache存储Bitmap对象。

2.3.1,将图片存入磁盘,使用DiskLruCache

       直接上代码,方法addBitmapToDiskCache如下:
private boolean addBitmapToDiskCache(InputStream in, String urlPath) {
	String key = makeStrToMd5(urlPath);
	try {
		editor = mDiskLruCache.edit(key);
		if (editor != null) {
			out = editor.newOutputStream(0);
			
			int len = 0;
			byte[] buffer = new byte[1024];
			while ((len = in.read(buffer)) != -1) {
				out.write(buffer, 0, len);
			}
			return true;
		}
	} catch (Exception e) {
		e.printStackTrace();
	} finally {
		try {
			in.close();
			out.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	return false;
}
       第1行,该方法有两个参数,一个是网络下载图片的输入流,一个是图片url。
       第2行,获取字符串url对应的MD5值,这里不再重复阐述。
       第4行,在前面讲到DiskLruCache获取图片缓存需要先获取SnapShot对象,然后获取到一个输入流。类似的,DiskLruCache存储图片缓存需要先获取Editor对象,不同url获取的Editor也是不同的。
       第6行,通过该url对应的Editor对象获取一个输出流,便可以将图片存入磁盘中。
       第13行,IO读写执行完成,则返回true;IO操作出现异常,则返回false。
       
       通过上面的操作,图片并没有真正写入磁盘中,还需要调用Editor$commit方法真正将图片写入文件系统。如果上面的IO操作出现异常,则调用Editor$abort方法回退整个操作。具体代码实现如下:
if (addBitmapToDiskCache(inputStream, urlPath)) {
	// 数据流全部写入磁盘
	editor.commit();
} else {
	// 数据流写入磁盘发生异常,回退操作
	editor.abort();
}
mDiskLruCache.flush();

       注意在最后一行,调用了DiskLruCache$flush方法,该方法是将内存中相关操作同步到journal文件中。journal文件路径与缓存的图片是同级的目录下的,它记录了缓存图片的存储,查询相关的操作,以及单个缓存图片的大小。因此DiskLruCache要想起作用,最后得调用一次flush方法,将相关操作记录同步到journal文件中。


2.3.2,将Bitmap对象存入内存,使用LruCache

     使用LruCache的put方法,key是url的md5值,value是Bitmap对象。

       代码实现比较简单 ,方法addBitmapToMemoryCache实现如下:

private void addBitmapToMemoryCache(String url, Bitmap bitmap) {
	if (mLruCache != null) {
		String key = makeStrToMd5(url);
		mLruCache.put(key, bitmap);
	}
}


       从网络上下载图片,方法loadBitmapFromHttp实现如下:    

public Bitmap loadBitmapFromHttp(String urlPath) {
	if (Looper.myLooper() == Looper.getMainLooper()) {
		throw new RuntimeException(
				"can not access network in main thread");
	}
	
	if (mDiskLruCache == null) {
		return null;
	}

	Bitmap bitmap = null;
	try {
		URL url = new URL(urlPath);
		HttpURLConnection conn = (HttpURLConnection) url
				.openConnection();
		InputStream inputStream = conn.getInputStream();

		// 将图片存入磁盘中
		if (addBitmapToDiskCache(inputStream, urlPath)) {
			editor.commit();
		} else {
			editor.abort();
		}
		mDiskLruCache.flush();

		bitmap = loadBitmapFromDiskCache(urlPath);

		// 将图片存入内存中
		addBitmapToMemoryCache(urlPath, bitmap);
	} catch (Exception e) {
		e.printStackTrace();
	}

	return bitmap;
}
       第2行,由于网络加载是一个耗时操作,必然不能在主线程中执行,否则会造成主线程阻塞。 通过主线程里的Looper检查当前代码执行是否在主线程中,不在主线程中执行则抛出异常。

       第7行,检查DiskLruCache对象是否为空。

       第13~16行,执行网络请求,最终返回一个输入流。

       最后,网络下载图片后,将图片缓存存入磁盘和内存中。

       

       小结:图片的磁盘缓存,实际操作的是文件流,存储是输出流,查询是输入流。图片的内存缓存,操作的则是Bitmap对象。


三,另外

       上面分别介绍了如何从内存,磁盘,网络获取图片,从磁盘,网络获取图片的操作是一个比较耗时的操作。因此,专门提供一个方法loadBitmapByExecuteTask,封装了从磁盘和网络获取图片,且该方法是在子线程中执行的。

       方法loadBitmapByExecuteTask实现如下:

private Bitmap loadBitmapByExecuteTask(String url) {
	Bitmap bitmap = null;
	bitmap = loadBitmapFromDiskCache(url);
	if (bitmap != null) return bitmap;

	bitmap = loadBitmapFromHttp(url);
	return bitmap;
}
       先从磁盘中获取图片,若没有则通过网络加载图片。loadBitmapFromDiskCache和loadBitmapFromHttp方法在前面已经做过详细分析。


四,图片的异步加载

       由于很多调用者开一个线程来获取一个图片,因此这里我们需要提供一个方法loadImageToImageView,该方法有两个参数,一个是ImageView对象,一个是图片字符串url,返回值当然是Bitmap对象。这样调用者只需要传入对应控件ImageView的引用,以及图片的url就可以获取图片。

       方法loadImageToImageView实现如下:

private void loadImageToImageView(final String url, final ImageView iv) {
	Bitmap bitmap = null;
	bitmap = loadBitmapFromMemoryCache(url);
	if (bitmap != null) {
		iv.setImageBitmap(bitmap);
		return;
	}

	Runnable r = new Runnable() {
		@Override
		public void run() {
			Bitmap b = loadBitmapByExecuteTask(url);
			LoaderResult mLoaderResult = new LoaderResult(iv, b);
			Message msg = mHandler.obtainMessage(MSG_LOAD_IMAGE,
					mLoaderResult);
			msg.sendToTarget();

		}
	};
	THREAD_POOL_EXECUTOR.execute(r);
}
       第3~7行,先从内存中取出图片缓存,若bitmap不为空,调用ImageView$setImageBitmap方法给控件设置图片,最后return。

       第20行,THREAD_POOL_EXECUTOR是一个ThreadPoolExecutor类型的变量,它是一个线程池。这里,是直接复制了AsyncTask源码中封装的线程池THREAD_POOL_EXECUTOR的创建过程,对这里不太理解的可以参考文章 Android 源码解析AsyncTask的工作原理 ,关于Android线程池不太了解的可以参考文章关于Android中常用的四种线程池的介绍 ,这里不再重复阐述。

       

       THREAD_POOL_EXECUTOR的创建过程如下:

private static final int CPU_COUNT = Runtime.getRuntime()
		.availableProcessors();
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE = 1;

private static ThreadFactory sThreadFactory = new ThreadFactory() {
	private final AtomicInteger mCount = new AtomicInteger(1);

	public Thread newThread(Runnable r) {
		return new Thread(r, "ImageLoader #" + mCount.getAndIncrement());
	}
};

private static BlockingQueue<Runnable> sPoolWorkQueue = new LinkedBlockingQueue<Runnable>(
		128);

private static Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
		CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
		TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
       

       继续回到方法loadImageToImageView方法:

       第9行,创建一个Runnable任务,并作为execute方法的参数传入,run方法执行的任务是在子线程中执行。

       第12行,调用loadBitmapByExecuteTask方法获取图片,前面已经有过介绍。

       第13行,创建一个类LoaderResult,用于封装ImageView,Bitmap的引用。

       第14~16行,通过Handler机制将代码逻辑切换到主线程,并在主线程中操作UI。

       LoaderResult如下:

class LoaderResult {
	ImageView iv;
	Bitmap bitmap;

	public LoaderResult(ImageView iv, Bitmap bitmap) {
		this.iv = iv;
		this.bitmap = bitmap;
	}
}	
       消息处理如下:

private static final int MSG_LOAD_IMAGE = 0;
private Handler mHandler = new Handler(Looper.getMainLooper()) {
	public void handleMessage(Message msg) {
		switch (msg.what) {
		case MSG_LOAD_IMAGE:
			LoaderResult result = (LoaderResult) msg.obj;
			result.iv.setImageBitmap(result.bitmap);
			break;

		default:
			break;
		}
	};
};
       第7行,调用ImageView$setImageBitmap方法,给控件设置图片。

       值得一提的是,在完成异步加载图片的实现时,可以从AsyncTask源码里挖掘一些有用信息。包括线程池THREAD_POOL_EXECUTOR的创建过程,就是直接复制过来的,感觉还比较好用。 在线程间传递数据,创建类LoaderResult封装两个类的引用,然后将 LoaderResult对象作为Message的obj传递,也是从AsyncTask源码学习而来。


五,最后

       使用缓存获取图片只需要提供给调用者方法:loadImageToImageView(String url, ImageView iv),调用者并不需要开启一个新的线程,也不需要去实现线程间通信相关的处理。方法loadImageToImageView里封装了线程池,Handler机制,以及图片缓存相关的代码。

       另外,对DiskLruCache更详细的分析,见文章 Android DiskLruCache完全解析,硬盘缓存的最佳方案 ,郭霖的这篇文章非常nice,感兴趣的哥们可以参考。

       Demo工程地址:ImageLoaderDemo 

       DiskLruCache源码见工程ImageLoaderDemo 的src目录


       






       





    






       
       

猜你喜欢

转载自blog.csdn.net/pihailailou/article/details/78927223