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

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

一,使任务可取消移除

第一步我们使用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,网络,图片,缓存)