美文网首页
Android图片加载框架之PictureCache

Android图片加载框架之PictureCache

作者: beizhi | 来源:发表于2019-11-25 14:13 被阅读0次

    本框架目前还在迭代阶段,并不适合于直接用于项目,只是我用来学习的小东西,源码地址:https://github.com/xiangmingzhe/PictureCache
     在Android开发中,如果图片过多,而我们又没有对图片进行有效的缓存,就很容易导致OOM(Out Of Memory)错误。因此,图片的缓存是非常重要的,尤其是对图片非常多的应用。现在很多框架都做了很好的图片缓存处理,如【Fresco】【Glide】等。

    1.说一下三级缓存的流程:

    当我们的APP中想要加载某张图片时,先去LruCache中寻找图片,如果LruCache中有,则直接取出来使用,如果LruCache中没有,则去SoftReference中寻找,如果SoftReference中有,则从SoftReference中取出图片使用,同时将图片重新放回到LruCache中,如果SoftReference中也没有图片,则去文件系统中寻找,如果有则取出来使用,同时将图片添加到LruCache中,如果没有,则连接网络从网上下载图片。图片下载完成后,将图片保存到文件系统中,然后放到LruCache中。

    2.实现:

    (1)网络访问工具类NetCache:

    package com.picture.lib_rhythm.cache;
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.graphics.drawable.Drawable;
    import android.os.AsyncTask;
    import android.os.Handler;
    import android.os.Message;
    import android.text.TextUtils;
    import android.util.Log;
    import android.widget.ImageView;
    
    import com.picture.lib_rhythm.R;
    import com.picture.lib_rhythm.RequestCreator;
    import com.picture.lib_rhythm.bean.TagInfo;
    import com.picture.lib_rhythm.utils.BitmapUtils;
    import com.picture.lib_rhythm.widgets.gif.GifImageView;
    
    import java.io.InputStream;
    import java.net.HttpURLConnection;
    import java.net.URL;
    import java.util.Iterator;
    import java.util.Map;
    
    /**
     * Time:2019/11/7
     * Author:xmz-dell
     * Description:网络缓存类
     */
    public class NetCache {
        private LruCache lruCache;
        private LocalCache localCache;
        private static final String TAG="NetCache";
        private Drawable errorDrawable;
        private Context mContext;
        private float radius=0f;
        public BitmapTask bitmapTask;
        public NetCache(Cache lruCache,LocalCache localCache){
            this.lruCache=(LruCache) lruCache;
            this.localCache=localCache;
        }
        Handler handler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                Object[] objects = (Object[]) msg.obj;
                ImageView imageView = (ImageView) objects[0];
                String mUrl = (String) objects[1];
                imageView.setTag(mUrl);
                setErrorView(objects,imageView);
            }
        };
    
        /**
         * 错误视图
         * @param errorDrawable
         * @return
         */
        public NetCache error(Drawable errorDrawable){
            this.errorDrawable=errorDrawable;
            return this;
        }
        /**
         * 设置圆角
         * @param radius 弧度
         * @return
         */
        public NetCache transform(float radius) {
            this.radius=radius;
            return this;
        }
    
        /**
         * 是否需要设置圆角
         * @return
         */
        private boolean isRoundCorner(){
          return radius!=0f?true:false;
        }
        /**
         * 设置错误视图
         * @param objects
         * @param imageView
         */
        private void setErrorView(Object[] objects,ImageView imageView){
            if(errorDrawable!=null){
                boolean isSuccess=(boolean) objects[2];
                if(!isSuccess){
                    if(isRoundCorner()){
                        Bitmap bitmap=BitmapUtils.toRoundCorner(errorDrawable,radius,0);
                        imageView.setImageBitmap(bitmap);
                    }else{
                        imageView.setImageDrawable(errorDrawable);
                    }
                }
            }
        }
        /**
         * 加载图片
         * @param iv
         * @param url
         */
        public void loadBitmap(final ImageView iv, final String url, Context context){
            if(iv==null||TextUtils.isEmpty(url)||context==null){
                throw new NullPointerException("ImageView Can not be empty || url Can not be empty || context Can not be empty");
            }
            this.mContext=context;
            new Handler().post(new Runnable() {
                @Override
                public void run() {
                    Bitmap bitmap=loadBitmap(url);
                    if(bitmap!=null){
                        sendBitmap(bitmap);
                    }else{
                        bitmapTask=new BitmapTask();
                        pushTaskToMap(url,bitmapTask);
                        bitmapTask.execute(iv, url);// 启动AsyncTask,
                    }
                }
            });
        }
    
        /**
         * 将所有异步任务存储
         * @param url
         * @param bitmapTask
         */
        private void pushTaskToMap(String url,BitmapTask bitmapTask){
            RequestCreator.getInstance().taskMap.put(url,bitmapTask);
        }
        /**
         * 取消单个请求
         */
        public void cancleTask(String tag){
            Map<String,NetCache.BitmapTask>taskMap= RequestCreator.getInstance().taskMap;
            Iterator<String> iterator =taskMap.keySet().iterator();// map中key(键)的迭代器对象
            while (iterator.hasNext()){// 循环取键值进行判断
                String key = iterator.next();// 键
                if(tag.equals(key)){
                    BitmapTask bitmapTask = taskMap.get(key);
                    if(bitmapTask!=null){
                        bitmapTask.cancel(true);
                        iterator.remove();
                        break;
                    }
                }
            }
        }
    
        /**
         * 取消全部请求
         */
        public void cancleAllTask(){
            Map<String,NetCache.BitmapTask>taskMap= RequestCreator.getInstance().taskMap;
            Iterator<String> iterator =taskMap.keySet().iterator();// map中key(键)的迭代器对象
            while (iterator.hasNext()){// 循环取键值进行判断
                String key = iterator.next();// 键
                    BitmapTask bitmapTask = taskMap.get(key);
                    if(bitmapTask!=null){
                        bitmapTask.cancel(true);
                        iterator.remove();
                    }
    
            }
        }
    
        /**
         * 尝试下加载内存-->磁盘
         * @param url
         * @return
         */
        private Bitmap loadBitmap(String url){
            if(lruCache.get(url)!=null){
                    return lruCache.get(url);
            }
            if(localCache.getBitmapFromLocal(url)!=null){
                return localCache.getBitmapFromLocal(url);
            }
            return null;
        }
    
    
        /**
         * Handler和线程池的封装
         * <p/>
         * 第一个泛型: 参数类型
         * 第二个泛型: 更新进度的泛型,
         * 第三个泛型是onPostExecute的返回结果
         */
        public class BitmapTask extends AsyncTask<Object, Void, Bitmap> {
    
            private ImageView ivPicture;
            private String url;
            private Object[] objects;
            private Message message;
    
            /**
             * 后台耗时方法在此执行, 子线程
             */
            @Override
            protected Bitmap doInBackground(Object... params) {
                ivPicture = (ImageView) params[0];
                url = (String) params[1];
                message = handler.obtainMessage();
                objects = new Object[]{ivPicture, url,true
                };
                message.obj = objects;
                handler.sendMessage(message);
                return downloadBitmap(url,ivPicture,objects,message);
            }
    
            /**
             * 更新进度, 主线程
             */
            @Override
            protected void onProgressUpdate(Void... values) {
                super.onProgressUpdate(values);
            }
    
            /**
             * 耗时方法结束后,执行该方法, 主线程
             */
            @Override
            protected void onPostExecute(Bitmap result) {
                if (result != null) {
                    String bindUrl = (String) ivPicture.getTag();
                    if (url.equals(bindUrl)) {// 确保图片设定给了正确的imageview
                        sendBitmap(result);
                        localCache.setBitmapToLocal(url, result);// 将图片保存在本地
                        lruCache.set(url, result);// 将图片保存在内存
                        Log.d(TAG,"从网络缓存读取图片啦");
                    }
                }else{
                    Log.d(TAG,"图片网络加载失败");
                    message = handler.obtainMessage();
                    objects = new Object[]{ivPicture, url,false
                    };
                    message.obj = objects;
                    handler.sendMessage(message);
    
                }
            }
        }
    
        /**
         * 下载图片
         *
         * @param url
         * @return
         */
        private Bitmap downloadBitmap(String url,ImageView iv,Object[]objects,Message message) {
    
            HttpURLConnection conn = null;
            try {
                conn = (HttpURLConnection) new URL(url).openConnection();
                conn.setConnectTimeout(5000);
                conn.setReadTimeout(5000);
                conn.setRequestMethod("GET");
                conn.connect();
    
                int responseCode = conn.getResponseCode();
                if (responseCode == 200) {
                    InputStream inputStream = conn.getInputStream();
    
                    //图片压缩处理
                    BitmapFactory.Options option = new BitmapFactory.Options();
                    // option.inSampleSize = 2;//宽高都压缩为原来的二分之一, 此参数需要根据图片要展示的大小来确定
                    option.inPreferredConfig = Bitmap.Config.RGB_565;//设置图片格式
    
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, option);
                    return bitmap;
                }
    
            } catch (Exception e) {
                Log.d(TAG,"图片加载失败,准备加载错误视图");
                message = handler.obtainMessage();
                objects = new Object[]{iv, url,false
                };
                message.obj = objects;
                handler.sendMessage(message);
                e.printStackTrace();
    
            } finally {
                if (conn != null) {
                    conn.disconnect();
                }
            }
    
            return null;
        }
    
        /**
         * 发送一个bitmap给上游
         * 下一个版本要替换成自定义rxandroid来实现。
         *
         */
        private void sendBitmap(Bitmap bitmap){
            if(onLoadSuccessListener!=null){
                onLoadSuccessListener.loadBitmapSuccess(bitmap);
            }
        }
        public OnLoadSuccessListener onLoadSuccessListener;
        public NetCache setOnLoadSuccessListener(OnLoadSuccessListener onLoadSuccessListener) {
            this.onLoadSuccessListener = onLoadSuccessListener;
            return this;
        }
    
        public interface OnLoadSuccessListener{
            void loadBitmapSuccess(Bitmap bitmap);
        }
    }
    
    
    

    (2)本地文件访问类LocalCache :

    package com.picture.lib_rhythm.cache;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.graphics.BitmapFactory;
    import android.os.Environment;
    import android.text.TextUtils;
    import android.util.Log;
    
    import com.picture.lib_rhythm.utils.Utils;
    
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    
    /**
     * Time:2019/11/7
     * Author:xmz-dell
     * Description:本地文件
     * */
    public class LocalCache {
        private String cachePath;
    
        public LocalCache(Context context, String uniqueName) {
            if(TextUtils.isEmpty(uniqueName)){
                throw new NullPointerException("uniqueName Can not be empty");
            }
            cachePath = getCacheDirString(context, uniqueName);
        }
    
        /**
         * 根据url获取bitmap
         * @param url
         * @return
         */
        public Bitmap getBitmapFromLocal(String url){
            try{
                File file = new File(cachePath, encode(url));
                if (file.exists()) {
                    // 如果文件存在
                    Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
                    return bitmap;
                }
            }catch (Exception e){
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 获取缓存目录的路径
         * @param context
         * @param uniqueName
         * @return
         */
        private String getCacheDirString(Context context, String uniqueName) {
            File file = null;
            if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())
                    || !Environment.isExternalStorageRemovable()) {
                file = new File(context.getExternalCacheDir(), uniqueName);
            } else {
                file = new File(context.getCacheDir(), uniqueName);
            }
            if (!file.exists()) {
                file.mkdirs();
            }
            return file.getAbsolutePath();
        }
    
        /**
         * 设置Bitmap数据到本地
         *
         * @param url
         * @param bitmap
         */
        public void setBitmapToLocal(String url, Bitmap bitmap) {
            if(TextUtils.isEmpty(url)||bitmap==null){
                throw new NullPointerException("url Can not be empty || bitmap Can not be empty");
            }
            if(Utils.calculateSdCardCacheSize()<Utils.getBitmapKB(bitmap)){
                throw new IllegalArgumentException("sdcard Insufficient space left");
            }
            FileOutputStream fos = null;
            try {
                String fileName = encode(url);
                File file = new File(cachePath, fileName);
                File parentFile = file.getParentFile();//获取上级所有目录
                if (!parentFile.exists()) {
                    // 如果文件不存在,则创建文件夹
                    parentFile.mkdirs();
                }
                Log.d("保存的地址:","file.getAbsolutePath():"+file.getAbsolutePath());
                fos = new FileOutputStream(file);
                // 将图片压缩到本地
                bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
            } catch (Exception e) {
                e.printStackTrace();
            }finally {
                if (fos != null) {
                    try {
                        fos.close();//关闭流
                        fos = null;
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
        }
    
    
        /***
         * md5 加密
         * @param pwd
         * @return
         */
        public  String encode(String pwd) {
            StringBuffer sb = new StringBuffer();
            try {
                MessageDigest digest = MessageDigest.getInstance("MD5");
                byte[] bytes = digest.digest(pwd.getBytes("UTF-8"));
                for (int i = 0; i < bytes.length; i++) {
                    String s = Integer.toHexString(0xff & bytes[i]);
    
                    if (s.length() == 1) {
                        sb.append("0" + s);
                    } else {
                        sb.append(s);
                    }
                }
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return sb.toString();
        }
    
    
    }
    
    

    (3)内存工具类LruCache :

    package com.picture.lib_rhythm.cache;
    
    import android.content.Context;
    import android.graphics.Bitmap;
    import android.text.TextUtils;
    
    import com.picture.lib_rhythm.utils.Utils;
    
    import java.util.LinkedHashMap;
    import java.util.Map;
    
    /**
     * Time:2019/11/7
     * Author:xmz-dell
     * Description:
     */
    public class LruCache implements Cache{
        private int size;
        private int maxSize;
        private int cacheCount; //缓存计数
        private int evictionCount;//清除计数
        private final LinkedHashMap<String, Bitmap> map; 
    
        public LruCache(Context context){
            this(Utils.calculateMemoryCacheSize(context));
        }
        public LruCache(int maxSize){
            if(maxSize<=0){
                throw new IllegalArgumentException("Max size Cannot be less than or equal to 0");
            }
            this.maxSize=maxSize;
            this.map = new LinkedHashMap<String, Bitmap>(0, 0.75f, true);
        }
    
        @Override
        public Bitmap get(String key) {
            if(TextUtils.isEmpty(key)){
                throw new NullPointerException("key Can not be empty");
            }
            synchronized (this){
                Bitmap bitmap;
                bitmap=map.get(key);
                if(bitmap!=null){
                    cacheCount++;
                    return bitmap;
                }
            }
            return null;
        }
    
        @Override
        public void set(String key, Bitmap bitmap) {
            if(TextUtils.isEmpty(key)||bitmap==null){
                throw new NullPointerException("key Can not be empty||bitmap Can not be empty");
            }
            Bitmap readyMoveBitmap;//等待移动的bitmap
            size+= Utils.getBitmapBytes(bitmap);//得到当前bitmap大小并叠加
            readyMoveBitmap=map.put(key,bitmap);
            if(readyMoveBitmap!=null){
                size-=Utils.getBitmapBytes(readyMoveBitmap);
            }
    
            reduceMemoryPressure();
        }
    
        /**
         * //如果当前size超过maxsize 就从前往后移除bitmap 来减少内存
         */
        public void reduceMemoryPressure(){
            while(true){
                String key;
                Bitmap bitmap;
                synchronized (this){
                    if(size<0||map.isEmpty()){
                        throw new NullPointerException("size Cannot be less than 0 ||map Can not be empty");
                    }
                    if(size<=maxSize||map.isEmpty()){//如果没有超过阈值就结束整个死循环;
                        break;
                    }
                    Map.Entry<String, Bitmap> decompose = map.entrySet().iterator().next();
                    key=decompose.getKey();
                    bitmap=decompose.getValue();
                    map.remove(key);
                    size-=Utils.getBitmapBytes(bitmap);
                    evictionCount++;
                }
            }
        }
        @Override
        public int size() {
            return size;
        }
    
        @Override
        public int maxSize() {
            return maxSize;
        }
    
        @Override
        public void clear() {
            reduceMemoryPressure();
        }
    
        @Override
        public void clearKeyUri(String keyPrefix) {
    
        }
    }
    
    
    

    目前已经实现的功能如下图:


    IMG20191125142151.jpg

    最后调用方式如下

    ## 设置图片四周圆角:
     Rhythm.with(context).load(url).transform(10.0f).into(imageView);
    ## 设置占位图: 
    Rhythm.with(context).load(url).placeholder(R.drawable.xx).into(imageView);
    ## 加载错误视图:
     Rhythm.with(context).load(url).error(R.drawable.xx).into(imageView);
    ## 开启加载gif图片(正在优化)
     Rhythm.with(context).load(url).openGif(false).into(imageView);
    ## 设置成圆形图片
     Rhythm.with(context).load(url).style(TypeEnum.CIRCLE).into(imageView);
    ## 设置圆形or圆角图片边框
     Rhythm.with(context).load(url).style(TypeEnum.CIRCLE).boarder(2).into(imageView);
     
     Rhythm.with(context).load(url).transform(10.0f).boarder(2).into(imageView);
    ## 增加图片渐变
    
    ## 图片懒加载
    
    ## 取消单个加载
     Rhythm.with(ListActivity.this).cancleTask(tag);
    ## 取消所有加载
     Rhythm.with(context).cancleAllTask();
    
    ## 加载图片高斯模糊效果
     Rhythm.with(context).bitmapTransform(new BlurTransformation(10)).into(imageView);
     其中BlurTransformation中分别包括模糊半径和指定模糊前缩小的倍数。
    
    
    # 动画相关
    ### 图片淡入淡出效果
     Rhythm.with(context).load(url).crossFade().into(imageView);
    ### 支持图片自定义动画
    Rhythm.with(context).load(url).Animation(R.anim.xx).into(imageView);
    ### 设置无动画
    Rhythm.with(context).load(url).dontAnimation().into(imageView);
    # 增加图片水印功能,详细功能:分为居中,左上角,右上角,左下角,右下角
    ### (1)居中水印
    Rhythm.with(context).load(url).watermark(new WatermarkInfo(Watermark.CENTER,watermakeBitmap)).into(imageView);
    ### (2)左上角水印
     Rhythm.with(context).load(url)
                         .watermark(new WatermarkInfo(Watermark.LEFT_TOP,watermakeBitmap,paddingLeft,paddingTop,paddingRight,paddingBottom))
                         .into(imageView);
    ### (3)右上角水印                    
     Rhythm.with(context).load(url)
                        .watermark(new WatermarkInfo(Watermark.RIGHT_TOP,watermakeBitmap,paddingLeft,paddingTop,paddingRight,paddingBottom))
                         .into(imageView);
    ### (4)左下角水印                    
     Rhythm.with(context).load(url)
                      .watermark(new WatermarkInfo(Watermark.LEFT_BOTTOM,watermakeBitmap,paddingLeft,paddingTop,paddingRight,paddingBottom))
                         .into(imageView);                     
    ### (5)右下角水印                    
     Rhythm.with(context).load(url)
                    .watermark(new WatermarkInfo(Watermark.RIGHT_BOTTOM,watermakeBitmap,paddingLeft,paddingTop,paddingRight,paddingBottom))
                         .into(imageView);   
     
    

    相关文章

      网友评论

          本文标题:Android图片加载框架之PictureCache

          本文链接:https://www.haomeiwen.com/subject/ekadwctx.html