【Android学习】Bitmap的加载和Cache缓存

1,图片加载

1)Bitmap

Bitmap在Android中指的是一张图片,可以是png、jpg等常见图片格式。

2)加载过程

BitmapFactory类提供了4类方法:
–decodeFile
从文件系统加载出一个Bitmap对象。
间接调用了decodeStream方法。
–decodeResource
从资源加载出一个Bitmap对象。
间接调用了decodeStream方法。
–decodeStream
从输入流加载出一个Bitmap对象。
–decodeByteArray
从字节数组加载出一个Bitmap对象。

3)OOM

由于Bitmap的特殊性以及Android对单个应用所施加的内存限制,这导致加载Bitmap的适合很容易出现内存溢出:java.lang.OutofMemoryError:bitmap size exceeds VM budget

4)如何有效地加载一个Bitmap?

采用BitmapFactory.Options加载所需尺寸的图片。

通过ImageView来显示图片时,ImageView可能没有图片的原始尺寸那么大,可以通过BitmapFactory.Options按一定的采样率来加载缩小后的图片。这样可以降低内存占用从而在一定程度上避免OOM,提高Bitmap加载时的性能。

–inSampleSize参数 采样率
当值<=1时,采样后的图片为原始大小;
当值>1时,比如是2,那么采样后的图片其宽高为原图的1/2,像素为原图的1/4,占有内存为原图的1/4。注意:取值应该为2的指数,否则系统会自动以最接近的2的指数代替。

注意:
通过采样率可有效地加载图片,为了避免图片不必要的拉升失真,获取采样率应遵循以下流程:
①将BitmapFactory.Options的inJustDecodeBounds参数设为true并加载图片。
(nJustDecodeBounds参数,值为true,BitmapFactory只解析图片的原始宽高,并不会加载图片,是轻量级操作

②从BitmapFactory.Options中取出图片的原始宽高信息,它们对应于outWidth和outHeight参数。
③根据采样率的规则并结合目标View的所需大小计算出采样率inSampleSize.
④将BitmapFactory.Options的inJustDecodeBounds参数设为false,然后重新加载图片。

避免OOM的常用方法:

---BitmapFactory.Options.inPreferredConfig:将ARGB_8888改为RGB_565,改变编码方式,节约内存。
---BitmapFactory.Options.inSampleSize:缩放比例,可以参考Luban那个库,根据图片宽高计算出合适的缩放比例。
---BitmapFactory.Options.inPurgeable:让系统可以内存不足时回收内存。

5)占用内存大小

Bitamp 占用内存大小 = 宽度像素 x (inTargetDensity / inDensity) x 高度像素 x (inTargetDensity / inDensity)x 一个像素所占的内存.

//在Bitmap里有两个获取内存占用大小的方法。
---getByteCount()
API12 加入,代表存储 Bitmap 的像素需要的最少内存。
---getAllocationByteCount()
API19 加入,代表在内存中为 Bitmap 分配的内存大小,代替了 getByteCount() 方法。

注:这里inDensity表示目标图片的dpi(放在哪个资源文件夹下),inTargetDensity表示目标屏幕的dpi,所以你可以发现inDensity和inTargetDensity会对Bitmap的宽高
进行拉伸,进而改变Bitmap占用内存的大小。

在不复用 Bitmap 时,getByteCount() 和 getAllocationByteCount 返回的结果是一样的。在通过复用 Bitmap 来解码图片时,那么 getByteCount() 表示新解码图片占用内存的大
小,getAllocationByteCount() 表示被复用 Bitmap真实占用的内存大小(即 mBuffer 的长度)。

6)性能优化

避免在Bitmap浪费过多的内存,使用压缩过的图片,也可以使用Fresco等库来优化对Bitmap显示的管理。

2,缓存策略

通过缓存策略,每次不需要在网上请求图片或者从存储设备中加载图片,这样就极大的提高了图片的加载效率以及产品的用户体验。

如何避免过多的流量消耗?—缓存
当程序第一次从网络加载图片后,就缓存到设备上,以后再用就不需要再重新获取,节省流量、提高用户体验。

