Android之Bitmap总结(加载、尺寸压缩、优化)

关于Bitmap加载

Bitmap的加载离不开BitmapFactory类,BitmapFactory类提供了四类方法用来加载Bitmap:

  • 第一种: BitmapFactory.decodeFile,从文件系统加载
    a. 通过Intent打开本地图片或照片
    b. 在onActivityResult中获取图片uri
    c. 根据uri获取图片的路径
    d. 根据路径解析bitmap:Bitmap bm = BitmapFactory.decodeFile(sd_path)
  • 第二种:BitmapFactory.decodeResource,以R.drawable.xxx的形式从本地资源中加载
    Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.aaa);
  • 第三种:BitmapFactory.decodeStream,从输入流加载
    a.开启异步线程去获取网络图片
    b.网络返回InputStream
    c.解析:Bitmap bm = BitmapFactory.decodeStream(stream),这是一个耗时操作,要在子线程中执行
  • 第四种:BitmapFactory.decodeByteArray, 从字节数组中加载
    a.开启异步线程去获取网络图片
    b.网络返回InputStream
    c. 把InputStream转换成byte[]
    d. 解析:Bitmap bm = BitmapFactory.decodeByteArray(myByte,0,myByte.length);

关于Bitmap的优化

提到bitmap,我们通常会联想到内存溢出,主要的原因就是我们加载的图片内存过大以及每个应用的内存有限。所以我们经常会看到报这种错误:

Bitmap too large to be uploaded into a texture (3120x4160, max=4096x4096)

Caused by: java.lang.OutOfMemoryError: Failed to allocate a 144764940 byte allocation with 16765264 free bytes and 109MB until OOM

一、Bitmap优化之高效加载---尺寸压缩

主要的做法就是使用系统提供给我们Options类来处理Bitmap。
通过BitmapFactory.Options按一定的采样率来加载缩小后的图片,然后在ImageView中使用缩小的图片这样就会降低内存占用避免【OOM】,提高了Bitamp加载时的性能。
这其实就是我们常说的图片压缩方案之尺寸压缩
尺寸压缩是压缩图片的像素,一张图片所占内存的大小 =【图片类型】(ALPHA_8/ARGB_4444/ARGB_8888/RGB_256)*【宽】*【高】,通过改变三个值减小图片所占的内存,防止OOM,当然这种方式可能会使图片失真 。

对于上面提到的图片类型,也就是android 色彩模式说明:

  1. ALPHA_8:每个像素占用1byte内存。
  2. ARGB_4444:每个像素占用2byte内存
  3. ARGB_8888:每个像素占用4byte内存
  4. RGB_565:每个像素占用2byte内存
    而android默认的色彩模式就是ARGB_8888,质量最高,同时内存也是最大。效果也肯定是最好的。

假设一张10241024,模式为ARGB_8888的图片,那么它占有的内存就是:10241024*4 = 4MB

Options类的inPreferredConfig

用这个改变内存大小的三个值得第一个。图片类型,指定为更小的色彩模式,可以让图片的内存变小。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig= Bitmap.Config.RGB_565;

Options类的inSampleSize采样率

options.inPreferredConfig=2;

通过采样率改变内存大小的三个值中的宽和高这两个值,采样率同时作用于宽和高。
以下有几点注意事项:

  • 当inSampleSize=1时,采样后的图片为图片的原始大小。
  • 除了1以外,inSampleSize的取值应该总为2的整数倍,否则会【向下取整】,取一个最接近2的整数倍,比如inSampleSize=3时,系统会取inSampleSize=2
  • 当inSampleSize=2时,采样后的图片的宽高均为原始图片宽高的1/2

假设一张10241024,模式为ARGB_8888的图片,inSampleSize=2,原始占用内存大小是4MB,采样后的图片占用内存大小就是(1024/2) * (1024/2 ) 4 = 1MB

我们用一个小例子来验证上述讲的:

代码如下:

