您的位置:首页 > 移动开发 > Android开发

Android 图片如何高效加载与缓存 —— (3) 改进和增加功能

2016-03-06 01:59 597 查看
再上一个文章中还有很多问题:比如要取消某个图片拉取任务、要在图片加载过程中的图片效果修改、一些逻辑上的问题,这次来一一修改!!

一,使任务可取消移除

第一步我们使用FutureTask和 Callable 进行搭配,来创建一项任务。

( 之前忽略了这个 FutureTask ,还以为是ION里面的东西,没想到这东西是自带的QAQ 唉。。 )

我们用其中一个作为例子,这个是网络拉取任务的,其他的任务创建也是如此。

private class Fetcher implements Callable<String>,ThreadinterFace {

//网络拉取任务
... ...
@Override
public String call() throws Exception {

if (imageView != null || onImageLoad != null){
OkHttpClient okHttpClient = new OkHttpClient.Builder().connectTimeout(5,TimeUnit.SECONDS).build();
Request request = new Request.Builder().url(imageUrl).build();
try {
Call call = okHttpClient.newCall(request);
Response response = call.execute();
cacheImage = BitmapFactory.decodeStream(response.body().byteStream());
if (cacheImage != null) imageCacher.putCache(tag,cacheImage);
if (onLoaded != null) cacheImage = onLoaded.reduce(cacheImage,tag);
} catch (IOException e) {
Log.e("OCImageLoader", "\nurl:" + imageUrl +"\nError:"+ e.toString());
onError(0);
}
runOnUIThread(new Runnable() {
@Override
public void run() {
Log.d("OCImageLoader","Image :"+imageUrl+" downloaded");
onDownloadCompleted(cacheImage,imageView,tag,onImageLoad);
}
});
}else {
runOnUIThread(new Runnable() {
@Override
public void run() {
onError(1);
}
});
}
return null;
}

... ...
}


第二步:自定义线程池,实现任务的控制

public class OCThreadExecutor extends ThreadPoolExecutor {

//这里我们使用HashMap来保存每个任务的引用,以用来处理
private Map<String,FutureTask> runnableMap;

public OCThreadExecutor(int maxRunningThread, String poolName) {
super(maxRunningThread, maxRunningThread, 0l, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), new OCThreadFactory(poolName));
runnableMap = new HashMap<>();
}

public void submit(FutureTask task , String tag){

//执行线程
if ( !runnableMap.containsKey(tag) ){
//如果池内没有相同的任务则可以执行
runnableMap.put(tag, task);
submit(task);
}else {
Log.d("OCThreadExecutor", "Same task TAG. Skipped. ");
}
}

public boolean cancelTask(String tag){

//中断任务

//如果请求的tag对应的任务在列表中
if ( runnableMap.containsKey(tag) ){
//先将任务从队列中移除
remove(runnableMap.get(tag));
//无论任务的状态是什么,一律任务终止,然后移除引用
runnableMap.remove(tag).cancel(true);
return true;
}else{
Log.d("OCThreadExecutor", "TAG dose not exist. Skipped. ");
return false;
}
}

public boolean cancelAllTask(){
//终止所有任务

//用迭代器来遍历所有任务
Iterator<FutureTask> taskList = runnableMap.values().iterator();
int count = 0;
while (taskList.hasNext()){
count++;
FutureTask task = taskList.next();
task.cancel(true);
remove(task);
}
runnableMap.clear();
Log.d("OCThreadExecutor",count +" Tasks canceled.");
return count > 0;
}

public boolean removeTag(String tag){

//移除TAG,调用于任务结束时
if (runnableMap.remove(tag) != null){
Log.d("OCThreadExecutor","TAG removed.");
return true;
}else {
Log.d("OCThreadExecutor","TAG dose not exist. Skipped. ");
return false;
}
}

static class OCThreadFactory implements ThreadFactory {...}

static class OCThread extends Thread {...}


最后,制作供外部调用的接口:

//终止任务
public void cancelTask(String tag){

//优先终止网络拉取线程池内的任务
fetherExecutor.cancelTask(tag);

//再终止缓存线程池内的任务
cacheExecutor.cancelTask(tag);
}