目前常用的缓存算法是LRU(Lest Recently Used,最近最少使用),当缓存快满时,淘汰近期最少使用的缓存目标。采用LRU算法的缓存有2种:

1)LRU算法

①LruCache(least recentlly use,最少最近使用算法)

用于内存缓存。是线程安全的。
LruCache是Android3.1所提供的一个缓存类,为了兼容2.2版本,建议采用support-v4兼容包种提供的LruCache。

LruCache是一个泛型类,内部是一个LinkedHashMap以强引用的方式存储外界缓存对象,提供了get和put完成缓存的获取和添加操作。

为什么会选择LinkedHashMap呢?

这跟LinkedHashMap的特性有关,LinkedHashMap的构造函数里有个布尔参数accessOrder,当它为true时,LinkedHashMap会以访问顺序为序排列元素,否则以插入顺序为序排序元素。

通过 LruCache

②DiskLruCache

用于存储设备缓存,即磁盘缓存。将缓存对象写入了文件系统。
不属于Android SDK的一部分,是官方文件的推荐。
DiskLruCache与LruCache原理相似,只是多了一个journal文件来做磁盘文件的管理。

2)图片的三级缓存

①概念

网络加载,不优先加载,速度慢,浪费流量
本地缓存,次优先加载,速度快
内存缓存,优先加载,速度最快

②为什么要使用三级缓存

每次启动app都从网络拉取图片,会消耗很多流量;
重复浏览一些图片时,每次都通过网络获取,浪费流量。
所以提出三级缓存策略,通过网络、本地、内存三级缓存图片,来减少不必要的网络交互,避免浪费流量。

③原理

首次加载 Android App 时,通过网络交互来获取图片,之后将图片保存至本地SD卡和内存中。
之后运行 App 时,优先访问内存中的图片缓存,若内存中没有,则加载本地SD卡中的图片。

3)ImageLoader

①概念

结合LruCache、DiskLruCache。

②功能

图片的同步/异步加载;
图片压缩;
内存缓存;
磁盘缓存;
网络拉取。

除此意外,还需要处理一些特殊的情况:
如ListView或者GridView中,View的复用造成的图片不同步问题。

3,列表的滑动流畅性

1)问题

ListView和GridView由于加载大量的子视图,当用户快速滑动时就容易出现卡顿现象。

2)解决

①在getView中执行了耗时操作

如直接在getView中加载图片。

解决方案:类似于ImageLoader实现的那样,通过异步的方式处理。

②控制异步任务的执行频率

如第一种方案中所述,在getView中通过异步加载图片,如果用户可以地频繁上下滑动,就会在一瞬间产生上百个异步任务,这些异步任务会造成线程池的拥堵并带来大量UI更新操作,从而造成卡顿。

解决方案:给ListView或者GridView设置setOnScrollListener,并在OnScrollListener的onScrollStateChanged方法中判断列表是否处于滑动状态,如果是则停止加载图片。

public void onScrollStateChanged(AbsListView view, int scrollState){
    if(scrollState == OnScrollListener.SCROLL_STATE_IDLE){
        mIsGridViewIdle = true;
        mImageAdapter.notifyDataSetChanged();
    }else{
        mIsGridViewIdle = false;
    }
}

然后再getView方法中,仅当列表静止时才能加载图片:

if(mIsGridViewIdle && mCanGetBitmapFromNetWork){
    imageView.setTag(uri);
    mImageLoader.bindBitmap(uri, imageView, mImageWidth, mImageWidth);  
}

③开启硬件加速

大多数情况下,硬件加速可以解决莫名的卡顿问题。
设置:android:hardwareAccelerated="true"可为Activity开启硬件加速 。

4)清除缓存

①场景

卸载时清除数据。

②目录介绍

i>getCacheDir()

用于获取:/data/data/(应用包名)/cache目录。
一般存放:临时缓存数据。
对应目录:设置->应用->应用详情里面的”清除缓存“选项

