加载Bitmap(ImageLoader)

添加DiskLruCache的依赖:

compile 'com.jakewharton:disklrucache:2.0.2'

图片压缩

package com.example.bitmap_demo;

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

/**
 * <p>文件描述:<p>
 * <p>创建时间:2019/3/10<p>
 */
public class ImageResizer {
    private static final String TAG = "vera ImageResizer";
    public Bitmap decodeSampledBitmapFromResource(Resources res,int resId,int reqWidth,int reqHeight){
//        可以通过BitmapFactory.Options来按照一定的采样率加载缩小后的图片
        final BitmapFactory.Options options=new BitmapFactory.Options();
//        当inJustDecodeBounds设置为true时,BitmapFactory只会解析图片的原始大小,并不会真正加载图片
        options.inJustDecodeBounds=true;
        BitmapFactory.decodeResource(res,resId,options);
//        设置采样率
        options.inSampleSize=calculateInSampleSize(options,reqWidth,reqHeight);
        options.inJustDecodeBounds=false;
        return BitmapFactory.decodeResource(res,resId,options);
    }

    private int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        if(reqWidth==0||reqHeight==0)return 1;
        final int rawHeight=options.outHeight;
        final int rawWidth=options.outWidth;
        Log.e(TAG, "calculateInSampleSize: origin width:"+rawWidth+"origin height:"+rawHeight );
        int inSampleSize=1;

        if(rawHeight>reqHeight||rawWidth>reqWidth){
            final int halfWidth=rawWidth/2;
            final int halfHeight=rawHeight/2;
//            如果改为||的话会导致所缩放后的图片小于ImageView所期望的大小,这样图片会被拉伸导致图片模糊。
            while((halfHeight/inSampleSize)>=reqHeight&&
                    (halfWidth/inSampleSize)>reqWidth){
                inSampleSize*=2;
            }
        }
//        如果图片原始大小本来就小于ImageView期望的大小,返回1.
        Log.e(TAG, "calculateInSampleSize: "+inSampleSize );
        return inSampleSize;

    }
    public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd,int reqWidth,int reqHeight){
        final 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);
    }

}

在ImageLoader的构造函数中初始化LruCache和DiskLruCache:

package com.example.bitmap_demo;

import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.Environment;
import android.os.StatFs;
import android.util.LruCache;

import com.jakewharton.disklrucache.DiskLruCache;

import java.io.File;
import java.io.IOException;

/**
 * <p>文件描述:<p>
 * <p>创建时间:2019/3/10<p>
 */
public class ImageLoader {
    private  boolean mIsDiskLruCacheCreated=false;
    private Context mContext;

    private LruCache<String , Bitmap> mLruCache;//内存缓存

    private DiskLruCache mDiskLruCache;//磁盘缓存

    private static final int DISK_CACHE_SIZE = 50*1024*1024;//50M

    public ImageLoader(Context context) {
        mContext = context;
        int maxMemory= (int) (Runtime.getRuntime().maxMemory()/1024);//单位是kb,当前进程可用内存
        int cacheSize=maxMemory/8;
        mLruCache=new LruCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes()*value.getHeight()/1024;//单位是kb,返回Bitmap对象的大小
            }
        };
        File diskCacheDir=getDiskCacheDir(mContext,"bitmap"); //在sd card中的位置
        if(!diskCacheDir.exists()){
            diskCacheDir.mkdirs();
        }
//        如果磁盘剩余空间小于DISK_CACHE_SIZE无法创建DiskLruCache
        if(getUsableSpace(diskCacheDir)>DISK_CACHE_SIZE){

            try {
                mDiskLruCache=DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
            }
            mIsDiskLruCacheCreated=true;
        }
    }

    private long getUsableSpace(File diskCacheDir) {
        if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.GINGERBREAD){
            return diskCacheDir.getUsableSpace();
        }
        final StatFs statFs=new StatFs(diskCacheDir.getPath());
        return statFs.getBlockSize()*statFs.getAvailableBlocks();
    }

    private File getDiskCacheDir(Context context, String Name) {
        boolean externalStorageAvailable= Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        final String cachePath;
        if(externalStorageAvailable){
            cachePath=context.getExternalCacheDir().getPath();
        }else{
            cachePath=context.getCacheDir().getPath();
        }
        return new File(cachePath+File.separator+Name);
    }


}

