Android Bitmap 的加载和三级缓存

三级缓存原理:就是当 App 需要引用缓存时,首先到内存缓存中读取,读取不到再到本地缓存中读取,还获取不到就到网络异步读取,读取成功之后再保存到内存和本地缓存中。

内存缓存:优先加载,加载速度快

本地缓存: 次优先加载,速度一般

网络缓存:加载优先级最低,消耗流量。使用时注意异步加载。

首先创建一个内存缓存类MemoryCacheUtil ,改类中有存储数据和获取数据方法

public class MemoryCacheUtil {

    private static final String TAG = "MemoryCacheUtil";

    private LruCache<String, Bitmap> mLruCache;

    public MemoryCacheUtil() {

        // maxMemory 是允许的最大值 ,超过这个最大值,则会回收
        int maxMemory = (int) Runtime.getRuntime().maxMemory();
        int cacheSize = maxMemory / 8;

        mLruCache = new LruCache<String, Bitmap>(cacheSize) {
            /**
             * 计算每张图片的大小
             * @param key
             * @param bitmap
             * @return
             */
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                return bitmap.getByteCount();
            }
        };

    }

    /**
     * 通过url从内存中获取图片
     *
     * @param url
     */
    public Bitmap getBitmapFromMemory(String url) {
        Log.e(TAG, "getBitmapFromMemory: mLruCache.get(url)="+mLruCache.get(url)+",个数="+mLruCache.size() );
        return mLruCache.get(url);
    }

    /**
     * 设置Bitmap到内存
     *
     * @param url
     * @param bitmap
     */
    public void setBitmapToMemory(String url, Bitmap bitmap) {
        Log.e(TAG, "setBitmapToMemory: getBitmapFromMemory(url)="+getBitmapFromMemory(url));

        if (getBitmapFromMemory(url) == null) {
            Log.e(TAG, "setBitmapToMemory: == null" );
            mLruCache.put(url, bitmap); // 设置图片
        }
    }

    /**
     * 从缓存中删除指定的Bitmap
     *
     * @param key
     */
    public void removeBitmapFromMemory(String key) {
        mLruCache.remove(key);
    }


}

之后创建一个本地缓存类LocalCacheUtil ,主要将图片压缩后保存到本地内存中,在这其中通过MD5加密文件名

public class LocalCacheUtil {

    private String cachePath;

    public LocalCacheUtil(Context context, String uniqueName) {
        cachePath = getCacheDirString(context, uniqueName);
    }


    /**
     * 设置Bitmap数据到本地
     *
     * @param url
     * @param bitmap
     */
    public void setBitmapToLocal(String url, Bitmap bitmap) {
        FileOutputStream fos = null;
        try {
            String fileName = MD5Encoder.encode(url);
            File file = new File(cachePath, fileName);
            File parentFile = file.getParentFile();//获取上级所有目录
            if (!parentFile.exists()) {
                // 如果文件不存在,则创建文件夹
                parentFile.mkdirs();
            }
            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();
                }
            }
        }

    }

    /**
     * 通过url获取Bitmap
     *
     * @param url
     */
    public Bitmap getBitmapFromLocal(String url) {
        try {
            File file = new File(cachePath, MD5Encoder.encode(url));
            if (file.exists()) {
                // 如果文件存在
                Bitmap bitmap = BitmapFactory.decodeStream(new FileInputStream(file));
                //Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
                return bitmap;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /*获取缓存目录的路径:String类型*/
    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);
            //file = new File(Environment.getExternalStorageDirectory(), uniqueName);
        } else {
            file = new File(context.getCacheDir(), uniqueName);
        }

        if (!file.exists()) {
            file.mkdirs();
        }

        return file.getAbsolutePath();
    }
}

然后新建一个网络获取数据类NetCacheUtil,通过AsyncTask 去网络下载图片,然后将图片分别保存到内存中和本地种

public class NetCacheUtil {

