美文网首页APP & program
Android中BitMap加载和缓存

Android中BitMap加载和缓存

作者: BlueSocks | 来源:发表于2022-11-21 17:37 被阅读0次

    1.Bitmap的高效加载

    1.1 通常如何加载Bitmap

    Bitmap在Android指的是一张图,可以是.png/.jpg等其他格式

    BitmapFactory提供四类方法:

    decodeFile、decodeResource、decodeStream、decodeByteArray

    对应从文件系统、资源、输入流、字节数组中加载出一个Bitmap对象

    decodeFile、decodeResource又间接调用了decodeStream方法

    1.2 如何高效加载Bitmap

    核心思想是采用BitmapFactory.Options来加载所需尺寸的图片

    比如通过ImageView来显示图片,通常ImageView没有图片原始尺寸这么大,这时就通过BitmapFactory.Options按一定的采样率来加载缩小后的图片,降低内存占用

    1.3 BitmapFactory.Options

    BitmapFactory.Options缩放图片,主要是用到了 inSampleSize 参数,即采样率
    inSampleSize为1时,采样为原始大小
    inSampleSize 为2时,图片的宽高为原图的1/2,像素数为1/4,占用内存为1/4
    inSampleSize 为4,缩放比例就是1/16,即2的4次方
    inSampleSize应该为2的指数倍,2,4,8,16等
    inSampleSize小于1,相当于1
    inSampleSize不为2 的指数,向下取整为2的指数

    1.4 如何获取采样率

    ●将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片
    ●设置为true后BitmapFactory只会解析图片的原始宽高信息,并不会加载图片
    ●根据结果设置合适的采样率
    ● inJustDecodeBounds设回false然后重新加载图片

    举例

    public Bitmap decodeSampledBitmapFromFileDescriptor(FileDescriptor fd, int reqWidth, int reqHeight) {
            // First decode with inJustDecodeBounds=true to check dimensions
            final BitmapFactory.Options options = new BitmapFactory.Options();//拿到
            options.inJustDecodeBounds = true;//设置
            BitmapFactory.decodeFileDescriptor(fd, null, options);
    
            // Calculate inSampleSize
            options.inSampleSize = calculateInSampleSize(options, reqWidth,
                    reqHeight);//计算
    
            // Decode bitmap with inSampleSize set
            options.inJustDecodeBounds = false;
            return BitmapFactory.decodeFileDescriptor(fd, null, options);
        }
    
        public int calculateInSampleSize(BitmapFactory.Options options,
                int reqWidth, int reqHeight) {
            if (reqWidth == 0 || reqHeight == 0) {
                return 1;
            }
    
            // Raw height and width of image
            final int height = options.outHeight;
            final int width = options.outWidth;
            Log.d(TAG, "origin, w= " + width + " h=" + height);
            int inSampleSize = 1;
    
            if (height > reqHeight || width > reqWidth) {
                final int halfHeight = height / 2;
                final int halfWidth = width / 2;
    
                // Calculate the largest inSampleSize value that is a power of 2 and
                // keeps both
                // height and width larger than the requested height and width.
                while ((halfHeight / inSampleSize) >= reqHeight
                        && (halfWidth / inSampleSize) >= reqWidth) {
                    inSampleSize *= 2;
                }
            }
    
            Log.d(TAG, "sampleSize:" + inSampleSize);
            return inSampleSize;
        }
    

    2 Android中的缓存策略

    缓存可以避免过多的消耗流量

    当用户第一次从网上加载图片后,会把图片缓存在内存中,再缓存到本地储存设备.这样当应用打算从网络请求一张图片的时候会先访问内存,再访问储存设备,都没有后才会去下载

    目前常用的缓存算法是LRU,近期最少使用算法

    2.1 LruCache

    最近最少使用缓存,它用强引用保存需要缓存的对象,内部维护一个队列(实际是LinkedhashMap内部的双向链表,LruCache对其进行了封装,添加了线程安全操作),当其中的一个值被访问时,它被放到队列的尾部,当缓存满了,头部的值会被丢弃,之后可以被垃圾回收。

    2.2 LruCache的实现

    需要提供缓存的总容量大小并重写sizeOf方法(计算缓存对象的大小)
    LruCache还支持删除操作,通过remove方法即可删除一个指定的缓存对象

                    //获取最大可用的内存空间
            int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
            int cacheSize = maxMemory / 8;//缓存大小为总容量的1/8,单位KB
            mLruCache = new LruCache<String, Bitmap>(cacheSize) {
                @Override
                protected int sizeOf(String key, Bitmap value) {//sizeOf()用来计算缓存对象的大小
                    return value.getRowBytes() * value.getHeight() / 1024;//除1024为了将单位转成KB
                }
            };
        }
    
    2.2.1 从Lrucache获取一个缓存对象

    mLruCache.get(key)

    2.2.2 Lrucache添加一个缓存对象

    mLruCache.put(key,bitmap)

    2.2.3 删除一个指定的缓存对象

    mLruCache.remove(key);

    2.3 DiskLruCache

    ● 用于实现存储设备缓存,磁盘缓存
    ● 将缓存对象写入文件系统,从而实现缓存效果

    2.3.1 DiskLruCache的创建

    DiskLruCache并不能通过构造方法来创建,它提供了open方法用于创建自身

        public static DiskLruCache open(File directory, 
                                        int appVersion, 
                                        int valueCount, 
                                        long maxSize)
    

    第一个参数:表示磁盘缓存在文件系统中的存储路径,可以选择SD卡.(如果希望应用卸载后删除缓存文件,那么就选择SD卡上的缓存目录,否则应该选择SD卡上的其他特定目录)
    第二个参数:表示应用的版本号,一般为1,版本号改变时会清空之前所有的缓存文件
    第三个参数:表示单个节点所对应的数据个数,一般为1
    第四个参数:表示缓存的总大小,比如50MB

    2.3.2 DiskLruCache的缓存添加

    ● 此操作是通过Editor来完成的
    ● 缓存首先要获取图片的url所对应的key(url的md5值)
    ● 缓存只允许编辑一个缓存对象
    ● 通过Editor对象得到一个文件输出流,写入到文件系统上
    ● 最后Editor.commit()
    ○ 第一步

        private String hashKeyFormUrl(String url){
            String cacheKey;
            try {
                MessageDigest mDigest = MessageDigest.getInstance("MD5");
                mDigest.update(url.getBytes());
                cacheKey = bytesToHexString(mDigest.digest());
            } catch (NoSuchAlgorithmException e) {
                cacheKey = String.valueOf(url.hashCode());
            }
            return cacheKey;//获取URL对应的key
        }
     
        private String bytesToHexString(byte[] digest) {
            StringBuffer sb = new StringBuffer();
            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();
    

    ○ 网络下载图片,通过文件输出流写入到文件系统上。

     private boolean downloadUrlToStream(String urlString,OutputStream outputStream){
            int IO_BUFFER_SIZE = 0;
            HttpURLConnection urlConnection = null;
            BufferedOutputStream out = null;
            BufferedInputStream in = null;
            try {
                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 (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(urlConnection != null){
                    urlConnection.disconnect();
                }
            }
            return false;
    }
    

    ○ 将图片的url转为key之后,就可以获取Editor对象了,对于这个key来说,如果当前不存在其他Editor对象,那么edit()就会返回一个新的Editor对象,通过它就可以得到一个文件输入流,通过Editor的commit完成提交。
    int DISK_CACHE_INDEX = 0;//由于前面的open设置了一个节点只能有一个数据,因此DISK_CACHE_SIZE = 0

                    String key = hashKeyFormUrl(url);
                    try {
                        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();
                            }
                             //更新操作
                             mDiskCache.flush();
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
    
    2.3.3 DiskLruCache的缓存的查找

    ● 查找也需要将url转换为key
    ● 然后通过DiskLruCache的get方法得到一个Snapshot对象
    ● 再通过Snapshot对象得到缓存的文件输入流
    ● 记得通过BitmapFactory.Options加载一个缩放后的图片

         Bitmap bitmap = null;
         String keys = hashKeyFormUrl(url);
         try {
           DiskLruCache.Snapshot snapshot = mDiskLruCache.get(keys);
           if(snapshot != null){
     FileInputStream fileInputStream = (FileInputStream) snapshot.getInputStream(DISK_CACHE_INDEX);
            FileDescriptor fileDescriptor = fileInputStream.getFD();
    bitmap=mImageResizer.decodeSampledBitmapFromFileDescriptor(fileDescriptor,reqWidth,reqHeight);
                            if(bitmap != null){
                                addBitmapToMemoryCache(keys,bitmap);
                            }
    

    2.4 ImageLoader

    2.4.1 ImageLoader的实现

    一个优秀的ImageLoader应具备如下功能:

    ● 图片的同步加载(从内存缓存、磁盘缓存、网络中获取的)
    ● 图片的异步加载(ImageLoader内部需要自己在线程中加载图片并将图片设置给所需的ImageView)
    ● 图片压缩
    ● 内存缓存
    ● 磁盘缓存
    ● 网络拉取

    ImageLoader还需要处理在ListView或者GridView中,快速下拉时图片在item错位的情况

    内存缓存和磁盘缓存是ImageLoader的核心,通过这两级缓存极大的提高了程序的效率并降低了流量消耗,只有这两级缓存都不可用时才需要从网络中拉去图片。

    2.4.2 优化卡顿现象

    ● 关键是不要在主线程中做太多耗时的操作
    ● 不要在getView中执行耗时操作(如加载图片)
    ● 控制异步任务的执行频率,比如频繁的上下滑动会产生N个异步任务.这时可以·考虑在列表滑动的时候停止加载图片,停下来再加载
    ● 开启硬件加速:android:hardwareAccelerated=“true”

    来自:https://www.yuque.com/mrprf/sgsv7s/hq8gm5#6e7ca743

    相关文章

      网友评论

        本文标题:Android中BitMap加载和缓存

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