Android Bitmap图像处理(2)- 图片缓存

上一节我们知道,Bitmap在Android开发中是比较占用内存和耗费资源的。我们不可能每次都从网络去下载图片,每次都从SD卡或者res去读取bitmap,因为这些操作很耗时间和资源的。这个时候,我们就需要用到图片缓存机制。

一、Bitmap图片缓存机制的流程图

我们先来假设,Bitmap即没有内存缓存、也没有SD卡缓存的情况下,怎样将Bitmap加载到ImageView上。

步骤思路:

  1. 网络请求服务器,然后以流InputStream的方式返回到客户端。
  2. 将流读取出byte[]转换成Bitmap。
  3. 在这过程中可以对Bitmap进行压缩,减少内存占用。
  4. 缓存Bitmap到SD卡
  5. 缓存Bitmap到内存
  6. 通过handler更新UI到ImageView

二、从网络获取Bitmap并压缩

思路:

  1. 开启一个AsyncTask,传入ImageView和图片url,其中ImageView使用软引用,更易被GC回收。
  2. 在doInBackground方法中执行后台任务,当联网成功并获取到了inputstream后,开始压缩Bitmap【在这里同时采用了质量和取样压缩法】。
  3. doInBackground返回Bitmap后,在onPostExecute中直接更新ImageView。

    private static final String PIC_URL =
     "http://7xijtp.com1.z0.glb.clouddn.com/
     8F055A06-FF58-4A43-
     A846-55CF05B0272C.png";
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
    
            setContentView(R.layout.activity_test3);
    
            ImageView iv_2 = (ImageView) findViewById(R.id.iv_2);
    
            new BitmapWorkerTask(iv_2, PIC_URL).execute();
    
    }
    
    class BitmapWorkerTask extends AsyncTask<Void, Void, Bitmap> {
    
            private final WeakReference<ImageView> imageViewReference;
            private String url;
    
            public BitmapWorkerTask(ImageView imageView, String url) {
                imageViewReference = new WeakReference<ImageView>(imageView);
                this.url = url;
            }
    
            @Override
            protected Bitmap doInBackground(Void... voids) {
                return getBitmapFromServer(url);
            }
    
            @Override
            protected void onPostExecute(Bitmap bitmap) {
    
                if (imageViewReference != null && bitmap != null) {
                    final ImageView imageView = imageViewReference.get();
                    if (imageView != null) {
                        imageView.setImageBitmap(bitmap);
                    }
                }
            }
    }
    
    /**
     * 通过图片url 从服务器获取Bitmap
     * @param picUrl
     * @return
     */
    private Bitmap getBitmapFromServer(String picUrl) {
    
            URL url = null;
            Bitmap bitmap = null;
            InputStream is = null;
    
            try {
                url = new URL(picUrl);
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                is = new BufferedInputStream(httpURLConnection.getInputStream());
                if (is != null) {
                    // 压缩图片
                    bitmap = decodeSampledBitmap(is, 10);
                }
                httpURLConnection.disconnect();
                return bitmap;
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (is != null) {
                    try {
                        is.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
    
            return null;
    }
    
    /**
     * 图片压缩
     *
     * @param quality
     * @return
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
    @NonNull
    private Bitmap decodeSampledBitmap(InputStream ins, int quality) {
    
            BitmapFactory.Options opts = new BitmapFactory.Options();
            Bitmap bm = null;
            ByteArrayOutputStream baos = null;
            try {
                byte[] bytes = readStream(ins);
    
                opts.inJustDecodeBounds = true;
    
                bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
    
                opts.inJustDecodeBounds = false;
    
                int picWidth = opts.outWidth;// 得到图片宽度
                int picHeight = opts.outHeight;// 得到图片高度
                Log.e("原图片高度:", picHeight + "");
                Log.e("原图片宽度:", picWidth + "");
    
                opts.inSampleSize = 2;//设置缩放比例
    
                bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, opts);
    
                int picWidth2 = opts.outWidth;// 得到图片宽度
                int picHeight2 = opts.outHeight;// 得到图片高度
    
                Log.e("压缩后的图片宽度:", picWidth2 + "");
                Log.e("压缩后的图片高度:", picHeight2 + "");
                Log.e("压缩后的图占用内存:", bm.getByteCount() + "");
    
                // 开始质量压缩
                baos = new ByteArrayOutputStream();
                bm.compress(Bitmap.CompressFormat.PNG, quality, baos);
    
                byte[] b = baos.toByteArray();
                bm = BitmapFactory.decodeByteArray(b, 0, b.length, opts);
    
                Log.e("质量压缩后的占用内存:", bm.getByteCount() + "");
                return bm;
            } catch (Exception e) {
                e.printStackTrace();
                if (baos != null) {
                    try {
                        baos.close();
                    } catch (IOException e1) {
                        e.printStackTrace();
                    }
                }
            }
            return bm;
    }
    
    /*
     * 得到图片字节流 数组大小
     * */
    public static byte[] readStream(InputStream inStream) throws Exception {
        ByteArrayOutputStream outStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int len = 0;
        while ((len = inStream.read(buffer)) != -1) {
            outStream.write(buffer, 0, len);
        }
        outStream.close();
        inStream.close();
        return outStream.toByteArray();
    }
    

运行Log的结果:

三、Bitmap缓存到SD本地

/** 
 * 保存Image的方法。
 * 需要注意的地方有:1、判断是否有SD卡;2、判断SD卡存储空间是否够用。
 * @param fileName  
 * @param bitmap    
 * @throws IOException 
 */  
public void savaBitmap(String fileName, Bitmap bitmap){

    FileOutputStream fos = null;
    try {
        if(bitmap == null){
            return;
        }
        String path = getStorageDirectory();
        File folderFile = new File(path);
        if(!folderFile.exists()){
            folderFile.mkdir();
        }
        File file = new File(path + File.separator + fileName);
        file.createNewFile();
        fos = new FileOutputStream(file);
        bitmap.compress(CompressFormat.PNG, 100, fos);

    }catch (IOException E){

    }finally {
        if(fos != null) {
            try {
                fos.flush();
                fos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

四、Bitmap缓存到内存

首先这里先引用一段Google文档的说明:

Note: In the past, a popular memory cache implementation was a SoftReference or WeakReference bitmap cache, however this is not recommended. Starting from Android 2.3 (API Level 9) the garbage collector is more aggressive with collecting soft/weak references which makes them fairly ineffective. In addition, prior to Android 3.0 (API Level 11), the backing data of a bitmap was stored in native memory which is not released in a predictable manner, potentially causing an application to briefly exceed its memory limits and crash.

简单翻译过来的意思就是:
在以前使用内存缓存的时候一般喜欢使用SoftReference或WeakReference的位图缓存,例如:

HashMap<String, SoftReference<Bitmap>> imageCache

但是Google不建议这样做,原因有两点。

  1. 从Android 2.3开始,垃圾回收器更积极的回收持有软引用或弱引用的对象,导致软引用缓存的数据极易被释放,这使得软引用和弱引用变得没有效果。
  2. 此外,在Android 3.0 之前,图片会存储在native memory内存中,不是以一种可预见的方式释放,可能导致应用程序暂时超过其内存限制而崩溃。

来源:http://developer.android.com/intl/zh-cn/training/displaying-bitmaps/cache-bitmap.html

所以,在这里Google推荐使用Android自带的API:LruCache 来实现内存缓存

4.1 介绍LruCache

LruCache 底层是把最近使用的对象用强引用存储在LinkedHashMap中,当LruCache的缓存值达到预设定值的容量时,就会把最近最少使用的对象从内存中移除。

LRU图片缓存对象初始化:

// 获取应用的最大可用内存
 int maxMemory = (int) Runtime.getRuntime().maxMemory();
 // 设置LruCache缓存最大值
 int cacheMemory = maxMemory / 8;
 LruCache<String,Bitmap> mLruCache = new LruCache<String, Bitmap>(cacheMemory) {

    @Override
     protected int sizeOf(String key, Bitmap value) {
             return value.getRowBytes() * value.getHeight();
     }

 };

解析:

  • mLruCache 每次添加Bitmap图片缓存的时候(put操作),都会调用sizeOf方法,返回Bitmap。的内存大小给LruCache,然后循环增加这个size。
  • 当这个Size内存大小超过初始化设定的cacheMemory大小时,则遍历map集合,把最近最少使用的元素remove掉
4.2 LruCache缓存思路

1、先去LruCahce里面找有没有Bimap,如果有,直接设置ImageView.setImageBitmap。如果没有,先去SD卡,如果SD卡有,则从SD卡读取获取;如果SD卡也没用,最终联网获取。 

public void showImageByAsyncTask(String url, ImageView imageView) {
    // 1.先从内存缓存获取Bitmap
    Bitmap bitmap = getBitmapFromMemoryCache(url);
    if (bitmap == null) {
        // 当内存缓存没有的时候,从SD卡获取
        bitmap = getBitmapFromSD(sdUrl);
        if (bitmap == null) {
            // SD卡也没有的时候,联网获取
            BitmapAsyncTask myAsyncTask = new BitmapAsyncTask(url, imageView);
            myAsyncTask.execute();
        } else {
            if ((url).equals(imageView.getTag()))
                imageView.setImageBitmap(bitmap);
        }
    } else {
        if ((url).equals(imageView.getTag()))
            imageView.setImageBitmap(bitmap);
    }
}

// 通过图片url获取缓存在LruCache中的Bitmap
private Bitmap getBitmapFromMemoryCache(String url) {
    return mLruCaches.get(url);
}

2、联网获取到的Bitmap,如果不为空,就put到缓存。先去判断缓存里面有没有这个对象,如果没有就put,有了则不需要再重复put。

// 把获取到的Bitmap缓存到LruCache
private void addCache(String url, Bitmap bitmap) {
    if (getBitmapFromMemoryCache(url) == null) {
        mLruCaches.put(url, bitmap);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值