一步步撸出图片加载框架

超实用,超轻量的图片框架

Posted by Mio4kon on 2014-12-19

难得空闲,看了鸿洋大神的博客,将代码整合了一下撸出一个简单的图片加载框架,超实用,超轻量,只有2个类(≖ ‿ ≖)✧

参考链接: 鸿洋大神的博客

效果和代码与鸿洋大神博客所写的差不多,主要是加强对这种图片加载框架的理解.

准备工作:图片工具类

压缩图片

/**
 * 计算图片相对于ImageView控件的压缩比例
 *
 * @param options
 * @param reqWidth
 * @param reqHeight
 * @return
 */
public static int caculateZoomSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

    //拿到图片的宽高
    int width = options.outWidth;
    int height = options.outHeight;

    //如果图片比ImageView小,不需要压缩了
    int widthZoomSize = 1;
    int heightZoomSize = 1;
    if (reqWidth > width) {
        widthZoomSize = Math.round (reqWidth * 1.0f / width);
    }

    if (reqHeight > height) {
        heightZoomSize = Math.round (reqHeight * 1.0f / height);
    }
    return Math.max (widthZoomSize, heightZoomSize);

}


/**
 * 压缩图片
 *
 * @param imagePath
 * @param reqWidth
 * @param reqHeight
 * @return
 */
public static Bitmap zoomBitmap(String imagePath, int reqWidth, int reqHeight) {
    BitmapFactory.Options op = new BitmapFactory.Options ();
    op.inJustDecodeBounds = true;
    BitmapFactory.decodeFile (imagePath, op);
    int zoomSize = caculateZoomSize (op, reqWidth, reqHeight);
    Log.d (TAG, "压缩比例:zoomSize:" + zoomSize);
    op.inSampleSize = zoomSize;
    op.inJustDecodeBounds = false;
    return BitmapFactory.decodeFile (imagePath, op);
}

从网络下载图片

 /**
 * 根据url下载图片,自动压缩
 *
 * @param urlStr
 * @param imageview
 * @return
 */
public static Bitmap loadImageFromNet(String urlStr, ImageView imageview) {
    FileOutputStream fos = null;
    InputStream is = null;
    try {
        URL url = new URL (urlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection ();
        is = new BufferedInputStream (conn.getInputStream ());
        is.mark (is.available ());

        BitmapFactory.Options opts = new BitmapFactory.Options ();
        opts.inJustDecodeBounds = true;
        Bitmap bitmap = BitmapFactory.decodeStream (is, null, opts);

        //获取imageview想要显示的宽和高
        ImageSize imageViewSize = getImageViewSize (imageview);
        opts.inSampleSize = caculateZoomSize (opts,
                imageViewSize.width, imageViewSize.height);

        opts.inJustDecodeBounds = false;
        is.reset ();
        bitmap = BitmapFactory.decodeStream (is, null, opts);

        conn.disconnect ();
        return bitmap;

    } 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 null;
}

从网络下载图片缓存在本地

/**
 * @param urlStr 图片url
 * @param file   存储disk address
 * @return
 */
public static boolean loadImageFromNet2Disk(String urlStr, File file) {
    Log.d (TAG, "从网络读取图片到disk,url:" + urlStr);
    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[512];
        int len = 0;
        while ((len = is.read (buf)) != -1) {
            fos.write (buf, 0, len);
        }
        fos.flush ();
        return true;

    } catch (Exception e) {
        Log.e (TAG, "loadImageFromNet2Disk--->error");
        e.printStackTrace ();
    } finally {
        try {
            if (is != null) {
                is.close ();
            }
        } catch (IOException e) {
            Log.e (TAG, "loadImageFromNet2Disk--->IO error");
        }
        try {
            if (fos != null) {
                fos.close ();
            }
        } catch (IOException e) {
            Log.e (TAG, "loadImageFromNet2Disk--->IO error");
        }
    }

    return false;
}

图片加载类:ImageLoader

思路:后台有一个线程维护线程池.线程池中存放着读取图片的任务.一旦增加了一个任务,线程池开始执行该任务(通过信号量控制最新的任务先执行),任务执行完成刷新UI线程ImageView图片.

线程池的任务

先判断本地是否存有图片,如果有直接加在本地图片,否则网络获取.

判断是否开启本地缓存,如果开启,则先将图片下载本地,在读取本地图片,否则直接下载图片

图片加载完发送图片信息到UIHander刷新UI界面.并释放线程池信号量.

/**
 * **创建任务:如果内存有就不会进入到这里**
 *
 * @param path
 * @param imageView
 * @param isFromNet
 * @return
 */
