关于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 色彩模式说明:
- ALPHA_8:每个像素占用1byte内存。
- ARGB_4444:每个像素占用2byte内存
- ARGB_8888:每个像素占用4byte内存
- 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);
}
}
根据输出,我们可得到没有对图片处理前图片类型:
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;
}