Android之路—中章
Android中用到很多图片, 今日记录一下图片的一些处理, 一些优化.
Bitmap
Bitmap extends Object implements Parcelable
java.lang.Object
↳ android.graphics.Bitmap
Bitmap是图像处理中最重要的类之一, 其中可以使用这个类对图片进行一些颜色变换, 剪切, 旋转和缩放等操作.并可以指定保存图像文件.
内部枚举类
Bitmap 内部有两个枚举类: Config (配置) 和 CompressFormat (压缩格式)
枚举项 | 解释 |
---|---|
ALPHA_8 (1) | 颜色信息只由透明度组成,占8位。 |
RGB_565 (3) | 颜色信息由R(Red),G(Green),B(Blue)三部分组成,R占5位,G占6位,B占5位,总共占16位。 |
ARGB_8888 (5) | 颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占8位,总共占32位。 |
ARGB_4444 | 颜色信息由透明度与R(Red),G(Green),B(Blue)四部分组成,每个部分都占4位,总共占16位。 |
RGBA_F16 (6) | 每个像素存储在8个字节上。每个通道(用于半透明的RGB和alpha)都存储为一个半精度浮点值。 |
HARDWARE (7) | 特殊配置,当位图仅存储在图形内存中时。 |
配置ARGB_8888是Bitmap默认的颜色配置信息,也是最占空间的一种配置.
配置RGBA_F16特别适用于宽色域和HDR (高动态范围图像), 运用下面的代码打包成64位 :
1 | long color = (A & 0xffff) << 48 | (B & 0xffff) << 32 | (G & 0xffff) << 16 | (R & 0xffff); |
可以调用 Bitmap 中的方法,设置配置:
1 | public void setConfig(Config config) |
枚举项 | 解释 |
---|---|
JPEG (0) | 以JPEG压缩算法进行图像压缩,压缩后的格式可以是”.jpg”或者”.jpeg”,是一种有损压缩 |
PNG (1) | 以PNG压缩算法进行图像压缩,压缩后的格式可以是”.png”,是一种无损压缩 |
WEBP (2) | 以WebP压缩算法进行图像压缩,压缩后的格式可以是”.webp”,是一种有损压缩 |
质量相同的情况下,WebP格式图像的体积要比JPEG格式图像小40%,但是,WebP格式图像的编码时间比JPEG格式图像长8倍.
常用方法
创建位图,它有多个重载方法.
1 | //从源位图的(x,y)坐标截取width x height的位图 ① |
其实 createBitmap 还有很多重载,但基本都是上述方法②的调用,如下
1 | public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) { |
方法①返回的是方法②产生的参数, 方法③返回的是方法①,实际还是方法②, 就是设置变化 Matrix 为 NULL , 不过滤源图.
在方法②中 boolean filter 参数为true*时表示 *source 会被过滤,仅仅当变换矩阵 Matrix m 操作不仅包含移动操作,还包含别的操作时才适用.
复制位图
1 | public Bitmap copy(Config config, boolean isMutable) |
根据当前位图尺寸复制出新的位图. 将传入的配置参数 Config config 作为新位图的配置, 此时的 boolean isMutable 参数应为 true , 此时返回的位图是和原始位图一样的密度和颜色空间 ,如果不支持转换,即 boolean isMutable 为 false ,此时该方法返回NULL.
修改位图方法
1 | public void reconfigure(int width, int height, Config config) |
修改位图的为传入参数的宽,高和配置, 不影响位图的底层分配, 并且不重新创建新的位图.
对应的 get() 和 set() 方法
1 | public final int getWidth() //返回位图宽 |
其中 set() 方法内部其实是调用了 reconfigure(int width, int height, Config config) 方法, 只是参数互相用 get() 方法代替.
BitmapFactory
Bitmap创建有很多方法, 但是Bitmap的加载离不开BitmapFactory类
java.lang.Object
↳ android.graphics.BitmapFactory
BitmapFactory同样是Android中图像处理的一个重要的类, 可以取其意译理解为一个加工Bitmap的工厂.
BitmapFactory类可以加载来自不同来源的文件,创建为位图对象.
BitmapFactory的Option参数
参数方法 | 说明 |
---|---|
public boolean inJustDecodeBounds | 如果设置为true,不获取图片,不分配内存,但会返回图片的高度宽度信息 |
public int inSampleSize | 图片缩放的倍数 |
public int outWidth | 获取图片的宽度值 |
public int outHeight | 获取图片的高度值 |
public int inDensity | 用于位图的像素压缩比 |
public int inTargetDensity | 用于目标位图的像素压缩比 |
public Bitmap.Config inPreferredConfig | 设置解码器, 即设置 Bitmap 的 Config |
public int inScreenDensity | 当前屏幕的像素密度 |
BitmapFactory加载Bitmap
BitmapFactory加载位图的五种基本方法
从文件读取图片
1
public static Bitmap decodeFile(String pathName, Options opts)
从文件高效率读取图片
1
public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)
从输入流读取图片
1
public static Bitmap decodeStream(InputStream is,Rect outPadding,Options opts)
从资源文件读取图片
1
public static Bitmap decodeResource(Resources res, int id, Options opts)
从数组读取图片
1
public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)
将这些方法封装成完整的方法
从本地读取文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public static Bitmap getBitmapbyFileDescriptor(String filePath, int width, int height) {
try {
/*---------①---------*/
FileInputStream fis = new FileInputStream(filePath);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);
/*---------②---------*/
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
int inSampleSize = 1;
if (srcHeight > height || srcWidth > width) {
if (srcWidth > srcHeight) {
inSampleSize = Math.round(srcHeight / height);//四舍五入
} else {
inSampleSize = Math.round(srcWidth / width);
}
}
/*---------③---------*/
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
return BitmapFactory.decodeFileDescriptor(fis.getFD(), null, options);
} catch (Exception ex) {
}
return null;
}传入参数:文件路径, 像素的宽, 像素的高, 例如在app手机存储路径中有一张 img.jpg 的图片 :
1
2String filePath = getExternalCacheDir().toString() + "/img.jpg";
Bitmap bitmap1 = getBitmapbyFileDescriptor(filePath,1024,1024);从输入流读取文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public static Bitmap getBitmapbyInputStream(InputStream ins, int width, int height) {
/*---------①---------*/
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(ins, null, options);
/*---------②---------*/
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
int inSampleSize = 1;
if (srcHeight > height || srcWidth > width) {
if (srcWidth > srcHeight) {
inSampleSize = Math.round(srcHeight / height);
} else {
inSampleSize = Math.round(srcWidth / width);
}
}
/*---------③---------*/
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
return BitmapFactory.decodeStream(ins, null, options);
}传入参数:输入流字符串, 像素的宽, 像素的高, 例如在工程资源 drawable 文件中有一张 ic_launcher.png 图片 :
1
2
3String assPath = "/res/drawable/ic_launcher.png";
InputStream ins = getClass().getResourceAsStream(assPath);
Bitmap bitmap2 = getBitmapbyInputStream(ins,1024,1024);Resource资源加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public static Bitmap getBitmapbyResource(Resources resources, int resourcesId, int width, int height) {
/*---------①---------*/
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(resources, resourcesId, options);
/*---------②---------*/
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
int inSampleSize = 1;
if (srcHeight > height || srcWidth > width) {
if (srcWidth > srcHeight) {
inSampleSize = Math.round(srcHeight / height);
} else {
inSampleSize = Math.round(srcWidth / width);
}
}
/*---------③---------*/
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
return BitmapFactory.decodeResource(resources, resourcesId, options);
}传入参数:当前资源文件, 图片id , 像素的宽, 像素的高, 例如在工程资源drawable文件中有一张 ic_launcher.png 图片 :
1
Bitmap bitmap3 = getBitmapbyResource(getResources(),R.drawable.ic_launcher,1024,1024);
Assets资源加载
当调用资源文件夹assets中的图片时, 需要上下文 context 创建一个 AssetManager 对象, 来获取资源图片, 这是一个提供低级别的访问应用资源的Api.
1
2
3
4
5
6
7
8
9
10
11
12public Bitmap getBitmapbyAssetsFile(Context context, String filePath) {
Bitmap image = null;
AssetManager am = context.getResources().getAssets();
try {
InputStream is = am.open(filePath);
image = BitmapFactory.decodeStream(is);
is.close();
} catch (IOException e) {
e.printStackTrace();
}
return image;
}传入参数:上下文 context , 资源文件名字符串 例如在工程资源 assets 文件中有一张 ic_launcher.png 图片 :
1
2String assPath = "ic_launcher.png";
Bitmap bitmap4 = getBitmapbyAssetsFile(this,assPath);从二进制数据读取图片
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21public static Bitmap getBitmapbyByteArray(byte[] data, int width, int height) {
/*---------①---------*/
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, 0, data.length, options);
/*---------②---------*/
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
int inSampleSize = 1;
if (srcHeight > height || srcWidth > width) {
if (srcWidth > srcHeight) {
inSampleSize = Math.round(srcHeight / height);
} else {
inSampleSize = Math.round(srcWidth / width);
}
}
/*---------③---------*/
options.inJustDecodeBounds = false;
options.inSampleSize = inSampleSize;
return BitmapFactory.decodeByteArray(data, 0, data.length, options);
}从二进制数组读取时,很容易就返回NULL, 因为inJustDecodeBounds设置为true,不获取图片,不分配内存,只会返回图片的高度宽度信息, 所以要设置为false后才调用加载方式.
以上方法都可以看做以下面循序进行
①创建对象,设置 inJustDecodeBounds 为 true , 只获取其长宽信息
②将传入的像素大小与位图大小比较,获取纵横比
③设置 inJustDecodeBounds 为 false , 获取输入流的图片,调用相应的BitmapFactory方法返回对应纵横比的位图.
位图对象之间的转换
Drawable 和 Bitmap
创建bitmap时, 用Drawable的方式创建位图对象,能够有很多好处,简单看下表:
对比项 | 清晰度 | 占用内存 | 缩放 | 色差调整 | 旋转 | 透明色 | 绘制速度 | 像素操作 |
---|---|---|---|---|---|---|---|---|
Bitmap | 相同 | 大 | 支持 | 支持 | 支持 | 支持 | 慢 | 支持 |
Drawable | 相同 | 小 | 支持 | 不支持 | 支持 | 支持 | 快 | 不支持 |
1 | BitmapDrawable bpd = (BitmapDrawable) getResources().getDrawable(R.drawable.xxx); |
BitmapDrawable里面封装了一个图片对象,调用它的 getBitmap() 方法, 转为为一个Bitmap对象.
Bitmap 和 Byte[]
1
2
3
4ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
byte[] imgbytes = baos.toByteArray();
baos.close();
创建二进制数组输出对象, 调用bitmap的压缩方法, 设置压缩方式, 转换为二进制数组.
Bitmap的内存优化
在Android系统中,读取位图Bitmap时,分给虚拟机中的图片的堆栈大小只有8M,如果超出了,就会出现 OOM (OutOfMemory) 异常。所以,对于图片的内存优化,是Android应用开发中比较重要的内容.
及时回收Bitmap
在Android2.3.3之前推荐使用Bitmap.recycle()方法进行Bitmap的内存回收
1
2
3
4
5if (!bmp.isRecycle()) {
bmp.recycle(); //回收图片所占的内存
bitmap = null;
system.gc(); //提醒系统及时回收
}虽然调用recycle()并不能保证立即释放占用的内存,但是可以加速Bitmap的内存的释放。
释放内存以后,就不能再使用该Bitmap对象了,如果再次使用,就会抛出异常。所以一定要保证不再使用的时候释放。可以在Activity的onStop()或者onDestroy()方法中进行回收。系统会在内存不足的情况下杀死一些低优先级的进程,以提供给其它进程充足的内存空间。在实际项目开发过程中,作为开发者,尽量会在退出程序的时候使用Process.killProcess(Process.myPid())的方式将自己的进程杀死,而不是仅仅使用Activity.finish()方法的方式关闭掉所有的Activity.
捕获异常
因为Bitmap非常耗内存,了避免应用在分配Bitmap内存的时候出现OutOfMemory异常以后Crash掉,在实例化Bitmap的代码中,一定要对OutOfMemory异常进行捕获,例如上面加载Bitmap的方法中那样.
缓存通用的Bitmap对象
使用LruCache对Bitmap进行缓存,当再次使用到这个Bitmap的时候直接获取,而不用重走编码流程。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22private LruCache<String, Bitmap> mMemoryCache;
//获取LruCache的缓存大小,计算容量
int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
int cacheSize = maxMemory / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
protected int sizeOf(String key, Bitmap bitmap) {
return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
}
};
//LruCache的存
private void putBitmapToMemoryCache(String key, Bitmap bitmap) {
if (getBitmapFromMemCache(key) == null) {
mMemoryCache.put(key, bitmap);
}
}
//LruCache的取
private Bitmap getBitmapFromMemCache(String key) {
return mMemoryCache.get(key);
}