private Runnable buildTask(final String path, final ImageView imageView, final boolean isFromNet) {

    return new Runnable () {
        @Override
        public void run() {
            Bitmap bitmap = null;
            File file = getDiskCacheDir (imageView.getContext (), md5 (path));
            if (file.exists ()) {
                //get image from disk
                bitmap = ImageUtils.loadImageFromDisk (file.getAbsolutePath (), imageView);
            } else {

                if (isDiskCacheEnable) {
                    // load  image from net to disk
                    if (ImageUtils.loadImageFromNet2Disk (path, file)) {
                        Log.d (TAG, " finish loadImageFromNet2Disk ");
                        //get image from disk
                        bitmap = ImageUtils.loadImageFromDisk (file.getAbsolutePath (), imageView);
                    }

                } else {
                    // go directly to get image from net
                    Log.d (TAG, "go directly to get image from net");
                    bitmap = ImageUtils.loadImageFromNet (path, imageView);
                }
            }

            // image to lruCache
            addBitmapToLruCache (path, bitmap);
            refreashBitmap (path, imageView, bitmap);
            mThreadPoolSemaphore.release ();
        }
    };
}

使用框架的主要方法loadImage

包含UIHander 刷新imageView

从内存LruCache中获取图片.如果获取不到添加任务到任务队列

当添加任务给任务队列,此时后台维护线程池的线程就应该从线程池取出任务并执行.

/**
 * 通过Path给ImageView设置方法
 *
 * @param path
 * @param imageView
 * @param isFromNet
 */
public void loadImage(String path, ImageView imageView, boolean isFromNet) {

    imageView.setTag (path);

    if (mUIHandler == null) {
        //refresh UI
        mUIHandler = new Handler () {
            @Override
            public void handleMessage(Message msg) {
                ImageHolder holder = (ImageHolder) msg.obj;
                Bitmap bitmap = holder.bitmap;
                ImageView imageView = holder.imageView;
                String path = holder.path;

                if (imageView.getTag ().toString ().equals (path)) {
                    imageView.setImageBitmap (bitmap);
                }
            }
        };

    }

    Bitmap bitmap = getBitmapFromLruCache (path); //get bitmap from lruCache

    if (bitmap != null) {
        Log.d (TAG, "读取内存缓存中图片.....");
        if (imageView.getTag ().toString ().equals (path)) {
            imageView.setImageBitmap (bitmap);
        }
        return;
    }

    addTask (buildTask (path, imageView, isFromNet)); //get bitmap from Disk/Net
}

添加任务

将任务runnable添加到任务队列

initSemaphore信号量初始化只有一个,此时占坑阻塞.防止mBackThreadHander为空

发送空消息给后台线程,告诉他可以从线程池中去任务执行

private synchronized void addTask(Runnable runnable) {
    mTaskQueue.add (runnable);
    try {
        if (mBackThreadHander == null)
            initSemaphore.acquire (); //阻塞等待initBackThread方法完成
    } catch (InterruptedException e) {
        e.printStackTrace ();
    }
    mBackThreadHander.sendEmptyMessage (0x11111);

}        

后台线程

取任务执行

线程池信号量占一个坑,当坑与线程池数量相同既满了的时候,就阻塞,只有信号量放行了才从任务队列取任务,任务完成释放一个信号量

initSemaphore信号量释放,说明初始化后台线程完成

/**
 * 初始化后台线程,当添加任务后取出任务
 */
private void initBackThread() {

    mBackThread = new Thread () {

        @Override
        public void run() {
            Looper.prepare ();
            //阻塞的,当addTask后才会执行任务
            mBackThreadHander = new Handler () {
                @Override
                public void handleMessage(Message msg) {
                    mThreadPool.execute (getTask ());
                    try {
                        mThreadPoolSemaphore.acquire ();
                    } catch (InterruptedException e) {
                        e.printStackTrace ();
                    }
                    initSemaphore.release ();
                }
            };
            Looper.loop ();
        }
    };
    mBackThread.start ();

}   

LruCache初始化

可运行内存的1/8用于内存缓存

/** 初始化LruCache **/
int maxMemory = (int) Runtime.getRuntime ().maxMemory ();
int cacheMemory = maxMemory / 8;
mLruCache = new LruCache<String, Bitmap> (cacheMemory) {
    @Override
    protected int sizeOf(String key, Bitmap value) {
        return value.getRowBytes () * value.getHeight ();
    }
};

使用方法

imageLoader = ImageLoader.getInstance ();  
imageLoader.loadImage (url,imageView,true);

项目地址: Github