android教你打造独一无二的图片加载框架

前言

首先,最近是在忙okhttp没错。不过或许有人问为什么忙着okhttp怎么又扯到了图片加载上了。其实,最近想实现下断点续传以及多文件下载,但并不知道怎么搞。群里有小伙伴提出了控制线程池来实现。然后我就想到了图片加载需要控制线程池,所以在此巩固下。

概述

好了,进入正题了。优秀的图片加载框架不要太多,什么UIL,Picasso,Glide等等。但我们需要了解其中的原理。所以今天我来介绍下如何自己写一个图片加载框架。有人可能会说,自己写会不会很渣,运行效率,内存溢出神马的。至于这个,等你看完这篇博客自己试试就知道了。

缓存

加载图片肯定是需要用到缓存的,这样可以提高我们的加载效率,而且还可以省流量。所有的图片从缓存读取,保证图片的内存不会超过预期的空间。

压缩

除了缓存,更重要的就是压缩了。不然别人用起来。直接oom,肯定要破口大骂,尼玛炸了!!

加载速度

好了。谈完压缩,缓存,基本没问题了把?不不不,你加载速度不快谁用?加载速度分两种,先进先出和后进先出。如果一个列表有几千甚至上万张图片,我一次性滑倒底,你要是在从第一张开始加载。我估计我一把lol打完你还没加载完把。所以我们选择后进先出,当前呈现给用户的,最新加载;当前未呈现的,选择加载。

小结

关于加载网络图片,其实原理差不多,就多了个是否启用硬盘缓存的选项,如果启用了,加载时,先从内存中查找,然后从硬盘上找,最后去网络下载。下载完成后,别忘了写入硬盘,加入内存缓存。如果没有启用,那么就直接从网络压缩获取,加入内存即可。

效果图

终于扯完了,我们先来看下效果图:

加载本地

这里写图片描述
很流畅对吧,我们在看看网络图片.

加载网络

这里写图片描述
效果还是不错的,之前花了好久找的100个妹纸图。想录长一点的,结果要求必须小于2M,也是醉了。整体效果应该可以看出来。很流畅。perfect~~~

整体实现

整体实现我采用了建造者模式,对建造者模式不了解的。去看android建造者模式

图片压缩

不管是从网络还是本地的图片,加载都需要进行压缩,然后显示。
首先压缩。我们是需要得到ImageView的宽和高,也就是大小。没大小怎么压缩?

//压缩
    protected Bitmap SampledBitmap(String path, Edit edit) {
        // 获得图片的宽和高,并把图片加载到内存中
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(path, options);
        options.inSampleSize = calculateInSampleSize(options, edit.imageView.getWidth(), edit.imageView.getHeight());
//         使用获得到的InSampleSize再次解析图片
        options.inJustDecodeBounds = false;
        Bitmap bitmap = BitmapFactory.decodeFile(path, options);
        if (edit.orientation != 0) {
            int drawablewidth = bitmap.getWidth();
            int drawableheight = bitmap.getHeight();
            Matrix matrix = new Matrix();
            matrix.setRotate(edit.orientation);
            bitmap = Bitmap.createBitmap(bitmap, 0, 0, drawablewidth, drawableheight,
                    matrix, true);
        }
        return bitmap;
    }

我们需要设置恰当的insamplesize。根据需求的宽和高以及图片实际的宽和高计算SampleSize 。


    public int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        //源图片的高度和宽度
        final int height = options.outHeight;//得到要加载的图片高度
        final int width = options.outWidth;
        int inSampleSize = 1;
        if (height > reqHeight || width > reqWidth) {
            //计算出实际宽高和目标宽高的比例
            final int heightRatio = Math.round((float) height / (float) reqHeight);
            final int widthRatio = Math.round((float) width / (float) reqWidth);

            // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高
            // 一定都会大于等于目标的宽和高。
            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;

        }
        return inSampleSize;
    }

上面是进行本地图片的压缩。网络图片就方便了。只要把网络图片下载到sd卡,然后在进行本地压缩。

 public static boolean downloadImgByUrl(String urlStr, File file) {
        boolean isok = false;
        FileOutputStream fos = null;
        InputStream is = null;
        try {
            URL url = new URL(urlStr);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            is = conn.getInputStream();
            fos = new FileOutputStream(file);
            byte[] buf = new byte[1024 * 4];
            int len = 0;
            while ((len = is.read(buf)) != -1) {
                fos.write(buf, 0, len);
            }
            isok = true;

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null)
                    is.close();
            } catch (IOException e) {
            }

            try {
                if (fos != null)
                    fos.close();
            } catch (IOException e) {
            }
        }
        return isok;
    }

到这边图片压缩是结束了,接下来是缓存。

缓存

    // 图片缓存的核心对象
    private LruCache<String, Bitmap> lruCache;
    private Context context;
    //任务队列
    private LinkedList<Runnable> taskQueue = new LinkedList<>();
    //线程池
    private ExecutorService threadPool;
    private Semaphore semaphoreThreadPool;
    private Semaphore semaphorePoolThreadHandler = new Semaphore(0);
    //UI线程
    private Handler uiHandler;
    //后台线程
    private Thread backthread;
    private Handler backthreadhandler;
    //线程数量
    private static final int THREAD_POOL_COUNT = 3;
    private void BackGroundThread() {
        // 后台轮询线程
        backthread = new Thread() {
            public void run() {
                Looper.prepare();
                backthreadhandler = new Handler() {
                    public void handleMessage(Message msg) {
                        // 线程池去取出一个任务进行执行
                        threadPool.execute(taskQueue.removeLast());
                        try {
                            semaphoreThreadPool.acquire();
                        } catch (InterruptedException e) {
                        }
                    }
                };
                semaphorePoolThreadHandler.release();
                Looper.loop();
            }
        };
        backthread.start();
    }

    private void setLruCache() {
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;
        lruCache = new LruCache<String, Bitmap>(cacheSize) {
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getRowBytes() * bitmap.getHeight();
            }
        };
        // 创建线程池
        threadPool = Executors.newFixedThreadPool(THREAD_POOL_COUNT);
        semaphoreThreadPool = new Semaphore(THREAD_POOL_COUNT);
    }