//终止所有任务
public void cancelAllTask(){

//优先终止网络拉取线程池内的所有任务
fetherExecutor.cancelAllTask();

//再终止缓存线程池内的所有任务
cacheExecutor.cancelAllTask();
}


第二,在图片加载中处理图片

我们要让图片的处理在图片加载的线程中同时处理,这样实现的方式也挺简单的,就是在图片加载完成之后,通过接口回调,传入原图,传出处理后的图片即可!

我们还是用网络拉取的任务做例子:

private class Fetcher implements Callable<String>,ThreadinterFace {

... ...

@Override
public String call() throws Exception {

if (imageView != null || onImageLoad != null){
OkHttpClient okHttpClient = new OkHttpClient.Builder().connectTimeout(5,TimeUnit.SECONDS).build();
Request request = new Request.Builder().url(imageUrl).build();
try {
Call call = okHttpClient.newCall(request);
Response response = call.execute();
cacheImage = BitmapFactory.decodeStream(response.body().byteStream());
if (cacheImage != null) imageCacher.putCache(tag,cacheImage);

//这里就把图片进行处理了,并传出
if (onLoaded != null) cacheImage = onLoaded.reduce(cacheImage,tag);
} catch (IOException e) {
Log.e("OCImageLoader", "\nurl:" + imageUrl +"\nError:"+ e.toString());
onError(0);
}
runOnUIThread(new Runnable() {
@Override
public void run() {
Log.d("OCImageLoader","Image :"+imageUrl+" downloaded");
onDownloadCompleted(cacheImage,imageView,tag,onImageLoad);
}
});
}else {
runOnUIThread(new Runnable() {
@Override
public void run() {
onError(1);
}
});
}
return null;
}


图片处理的调用:

OCImageLoader.loader().loadImage(任务tag, 任务加载网址, imageView对象, new OnImageLoad() {
@Override
public void onLoadCompleted(Bitmap image, String tag) {
//图片加载完成后的操作
... ...
}

@Override
public void onLoadFailed() {
//图片加载失败后的操作
... ...
}
}, new HandleOnLoaded() {
@Override
public Bitmap reduce(Bitmap bitmap, String tag) {

//使用传入的bitmap原图进行处理,Return传出的是处理后的。在子线程中
return Blur.fastblur(UserDetailActivity.this,bitmap,20);
}
});


第三,增加图片异步处理任务

写完了才想起之前写过这个了。。。不管了,这次的解释比上次的更清楚 -_-

private class ReduceImageEffect implements Callable<String>,ThreadinterFace{

private OnHandleBitmap onHandleBitmap;  //处理回调接口
private boolean cacheAsFile;  //是否要本地缓存
private BitmapFactory.Options options;
private Bitmap bitmap;  //事先提供处理对象
private String path , url;  //本地路径或网址
private String cacheTAG;  //图片的唯一标签

public ReduceImageEffect(OnHandleBitmap onHandleBitmap, Bitmap bitmap,BitmapFactory.Options options,boolean cacheAsFile,String path,String url,String id) {
this.onHandleBitmap = onHandleBitmap;
this.options = options;
this.bitmap = bitmap;
this.cacheAsFile = cacheAsFile;
this.url = url;
this.path = path;
this.cacheTAG = id;
}

@Override
public String call() throws Exception {

//如果并没有提供 Bitmap 对象,或者需要带Option读取图片的
if(bitmap == null || options != null){

//我们先尝试从 LRU缓存 内读取之前处理过的图片
//这里要说下,因为TAG对应的是唯一的处理效果的图片,所以并不会提取到其他的资源
bitmap = imageCacher.getByLruCache(cacheTAG);

if (bitmap == null){

//如果LRU内并没有,则尝试从本地缓存文件获取
Log.e("OCImageLoader","No reduced LRUcache.Trying to load reduced File cache...");
bitmap = imageCacher.getByFileCache(cacheTAG);
if (bitmap == null){

//如果还是没有读取到数据,则尝试读取原始数据
Log.e("OCImageLoader","No reduced File cache.Trying to load original File cache...");

String cachePath = null;
if (url != null){
//如果提供了网络地址,则尝试读取该网址对应的本地缓存文件的路径
cachePath = imageCacher.getCacheFile(buildTag(url));
}else if (path != null){

//如果提供了文件路径,则先尝试读取这个文件的本地缓存文件的路径
cachePath = imageCacher.getCacheFile(buildTag(path));
}

if (cachePath == null && path != null && imageCacher.isCanCacheAsFile()){
//如果依旧没有任何本地缓存,同时指定了路径而且应用有本地读取权限

//使用Option(如果option为NULL也可)来读取本地文件
Log.e("OCImageLoader","No original File cache.Trying to load original File by path...");
bitmap = BitmapFactory.decodeFile(path,options);
}else if (cachePath != null){
//如果有缓存的路径

//使用Option(如果option为NULL也可)来读取本地缓存
bitmap = BitmapFactory.decodeFile(cachePath,options);
}
}
}else {
Log.d("OCImageLoader","LRUcache found.");
}
}else{
Log.d("OCImageLoader","Option is NULL , using original cache");
}

if (bitmap != null && onHandleBitmap != null){
//如果提供或者是得到了 Bitmap 对象,并且处理回调接口不为空

//通过回调接口处理对象
bitmap = onHandleBitmap.onAsynHandleBitmap(path, bitmap);
if (bitmap != null){
//若处理后的Bitmap不为空

if (cacheAsFile){
//如果要求本地缓存,则进行缓存
Log.d("OCImageLoader","Tag:"+cacheTAG+" Cached as LRU & File ");
imageCacher.putCache(cacheTAG,bitmap);
}else {
//不然只进行 LRU缓存
Log.d("OCImageLoader","Tag:"+cacheTAG+" Cached as LRU ");
imageCacher.putInLruCaches(cacheTAG,bitmap);
}

//移除线程池里的标签
cacheExecutor.removeTag(cacheTAG);
runOnUIThread(new Runnable() {
@Override
public void run() {
//返回UI线程,传回处理完成的对象
onHandleBitmap.onReduceCompleted(bitmap);
}
});
}else {
//如果处理后的Bitmap为空,则使用读取失败回调接口
Log.e("OCImageLoader","Bitmap become NULL , after onHandleBitmap.");
runOnUIThread(new Runnable() {
@Override
public void run() {
onError(2);
}
});
}
}else {
//如果怎么都读取不到Bitmap对象,则使用读取失败回调接口
Log.e("OCImageLoader","Failed to load bitmap...");
runOnUIThread(new Runnable() {
@Override
public void run() {
onError(0);
}
});
}
return null;
}

@Override
public void onDownloadCompleted(Bitmap bitmap, ImageView imageView, String tag, OnImageLoad onImageLoadCompleted) {

}

@Override
public void onError(int status) {
cacheExecutor.removeTag(cacheTAG);
}

@Override
public int hashCode() {
return cacheTAG.hashCode();
}

@Override
public boolean equals(Object o) {
return this.hashCode() == o.hashCode() && o instanceof ReduceImageEffect;
}

}


第四,之前发现的逻辑错误修正

懵比了 QwQ..之前在图片读取完成之后的逻辑有问题,会导致回调出问题

@Override
public void onDownloadCompleted(Bitmap bitmap, ImageView imageView, String tag, OnImageLoad onImageLoadCompleted) {
//当图片读取完成

//移除线程池内的标签
cacheExecutor.removeTag(tag);

if (bitmap != null && imageView != null && imageView.getTag().equals(tag)){
//如果得到的Bitmap对象不为空,目标ImageView不为空,同时目标ImageView为当初指定的那个

if (durationMillis > 0 ){
//若有淡出淡入的需求则展示出来
Drawable prevDrawable = imageView.getDrawable();
if (prevDrawable == null) {
prevDrawable = new ColorDrawable(TRANSPARENT);
}
Drawable nextDrawable = new BitmapDrawable(imageView.getResources(), bitmap);
TransitionDrawable transitionDrawable = new TransitionDrawable(
new Drawable[] { prevDrawable, nextDrawable });
imageView.setImageDrawable(transitionDrawable);
transitionDrawable.startTransition(durationMillis);
}else {
imageView.setImageBitmap(bitmap);
}
}

if (onImageLoadCompleted != null){
//如果回调接口不为空,则使用
onImageLoadCompleted.onLoadCompleted(bitmap,tag);
}

}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android 图片 缓存 网络