您的位置:首页 > 移动开发 > Android开发

Android框架设计03-图片加载框架

2017-04-22 13:19 429 查看
本片文章介绍一个图片加载的框架。

框架优点:

支持高并发,支持不同的加载策略(加载图片的优先级),显示图片自适应(老生常谈的图片压缩),支持缓存策略扩展。

设计模式:

建造者模式,生产者消费者模式,单例模式,策略模式,模板方法模式

其他知识点

内存缓存LruCache,磁盘缓存DiskLruCache,下载时将请求任务转发

类图 (看不清可以右键,在新标签中打开)



流程分析

由于整个框架的代码比较多,所以没有把所有的代码都贴出来。分析框架主要看框架的思想,所以这里不会太拘泥于细节。对框架感兴趣的可以下载源码。由于整个类图看上去我从下手。所以我按照在UML里面的序号进行分析。这样不至于摸不着头绪。

1 客户端调用框架

客户端首先需要构建SimpleImageLoader对象

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

ImageLoaderConfig.Builder builder = new ImageLoaderConfig.Builder();

builder.setThreadCount(3)//线程数量
.setLoadPolicy(new ReversePolicy())//加载策略
.setCachePolicy(new DoubleCache())//缓存策略
.setLoadingImage(R.drawable.loading) //加载中的图片
.setFaildImage(R.drawable.not_found);//加载失败显示的图片

ImageLoaderConfig config = builder.build();
imageLoader = SimpleImageLoader.getInstance(config);

inflater = LayoutInflater.from(this);
GridView gridView = (GridView) findViewById(R.id.gv);
gridView
4000
.setAdapter(new MyAdapter());

}


通过上面的代码可以看出,首先要构建一个ImageLoaderConfig对象。然后用此对象来构建SimpleImageLoader对象。

然后通过imageLoader对象的disPlayImage()方法来加载图片。这就是客户端(MainActivity)的调用代码。

imageLoader.disPlayImage(viewHolder.imageView, imagebUrl);


可以看出此方法加载图片的这种方式和Glide框架类似。只是传一个ImageView对象和Url.需要说明的是,上面在构建ImageLoaderConfig对象时有关的缓存策略,加载策略线程数都是有默认值的。所以也可以不做设置。如在ImageLoaderConfig中:

//缓存策略
private BitmapCache bitmapCache = new MemoryCache();

//加载策略
private LoadPolicy loadPolicy = new ReversePolicy();

//默认线程数
private int threadCount = Runtime.getRuntime().availableProcessors();


2 通过ImageLoaderConfig来构建SImpleImageLoader对象

简单来说,SimpleImageLoader是负责图片加载的代理。而ImageLoaderConfig是封装有关图片请求需要的参数。所以构建SimpleImageLoader要先构建一个ImageLoaderConfig对象。

在1中也有体现。

ImageLoaderConfig.Builder builder = new ImageLoaderConfig.Builder();

builder.setThreadCount(3)//线程数量
.setLoadPolicy(new ReversePolicy())//加载策略
.setCachePolicy(new DoubleCache())//缓存策略
.setLoadingImage(R.drawable.loading) //加载中的图片
.setFaildImage(R.drawable.not_found);//加载失败显示的图片

//构建 ImageLoaderConfig
ImageLoaderConfig config = builder.build();
//构建 SimpleImageLoader
imageLoader = SimpleImageLoader.getInstance(config);


3,4,5构建ImageLoaderConfig所需的三个参数

3 DisPlayConfig 封装了加载中和加载失败的图片id。

4 BitmapCache 封装了图片缓存的策略。如内存缓存和SD卡缓存

5 loadPolicy加载策略。加载时是可以选择顺序加载和倒叙加载。但是此功能没有实现。可以在扩展时添加。

6 维护阻塞队列的RequestQueue

这个类有什么作用?

在RequestQueue中维护了一个线程阻塞队列。

/**
* 阻塞式线程
* 多线程共同轮训此队列
* 优先级高的队列先被消费
* 都一个产品都有编号
*/
private BlockingQueue<BitmapRequest> requestQueue = new PriorityBlockingQueue<>();