对图片进行完压缩以及缓存后,下面就是加载图片了。

加载图片

我们需要调用一个方法来执行加载图片的操作:

 public void loadImage(Edit edit) {
        edit.imageView.setTag(edit.loadUrl);
        edit.imageView.setImageResource(edit.load_pic);
        if (uiHandler == null) {
            uiHandler = new Handler() {
                public void handleMessage(Message msg) {
                    myImageBean myImageBean = (myImageBean) msg.obj;
                    Bitmap bm = myImageBean.bitmap;
                    ImageView imageview = myImageBean.imageView;
                    String path = myImageBean.path;
                    if (imageview.getTag().toString().equals(path)) {
                        imageview.setImageBitmap(bm);
                    }
                }
            };
        }
        // 根据path在缓存中获取bitmap
        Bitmap bitmap = getBitmapFromLruCache(edit.loadUrl);
        if (bitmap != null) {
            refreashBitmap(edit, bitmap);
        } else {
            addTask(buildTask(edit));
        }
    }

我们需要得到他的缓存,所以:

    private Bitmap getBitmapFromLruCache(String key) {
        return lruCache.get(key);
    }

如果缓存存在的话,那么我们需要刷新bitmap。代表这个图片已经完成加载了。

  private void refreashBitmap(Edit edit, Bitmap bitmap) {
        Message message = Message.obtain();
        myImageBean holder = new myImageBean();
        holder.bitmap = bitmap;
        holder.path = edit.loadUrl;
        holder.imageView = edit.imageView;
        message.obj = holder;
        uiHandler.sendMessage(message);
    }

否则,我们需要去把他添加到lurcache中,以便之后快捷的加载。


    private void addTask(Runnable runnable) {
        taskQueue.add(runnable);
        try {
            if (backthreadhandler == null)
                semaphorePoolThreadHandler.acquire();
        } catch (InterruptedException e) {
        }
        backthreadhandler.sendEmptyMessage(0x250);
    }

    private Runnable buildTask(final Edit edit) {
        return new Runnable() {
            public void run() {
                Bitmap bitmap = null;
                if (edit.isNet) {
                    File file = getDiskCacheDir(context, MD5Utils.hashKeyFromUrl(edit.loadUrl));
                    if (file.exists()) {
                        bitmap = loadImageFromLocal(file.getAbsolutePath(), edit);
                    } else {
                        boolean success = downloadImgByUrl(edit.loadUrl, file);
                        if (success) {
                            bitmap = loadImageFromLocal(file.getAbsolutePath(), edit);
                        }
                    }
                } else {
                    bitmap = loadImageFromLocal(edit.loadUrl, edit);
                }
                if (lruCache.get(edit.loadUrl) == null) {
                    if (bitmap != null)
                        lruCache.put(edit.loadUrl, bitmap);
                }
                addBitmapToLruCache(edit.loadUrl, bitmap);
                refreashBitmap(edit, bitmap);
                semaphoreThreadPool.release();
            }

        };
    }

下载到本地之后,其实也是执行一个压缩操作,代码很简单:

  private Bitmap loadImageFromLocal(String path, Edit edit) {
        Bitmap bm;
        bm = SampledBitmap(path, edit);
        return bm;
    }

到此,我们的代码应该分析完了,不过好像还缺少了什么,对了。Edit这个。到底做了什么处理呢。



    public class Edit {
        private boolean isNet = true;
        private int orientation = 0;
        private String loadUrl;
        private ImageView imageView;
        private int load_pic=R.drawable.ic_loading;

        public Edit ic_loading(int load_pic) {
            this.load_pic = load_pic;
            return this;
        }

        public Edit into(ImageView imageView) {
            this.imageView = imageView;
            return this;
        }

        public Edit setUrl(String loadUrl) {
            this.loadUrl = loadUrl;
            return this;
        }

        public Edit isNet(boolean net) {
            isNet = net;
            return this;
        }

        public void load() {
            loadImage(this);
        }

        public Edit setOrientation(int orientation) {
            this.orientation = orientation;
            return this;
        }
    }

一个个介绍。isnet代表是否从网络加载,默认是从网络加载,至于orientation是什么呢,这个是图片的旋转角度,在座的小伙伴如果有三星手机的,你打开boss直聘选择头像,然后读取本地图片会发现,一些图片是经过旋转的,其实这是三星手机的一个bug。当然我们也知道了。他的图片加载其实也是自己写的,并没有用三方的jar。其他属性应该不用介绍了把。

参考

洪洋:http://blog.csdn.net/lmj623565791/article/details/41874561

总结

图片加载主要考虑的就是压缩,缓存,加载速度以及线程池(一次加载几张)。只要掌握好这些,你也可以写一个图片加载框架。
对于开发最常用的就是,网络加载,图片加载以及布局的刷新加载。现在就剩对okhttp的深入了解了。后续我会更上。我们需要知道其中的原理来解决这些问题,虽然网上都有现成的。但我们如果不分析直接拿来用的话,对自己水平提高的实在是太少了。今天就这样吧~~~

评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值