读《Android开发艺术探索》----ImageLoader的实现

bitmap的高效加载和cache存储的策略

1.1 bitmap的高效加载。
----|bitmap的加载图片的四种方式,decodeFile、decodeResource、decodeStream
     decodeByteArray.
----|使用bitmapFactory.options方法进行图片的缩放。
      图片缩放的策略是 采用inSampleSize(采样率)来设置缩放的比例大小。

通过这种方式来高效的加载图片,并不是直接加载原图

package lzl.edu.com.bitmapcachedemo.util;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import java.io.FileDescriptor;

/**
 * Created by admin on 2016/1/6.
 * 图片的高效加载。分别是从资源文件中加载和本地文件中加载
 */
public class ImageResizer {
    /**
     * 从资源文件中加载图片。
     * @param res
     * @param resId
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeSampleBitmapFromResource(Resources res,int resId,int reqWidth,int reqHeight){

        BitmapFactory.Options options = new BitmapFactory.Options();
        //第一个options设置为true,去加载图片
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res,resId,options);
        //计算采样率
        options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res,resId,options);
    }

    /**
     * 从内存卡中加载图片
     * @param fd
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public static Bitmap decodeSampleBitmapFromFile(FileDescriptor fd,int reqWidth,int reqHeight){
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFileDescriptor(fd,null,options);
        options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeFileDescriptor(fd,null,options);
    }
    /**
     *计算图片的采样率
     * 原理,如果设置的图片宽、高小于原图的宽、高。则inSampleSize呈2的指数缩小
     * @param options
     * @param reqWidth
     * @param reqHeight
     * @return
     */

    private static int calculateInSampleSize(BitmapFactory.Options options,int reqWidth,int reqHeight){
        if(reqHeight==0 || reqWidth ==0){
            return 1;
        }
        //默认的采样率
        int inSampleSize = 1;
        //获取原图的宽高
        int width = options.outWidth;
        int height = options.outHeight;

        if(width > reqWidth || height > reqHeight){
            int halfWidth = width / 2;
            int halfHeight = height / 2;
            while((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth){
                inSampleSize *= 2;
            }
        }
        return inSampleSize;
    }
}

ImageLoader的实现

package lzl.edu.com.bitmapcachedemo.util;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Build;
import android.os.Environment;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.StatFs;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.Executor;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import lzl.edu.com.bitmapcachedemo.R;

/**
 * Created by admin on 2016/1/6.
 * 图片的高效缓存类。
 * 主要通过LruCache内存缓存和DiskLruCache磁盘缓存来工作。
 * 该类的实现思想:
  通过loadBitmap方法来分别从缓存、磁盘和网络中加载图片(这里是一层一层来判断的,
 * 如果缓存中有这个图片程序变不再向下执行)。
 * 步骤:
 * 1、ImageLoader构造方法,初始化LruCache和DiskLruCache类。
 * 2、分别实现LruCache和DiskLruCache的增加、删除、查找方法。
   3、通过LoadBitmap方法分别从缓存中加载bitmap、磁盘中加载图片、网络中加载图片。
 */
public class ImageLoader {
    private static final String TAG = "ImageLoader";
    private LruCache<String,Bitmap> mMemoryCache;
    private DiskLruCache mDiskLruCache;
    private Context mContext;
    private boolean mIsDiskLruCacheCreated = false;
    private static final int TAG_KEY_URI = R.id.mImageView;
    private static final int IO_BUFFER_SIZE = 8*1024;
    private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50;
    private static final int DISK_CACHE_INDEX = 0;
    private static final int MESSAGE_POST_result = 1;

    //cpu核心数
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
    //cpu核心线程数
    private static final int CORE_POOL_SIZE = CPU_COUNT +1;
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT *2+1;
    //线程超时时长
    private static final long KEEP_ALIVE = 10l;

    private static final ThreadFactory sThreadFactory = new ThreadFactory() {
        private final AtomicInteger mCount = new AtomicInteger(1);

        @Override
        public Thread newThread(Runnable r) {
            return new Thread(r,"imageLoader#"+mCount.getAndIncrement());
        }
    };
    public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(
            CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE, TimeUnit.SECONDS,
            new LinkedBlockingDeque<Runnable>(),sThreadFactory);
    private Handler mMainHandler = new Handler(Looper.getMainLooper()){
        @Override
        public void handleMessage(Message msg) {
            LoaderResult result = (LoaderResult) msg.obj;
            ImageView iv = result.imageView;
            Bitmap bitmap = result.bitmap;
            String getUrl = (String) iv.getTag(TAG_KEY_URI);
            if(getUrl.equals(result.url)){
                iv.setImageBitmap(bitmap);
            }else{
                Log.i(TAG,"set image bitmap,but url has changed");
            }
        }
    };
    //无参构造方法,来初始化LruCache|DiskLruCache
    public ImageLoader(Context context){
        mContext = context.getApplicationContext();
        initLruCache();
        initDiskLruCache();
    }