ii>getFilesDir()

用于获取:/data/data//files目录。
一般存放:长时间保存的数据。
对应目录:设置->应用->应用详情里面的”清除数据“选项

iii>getExternalFilesDir()
iv>getExternalCacheDir()

较优秀的程序都会专门写一个方法来获取缓存地址,如下所示:

public String getDiskCacheDir(Context context) {  
    String cachePath = null;  
    if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())  
            || !Environment.isExternalStorageRemovable()) {//当SD卡存在或者SD卡不可被移除的时候
        cachePath = context.getExternalCacheDir().getPath();  
    } else {  
        cachePath = context.getCacheDir().getPath();  
    }  
    return cachePath;  
}  

5)

4,demo

1)获取View对应的Bitmap

public static Bitmap getBitmapFromView(View v) {
    Bitmap b = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.RGB_565);
    Canvas c = new Canvas(b);
    v.layout(v.getLeft(), v.getTop(), v.getRight(), v.getBottom());
    // Draw background
    Drawable bgDrawable = v.getBackground();
    if (bgDrawable != null)
        bgDrawable.draw(c);
    else
        c.drawColor(Color.WHITE);
    // Draw view to canvas
    v.draw(c);
    return b;
}

2)清除缓存

获取缓存:

CacheUtils.getIntense().getTotalCacheSize(context)

清除缓存:

CacheUtils.getIntense().clearAllCache(context);

注意:
使用时,注意传入的context级别,防止内存泄漏。为了安全,应该直接在application中清除缓存。
CacheUtils.java:

package com.rendu.bim.util.manager.cache;

import android.content.Context;
import android.os.Environment;

import java.io.File;
import java.math.BigDecimal;

public class CacheUtils {

    public static CacheUtils intance = new CacheUtils();

    public static CacheUtils getIntance() {
        return intance;
    }

    /**
     * 清除缓存
     */
    public void clearAllCache(Context context) {
        deleteDir(context.getCacheDir());
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            deleteDir(context.getExternalCacheDir());
        }
    }

    /**
     * 删除缓存文件
     *
     * @param dir
     * @return
     */
    private  boolean deleteDir(File dir) {
        if (dir != null && dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        return dir.delete();
    }

    public  long getFolderSize(File file) throws Exception {
        long size = 0;
        try {
            File[] fileList = file.listFiles();
            for (int i = 0; i < fileList.length; i++) {
                // 如果下面还有文件
                if (fileList[i].isDirectory()) {
                    size = size + getFolderSize(fileList[i]);
                } else {
                    size = size + fileList[i].length();
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return size;
    }

    /**
     * 格式化单位
     *
     * @param size
     */
    public  String getFormatSize(double size) {
        double kiloByte = size / 1024;
        double megaByte = kiloByte / 1024;
        if (megaByte < 1) {
            BigDecimal result1 = new BigDecimal(Double.toString(kiloByte));
            return result1.setScale(2, BigDecimal.ROUND_HALF_UP)
                    .toPlainString() + "KB";
        }

        double gigaByte = megaByte / 1024;
        if (gigaByte < 1) {
            BigDecimal result2 = new BigDecimal(Double.toString(megaByte));
            return result2.setScale(2, BigDecimal.ROUND_HALF_UP)
                    .toPlainString() + "MB";
        }

        double teraBytes = gigaByte / 1024;
        if (teraBytes < 1) {
            BigDecimal result3 = new BigDecimal(Double.toString(gigaByte));
            return result3.setScale(2, BigDecimal.ROUND_HALF_UP)
                    .toPlainString() + "GB";
        }
        BigDecimal result4 = new BigDecimal(teraBytes);
        return result4.setScale(2, BigDecimal.ROUND_HALF_UP).toPlainString()
                + "TB";
    }

    public String getTotalCacheSize(Context context) throws Exception {
        long cacheSize = getFolderSize(context.getCacheDir());
        if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
            cacheSize += getFolderSize(context.getExternalCacheDir());
        }
        return getFormatSize(cacheSize);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值