通过在requestQueue中添加BitmapRequest和去取出BitmapRequest来实现任务的生产和消费。这里用了生产者消费者模式。

什么时候添加任务?

在MainActivity中

imageLoader.disPlayImage(viewHolder.imageView, imageThumbUrls[position]);


在SimpleImageLoader的disPlayImage()方法中

requestQueue.addRequest(bitmapRequest);


在RequestQueue中

public void addRequest(BitmapRequest bitmapRequest){
if(!requestQueue.contains(bitmapRequest)){
bitmapRequest.setSeriaNo(atomicInteger.incrementAndGet());
requestQueue.add(bitmapRequest);
}else{
Log.i("","请求已经存在 编号:"+bitmapRequest.getSeriaNo());
}

}


此时一个任务已经添加到阻塞队列中

什么时候轮询任务队列里的任务?

在SimpleImageLoader中的构造方法时,将调用RequestQueue的start()方法。

private SimpleImageLoader(ImageLoaderConfig config) {
this.imageLoaderConfig = config;
this.requestQueue = new RequestQueue(imageLoaderConfig.getThreadCount());
requestQueue.start();
}


在RequestQueue中

public void start(){
stop();
startDisPatcher();
}


private void startDisPatcher() {
dispatchers = new RequestDispatcher[threadCount];
for(int i=0;i<dispatchers.length;i++){
//1
RequestDispatcher requestDispatcher = new RequestDispatcher(requestQueue);
dispatchers[i] = requestDispatcher;
//2
dispatchers[i].start();
}
}


1 通过startDisPatcher()方法,把RequestQueue中维护的队列传到RequestDispatcher 中

RequestDispatcher requestDispatcher = new RequestDispatcher(requestQueue);


2然后通过RequestDispatcher 中的start()方法去轮询传来的队列。

由于RequestDispatcher 继承自Thread,所以run()方法将会执行

@Override
public void run() {
while (!isInterrupted()) {
try {
//阻塞式队列  取出任务
BitmapRequest bitmapRequest = requestBlockingQueue.take();
String schema = parseSchema(bitmapRequest.getImageUrl());
Loader loader = LoaderManager.getInstance().getLoader(schema);
loader.loadImage(bitmapRequest);

} catch (InterruptedException e) {
e.printStackTrace();
}
}
}


至此RequestQueue维护的队列的任务已经被取出。

用图来表示



可以看出有多个RequestDispatcher 共同来轮询取出RequestQueue维护的队列中的任务。RequestDispatcher 的个数等于在ImageLoaderConfig中设置的个数。

dispatchers = new RequestDispatcher[threadCount];


由于任务队列是线程阻塞的,所以当任务队列中的BitmapRequest对象个数为0时,线程将会阻塞。直到新任务添加进队列。

7 RequestDispatcher 轮询队列

这一过程在6中已经说明了。简单来说就是RequestDispatcher 做为一个线程对象。在RequestQueue中的startDispatcer()中开启这些线程。然后这些线程开始共同轮询这个队列。不断的在队列中取出任务并执行。

8 通过LooperManager()来判断使用哪种加载 方式

在RequestDispatcher 的run()方法中

@Override
public void run() {
while (!isInterrupted()) {
try {
//1 取出任务
BitmapRequest bitmapRequest = requestBlockingQueue.take();
//2 判断开头是http开头还是file开头
String schema = parseSchema(bitmapRequest.getImageUrl());
//3 根据 schema 来获得具体的请实例
Loader loader = LoaderManager.getInstance().getLoader(schema);
//4 加载器开始执行加载任务
loader.loadImage(bitmapRequest);

} catch (InterruptedException e) {
e.printStackTrace();
}
}
}


做了四件事。1 先是取出任务。2 然后根据开头来判断该用哪种请求方式。

//http://www.baidu.com------->http
private String parseSchema(String imageUrl) {
if (imageUrl.contains("://")) {
return imageUrl.split("://")[0];
} else {
Log.i("RequestDispatcher", "不支持此类型");
}
return null;
}


