概述

Android高效异步图片加载框架:一个高效的异步加载显示的图片加载框架,同时具备图片压缩,缓存机制等特性.

详细

一.概述

目前为止,第三方的图片加载框架挺多的,比如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){
@Override
protected 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(){
@Override
public void run() {
Looper.prepare();
mPoolThreadHandler = new Handler(){
@Override
public 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(){
@Override
public 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在缓存中获取bitmap
Bitmap 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()
{
@Override
public 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"> <GridView
android: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">
<ImageView
android:id="@+id/image_view"
android:layout_width="match_parent"
android:layout_height="120dp"
android:scaleType="centerCrop"/>
<TextView
android: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;
@Override
protected 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;
}
@Override
public 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;
}
}
}

五.项目代码目录结构图

注:本文著作权归作者,由demo大师发表,拒绝转载,转载需要作者授权

Android高效异步图片加载框架的更多相关文章

  1. Android 框架修炼-自己开发高效异步图片加载框架

    一.概述 目前为止,第三方的图片加载框架挺多的,比如UIL , Volley Imageloader等等.但是最好能知道实现原理,所以下面就来看看设计并开发一个加载网络.本地的图片框架. 总所周知,图 ...

  2. Fresco从配置到使用(最高效的图片加载框架)

    Frescoj说明:      facebook开源的针对android应用的图片加载框架,高效和功能齐全. 支持加载网络,本地存储和资源图片: 提供三级缓存(二级memory和一级internal ...

  3. YYWebImage——iOS异步图片加载框架

    本文转载至 http://www.mobile-open.com/2015/86582.html   YYWebImage 是一个异步图片加载框架 (YYKit 组件之一). 其设计目的是试图替代 S ...

  4. Android 框架练成 教你打造高效的图片加载框架(转)

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41874561,本文出自:[张鸿洋的博客] 1.概述 优秀的图片加载框架不要太多, ...

  5. Android 框架练成 教你打造高效的图片加载框架

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/41874561,本文出自:[张鸿洋的博客] 1.概述 优秀的图片加载框架不要太多, ...

  6. Android图片加载框架最全解析(一),Glide的基本用法

    现在Android上的图片加载框架非常成熟,从最早的老牌图片加载框架UniversalImageLoader,到后来Google推出的Volley,再到后来的新兴军Glide和Picasso,当然还有 ...

  7. 【光速使用开源框架系列】图片加载框架ImageLoader

    [关于本系列] 最近看了不少开源框架,网上的资料也非常多,但是我认为了解一个框架最好的方法就是实际使用.本系列博文就是带领大家快速的上手一些常用的开源框架,体会到其作用. 由于作者水平有限,本系列只会 ...

  8. 一起写一个Android图片加载框架

    本文会从内部原理到具体实现来详细介绍如何开发一个简洁而实用的Android图片加载缓存框架,并在内存占用与加载图片所需时间这两个方面与主流图片加载框架之一Universal Image Loader做 ...

  9. Android之Glide(非常好用的图片加载框架)

    谷歌开发者论坛上,谷歌为我们介绍了一个名叫 Glide 的图片加载库,作者是bumptech. Glide是一种快速.高效的开源媒体管理和Android的包裹mediadecoding图像加载框架,内 ...

随机推荐

  1. Struts+HIbernate+Spring

    1.Struts 取代JSP中的控制功能,为系统添加独立的控制,采用Struts引入标签,实现JSP与后台JAVA代码的分离,JSP只负责显示,与struts.xml配合实现页面跳转 实现:接收请求, ...

  2. OC 导入类 #import和@class 区别复习

    objective-c中#import和@class的区别 在Objective-C中,可以使用#import和@class来引用别的类型, 但是你知道两者有什么区别吗? @class叫做forwar ...

  3. 数论E - Biorhythms(中国剩余定理,一水)

    E - Biorhythms Time Limit:1000MS     Memory Limit:10000KB     64bit IO Format:%I64d & %I64u Subm ...

  4. Index downloads are disabled, search results may be incomplete.

    20元现金领取地址:http://jdb.jiudingcapital.com/phone.html内部邀请码:C8E245J (不写邀请码,没有现金送) 国内私募机构九鼎控股打造,九鼎投资是在全国股 ...

  5. jetty7.6运行struts2项目问题解决

    运行struts2项目报错:报错1:11:56:51,400  WARN Dispatcher:68 - Could not find action or result: /credit_public ...

  6. Nuget server on IIS6 returns 404

    Nuget server on IIS6 returns 404 when downloading package after upgrade 2011年9月2日 8:03:30 (GMT Dayli ...

  7. Python学习(六)模块 —— 标准模块

    Python 标准模块 Python 带有一个标准模块库,并发布有独立的文档(库参考手册).对于程序员而言,标准库与语言本身同样重要,它好比一个百宝箱,能为各种常见的任务提供完美的解决方案. 这边简单 ...

  8. jenkins双向备份;高可用部署;

    如果把一个Jenkins的整个目录赋值到另一个Jenkins的目录,则需要务必保持两个Jenkins版本是相同的,不然容易出现Jenkins插件兼容性问题. 另外使用inotify+rsync备份的时 ...

  9. TCP第三次握手失败怎么办

    笔试题中经常会遇到这个问题:如果tcp建立连接时第三次握手失败,tcp会做何操作?该问题的本质是判断我们对tcp的状态转换是否能有比较深刻的理解.只要理解了下面的状态转换图,很容易回答上述问题. 在此 ...

  10. 【转载】Redis在新浪微博中的应用

    转载自文章 http://blog.me115.com/2013/12/19/redis-e5-9c-a8-e6-96-b0-e6-b5-aa-e5-be-ae-e5-8d-9a-e4-b8-ad-e ...