public class MainActivity extends AppCompatActivity {
    ImageView image;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main_test);
        image= (ImageView) findViewById(R.id.image);
        //获取我们要显示的图片
        Bitmap bm = decodeBitmapFromResource();
        //通过imageview显示出来
        image.setImageBitmap(bm);
    }

    //在这个方法里我们对图片做处理
    private Bitmap decodeBitmapFromResource(){

        Log.d("log","bitmap原始图片内存大小:"+BitmapFactory.decodeResource(getResources(), R.drawable.tupian).getAllocationByteCount());
        //获取  BitmapFactory.Options的实例
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig= Bitmap.Config.RGB_565;
        //inJustDecodeBounds参数设为true并加载图片。
        //这样做的原因是inJustDecodeBounds为true时
        //BitmapFactory只会解析图片的原始宽高信息,并不会真正的加载图片。
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(getResources(), R.drawable.tupian, options);
        Log.d("log","原始图片的宽width:" +options.outWidth+"原始图片的高height:"+options.outHeight);
//        Log.d("log","bitmap原始图片内存大小:"+ bitmap.getAllocationByteCount());
    //原始图片的宽高已经存放在options中。options.outWidth和options.outHeight
    //我们可以用它去做一些判断,得到我们想要的采样率
        //options.inSampleSize = calculateSampleSize(options,300,300);
        options.inSampleSize=2;
    //得到采样率后,将BitmapFacpry.Options的inSampleSize参数设为false并重新加载图片。
    //此时是真正的加载图片。
        options.inJustDecodeBounds =false;
        Bitmap newbitmap=BitmapFactory.decodeResource(getResources(),R.drawable.tupian,options);
        Log.d("log","压缩后图片的宽width:" +options.outWidth+"压缩后图片的高height:"+options.outHeight);
        Log.d("log","bitmap压缩后图片内存大小:"+ newbitmap.getByteCount());
        return  BitmapFactory.decodeResource(getResources(),R.drawable.tupian,options);
    }
}
原始图片

log.输出

根据输出,我们可得到没有对图片处理前图片类型:

1411200/600/588=4;也就是android默认的色彩模式就是ARGB_8888,4byte

而代码中我们通过设置这两个值

options.inPreferredConfig= Bitmap.Config.RGB_565;
options.inSampleSize=2;

使得类型为RGB_565--2byte,同时宽高同时为原来的1/2,此时图片的内存为=2* 300 *294=176400;


二、Bitmap优化之日常使用注意事项

【1】对于不再使用的bitmap,要及时清理回收,Activity的onStop()或者onDestroy()方法中进行回收: bitmap.recycle(); //回收图片所占的内存
【2】捕获OutOfMemoryError
因为Bitmap非常耗内存,了避免应用在分配Bitmap内存的时候出现OutOfMemory异常以后Crash掉,需要特别注意实例化Bitmap部分的代码。通常,在实例化Bitmap的代码中,一定要对OutOfMemory异常进行捕获。很多开发者会习惯性的在代码中直接捕获Exception。但是对于OutOfMemoryError来说,这样做是捕获不到的。因为OutOfMemoryError是一种Error,而不是Exception。
Bitmap bitmap = null;
    try {
        // 实例化Bitmap
        bitmap = BitmapFactory.decodeFile(path);
    } catch (OutOfMemoryError e) {

    }
    if (bitmap == null) {
        return defaultBitmapMap; // 如果实例化失败 返回默认的Bitmap对象
    }
【3】不重复创建相同的bitmap
如果不进行缓存,尽管看到的是同一张图片文件,但是使用BitmapFactory类的方法来实例化出来的Bitmap,是不同的Bitmap对象。缓存可以避免新建多个Bitmap对象,避免内存的浪费。

如果我们的程序中要创建多个bitmap,比如某个场景,我们要在屏幕上洒各式各样的花瓣,但这其中总会有重复的,这里我们就可以使用一个hashmap,对每个bitmap以一个值作为标识,然后在创建函数中进行判断,

//简易代码,大概表达思想:
 HashMap<Integer, Bitmap> bitmapMap = new HashMap<Integer, Bitmap>();
 static Bitmap createBitmap(int value){
 Bitmap bitmap = bitmapMap.get(value);
        if (bitmap == null) {
           bitmap =BitmapFactory.decodeResource(getResources(), R.drawable.value);
            bitmapMap.put(value, bitmap);
        }
        return bitmap;
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容