3获得LooperManager的实例,并根据schema来获得Loader的请求实例。

在LooperManager的构造方法中初始化了这样一个map

private LoaderManager() {
regist("http", new UrlLoader());
regist("https", new UrlLoader());
regist("file", new LocalLoader());
}


用map来维护把网络请求和本地请求对应的加载器。然后通过getLoader()来获得具体的请求方式。

public Loader getLoader(String schema) {
if (mLoaderMap.containsKey(schema)) {
return mLoaderMap.get(schema);
}
return new NullLoader();
}


4 执行加载,调用loader的loadImage()方法。

loader.loadImage(bitmapRequest);


9,10通过网络请求和本地读取来加载Bitmap

UrlLoader和LocalLoader都是继承自AbstractLoader的,而AbstractLoader实现了Loader的接口。所以在8中的4步骤中调用loader的 loadImage()方法时,将会执行AbstractLoader的loadImage()方法。

@Override
public void loadImage(BitmapRequest bitmapRequest) {
//1 从缓存中读取
Bitmap bitmap = bitmapCache.get(bitmapRequest);
if (bitmap == null) {
//2 显示加载中的图片
showLoadingImage(bitmapRequest);

//3 真正开始加载
bitmap = onLoad(bitmapRequest);

//4 缓存图片
cacheBitmap(bitmapRequest, bitmap);
}
//5 切换到主线程显示图片
deliveryToUIThread(bitmapRequest,bitmap);
}


无论是网络加载UrlLoader还是本地加载LocalLoader都要执行这五个步骤。不同的是加载方式不同如步骤3调用了onLoad()方法。

//抽象加载策略 交给本地加载和网络加载去处理
protected abstract Bitmap onLoad(BitmapRequest bitmapRequest);


本地加载

在本地加载的onLoad()方法中

@Override
protected Bitmap onLoad(BitmapRequest bitmapRequest) {
//1 获得本地图片的路径
final String path = Uri.parse(bitmapRequest.getImageUrl()).getPath();
File file = new File(path);
if (!file.exists()) {
return null;
}

BitmapDecoder bitmapDecoder = new BitmapDecoder() {
@Override
public Bitmap
b231
decodeBitmapWithOptions(BitmapFactory.Options options) {
//2 根据文件文件解码出bitmap
return BitmapFactory.decodeFile(path, options);
}
};
//3 通过ImageView的宽和高和之前的图片路径来解码出bitmap对象。
return bitmapDecoder.decodeBitmap(ImageViewHelper.getImageViewWidth(bitmapRequest.getImageView()),
ImageViewHelper.getImageViewHeight(bitmapRequest.getImageView()));
}


做了三步。

1 不用多说,只是获得本地图片的路径

2 根据图片文件解码处bitmap。在此步骤中直接new了一个匿名抽象类。并实现了其抽象方法。此方法是为了在步骤3中调用的。所以还是要先看3.

3 压缩图片 解码bitmap对象

public Bitmap decodeBitmap(int reqWidth, int reqHeight) {
//a 初始化options
BitmapFactory.Options options = new BitmapFactory.Options();

//b 仅加载边界属性
options.inJustDecodeBounds = true;

//c 解码出bitmap
decodeBitmapWithOptions(options);

//d 计算压缩比例
caculateSampleSizeWithOptions(options, reqWidth, reqHeight);
return decodeBitmapWithOptions(options);
}


在步骤3中的ImageLoaderHelper是一个用来获得ImageView的宽高的辅助类。当ImageView没有明确具体宽高时。做了相应的处理。获取到ImageView的宽高后。在根据宽高来来计算出options的inSampleSize。再通过inSampleSize来压缩图片。最后获得bitmap对象。

网络加载

和网络加载和UrlLoader加载图片的过程不同。但是功能相同。因为本文主要是说明架构的思想。所以不再分析网络加载部分。至此整个流程已经结束。

下载源码
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: