关于imageLoader的轻量级实现
对于Android的imageLoader是一个老生常谈的问题,并且已经有开源大神贡献过关于imageLoader的成熟架包,如下是下载链接:
https://github.com/nostra13/Android-Universal-Image-Loader;
虽然已经有开源大神实现了,重复造轮子的事情,也不提倡,但是闲着也是闲着,并且这里更多的是注重设计的一种理念,最近在看一本关于设计模式的书,软件设计里面的六大原则:SOLID和LOD。
分别如下:
S : single responsibility principle 单一职责原则 注释:一个类干一件事
O: open close principle 开闭原则 注释: 对修改关闭,对扩展打开
L :liskov substitution principle 里氏替换原则 注释:实现代替抽象
I :interface segregation principle 接口隔离原则 注释:用最小的接口干活
D :Dependence inversion principle 依赖倒置原则 注释:依赖抽象不依赖具体
LOD :Law Of Demeter 迪米特原则 注释:只和最近的朋友耦合
定义图片缓存接口
import android.graphics.Bitmap;
/**
* Created by cjw on 2016/7/19.
*/
public interface ImageCacheInterface {
public Bitmap getBitmap(String url);
public void putBitmap(String url, Bitmap bitmap);
}
这是定义图片的缓存接口,不管是已经实现了内存缓存、文件磁盘缓存、文件和磁盘双缓存,又或者是用户需要扩展的自定义缓存,都是需要实现该接口。
实现图片缓存的类
目前图片缓存的类有三个,内存缓存,磁盘缓存,还有双缓存,默认是用双缓存;如下是缓存的三个具体实现:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by cjw on 2016/7/18.
*/
public class ImageLoader {
// 图片缓存
private ImageCacheInterface mImageCache = null;
// 线程池
private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
// 上下文
private Context mContext = null;
public ImageLoader(Context context) {
mContext = context;
mImageCache = new MemoryImageCache();
}
/**
* 注入缓存实现
*
* @param imageCacheInterface
*/
public void setImageCacheInterface(ImageCacheInterface imageCacheInterface) {
mImageCache = imageCacheInterface;
}
/**
* 显示图片
*
* @param url
* @param imageView
*/
public void displayImage(String url, ImageView imageView) {
Bitmap bitmap = mImageCache.getBitmap(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 如果缓存没有,由网络下载
submitLoadRequest(url, imageView);
}
/**
* 提交任务到线程池中去获取数据
*
* @param imageUrl
* @param imageView
*/
private void submitLoadRequest(final String imageUrl, final ImageView imageView) {
imageView.setTag(imageUrl);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(imageUrl);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(imageUrl)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.putBitmap(imageUrl, bitmap);
}
});
}
/**
* 根据URL,下载图片
*
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
}
return bitmap;
}
}
文件缓存:
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import com.example.cwx343067.imageloader.manager.image.util.ImageDiskCacheUitl;
import com.example.cwx343067.imageloader.manager.utils.CloserUtil;
import com.example.cwx343067.imageloader.manager.utils.HexToStrByMD5;
import com.example.cwx343067.imageloader.manager.utils.ObtainAppVersion;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Created by cjw343067 on 2016/7/18.
*/
public class ImageDiskCache implements ImageCacheInterface {
private DiskLruCache mDiskLruCache = null;
private Context mContext = null;
public ImageDiskCache(Context context) {
mContext = context;
initDiskLruCache();
}
private void initDiskLruCache() {
try {
File file = ImageDiskCacheUitl.getDiskCacheDir(mContext, ImageLoaderConfig.IMAGE_CACHE_DIRS_NAME);
if(!file.exists()){
file.mkdirs();
}
mDiskLruCache = DiskLruCache.open(file, ObtainAppVersion.obtainVersion(mContext),1,ImageLoaderConfig.CACHE_MEMORY_SIZE);
} catch (Exception e) {
} finally {
}
}
/**
* 从缓存中获取图片
*
* @param url :key值
* @return
*/
@Override
public Bitmap getBitmap(String url) {
Bitmap bitmap = null;
String key = HexToStrByMD5.hashKeyForDiskName(url);
try{
DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
if (snapShot != null) {
InputStream is = snapShot.getInputStream(0);
bitmap = BitmapFactory.decodeStream(is);
}
}catch (Exception e){
e.printStackTrace();
}finally {
}
return bitmap;
}
/**
* 将图片缓存到sdcard中
*
* @param url key值
* @param bitmap 对象值
*/
@Override
public void putBitmap(String url, Bitmap bitmap) {
String key = HexToStrByMD5.hashKeyForDiskName(url);
try {
DiskLruCache.Editor editor = mDiskLruCache.edit(key);
if (editor != null && bitmap != null) {
OutputStream outputStream = editor.newOutputStream(0);
// 压缩不变大小
bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
editor.commit();
}
mDiskLruCache.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
}
}
}
双缓存
import android.content.Context;
import android.graphics.Bitmap;
/**
* Created by cjw on 2016/7/18.
*/
public class DoubleCache implements ImageCacheInterface {
private MemoryImageCache mMemoryImageCache = null;
private ImageDiskCache mImageDiskCache = null;
private Context mContext = null;
public DoubleCache(Context context) {
mContext = context;
mImageDiskCache = new ImageDiskCache(mContext);
mMemoryImageCache = new MemoryImageCache();
}
/**
* 从内存或者是从sdcard里面获取
*
* @param url
* @return
*/
@Override
public Bitmap getBitmap(String url) {
Bitmap bitmap = null;
bitmap = mMemoryImageCache.getBitmap(url) == null ? mImageDiskCache.getBitmap(url) : mMemoryImageCache.getBitmap(url);
return bitmap;
}
/**
* 将数据缓存到sdcard和内存中
*
* @param url
* @param bitmap
*/
@Override
public void putBitmap(String url, Bitmap bitmap) {
mMemoryImageCache.putBitmap(url, bitmap);
mImageDiskCache.putBitmap(url, bitmap);
}
}
其中对于文件缓存是采用了DiskLruCache,不知道的可以百度下。
目前已经定义好了抽象和实现。接下来就是实现我们的图片主类ImageLoader了。
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.widget.ImageView;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Created by cjw on 2016/7/18.
*/
public class ImageLoader {
// 图片缓存
private ImageCacheInterface mImageCache = null;
// 线程池
private ExecutorService mExecutorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
// 上下文
private Context mContext = null;
public ImageLoader(Context context) {
mContext = context;
mImageCache = new MemoryImageCache();
}
/**
* 注入缓存实现
*
* @param imageCacheInterface
*/
public void setImageCacheInterface(ImageCacheInterface imageCacheInterface) {
mImageCache = imageCacheInterface;
}
/**
* 显示图片
*
* @param url
* @param imageView
*/
public void displayImage(String url, ImageView imageView) {
Bitmap bitmap = mImageCache.getBitmap(url);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
return;
}
// 如果缓存没有,由网络下载
submitLoadRequest(url, imageView);
}
/**
* 提交任务到线程池中去获取数据
*
* @param imageUrl
* @param imageView
*/
private void submitLoadRequest(final String imageUrl, final ImageView imageView) {
imageView.setTag(imageUrl);
mExecutorService.submit(new Runnable() {
@Override
public void run() {
Bitmap bitmap = downloadImage(imageUrl);
if (bitmap == null) {
return;
}
if (imageView.getTag().equals(imageUrl)) {
imageView.setImageBitmap(bitmap);
}
mImageCache.putBitmap(imageUrl, bitmap);
}
});
}
/**
* 根据URL,下载图片
*
* @param imageUrl
* @return
*/
private Bitmap downloadImage(String imageUrl) {
Bitmap bitmap = null;
try {
URL url = new URL(imageUrl);
final HttpURLConnection conn = (HttpURLConnection) url.openConnection();
bitmap = BitmapFactory.decodeStream(conn.getInputStream());
conn.disconnect();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
}
return bitmap;
}
}
理论上到了这一步就可以了,基本实现我们的轻量级ImageLoader了,但是,我们不能让用户使用的时候就新建一个对象来实现吧,这里觉得应该采用单例模式,防止对象过的多次创建和销毁,导致消耗不必要的系统资源。
这里的单例模式是采用枚举类来实现单例模式的,为什么不用传统的饿汉模式,懒汉模式,或者是双重检查(DCL)模式,又或者是静态内部类模式。主要的原因是因为考虑到反序列化创造对象和同步锁的导致的不同步的原因,更重要的是:我又把枚举类重新学了一下,学了就要用嘛!!
接下来是枚举类
import android.content.Context;
import android.widget.ImageView;
/**
* Created by cjw on 2016/7/22.
*/
public enum ImageLoaderSingle {
IMAGELOADER;
private ImageLoader mImageLoader = null;
/**
* 获取ImageLoader的方法,主要是为了隐藏ImageLoader的方法,所以才在这里设置为私有的方法
* @param context 上下文
* @return
*/
private ImageLoader getImageLoaderInstances(Context context) {
if(mImageLoader != null){
return mImageLoader;
}
if (context != null) {
mImageLoader = new ImageLoader(context);
}
return mImageLoader;
}
/**
* 客户端调用的方法,这个枚举只有一个子类对象
* 这个方法是显示图片的
* @param context 上下问
* @param url 图片的url
* @param imageView 需要显示图片的ImageView
*/
public void displayImage(Context context , String url, ImageView imageView){
if(mImageLoader == null){
mImageLoader = getImageLoaderInstances(context);
}
mImageLoader.displayImage(url,imageView);
}
/**
* 设置自定义的缓存模式,这个方法是注入依赖
* @param context 上下文
* @param imageCacheInterface 缓存接口
*/
public void setImageCacheInterface(Context context,ImageCacheInterface imageCacheInterface){
if(mImageLoader == null){
mImageLoader = getImageLoaderInstances(context);
}
mImageLoader.setImageCacheInterface(imageCacheInterface);
}
}
基本到这里,ImageLoader已经实现。
使用
简单的一行代码就可以实现:
ImageLoaderSingle.IMAGELOADER.displayImage(this.getApplicationContext(), picUrl, imageView);
参数我觉得不用多说了吧,其中注意的点是:上下问用全局上下文不要用activity的上下问,防止内存泄漏。
效果图
总结
这是imageLoader已经能够实现我们的需求,在这里面的代码并不全,有些工具类,比如获取MD5值的类,获取版本号,判断是否有SD卡等的方法,都没有提出来,想更加相信了解的话,移步到我的github上看完整代码。
github地址:
https://github.com/chenjiewei/ImageLoader
ithub.com/benweet/stackedit