一、考虑方向
关于图片加载功能,目前针对如下几个场景考虑性能:
- 避免主线程因加载图片导致block,并需要考虑涉及到快速加载多张图片的情形;
==>所以此处加载图片需要开工作线程处理,最好可以使用线程池; - 用户使用时有反复加载某张图片的场景;
==>所以添加buffer存放最近有加载过的图片,该buffer使用LRU算法更新; - 大图片的加载会耗时很久,而在此项目800*480分辨率屏幕上显示效果并不能体现;
==>所以加载图片时会获取对应分辨率大小的图片; - 使用效果优化,如果加载时间较长,则用户体验不好;
==>需要添加预加载功能,此部分因为显示是使用的viewpager,已经支持预加载; - 快速切换图片场景,大量图片快速切换,则切换过程中的图片是否需要完成加载并显示部分需要考虑,
==>需要添加取消图片加载的功能,则需要建立一个队列获取图片,如果当前已经切换到其他图片时可以cancel加载过程;
二、功能逻辑
则针对图片加载功能,分为如下三个方向的处理:
-
针对于图片加载策略:
==>工作队列记录,需要加载的图片添加到工作队列中,加载完成则从工作队列中移除;
==>添加到工作队列的任务添加到线程池中处理(目前设置为5个线程,任务添加超过5个会block)
==>加载图片时:- 判断当前mem中是否已经存储了此图片,有则直接取出返回,没有则进入第二步;
- 直接从数据库中decode图片并返回,如果取数据失败,则加载默认图片image demage;
- 加载完成,将其添加到mem中,给后续使用;
==>在加载图片时添加判断,如果该图片已经不再当前显示范围内则直接返回,不做后续处理,以提高性能;(待后续测试确认是否需要添加此功能)
-
针对于图片加载过程的处理:
==>此部分主要考虑图片过大情况下加载时间过长的情况,则在加载图片过程中计算src大小,并根据显示分辨率计算缩放比,获取缩放过的图片,可以提高性能; -
针对于图片显示策略:
==>使用viewpager显示控件,该控件提供预加载功能(涉及到图片切换后更新显示的问题,当前已去除此功能)
三、具体实现:
- 工作队列
- 线程池
- 缓存机制
- 图片加载过程
- 图片显示
目前上述各个功能主要都是使用android现有工具类:
3.1 工作队列:
3.1.1 逻辑:
- 在ImageLoader构造工作类,并建立该工作类list;
- 实现向该队列中添加任务函数,此处增加一个判断,如果该任务已经添加过,则不需要重复添加;
- 实现从该队列中移除任务函数;
3.1.2 code:
private List<LoadPhotoTask> mTaskQueue = null;
class LoadPhotoTask implements Runnable {
private String path;
private ImageView view;
private boolean isThumbnail;
private onImageLoadedListener callback;
private Bitmap bitmap;
LoadPhotoTask(String path,
ImageView imageView,
boolean isThumbnail,
onImageLoadedListener callback) {
Debug.d(TAG, "loadImage new LoadPhotoTask");
this.path = path;
this.view = imageView;
this.isThumbnail = isThumbnail;
this.callback = callback;
this.bitmap = null;
}
@Override
public void run() {
//加载图片逻辑
}
public ImageView getView() {
return view;
}
}
public void loadImage(ImageView view,
String path,
boolean isThumbnail,
int reqWidth,
int reqHeight,
onImageLoadedListener callback) {
if(isTaskExisted(view)) {
return;
}
LoadPhotoTask task = new LoadPhotoTask(path, view, isThumbnail, callback);
synchronized (mTaskQueue) {
mTaskQueue.add(task);
}
mExecutor.execute(task);
}
private boolean isTaskExisted(ImageView view) {
if(view == null)
return false;
synchronized (mTaskQueue) {
int size = mTaskQueue.size();
for(int i=0; i<size; i++) {
LoadPhotoTask task = mTaskQueue.get(i);
if(task != null && task.getView().equals(view))
return true;
}
}
return false;
}
private void removeTask(LoadPhotoTask task) {
synchronized (mTaskQueue) {
mTaskQueue.remove(task);
}
}
3.2 线程池
这里直接使用java提供的线程池ExecutorService:
3.2.1 逻辑:
- 在ImagLoader类建立线程池;
- 上述工作队列中执行加载图片过程时,使用该线程池处理;
3.2.2 code:
private ExecutorService mExecutor;
mExecutor = Executors.newFixedThreadPool(Constant.LOAD_IMAGE_THREAD);
mExecutor.execute(task);
3.3 buffer
此部分使用Android提供的LRU buffer:
3.3.1 逻辑:
- 在ImagLoader类中添加buffer,设置buffer大小(buffer更新机制为LRU);
- 实现buffer添加和读取接口,获取到图片后,将其添加到buffer中;
- 在view adapter获取图片时,首先在buffer中查询,有则返回,没有则到数据库中读取;
3.3.2 code:
private LruCache<String, Bitmap> mCache = null;
ActivityManager am =(ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
int maxSize = am.getMemoryClass()*1024*1024/8;
mCache = new LruCache<String, Bitmap>(maxSize){
@Override
protected int sizeOf(String key, Bitmap value) {
return value.getRowBytes() * value.getHeight();
}
};
public void putBitmapInCache(String path, Bitmap bitmap){
if(null == path) return;
if(null == bitmap) return;
if(null == getBitmapFromCache(path)){
this.mCache.put(path, bitmap);
}
}
public Bitmap getBitmapFromCache(String path){
return mCache.get(path);
}
String key = String.valueOf(isThumbnail) + path;
bitmap = getBitmapFromCache(key);
if (null == bitmap) {
String tag = view.getTag().toString();
if (tag.equals(path)) {
if (isThumbnail) {
bitmap = loadThumbnailInBackground(path, 92, 49);
} else {
bitmap = loadImageInBackground(path, 800, 480);
}
String cacheKey = String.valueOf(isThumbnail) + path;
putBitmapInCache(cacheKey, bitmap);
}
}
show(view, callback, bitmap);
removeTask(this);
3.4. 加载图片时根据屏幕分辨率进行缩放处理
3.4.1 逻辑:
- 获取图片源信息;
- 计算缩放比;
- load 图片;
3.4.2 code:
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds=true;//设置为true则不会load图片,而是获取图片信息填充到option中
BitmapFactory.decodeFile(path, options);
public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
final int height = options.outHeight;
final int width = options.outWidth;
int inSampleSize = 1;
int beWidth = Math.round((float) width / (float) reqWidth);
int beHeight = Math.round((float) height / (float) reqHeight);
if (height > reqHeight || width > reqWidth) {
if (beWidth < beHeight) {
inSampleSize = beWidth;
} else {
inSampleSize = beHeight;
}
if (inSampleSize <= 0) {
inSampleSize = 1;
}
}
return inSampleSize;
}
options.inJustDecodeBounds = false;
Bitmap bitmap= BitmapFactory.decodeFile(path, options);
3.5. 图片显示
3.5.1逻辑:
- 通过message handler的方式在主线程显示;
- 设置显示图片类,传递view、bitmap、callback;
- 上述callback为adapter实现,具体操作获取图片后的处理;
3.5.2 code:
private void show(ImageView imageView, onImageLoadedListener callback, Bitmap bitmap) {
BitmapDisplayer bitmapDisplayer = new BitmapDisplayer(imageView, callback, bitmap);
Message message = Message.obtain();
message.obj = bitmapDisplayer;
message.what = Constant.MSG_PHOTO_LOAD_CMPLETE;
mUIHandler.sendMessage(message);
}
class BitmapDisplayer {
private Bitmap bitmap;
private ImageView imageView;
private onImageLoadedListener callback;
public BitmapDisplayer(ImageView imageView, onImageLoadedListener callback, Bitmap b) {
this.bitmap = b;
this.imageView = imageView;
this.callback = callback;
}
}
Handler mUIHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
switch (msg.what) {
case Constant.MSG_PHOTO_LOAD_CMPLETE:
BitmapDisplayer b = (BitmapDisplayer)msg.obj;
Bitmap bitmap = b.bitmap ;
onImageLoadedListener callback = b.callback;
ImageView view = b.imageView;
callback.displayImage(view, bitmap);
break;
default:
break;
}
}
};
//call back
public interface onImageLoadedListener {
void displayImage(ImageView view, Bitmap bitmap);
}