Android高效异步图片加载框架
概述
详细
代码下载:http://www.demodashi.com/demo/12143.html
一.概述
目前为止,第三方的图片加载框架挺多的,比如UIL , Volley Imageloader等等。但是最好能知道实现原理,所以下面就来看看设计并开发一个加载网络、本地的图片框架。
总所周知,图片框架中肯定需要用到缓存,这里我们和其他框架一样,采用LruCache来管理图片的缓存,当然图片的加载测量使用LIFO比较好点,因为要加载最新的给用户。
我们采用异步消息处理机制来实现图片异步加载任务:用于UI线程当Bitmap加载完成后更新ImageView。
加载网络图片的原理,就是如果启用了硬盘缓存,加载时,先从内存中加载,然后从硬盘加载,最后再从网络下载。下载完成后,写入硬盘和内存缓存。
如果没有启用硬盘缓存,就直接从网络压缩下载获取,最后加入内存缓存即可。
二.演示效果图
三.图片加载框架实现解析
1、图片压缩
很多情况下,网络或者本地的图片都比较大,而我们的ImageView显示大小比较小,这时候就需要我们进行图片的压缩,以显示到ImageView上面去。
1.1、本地图片压缩
(1)获取ImageView所显示的大小
/*** 获取ImageView所要显示的宽和高*/public static ImageSize getImageViewSize(ImageView imageView){ImageSize imageSize = new ImageSize();DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();ViewGroup.LayoutParams lp = imageView.getLayoutParams();// 获取imageview的实际宽度int width = imageView.getWidth();if (width <= 0){// 获取imageview在layout中声明的宽度width = lp.width;}if (width <= 0){// 检查最大值width = getImageViewFieldValue(imageView, "mMaxWidth");}if (width <= 0){width = displayMetrics.widthPixels;}// 获取imageview的实际高度int height = imageView.getHeight();if (height <= 0){// 获取imageview在layout中声明的宽度height = lp.height;}if (height <= 0){// 检查最大值height = getImageViewFieldValue(imageView, "mMaxHeight");}if (height <= 0){height = displayMetrics.heightPixels;}imageSize.width = width;imageSize.height = height;return imageSize;}
上面代码中最大宽度,没有用getMaxWidth();用的是反射获取的,这是因为getMaxWidth竟然要API 16,没办法,为了兼容问题,只能采用反射机制,所以不太赞同反射。
(2)设置图片的inSampleSize
根据ImageView所要显示的大小和图片的实际大小来计算inSampleSize,实现如下:
/*** 根据ImageView的宽高和图片实际的宽高计算SampleSize*/public static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){int width = options.outWidth;int height = options.outHeight;int inSampleSize = 1;if (width > reqWidth || height > reqHeight){int widthRadio = Math.round(width * 1.0f / reqWidth);int heightRadio = Math.round(height * 1.0f / reqHeight);inSampleSize = Math.max(widthRadio, heightRadio);}return inSampleSize;}
1.2、网络压缩
上面是本地的图片的压缩,如果是网络图片的话, 分两种情况,如果硬盘缓存开启的话, 就把图片下载到本地,然后在采用上面本地压缩方法;
如果硬盘缓存没有开启的话,才用BitmapFactory.decodeStream()来获取bitmap,然后和本地压缩一样的方法来计算采样率压缩。如下:
/*** 根据url下载图片并压缩*/public static Bitmap downloadImageByUrl(String urlStr, ImageView imageview){InputStream is = null;try{URL url = new URL(urlStr);HttpURLConnection conn = (HttpURLConnection) url.openConnection();is = new BufferedInputStream(conn.getInputStream());is.mark(is.available());BitmapFactory.Options opts = new BitmapFactory.Options();opts.inJustDecodeBounds = true;Bitmap bitmap = BitmapFactory.decodeStream(is, null, opts);//获取imageview想要显示的宽和高ImageSize imageViewSize = ImageUtils.getImageViewSize(imageview);opts.inSampleSize = ImageUtils.calculateInSampleSize(opts,imageViewSize.width, imageViewSize.height);opts.inJustDecodeBounds = false;is.reset();bitmap = BitmapFactory.decodeStream(is, null, opts);conn.disconnect();return bitmap;} catch (Exception e){e.printStackTrace();} finally{try{if (is != null)is.close();} catch (IOException e){}}return null;}
图片压缩差不多就这样了,下面来看看图片加载框架的设计与实现
2、图片加载框架的设计架构
图片压缩完了,就放入我们的LruCache,然后通过setImageBitmap方法设置到我们的ImageView上。
图片加载框架的整体架构如下:
(1)、单例实现,单例默认不传参数,当然也支持传参单例调用框架。
(2)、图片缓存管理:包含一个LruCache用于管理我们的图片。
(3)、任务队列:每来一次新的加载图片的请求,封装成Task添加到的任务队列TaskQueue中去;
(4)、后台轮询线程:该线程在第一次初始化实例的时候启动,然后会一直在后台运行;当每来一次加载图片请求的时候,
除了会创建一个新的任务到任务队列中去,同时发一个消息到后台线程,后台线程去使用线程池去TaskQueue去取一个任务执行;
基本的框架设计架构就是上面这些,下面来看看具体的实现:
3、图片加载框架的具体实现
3.1、单例实现以及构造方法:
public static XCImageLoader getInstance(){if (mInstance == null){synchronized (XCImageLoader.class){if (mInstance == null){mInstance = new XCImageLoader(DEAFULT_THREAD_COUNT,Type.LIFO);}}}return mInstance;}public static XCImageLoader getInstance(int threadCount,Type type){if (mInstance == null){synchronized (XCImageLoader.class){if (mInstance == null){mInstance = new XCImageLoader(threadCount,type);}}}return mInstance;}private XCImageLoader(int threadCount,Type type){init(threadCount, type);}/*** 初始化信息* @param threadCount* @param type*/private void init(int threadCount,Type type){initBackThread();//获取当前应用的最大可用内存int maxMemory = (int) Runtime.getRuntime().maxMemory();mLruCache = new LruCache<String,Bitmap>(maxMemory/8){@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getRowBytes() * value.getHeight();}};//创建线程池mThreadPool = Executors.newFixedThreadPool(threadCount);mTaskQueue = new LinkedList<Runnable>();mType = type;mPoolTThreadSemaphore = new Semaphore(threadCount);}
3.2、后台轮询线程:
后台线程中,创建一个Handler用来处理图片加载任务发过来的图片显示消息。
/*** 初始化后台轮询线程*/private void initBackThread() {//后台轮询线程mPoolThread = new Thread(){@Overridepublic void run() {Looper.prepare();mPoolThreadHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {//从线程池中取出一个任务开始执行mThreadPool.execute(getTaskFromQueue());try {mPoolTThreadSemaphore.acquire();} catch (InterruptedException e) {e.printStackTrace();}}};//释放信号量mPoolThreadHandlerSemaphore.release();Looper.loop();}};mPoolThread.start();}
3.3、使用框架显示图片-加载图片并显示到ImageView上
加载显示图片的时候,判断是否有LruCache,如果有的话,就从LruCache中取出来加载显示;
否则的话,就新建一个图片加载任务并添加到任务队列中。
/*** 加载图片并显示到ImageView上*/public void displayImage(final String path,final ImageView imageView,final boolean isFromNet){imageView.setTag(path);if(mUIHandler == null){mUIHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {// 获取得到图片,为imageview回调设置图片ImageHolder holder = (ImageHolder) msg.obj;Bitmap bmp = holder.bitmap;ImageView imageview = holder.imageView;String path = holder.path;// 将path与getTag存储路径进行比较,防止错乱if (imageview.getTag().toString().equals(path)){if(bmp != null){imageview.setImageBitmap(bmp);}}}};}// 根据path在缓存中获取bitmapBitmap bm = getBitmapFromLruCache(path);if (bm != null){refreshBitmap(path, imageView, bm);}else{//如果没有LruCache,则创建任务并添加到任务队列中addTaskToQueue(createTask(path, imageView, isFromNet));}}
3.4、创建图片加载任务并添加到任务队列中
图片加载任务首先会判断是否从网络加载,如果是的话,再一次判断是否有LruCache和DiskCache,如果都没有的话, 就从网络下载加载;
如果不从网络加载,就直接从本地加载;最后无论是否网络加载,都要把图片写入到LruCache和DiskCache中去,并且刷新显示Bitmap到
ImageView上。
当然最后添加任务到任务队列后,会通过mPoolThreadHandler.sendEmptyMessage(24)方法来通知后台线程去任务线程池中取出一个
任务线程来执行。
/*** 添加任务到任务队列中*/private synchronized void addTaskToQueue(Runnable runnable){mTaskQueue.add(runnable);try{if (mPoolThreadHandler == null)mPoolThreadHandlerSemaphore.acquire();} catch (InterruptedException e){e.printStackTrace();}mPoolThreadHandler.sendEmptyMessage(24);}
/*** 根据参数,创建一个任务*/private Runnable createTask(final String path, final ImageView imageView,final boolean isFromNet){return new Runnable(){@Overridepublic void run(){Bitmap bm = null;if (isFromNet){File file = getDiskCacheDir(imageView.getContext(),Utils.makeMd5(path));if (file.exists())// 如果在缓存文件中发现{Log.v(TAG, "disk cache image :" + path);bm = loadImageFromLocal(file.getAbsolutePath(),imageView);} else{if (mIsDiskCacheEnable)// 检测是否开启硬盘缓存{boolean downloadState = ImageDownloadUtils.downloadImageByUrl(path, file);if (downloadState)// 如果下载成功{Log.v(TAG,"download image :" + path+ " to disk cache: "+ file.getAbsolutePath());bm = loadImageFromLocal(file.getAbsolutePath(),imageView);}} else{// 直接从网络加载bm = ImageDownloadUtils.downloadImageByUrl(path,imageView);}}} else{bm = loadImageFromLocal(path, imageView);}// 3、把图片加入到缓存setBitmapToLruCache(path, bm);refreshBitmap(path, imageView, bm);mPoolTThreadSemaphore.release();}};}
3.4、显示Bitmap到ImageView上
通过UIHandler发消息来显示Bitmap到ImageView上去。
/*** 刷新图片到ImageView*/private void refreshBitmap(final String path, final ImageView imageView,Bitmap bm){Message message = Message.obtain();ImageHolder holder = new ImageHolder();holder.bitmap = bm;holder.path = path;holder.imageView = imageView;message.obj = holder;mUIHandler.sendMessage(message);}
最后,框架中使用到了两个信号量,下面稍微解析下:
第一个:mPoolThreadHandlerSemaphore= new Semaphore(0); 用于控制我们的mPoolThreadHandler的初始化完成,我们在使用mPoolThreadHandler会进行判空,如果为null,会通过mPoolThreadHandlerSemaphore.acquire()进行阻塞;当mPoolThreadHandler初始化结束,我们会调用.release();解除阻塞。
第二个:mPoolTThreadSemaphore= new Semaphore(threadCount);这个信号量的数量和我们加载图片的线程个数一致;每取一个任务去执行,我们会让信号量减一;每完成一个任务,会让信号量+1,再去取任务;目的是什么呢?为什么当我们的任务到来时,如果此时在没有空闲线程,任务则一直添加到TaskQueue中,当线程完成任务,可以根据策略去TaskQueue中去取任务,只有这样,我们的LIFO才有意义。
四.框架的使用实例
这里,我们用一个简单GridView加载显示1000张图片来演示我们的框架使用。
4.1、布局文件实现:
activity_xcimager_loader.xml:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".XCImagerLoaderActivity"><GridViewandroid:id="@+id/gridview"android:layout_width="match_parent"android:layout_height="match_parent"android:numColumns="3"android:horizontalSpacing="5dp"android:verticalSpacing="5dp"></GridView></RelativeLayout>
layout_gridview_item.xml:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical" android:layout_width="match_parent"android:layout_height="120dp"><ImageViewandroid:id="@+id/image_view"android:layout_width="match_parent"android:layout_height="120dp"android:scaleType="centerCrop"/><TextViewandroid:id="@+id/text_pos"android:layout_width="50dp"android:layout_height="50dp"android:layout_alignParentBottom="true"android:layout_alignParentRight="true"android:text="1"android:gravity="center"android:textColor="#000000"android:background="#FFFF00"/> </RelativeLayout>
4.2、实例演示类文件实现:
public class XCImagerLoaderActivity extends AppCompatActivity {private GridView mGridView;private String[] mUrlStrs = ImageSources.imageUrls;private XCImageLoader mImageLoader;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_xcimager_loader);init();mImageLoader = XCImageLoader.getInstance(3, XCImageLoader.Type.LIFO);}private void init() {mGridView = (GridView) findViewById(R.id.gridview);GridViewAdpter adapter = new GridViewAdpter(this,0,mUrlStrs);mGridView.setAdapter(adapter);}private class GridViewAdpter extends ArrayAdapter<String>{private Context mContext;public GridViewAdpter(Context context, int resource, String[] datas){super(context, 0, datas);mContext = context;}@Overridepublic View getView(int position, View convertView, ViewGroup parent){if (convertView == null){convertView = LayoutInflater.from(mContext).inflate(R.layout.layout_gridview_item, parent, false);}ImageView imageview = (ImageView) convertView.findViewById(R.id.image_view);imageview.setImageResource(R.mipmap.img_default);TextView textview = (TextView)convertView.findViewById(R.id.text_pos);textview.setText(""+(position + 1));mImageLoader.displayImage(getItem(position), imageview, true);return convertView;}}
}
五.项目代码目录结构图
代码下载:http://www.demodashi.com/demo/12143.html
注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权
Android高效异步图片加载框架相关推荐
- Android设计一个图片加载框架
本文不是具体编码去实现一个图片加载的框架,而是从理论上来讲解设计一个图片加载框架的注意事项和涉及的知识点,提供一个思路,或者帮助童鞋们应付面试.目前Android 发展至今优秀的图片加载框架太多,例如 ...
- Android 框架练成 教你打造高效的图片加载框架
转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/41874561 ,本文出自: [张鸿洋的博客] 1.概述 优秀的图片加载框架不要 ...
- 【Android 进阶】图片加载框架之Glide
简介 在泰国举行的谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide 的图片加载库,作者是 bumptech.这个库被广泛的运用在 google 的开源项目中,包括 2014 年 google I ...
- 【Android】Fresco图片加载框架(二)————Producer
/** * 本文可以随意转载到任何网站或者App, * BUT * 转载也要按"基本法", * 请注明原文出处和作者 */ 官方源码地址 fresco官方高大上介绍(1)(注意:前 ...
- Android Glide图片加载框架(一)基本用法
文章目录 一.前言 二.简介 三.基本用法 第一步:调用 Glide.with() 方法创建加载图片的实例 第二步:调用 load() 方法指定待加载的图片资源 第三步:调用 into() 方法绑定显 ...
- Android图片加载框架
这篇文章主要和大家一起动手编写Android图片加载框架,从内部原理到具体实现来详细介绍如何开发一个简洁而实用的Android图片加载缓存框架,感兴趣的小伙伴们可以参考一下 开发一个简洁而实用的And ...
- Android图片加载框架——Glide(Glide v4)
原文地址 Android图片加载框架--Glide(Glide v4) 前言 android中图片加载框架有很多,所有框架最终达到的目都是在Android平台上以极度简单的方式加载和展示图片,如果我们 ...
- Android Glide图片加载框架(三)缓存机制
文章目录 一.缓存简介 二.缓存用法 内存缓存方式 磁盘缓存方式 三.缓存KEY 四.内存缓存 内存缓存流程 五.磁盘缓存 磁盘缓存流程 Android Glide图片加载框架系列文章 Android ...
- Android图片加载框架最全解析(二),从源码的角度理解Glide的执行流程
转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/53939176 本文同步发表于我的微信公众号,扫一扫文章底部的二维码或在微信搜索 郭 ...
最新文章
- TensorFlow2020:如何使用Tensorflow.js执行计算机视觉应用程序?
- Qt Creator使用帮助模式
- php在一定范围内去随机整数,php-如何从随机位生成范围内的整数
- UE4 代理 BindRaw和BindUObject
- 照片看3秒就销毁的软件_3.9秒破百,比亚迪汉EV到店实拍:实车比照片更好看
- 解读mysql的索引和事务的正确姿势
- 用SeaMonkey写cnblogs博客时碰到的问题
- node怎么解析vue代码_vue中node_modules中第三方模块的修改使用详解
- SegmentFault 巨献 1024 程序猿游戏「红岸的呼唤」第四天任务攻略
- 训练赛20160403
- macos big sur永久关闭final cut Pro通知的技巧
- 常见消息部件(Common Message Components)
- php 正则 修饰符,php 正则修饰符
- 正态分布下贝叶斯决策的引入
- 百度用户增长SQL面试题
- 010editor pyc template
- JRE和JDK的区别(笔记)
- 常用图片格式JPG\PNG\SVG该如何选择?
- 基于大数据的音乐数据中心平台(附:源码 课件 项目部署文档)
- 计算机论文的摘要和关键词是什么意思,论文中的摘要和关键词是什么
热门文章
- s3c6410存储器映射
- ubuntu 将某个目录下的文件复制到_Ubuntu系统简单美化
- 《RabbitMQ 实战指南》第四章 RabbitMQ进阶(上)
- 【LeetCode】剑指 Offer 57 - II. 和为s的连续正数序列
- 【转载】Katalon Studio 基本用法--录制脚本并查看测试报告
- 四则运算编程题第二版
- MEF(可扩展框架)使用总结
- 编写高质量代码的十个秘诀(转)
- uva146-ID码
- jQueryUI modal dialog does not show close button (x) JQueryUI和BootStrap混用时候,右上角关闭按钮显示不出图标的解决办法...