Android框架设计03-图片加载框架
2017-04-22 13:19
429 查看
本片文章介绍一个图片加载的框架。
框架优点:
支持高并发,支持不同的加载策略(加载图片的优先级),显示图片自适应(老生常谈的图片压缩),支持缓存策略扩展。
设计模式:
建造者模式,生产者消费者模式,单例模式,策略模式,模板方法模式
其他知识点
内存缓存LruCache,磁盘缓存DiskLruCache,下载时将请求任务转发
类图 (看不清可以右键,在新标签中打开)
流程分析
由于整个框架的代码比较多,所以没有把所有的代码都贴出来。分析框架主要看框架的思想,所以这里不会太拘泥于细节。对框架感兴趣的可以下载源码。由于整个类图看上去我从下手。所以我按照在UML里面的序号进行分析。这样不至于摸不着头绪。
1 客户端调用框架
客户端首先需要构建SimpleImageLoader对象
通过上面的代码可以看出,首先要构建一个ImageLoaderConfig对象。然后用此对象来构建SimpleImageLoader对象。
然后通过imageLoader对象的disPlayImage()方法来加载图片。这就是客户端(MainActivity)的调用代码。
可以看出此方法加载图片的这种方式和Glide框架类似。只是传一个ImageView对象和Url.需要说明的是,上面在构建ImageLoaderConfig对象时有关的缓存策略,加载策略线程数都是有默认值的。所以也可以不做设置。如在ImageLoaderConfig中:
2 通过ImageLoaderConfig来构建SImpleImageLoader对象
简单来说,SimpleImageLoader是负责图片加载的代理。而ImageLoaderConfig是封装有关图片请求需要的参数。所以构建SimpleImageLoader要先构建一个ImageLoaderConfig对象。
在1中也有体现。
3,4,5构建ImageLoaderConfig所需的三个参数
3 DisPlayConfig 封装了加载中和加载失败的图片id。
4 BitmapCache 封装了图片缓存的策略。如内存缓存和SD卡缓存
5 loadPolicy加载策略。加载时是可以选择顺序加载和倒叙加载。但是此功能没有实现。可以在扩展时添加。
6 维护阻塞队列的RequestQueue
这个类有什么作用?
在RequestQueue中维护了一个线程阻塞队列。
通过在requestQueue中添加BitmapRequest和去取出BitmapRequest来实现任务的生产和消费。这里用了生产者消费者模式。
什么时候添加任务?
在MainActivity中
在SimpleImageLoader的disPlayImage()方法中
在RequestQueue中
此时一个任务已经添加到阻塞队列中
什么时候轮询任务队列里的任务?
在SimpleImageLoader中的构造方法时,将调用RequestQueue的start()方法。
在RequestQueue中
1 通过startDisPatcher()方法,把RequestQueue中维护的队列传到RequestDispatcher 中
2然后通过RequestDispatcher 中的start()方法去轮询传来的队列。
由于RequestDispatcher 继承自Thread,所以run()方法将会执行
至此RequestQueue维护的队列的任务已经被取出。
用图来表示
可以看出有多个RequestDispatcher 共同来轮询取出RequestQueue维护的队列中的任务。RequestDispatcher 的个数等于在ImageLoaderConfig中设置的个数。
由于任务队列是线程阻塞的,所以当任务队列中的BitmapRequest对象个数为0时,线程将会阻塞。直到新任务添加进队列。
7 RequestDispatcher 轮询队列
这一过程在6中已经说明了。简单来说就是RequestDispatcher 做为一个线程对象。在RequestQueue中的startDispatcer()中开启这些线程。然后这些线程开始共同轮询这个队列。不断的在队列中取出任务并执行。
8 通过LooperManager()来判断使用哪种加载 方式
在RequestDispatcher 的run()方法中
做了四件事。1 先是取出任务。2 然后根据开头来判断该用哪种请求方式。
3获得LooperManager的实例,并根据schema来获得Loader的请求实例。
在LooperManager的构造方法中初始化了这样一个map
用map来维护把网络请求和本地请求对应的加载器。然后通过getLoader()来获得具体的请求方式。
4 执行加载,调用loader的loadImage()方法。
9,10通过网络请求和本地读取来加载Bitmap
UrlLoader和LocalLoader都是继承自AbstractLoader的,而AbstractLoader实现了Loader的接口。所以在8中的4步骤中调用loader的 loadImage()方法时,将会执行AbstractLoader的loadImage()方法。
无论是网络加载UrlLoader还是本地加载LocalLoader都要执行这五个步骤。不同的是加载方式不同如步骤3调用了onLoad()方法。
本地加载
在本地加载的onLoad()方法中
做了三步。
1 不用多说,只是获得本地图片的路径
2 根据图片文件解码处bitmap。在此步骤中直接new了一个匿名抽象类。并实现了其抽象方法。此方法是为了在步骤3中调用的。所以还是要先看3.
3 压缩图片 解码bitmap对象
在步骤3中的ImageLoaderHelper是一个用来获得ImageView的宽高的辅助类。当ImageView没有明确具体宽高时。做了相应的处理。获取到ImageView的宽高后。在根据宽高来来计算出options的inSampleSize。再通过inSampleSize来压缩图片。最后获得bitmap对象。
网络加载
和网络加载和UrlLoader加载图片的过程不同。但是功能相同。因为本文主要是说明架构的思想。所以不再分析网络加载部分。至此整个流程已经结束。
下载源码
框架优点:
支持高并发,支持不同的加载策略(加载图片的优先级),显示图片自适应(老生常谈的图片压缩),支持缓存策略扩展。
设计模式:
建造者模式,生产者消费者模式,单例模式,策略模式,模板方法模式
其他知识点
内存缓存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加载图片的过程不同。但是功能相同。因为本文主要是说明架构的思想。所以不再分析网络加载部分。至此整个流程已经结束。
下载源码
相关文章推荐
- Android设计一个图片加载框架
- 设计简单的Android图片加载框架
- Android 二级图片缓存,图片优化,图片异步加载框架设计
- android图片加载框架ImageLoader涉及的设计模式
- Android使用开源框架加载图片
- Android 框架练成 教你打造高效的图片加载框架
- Android-Universal-Image-Loader异步加载图片框架学习研究
- android游戏开发的框架设计!(已更新资源图片)
- Android图片异步加载框架Android-Universal-Image-Loader
- Android使用开源框架加载图片
- [转]Android_开源框架_AndroidUniversalImageLoader网络图片加载
- Android-Universal-Image-Loader 图片异步加载框架
- Android 图片资源加载 简单框架
- 【Android】框架练成 教你打造高效的图片加载框架
- Android开源框架Universal-Image-Load网络图片加载
- 异步加载图片框架Android-Universal-Image-Loader的使用
- 图片异步加载框架 Android-Universal-Image-Loader
- Android 网络通信框架Volley简介以及获取JSON对象和图片加载例子
- android网络开源框架volley(四)——谈谈图片加载续——九张图片相册的展示(微信微博等)
- Android-Universal-Image-Loader异步加载图片框架学习研究