    /**
     * 初始化LruCache
     */
    private void initLruCache(){
        //计算缓存的大小
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        //设置缓存大小为当前进程可用内存的1/8
        int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes() * value.getHeight() / 1024;
            }
        };
    }

    /**
     * 初始化DiskLruCache
     */
    private void initDiskLruCache(){
        File diskCacheDir = getDiskCacheDir(mContext,"bitmap");
        if(!diskCacheDir.exists()){ //如果不存在你们 创建目录
            diskCacheDir.mkdir();
        }
        if(getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) {
            try {
                mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    public void bindBitmap(String url,ImageView imageView){
        bindBitmap(url,imageView,0,0);
    }
    /**
     * 通过异步方式加载bitmap。
     * 步骤,先从缓存中加载bitmap,如果存在就直接返回,并设置图片。
     * 不存在,就会在线程池中调用loadBitmap方法,来更新UI
     * @param url
     * @param imageView
     * @param reqWidth
     * @param reqHeight
     */
    public void bindBitmap(final String url, final ImageView imageView,final int reqWidth,
                           final int reqHeight){
        imageView.setTag(TAG_KEY_URI,url);
        Bitmap bitmap = loadBitmapFromMemCache(url);
        if(bitmap!=null){
            imageView.setImageBitmap(bitmap);
            return;
        }
        //缓存中不存在bitmap图片
        Runnable loadBitmapTask = new Runnable() {
            @Override
            public void run() {
                //线程池中加载bitmap,调用loadBitmap()方法
                Bitmap bitmap = loadBitmap(url,reqWidth,reqHeight);
                if(bitmap!=null){
                    LoaderResult result = new LoaderResult(imageView,url,bitmap);
                    mMainHandler.obtainMessage(MESSAGE_POST_result,result).sendToTarget();
                }
            }
        };
        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }
    /**
     * 获取磁盘中可用的空间
     * @param path
     * @return
     */
    private long getUsableSpace(File path) {
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD){
            return path.getUsableSpace();
        }
        final StatFs stats = new StatFs(path.getPath());
        return stats.getBlockSize()*stats.getAvailableBlocks();
    }

    /**
     * 同步加载网络图片
     * 加载图片的顺序:
     * 缓存中加载图片--->磁盘中加载图片--->网络中加载图片
     * @param url
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    public Bitmap loadBitmap(String url,int reqWidth,int reqHeight){
        //从缓冲中加载图片
        Bitmap bitmap = loadBitmapFromMemCache(url);
        if(bitmap!=null){
            return bitmap;
        }
        try {
            //从磁盘中加载bitmap
            bitmap = loadBitmapFromDiskCache(url,reqWidth,reqHeight);
            if(bitmap !=null){
                return bitmap;
            }
            //从网络中加载bitmap
            bitmap = loadBitmapFromHttp(url, reqWidth, reqHeight);
        } catch (IOException e) {
            e.printStackTrace();
        }
        if(bitmap == null && !mIsDiskLruCacheCreated){
        //如果bitmap为空,并且,磁盘中没有缓存bitmap。那么通过url路径来获取bitmap
            bitmap = downloadBitmapFromUrl(url);
        }
        return bitmap;
    }

    /**
     * 通过url路径加载网络图片,
     * 获取bitmap。
     * @param urlString
     * @return
     */
    private Bitmap downloadBitmapFromUrl(String urlString) {
        Bitmap bitmap = null;
        HttpURLConnection conn = null;
        BufferedInputStream bis = null;
        try {
            URL url = new URL(urlString);
            conn = (HttpURLConnection) url.openConnection();
            bis = new BufferedInputStream(conn.getInputStream(),IO_BUFFER_SIZE);
            bitmap = BitmapFactory.decodeStream(bis);
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if(conn!=null)
                conn.disconnect();
            if(bis!=null){
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return bitmap;
    }

    /**
     * 从缓存中加载bitmap
     * @param url
     * @return
     */
    private Bitmap loadBitmapFromMemCache(String url) {
        String key = hashKeyFromUrl(url);
        Log.i(TAG,"从缓存中加载数据");
        return getBitmapFromMemoryCache(key);
    }

    /**
     * LruCache的添加方法
     * 存储到缓存中。
     * @param key
     * @param bitmap
     */
    private void addBitmapToMemoryCache(String key,Bitmap bitmap){
        if(getBitmapFromMemoryCache(key)!=null)
            mMemoryCache.put(key,bitmap);
    }

    /**
     * LruCache的查找方法
     * 从缓冲中查询
     * @param key
     * @return
     */
    private Bitmap getBitmapFromMemoryCache(String key){
        return mMemoryCache.get(key);
    }

    /**
     * 获取磁盘的存储目录,并创建目录
     * @param mContext
     * @param bitmap
     * @return
     */
    private File getDiskCacheDir(Context mContext, String bitmap) {
        boolean externalStorageAvailable = Environment.getExternalStorageState().
                equals(Environment.MEDIA_MOUNTED);
        final String cachePath;
        if(externalStorageAvailable){
            cachePath = mContext.getExternalCacheDir().getPath();
        }else{
            cachePath = mContext.getCacheDir().getPath();
        }
        return new File(cachePath + File.separator+ bitmap);
    }

    /**
     * DiskLruCache添加方法(存储)
     * 将图片写入到本地文件中。
     * @param urlString
     * @param outputStream
     * @return
     */
    private boolean downloadUrlToStream(String urlString, OutputStream outputStream){
        HttpURLConnection conn = null;
        BufferedInputStream in = null;
        BufferedOutputStream out = null;
        try {
            URL url = new URL(urlString);
            conn = (HttpURLConnection) url.openConnection();
            in = new BufferedInputStream(conn.getInputStream(),IO_BUFFER_SIZE);
            out = new BufferedOutputStream(outputStream,IO_BUFFER_SIZE);
            int b;
            while((b = in.read())!=-1){
                out.write(b);
            }
            return true;
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if(conn !=null)
                conn.disconnect();
            if(in!=null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out!=null){
                try {
                    out.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return false;
    }

    /**
     * 从网络上加载图片
     * @param urlString
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    private Bitmap loadBitmapFromHttp(String urlString,int reqWidth,int reqHeight) throws IOException {
        Log.i(TAG,"从网络中加载数据");

        if(Looper.myLooper() == Looper.getMainLooper()){
            throw new RuntimeException("不能从主线程中访问网络图片");
        }
        if(mDiskLruCache == null){
            return null;
        }
        //将url转换成key
        String key = hashKeyFromUrl(urlString);
        DiskLruCache.Editor editor = mDiskLruCache.edit(key);
        if(editor!=null){
            OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX);
            if(downloadUrlToStream(urlString,outputStream)){ //存储到本地文件夹中
                editor.commit();
            }else{
                editor.abort();
            }
        }
        mDiskLruCache.flush();
        return loadBitmapFromDiskCache(urlString,reqWidth,reqHeight);
    }

    /**
     * 将url转换成String类型的key值
     * 这里采用url的md5值作为key值
     * @param url
     * @return
     */
    private String hashKeyFromUrl(String url){
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(url.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
           cacheKey = String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    /**
     * 具体将url转换成MD5值的方式。
     * @param digest
     * @return
     */
    private String bytesToHexString(byte[] digest) {
        StringBuilder sb = new StringBuilder();
        for(int i=0;i<digest.length;i++){
            String hex = Integer.toHexString(0xFF & digest[i]);
            if(hex.length() == 1){
                sb.append("0");
            }
            sb.append(hex);
        }
        return sb.toString();
    }

    /**
     * DiskLruCache是查询方法
     * 从磁盘中获取Bitmap
     * @param urlString
     * @param reqWidth
     * @param reqHeight
     * @return
     */
    private Bitmap loadBitmapFromDiskCache(String urlString, int reqWidth, int reqHeight) throws IOException {
        Log.i(TAG,"从磁盘中加载数据");
        if(Looper.myLooper() == Looper.getMainLooper()){
            throw new RuntimeException("不能从主线程中加载图片");
        }
        if(mDiskLruCache == null){
            return null;
        }
        Bitmap bitmap = null;
        String key = hashKeyFromUrl(urlString);
        //通过key值获取磁盘中的输入流
        DiskLruCache.Snapshot snapshot = mDiskLruCache.get(key);
        if(snapshot !=null){
            FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor descriptor = fileInputStream.getFD();
            //通过写的一个ImageResizer高效加载图片
            bitmap = ImageResizer.decodeSampleBitmapFromFile(descriptor,reqWidth,reqHeight);
            if(bitmap !=null){
                //向缓存中加入图片
                addBitmapToMemoryCache(key,bitmap);
            }
        }
        return bitmap;
    }

    /**
     * 静态方法创建ImageLoader
     * @param context
     * @return
     */
    public static ImageLoader bindBitmap(Context context){
        return new ImageLoader(context);
    }

    private class LoaderResult {
        public ImageView imageView;
        public String url;
        public Bitmap bitmap;
        public LoaderResult(ImageView imageView, String url, Bitmap bitmap) {
            this.bitmap = bitmap;
            this.imageView = imageView;
            this.url = url;
        }
    }
}

这里附上ImageLoader的代码实现和例子点击打开链接


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值