内存缓存的添加和获取:

    private void addBitmapToMemoryCache(String key,Bitmap bitmap){
        if(getBitmapFromMemoryCache(key)==null){
            mLruCache.put(key,bitmap);
        }
    }
    private Bitmap getBitmapFromMemoryCache(String key){
        return mLruCache.get(key);
    }

磁盘缓存的添加和获取:

    private Bitmap loadBitmapFromHttp(String url,int reqWidth,int reqHeight) throws IOException {
        if(Looper.myLooper()==Looper.getMainLooper()){
            throw new RuntimeException("can not visit network from ui thread.");
        }
        if(mDiskLruCache==null)return null;
        String key= hashKeyFormUrl(url);
        DiskLruCache.Editor editor=mDiskLruCache.edit(key);
        if (editor!=null){
            OutputStream outputStream=editor.newOutputStream(DISK_CACHE_INDEX);
            if(downloadUrlToStream(url,outputStream)){
                editor.commit();
            }else{
                editor.abort();
            }
            mDiskLruCache.flush();
        }
        return loadBitmapFromDiskCache(url,reqWidth,reqHeight);
    }

    private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) throws IOException {
        if(Looper.myLooper()==Looper.getMainLooper())
            Log.e(TAG, "loadBitmapFromDiskCache: load bitmap from UI thread,it's not recommended");
        if (mDiskLruCache==null)return null;
        Bitmap bitmap=null;
        String key= hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapshot=mDiskLruCache.get(key);
        if(snapshot!=null){
            FileInputStream fileInputStream= (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescripto=fileInputStream.getFD();
            bitmap=mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescripto,reqWidth,reqHeight);
            if (bitmap!=null)addBitmapToMemoryCache(key,bitmap);
        }
        return bitmap;
    }

    //有了文件输出流,图片就可以通过这个文件输出流写入到文件系统上
    private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        HttpURLConnection urlConnection=null;
        BufferedOutputStream out=null;
        BufferedInputStream in=null;
        try {
            final URL url=new URL(urlString);
            urlConnection= (HttpURLConnection) url.openConnection();
            in=new BufferedInputStream(urlConnection.getInputStream(),IO_BUFFER_SIZE);
            out=new BufferedOutputStream(outputStream,IO_BUFFER_SIZE);
            int b;
            while((b=in.read())!=-1)out.write(b);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(urlConnection!=null)urlConnection.disconnect();
            try {
                out.close();
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return false;

    }

    //图片的url可能会有特殊字符
    private String hashKeyFormUrl(String url) {
        String cacheKey;
        try {
            final MessageDigest messageDigest=MessageDigest.getInstance("MD5");
            messageDigest.update(url.getBytes());
            cacheKey=bytesToHexString(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey=String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder stringBuilder=new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex=Integer.toHexString(0xFF&bytes[i]);
            if(hex.length()==1)stringBuilder.append('0');
            stringBuilder.append(hex);
        }
        return stringBuilder.toString();
    }

同步加载和异步加载接口的设计:

    /**
     * 同步加载接口需要外部在线程中调用,因为同步加载可能很耗时
     */
    public Bitmap loadBitmap(String uri,int reqWidth,int reqHeight){
        Bitmap bitmap=loadBitmapFromMemCache(uri);//首先尝试从内存获取图片
        if (bitmap!=null){
            Log.e(TAG, "loadBitmap:loadBitmapFromMemCache,url: "+uri );
            return bitmap;
        }
        try {
            bitmap=loadBitmapFromDiskCache(uri,reqWidth,reqHeight);//再尝试从磁盘获取
            if (bitmap!=null){
                Log.e(TAG, "loadBitmap:loadBitmapFromDiskCache,url: "+uri );
                return bitmap;
            }
            bitmap=loadBitmapFromHttp(uri,reqWidth,reqHeight);//再尝试从网络拉取
            Log.e(TAG, "loadBitmap: loadBitmapFromHttp"+uri );
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (bitmap==null&&!mIsDiskLruCacheCreated){
            Log.e(TAG, "loadBitmap: encounter error,DiskLruCache is not created." );
            bitmap=downloadBitmapFromUrl(uri);
        }
        return bitmap;
    }

    /**
     * 异步加载
     */
    public void bindBitmap(final String uri, final ImageView imageView,final int reqWidth,final int reqHeight){

        imageView.setTag(TAG_KEY_URI,uri);
        Bitmap bitmap=loadBitmapFromMemCache(uri);
        if(bitmap!=null){
            imageView.setImageBitmap(bitmap);
            return;
        }
//        如果没用从内存中获取到图片,就去线程池中调用loadBitmap()方法。
        Runnable loadBitmapTask=new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap1=loadBitmap(uri,reqWidth,reqHeight);
                if (bitmap1!=null){
//                    图片加载成功之后,封装成一个LoadResult对象
                    LoaderResult result=new LoaderResult(imageView,uri,bitmap1);
                    //                    通过Handler向主线程发送消息,在主线程中给imageVIew设置照片
                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT,result).sendToTarget();
                }
            }
        };
        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }

完整的ImageLoader:

package com.example.bitmap_demo;

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.support.annotation.NonNull;
import android.util.Log;
import android.util.LruCache;
import android.widget.ImageView;

import com.jakewharton.disklrucache.DiskLruCache;

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 java.util.function.BiFunction;

/**
 * <p>文件描述:<p>
 * <p>创建时间:2019/3/10<p>
 */
public class ImageLoader {
    private static final String TAG = "vera";
    private static final int IO_BUFFER_SIZE = 8*1024;//8kb
    private static final int TAG_KEY_URI =R.id.imageloader_uri;
    private static final int MESSAGE_POST_RESULT = 1;
    private  boolean mIsDiskLruCacheCreated=false;
    private Context mContext;

    private LruCache<String , Bitmap> mLruCache;//内存缓存

    private DiskLruCache mDiskLruCache;//磁盘缓存

    private ImageResizer mImageResizer=new ImageResizer();

    private static final int DISK_CACHE_SIZE = 50*1024*1024;//50M
    private int DISK_CACHE_INDEX;

    private static final int CPU_COUNT=Runtime.getRuntime().availableProcessors();
    private static final int CORE_POOL_SIZE=CPU_COUNT+1;
    private static final int MAX_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(@NonNull Runnable r) {
            return new Thread(r,"ImageLoader#"+mCount.getAndDecrement());
        }
    };

    public static final Executor THREAD_POOL_EXECUTOR=new ThreadPoolExecutor(
            CORE_POOL_SIZE,MAX_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 imageView=result.imageView;
            String uri= (String) imageView.getTag(TAG_KEY_URI);
            if (uri.equals(result.uri)){
                imageView.setImageBitmap(result.bitmap);
            }else {
                Log.e(TAG, "handleMessage: set image bitmap,but url has changed,ignores!" );
            }
        }
    };
    public ImageLoader(Context context) {
        mContext = context;
        int maxMemory= (int) (Runtime.getRuntime().maxMemory()/1024);//单位是kb,当前进程可用内存
        int cacheSize=maxMemory/8;
        mLruCache=new LruCache<String,Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getRowBytes()*value.getHeight()/1024;//单位是kb,返回Bitmap对象的大小
            }
        };
        File diskCacheDir=getDiskCacheDir(mContext,"bitmap"); //在sd card中的位置
        if(!diskCacheDir.exists()){
            diskCacheDir.mkdirs();
        }