    private MemoryCacheUtil mMemoryCacheUtil;
    private LocalCacheUtil mLocalCacheUtil;

    public NetCacheUtil(MemoryCacheUtil mMemoryCacheUtil, LocalCacheUtil mLocalCacheUtil) {
        this.mMemoryCacheUtil = mMemoryCacheUtil;
        this.mLocalCacheUtil = mLocalCacheUtil;
    }

    /**
     * 获取服务端数据
     *
     * @param ivPhoto
     * @param url
     */
    public void getBitmapFromInternet(ImageView ivPhoto, String url) {

        new BitmapAsyncTask().execute(ivPhoto, url); // 开启AsyncTask

    }

    /**
     * 第一个泛型:参数类型  第二个泛型:更新进度的泛型, 第三个泛型是OnPostExecute的结果
     * Object : 传入的参数类型
     * Void : 进度
     * Bitmap : 返回类型
     */
    private class BitmapAsyncTask extends AsyncTask<Object, Void, Bitmap> {

        private ImageView ivPhoto;
        private String url;

        /**
         * 运行在子线程中:请求数据
         *
         * @param params
         * @return
         */
        @Override
        protected Bitmap doInBackground(Object... params) {

            ivPhoto = (ImageView) params[0]; // 获取两个参数
            url = (String) params[1];

            Bitmap bitmap = downloadBitmap(url); // 从网络上加载图片

            return bitmap;
        }

        /**
         * 在主线程中执行 用于更新界面
         *
         * @param bitmap
         */
        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);

            if (bitmap != null) {

                ivPhoto.setImageBitmap(bitmap); // 为ImageView设置图片

                System.out.println("从网络获取图片...");
                T.showShort(App.sContext,"从网络获取图片...");
                // 将获取到的图片加载到本地
                mLocalCacheUtil.setBitmapToLocal(url, bitmap);
                // 将获取到的图片加载到内存
                mMemoryCacheUtil.setBitmapToMemory(url, bitmap);
            }

        }
    }

    /**
     * 根据url从网络上获取图片
     *
     * @param imageUrl 图片路径
     * @return
     */
    private Bitmap downloadBitmap(String imageUrl) {

        HttpURLConnection conn = null;

        try {
            URL url = new URL(imageUrl);
            conn = (HttpURLConnection) url.openConnection(); // 打开连接
            conn.setReadTimeout(5000); // 设置读取超时时间
            conn.setConnectTimeout(5000); // 设置连接超时时间
            conn.setRequestMethod("GET"); // 设置请求方式
            conn.connect(); // 开始连接

            if (conn.getResponseCode() == 200) {
                // 访问成功
                InputStream is = conn.getInputStream(); // 获取流数据
                // 对图片进行压缩处理
                BitmapFactory.Options options = new BitmapFactory.Options();
                options.inSampleSize = 2; // 图片的宽高都为原来的一半(分辨率)
                Bitmap bitmap = BitmapFactory.decodeStream(is, null, options); // 将流数据转成Bitmap对象

                return bitmap;

            } else {
                // 访问失败
                return null;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                conn.disconnect(); // 断开连接
            }
        }
        return null;
    }

}

最后新建一个三级缓存工具类MyBitmapUtil.通过网址去判断地址是否存在缓存中,存在就返回展示,指导网络获取结束,

public class MyBitmapUtil {

    private static final String TAG ="MyBitmapUtil";

    private MemoryCacheUtil mMemoryCacheUtil = null; // 内存缓存(lrucache)
    private LocalCacheUtil mLocalCacheUtil = null; // 本地缓存(file)
    private NetCacheUtil mNetCacheUtil = null; // 网络缓存

    public MyBitmapUtil(Context context, String uniqueName) {
        mMemoryCacheUtil = new MemoryCacheUtil();
        mLocalCacheUtil = new LocalCacheUtil(context,uniqueName);
        mNetCacheUtil = new NetCacheUtil(mMemoryCacheUtil, mLocalCacheUtil);
    }

    /**
     * 将图片资源设置给控件
     *
     * @param url
     * @param ivPhoto
     */
    public void display(String url, ImageView ivPhoto) {

        Bitmap bitmap = null;

        // 1.判断内存中是否有缓存
        bitmap = mMemoryCacheUtil.getBitmapFromMemory(url); // 从内存中获取Bitmap
        Log.e(TAG, "display: bitmap="+bitmap );
        if (bitmap != null) {
            ivPhoto.setImageBitmap(bitmap); // 设置图片
            System.out.println("从内存获取图片...");
            T.showShort(App.sContext,"从内存获取图片...");
            return;
        }
        // 2.判断本地是否有缓存
        bitmap = mLocalCacheUtil.getBitmapFromLocal(url); // 从本地缓存中获取Bitmap
        if (bitmap != null) {
            ivPhoto.setImageBitmap(bitmap); // 设置本地图片
            mMemoryCacheUtil.setBitmapToMemory(url, bitmap); // 设置图片到内存
            System.out.println("从本地获取图片...");
            T.showShort(App.sContext,"从本地获取图片....");
            return;
        }
        // 3.从网络获取数据

        mNetCacheUtil.getBitmapFromInternet(ivPhoto, url); // 设置图片

    }


}

方法展示:

 // 三级缓存
        MyBitmapUtil myBitmapUtil =new MyBitmapUtil(this,"wudi");
        myBitmapUtil.display("http://c.hiphotos.baidu.com/image/h%3D300/sign=cfce96dfa251f3dedcb2bf64a4eff0ec/4610b912c8fcc3ce912597269f45d688d43f2039.jpg",image);
	//点击后直接从内存中获取
        image2.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myBitmapUtil.display("http://c.hiphotos.baidu.com/image/h%3D300/sign=cfce96dfa251f3dedcb2bf64a4eff0ec/4610b912c8fcc3ce912597269f45d688d43f2039.jpg",image2);
            }
        });

Bitmap 获取

decodeFile:从文件系统加载 Bitmap 对象
decodeResource:从资源文件中加载 Bitmap
decodeStream:从输入流加载 Bitmap
decodeByteArray:从字节数组中加载 Bitmap

bitmap高效加载

获取加载图片的宽高,内存大小,
主要使用 BitmapFactory.Options 的 inSampleSize 来设置采样率
nSampleSize 为1,原始图片
inSampleSize 为2,宽高均为原来的 1/2,像素为原来的 1/4
inSampleSize 为4,宽高均为原来的 1/4,像素为原来的 1/16

采样压缩方法和步骤

将 BitmapFactory.Options 的 inJustDecodeBounds 参数设置为 true;
从 BitmapFactory.Options 中取出图片的原始宽高信息,也就是 outWidth 和 outHeight 参数;
结合目标 View 所需大小来计算所需采样率 inSampleSize;
将 BitmapFactory.Options 的 inJustDecodeBounds 设置为 false,重新加载图片。

设置 inJustDecodeBounds 参数为 true 时只会解析图片的宽/高信息,并不会去加载图片,所以该操作是轻量级的。

1:设置图片

ImageView iv_decode = (ImageView)findViewById(R.id.iv_decode);
iv_decode.setImageBitmap(BitmapUtils.decodeSampledBitmapFromResoruce(getResources(),R	.drawable.mz,160,200));

2采样率的计算和图片读取

public static Bitmap decodeSampledBitmapFromResoruce(Resources res,int resId,
                                                     int reqWidth,int reqHeight){
    // 获取 BitmapFactory.Options,这里面保存了很多有关 Bitmap 的设置
    final BitmapFactory.Options options = new BitmapFactory.Options();
    // 设置 true 轻量加载图片信息
    options.inJustDecodeBounds = true;
    // 由于上方设置false,这里轻量加载图片
    BitmapFactory.decodeResource(res,resId,options);
    // 计算采样率
    options.inSampleSize = calculateInSampleSize(options,reqWidth,reqHeight);
    // 设置 false 正常加载图片
    options.inJustDecodeBounds = false;
    return BitmapFactory.decodeResource(res,resId,options);
}

public static int calculateInSampleSize(BitmapFactory.Options options,
                                        int reqWidth,int reqHeight){
    final int width = options.outWidth;
    final int height = options.outHeight;
    int inSampleSize = 1;
    // 宽或高大于预期就将采样率 *=2 进行缩放
    if(width > reqWidth || height > reqHeight){
        final int halfHeight = height/2;
        final int halfWidth = width/2;
        while((halfHeight / inSampleSize) >= reqHeight &&
                (halfWidth / inSampleSize) >= reqWidth){
            inSampleSize *= 2;
        }
    }
    return inSampleSize;
}

LruCache

内部采用 LinkedHashMap 以强引用的方式储存缓存对象,并提供 get/put 来完成获取和添加操作。当缓存满时,会移除较早使用的缓存对象,然后添加新的缓存对象。

LruCache 通常被用来缓存图片,但是也可以缓存其它内容到内存中

  //图片缓存
    public static LruCache<String, Bitmap> mMemoryCache;
    private MyHandler handler;
     // handler 避免内存泄漏
    static class MyHandler extends Handler {
        private WeakReference<Activity> weakReference;

        public MyHandler(Activity activity) {
            this.weakReference = new WeakReference<Activity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Activity activity = weakReference.get();
            if (activity != null) {
                switch (msg.what) {
                    case 1:
                        Log.e(TAG, "handleMessage: Main创建开始" );
                        break;
                    case 1001:
                        Log.e(TAG, "handleMessage: 异步加载" );
                        Bitmap bitmap =BitmapFactory.decodeResource(activity.getResources(), R.mipmap.guide_icon01);
//                        Bitmap bitmap =BitmapUtils.decodeSampledBitmapFromResoruce(activity,R	.drawable.mz,160,200)
                        Log.e(TAG, "handleMessage: String.valueOf(bitmap)="+String.valueOf(bitmap)+",bitmap="+bitmap );
                        addBitmapToMemoryCache(String.valueOf(bitmap), bitmap);
                        break;
                }
            }
        }
    }
  public  void loadBitmap(int resId, ImageView imageView) {
        final String imageKey = String.valueOf(resId);
        final Bitmap bitmap = getBitmapFromMemCache(imageKey);
        if (bitmap != null) {
            Log.e(TAG, "loadBitmap: bitmap!=null" );
            imageView.setImageBitmap(bitmap);
        } else {
            Log.e(TAG, "loadBitmap: bitmap==null" );
            imageView.setImageResource(R.drawable.image_placeholder);
            Message message = Message.obtain();
            message.what = 1001;
            handler.handleMessage(message);
        }
    }

    public static void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
           upDataUI(key);
        }
    }

    private static void upDataUI(String key) {
        final String imageKey = String.valueOf(key);
        final Bitmap bp = getBitmapFromMemCache(imageKey);
        image_forth.setImageBitmap(bp);
    }

    public static Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }
    //onCreate()
  // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。
        // LruCache通过构造函数传入缓存值,以KB为单位。
        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        Log.e(TAG, "initData: maxMemory="+maxMemory );
        // 使用最大可用内存值的1/8作为缓存的大小。
        int cacheSize = maxMemory / 8;
        Log.e(TAG, "initData: cacheSize="+cacheSize );
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            protected int sizeOf(String key, Bitmap bitmap) {
                // 重写此方法来衡量每张图片的大小,默认返回图片数量。
                return bitmap.getByteCount() / 1024;
            }
        };

        handler = new MyHandler(this);
        Message message = Message.obtain();
        message.what = 1;
        handler.handleMessage(message);

        loadBitmap(R.mipmap.guide_icon01,image_forth);
private LruCache<String, BitmapDrawable> mMemoryCache;  
  
    public ImageAdapter(Context context, int resource, String[] objects) {  
        super(context, resource, objects);  
        // 获取应用程序最大可用内存  
        int maxMemory = (int) Runtime.getRuntime().maxMemory();  
        int cacheSize = maxMemory / 8;  
        mMemoryCache = new LruCache<String, BitmapDrawable>(cacheSize) {  
            @Override  
            protected int sizeOf(String key, BitmapDrawable drawable) {  
                return drawable.getBitmap().getByteCount();  
            }  
        };  
    }  
  
    @Override  
    public View getView(int position, View convertView, ViewGroup parent) {  
        String url = getItem(position);  
        View view;  
        if (convertView == null) {  
            view = LayoutInflater.from(getContext()).inflate(R.layout.image_item, null);  
        } else {  
            view = convertView;  
        }  
        ImageView image = (ImageView) view.findViewById(R.id.image);  
        BitmapDrawable drawable = getBitmapFromMemoryCache(url);  
        if (drawable != null) {  
            image.setImageDrawable(drawable);  
        } else {  
            BitmapWorkerTask task = new BitmapWorkerTask(image);  
            task.execute(url);  
        }  
        return view;  
    }  
  
    /** 
     * 将一张图片存储到LruCache中。 
     *  
     * @param key 
     *            LruCache的键,这里传入图片的URL地址。 
     * @param drawable 
     *            LruCache的值,这里传入从网络上下载的BitmapDrawable对象。 
     */  
    public void addBitmapToMemoryCache(String key, BitmapDrawable drawable) {  
        if (getBitmapFromMemoryCache(key) == null) {  
            mMemoryCache.put(key, drawable);  
        }  
    }  
  
    /** 
     * 从LruCache中获取一张图片,如果不存在就返回null。 
     *  
     * @param key 
     *            LruCache的键,这里传入图片的URL地址。 
     * @return 对应传入键的BitmapDrawable对象,或者null。 
     */  
    public BitmapDrawable getBitmapFromMemoryCache(String key) {  
        return mMemoryCache.get(key);  
    }  
  
    /** 
     * 异步下载图片的任务。 
     *  
     * @author guolin 
     */  
    class BitmapWorkerTask extends AsyncTask<String, Void, BitmapDrawable> {  
  
        private ImageView mImageView;  
  
        public BitmapWorkerTask(ImageView imageView) {  
            mImageView = imageView;  
        }  
  
        @Override  
        protected BitmapDrawable doInBackground(String... params) {  
            String imageUrl = params[0];  
            // 在后台开始下载图片  
            Bitmap bitmap = downloadBitmap(imageUrl);  
            BitmapDrawable drawable = new BitmapDrawable(getContext().getResources(), bitmap);  
            addBitmapToMemoryCache(imageUrl, drawable);  
            return drawable;  
        }  
  
        @Override  
        protected void onPostExecute(BitmapDrawable drawable) {  
            if (mImageView != null && drawable != null) {  
                mImageView.setImageDrawable(drawable);  
            }  
        }  
  
        /** 
         * 建立HTTP请求,并获取Bitmap对象。 
         *  
         * @param imageUrl 
         *            图片的URL地址 
         * @return 解析后的Bitmap对象 
         */  
        private Bitmap downloadBitmap(String imageUrl) {  
            Bitmap bitmap = null;  
            HttpURLConnection con = null;  
            try {  
                URL url = new URL(imageUrl);  
                con = (HttpURLConnection) url.openConnection();  
                con.setConnectTimeout(5 * 1000);  
                con.setReadTimeout(10 * 1000);  
                bitmap = BitmapFactory.decodeStream(con.getInputStream());  
            } catch (Exception e) {  
                e.printStackTrace();  
            } finally {  
                if (con != null) {  
                    con.disconnect();  
                }  
            }  
            return bitmap;  
        }  
  
    }  
  
}  


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值