- 为什么要写这章
在最近的项目中,有一个高清大图片处理任务,自己尝试进行了一些封装,虽然使用起来还算顺利,但中间还是遇到一些槽点,如不当调用bitmap的recycle方法导致app crash,图片缓存策略设计不够合理,框架只能适应用于较少的任务…顾方才有了此系列
- Android-Universal-Image-Loader(Android通过图片加载框架)基本使用
实现效果如下:
在gridview中显示N张下载的图片,下载的图片会被三级缓存,同时每一张照片淡入显示
代码部分
package com.cn.china.imageloader;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;
import android.widget.ProgressBar;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;
import com.nostra13.universalimageloader.core.display.CircleBitmapDisplayer;
import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer;
import com.nostra13.universalimageloader.core.display.RoundedBitmapDisplayer;
import com.nostra13.universalimageloader.core.listener.ImageLoadingProgressListener;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
public class ImageLoaderActivity extends Activity {
private DisplayImageOptions options;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image_loader);
initImageLoader(this);
configPic();
GridView gridView = (GridView) findViewById(R.id.gridview);
gridView.setAdapter(new GridAdapter());
}
/*
* 初始化ImageLoader
*/
private void initImageLoader(Context context) {
ImageLoaderConfiguration.Builder config = new ImageLoaderConfiguration.Builder(context);
//设置线程有限级,默认为Thread.NORM_PRIORITY - 2,这行代码我们可以不写,这里只是做一个学习用,线程的优先级会在构造ImageLoaderConfiguration的时候,自动设置
config.threadPriority(Thread.NORM_PRIORITY);
//不允许缓存同一张图片的多个尺寸,调用之后,会已图片uil为key,如果缓存中有相同的图片,则会将原先的bitmap进行覆盖
config.denyCacheImageMultipleSizesInMemory();
//图片缓存的时候总需要一个名字吧,这里以MD5的方式加上图片URL来生成一个缓存文件来表示一个图片
config.diskCacheFileNameGenerator(new Md5FileNameGenerator());
//设置硬盘缓存的最大存储空间50M
config.diskCacheSize(50 * 1024 * 1024);
//设置加载和显示图片的队列类型,默认为FIFO,即先进先出的队列;此处设置为LIFO表示后进先出
//当加载、显示、和缓存图片的队列满的时候,其他待进行的任务将按照FIFO或者LIFO的策略进行排队
config.tasksProcessingOrder(QueueProcessingType.LIFO);
//控制log的输出,不调用则不输出相关日志
config.writeDebugLogs();
//初始化ImageLoader
ImageLoader.getInstance().init(config.build());
}
private void configPic() {
options = new DisplayImageOptions.Builder()
//图片在刚加载的时候图片
.showImageOnLoading(R.drawable.ic_stub)
//url为空时展示的图片
.showImageForEmptyUri(R.drawable.ic_empty)
//图片加载失败时的图片
.showImageOnFail(R.drawable.ic_error)
//缓存到内存
.cacheInMemory(true)
//缓存到磁盘
.cacheOnDisk(true)
//考虑JPEG图像EXIF参数(旋转,翻转)
.considerExifParams(true)
//设置图片显示方式,此处为淡入模式
.displayer(new FadeInBitmapDisplayer(1500))
//设置图片的解码模式为RGB_565
.bitmapConfig(Bitmap.Config.RGB_565).build();
}
class GridAdapter extends BaseAdapter {
private LayoutInflater inflater;
GridAdapter() {
inflater = LayoutInflater.from(ImageLoaderActivity.this);
}
@Override
public int getCount() {
return Constants.imageThumbUrls.length;
}
@Override
public Object getItem(int position) {
return Constants.imageThumbUrls[position];
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.item_grid_image,parent, false);
holder = new ViewHolder();
holder.imageView = (ImageView) convertView.findViewById(R.id.image);
holder.progressBar = (ProgressBar) convertView.findViewById(R.id.progress);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
String url = Constants.imageThumbUrls[position];
ImageLoader.getInstance().displayImage(url,holder.imageView, options,
new SimpleImageLoadingListener() {
@Override
public void onLoadingStarted(String imageUri, View view) {
holder.progressBar.setProgress(0);
holder.progressBar.setVisibility(View.VISIBLE);
}
@Override
public void onLoadingFailed(String imageUri, View view,FailReason failReason) {
holder.progressBar.setVisibility(View.GONE);
}
@Override
public void onLoadingComplete(String imageUri,View view, Bitmap loadedImage) {
holder.progressBar.setVisibility(View.GONE);
}
}, new ImageLoadingProgressListener() {
@Override
public void onProgressUpdate(String imageUri, View view, int current, int total) {
holder.progressBar.setProgress(Math.round(100.0f * current / total));
}
});
return convertView;
}
}
private static class ViewHolder {
ImageView imageView;
ProgressBar progressBar;
}
}
第44行,initImageLoader方法主要作用是完成ImageLoader的配置和初始化
第47行,线程池里面待执行的任务以线程的方式存在,而线程的优先级会影响线程的执行先后顺序,在Android-Universal-Image-Loader(后面简称UIL)中线程的优先级默认为Thread.NORM_PRIORITY - 2;新建一个线程默认的级别是thread.norm_priority,线程的优先级从1~10,其中1为thread.min_priority,5为thread.norm_priority,10为thread.max_priority;其实我们大可不必进行这样的设置,因为这些现在cpu的性能已经比较强悍,先后执行的任务之间差别很小很小
第48行,当我们下载一张图片之后,如果这个图片的尺寸有所改变,那么通过denyCacheImageMultipleSizesInMemory我们就可以以覆盖的形式将之前保存的图片给覆盖掉,以保证缓存中只有这个图片的唯一副本
第56行,通过tasksProcessingOrder来设置当队列开始排队的时候,后续的任务的插入方式,默认为FIFO,即先进先出的队列;tasksProcessingOrder会影响执行加载线程池和缓存线程池中队列的创建,如果设置了tasksProcessingOrder方法为FIFO,则这两个线程池创建的时候采用的队列是LinkedBlockingQueue,反之这两个线程池创建的时候采用的队列是LIFOLinkedBlockingDeque,其中LIFOLinkedBlockingDeque是UIL框架中根据LIFO算法自定义的一个队列
第63行的configPic方法的作用是针对图片进行一些配置
第76行,通过调用considerExifParams方法,我们可以获取到图片的exif信息,
补充:exif信息是什么
Exif就是用来记录拍摄图像时的各种信息:图像信息(厂商,分辨率等),相机拍摄记录(ISO,白平衡,饱和度,锐度等),缩略图(缩略图宽度,高度等),gps(拍摄时的经度,纬度,高度)等,将这些信息按照JPEG文件标准放在图像文件头部。
你打开手机里面的Gallery,点击一张图片查看详细信息的时候,你变可以看见这张图片的这些设置,有了这些设置,我们就可以做一些额外的工作了,比如按照gps排序显示…
第80行,设置图片的解码方式,此处配置为Bitmap.Config.RGB_565
补充:RGB_565,ARGB_8888,ARGB_4444,ALPHA_8区别
上面四种我们称之为四种色彩模式,A:透明度,R:红色;G:绿色; B: 蓝色
Bitmap.Config ARGB_8888:由4个8位组成,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位(4字节)
Bitmap.Config ARGB_4444:由4个4位组成,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位 (2字节)
Bitmap.Config RGB_565:没有透明度,R=5,G=6,B=5,那么一个像素点占5+6+5=16位(2字节)
Bitmap.Config ALPHA_8:每个像素占8位,只有透明度,没有颜色。
一张图片是由若干个像素组成,假设一张480x800的图片,在色彩模式为ARGB_8888的情况下,会占用 4808004/1024KB=1500KB 的内存;而在RGB_565的情况下,占用的内存为:4808002/1024KB=750KB
所以一张图片如果以RGB_565的色彩模式进行存储,其占用内存会较少,但是我们也应该注意一点,这种色彩模式会损失一部分的真度,ARGB_4444色彩损失更多;ALPHA_8试用场景比较特殊,只保存透明度,可用于在图片上设置遮盖效果
第118行,调用displayImage方法去加载并显示图片
第119行,SimpleImageLoadingListener用于监听加载过程中的几个阶段,开始加载,加载失败,加载完成
第135行,ImageLoadingProgressListener用于监听下载的进度
-
完整代码下载
ImageLoader源码下载 -
UIL流程简析
Post-process Bitmap表示在显示图片之前,对这个bitmap的预处理;
Pre-process Bitmap表示在缓存这张图片之前,对这个bitmap的预处理;
其完成的流程为:
如果在内存中存在图片缓存,则对这张图片进行预处理,然后显示;
如果内存中没有缓存,但是在硬盘缓存了这张图片,则先将这张图片解码成bitmap,然后对其进行Pre-process处理,保存到内存缓存中,然后经过Post-process处理,再显示这张图片;
如果内存和硬盘缓存都没有,则下载这张图片,然后缓存到硬盘,并将这张图片解码成bitmap,然后对其进行Pre-process处理,保存到内存缓存中,然后经过Post-process处理,再显示这张图片(这要是一个app最原始的状态)
- UIL API简介
DefaultConfigurationFactory:为ImageLoaderConfiguration提供一些默认的配置,我们知道在实例化ImageLoader之前,需要配置ImageLoaderConfiguration,在里面进行参数的设置,如果在ImageLoaderConfiguration设置参数的时候漏掉一些参数,则这些参数将有DefaultConfigurationFactory来进行补充
DisplayBitmapTask:显示图片到imageview的一个任务,是实现了runable接口的类
DisplayImageOptions:显示图片的时候一些配置参数
ImageLoader:核心类,用于加载和显示图片
ImageLoaderEngine:ImageLoader的引擎,负责LoadAndDisplayImageTask任务的执行
LoadAndDisplayImageTask:负责加载和显示一个图片的任务,用于从Internet或文件系统加载图像,将其解码为Bitmap,以及使用DisplayBitmapTask在ImageAware中显示它,其中ImageAware是一个接口,在我们把图片显示在一个imgeview上的时候,首先会将这个imageview构建为ImageViewAware,ImageViewAware的作用是对imageview做了弱引用,并防止内存泄漏;ImageViewAware继承自ViewAware,ViewAware的作用是和一个view保持弱引用,并防止内存泄漏;
ViewAware实现了ImageAware接口;所以简单的理解LoadAndDisplayImageTask就是最终显示在imageview上,只不过在中间我们做了弱引用和防止内存泄漏的动作
ProcessAndDisplayImageTask:通过DisplayBitmapTask来将一张图片显示在一个imageview上