//        如果磁盘剩余空间小于DISK_CACHE_SIZE无法创建DiskLruCache
        if(getUsableSpace(diskCacheDir)>DISK_CACHE_SIZE){

            try {
                mDiskLruCache=DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);
            } catch (IOException e) {
                e.printStackTrace();
            }
            mIsDiskLruCacheCreated=true;
        }
    }

    public static ImageLoader build(Context context){
        return new ImageLoader(context);
    }
    private long getUsableSpace(File diskCacheDir) {
        if(Build.VERSION.SDK_INT>= Build.VERSION_CODES.GINGERBREAD){
            return diskCacheDir.getUsableSpace();
        }
        final StatFs statFs=new StatFs(diskCacheDir.getPath());
        return statFs.getBlockSize()*statFs.getAvailableBlocks();
    }

    private File getDiskCacheDir(Context context, String Name) {
        boolean externalStorageAvailable= Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);
        final String cachePath;
        if(externalStorageAvailable){
            cachePath=context.getExternalCacheDir().getPath();
        }else{
            cachePath=context.getCacheDir().getPath();
        }
        return new File(cachePath+File.separator+Name);
    }

    private void addBitmapToMemoryCache(String key,Bitmap bitmap){
        if(getBitmapFromMemoryCache(key)==null){
            mLruCache.put(key,bitmap);
        }
    }
    private Bitmap getBitmapFromMemoryCache(String key){
        return mLruCache.get(key);
    }

    private Bitmap loadBitmapFromHttp(String url,int reqWidth,int reqHeight) throws IOException {
        if(Looper.myLooper()==Looper.getMainLooper()){
            throw new RuntimeException("can not visit network from ui thread.");
        }
        if(mDiskLruCache==null)return null;
        String key= hashKeyFormUrl(url);
        DiskLruCache.Editor editor=mDiskLruCache.edit(key);
        if (editor!=null){
            OutputStream outputStream=editor.newOutputStream(DISK_CACHE_INDEX);
            if(downloadUrlToStream(url,outputStream)){
                editor.commit();
            }else{
                editor.abort();
            }
            mDiskLruCache.flush();
        }
        return loadBitmapFromDiskCache(url,reqWidth,reqHeight);
    }

    private Bitmap loadBitmapFromDiskCache(String url, int reqWidth, int reqHeight) throws IOException {
        if(Looper.myLooper()==Looper.getMainLooper())
            Log.e(TAG, "loadBitmapFromDiskCache: load bitmap from UI thread,it's not recommended");
        if (mDiskLruCache==null)return null;
        Bitmap bitmap=null;
        String key= hashKeyFormUrl(url);
        DiskLruCache.Snapshot snapshot=mDiskLruCache.get(key);
        if(snapshot!=null){
            FileInputStream fileInputStream= (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescripto=fileInputStream.getFD();
            bitmap=mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescripto,reqWidth,reqHeight);
            if (bitmap!=null)addBitmapToMemoryCache(key,bitmap);
        }
        return bitmap;
    }

    //有了文件输出流,图片就可以通过这个文件输出流写入到文件系统上
    private boolean downloadUrlToStream(String urlString, OutputStream outputStream) {
        HttpURLConnection urlConnection=null;
        BufferedOutputStream out=null;
        BufferedInputStream in=null;
        try {
            final URL url=new URL(urlString);
            urlConnection= (HttpURLConnection) url.openConnection();
            in=new BufferedInputStream(urlConnection.getInputStream(),IO_BUFFER_SIZE);
            out=new BufferedOutputStream(outputStream,IO_BUFFER_SIZE);
            int b;
            while((b=in.read())!=-1)out.write(b);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(urlConnection!=null)urlConnection.disconnect();
            try {
                out.close();
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return false;

    }

    //图片的url可能会有特殊字符
    private String hashKeyFormUrl(String url) {
        String cacheKey;
        try {
            final MessageDigest messageDigest=MessageDigest.getInstance("MD5");
            messageDigest.update(url.getBytes());
            cacheKey=bytesToHexString(messageDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey=String.valueOf(url.hashCode());
        }
        return cacheKey;
    }

    private String bytesToHexString(byte[] bytes) {
        StringBuilder stringBuilder=new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex=Integer.toHexString(0xFF&bytes[i]);
            if(hex.length()==1)stringBuilder.append('0');
            stringBuilder.append(hex);
        }
        return stringBuilder.toString();
    }
    /**
     * 同步加载接口需要外部在线程中调用,因为同步加载可能很耗时
     */
    public Bitmap loadBitmap(String uri,int reqWidth,int reqHeight){
        Bitmap bitmap=loadBitmapFromMemCache(uri);//首先尝试从内存获取图片
        if (bitmap!=null){
            Log.e(TAG, "loadBitmap:loadBitmapFromMemCache,url: "+uri );
            return bitmap;
        }
        try {
            bitmap=loadBitmapFromDiskCache(uri,reqWidth,reqHeight);//再尝试从磁盘获取
            if (bitmap!=null){
                Log.e(TAG, "loadBitmap:loadBitmapFromDiskCache,url: "+uri );
                return bitmap;
            }
            bitmap=loadBitmapFromHttp(uri,reqWidth,reqHeight);//再尝试从网络拉取
            Log.e(TAG, "loadBitmap: loadBitmapFromHttp"+uri );
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (bitmap==null&&!mIsDiskLruCacheCreated){
            Log.e(TAG, "loadBitmap: encounter error,DiskLruCache is not created." );
            bitmap=downloadBitmapFromUrl(uri);
        }
        return bitmap;
    }

    /**
     * 异步加载
     */
    public void bindBitmap(final String uri, final ImageView imageView,final int reqWidth,final int reqHeight){

        imageView.setTag(TAG_KEY_URI,uri);
        Bitmap bitmap=loadBitmapFromMemCache(uri);
        if(bitmap!=null){
            imageView.setImageBitmap(bitmap);
            return;
        }
//        如果没用从内存中获取到图片,就去线程池中调用loadBitmap()方法。
        Runnable loadBitmapTask=new Runnable() {
            @Override
            public void run() {
                Bitmap bitmap1=loadBitmap(uri,reqWidth,reqHeight);
                if (bitmap1!=null){
//                    图片加载成功之后,封装成一个LoadResult对象
                    LoaderResult result=new LoaderResult(imageView,uri,bitmap1);
                    //                    通过Handler向主线程发送消息,在主线程中给imageVIew设置照片
                    mMainHandler.obtainMessage(MESSAGE_POST_RESULT,result).sendToTarget();
                }
            }
        };
        THREAD_POOL_EXECUTOR.execute(loadBitmapTask);
    }
    private Bitmap downloadBitmapFromUrl(String urlString) {
        Bitmap bitmap=null;
        HttpURLConnection urlConnection=null;
        BufferedInputStream in=null;
        try {
            final URL url=new URL(urlString);
            urlConnection= (HttpURLConnection) url.openConnection();
            in=new BufferedInputStream(urlConnection.getInputStream(),IO_BUFFER_SIZE);
            bitmap= BitmapFactory.decodeStream(in);
        } catch (IOException e) {
            Log.e(TAG, "downloadBitmapFromUrl: error " +e);
        }finally {
            if(urlConnection!=null)urlConnection.disconnect();
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
       return bitmap;

    }

    private Bitmap loadBitmapFromMemCache(String url) {
        final String key=hashKeyFormUrl(url);
        Bitmap bitmap=getBitmapFromMemoryCache(key);
        return bitmap;
    }

    private static class LoaderResult {
        public ImageView imageView;
        public String uri;
        public Bitmap bitmap;

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值