@946898963
2020-03-09T17:29:42.000000Z
字数 10448
阅读 845
Bitmap
BitmapFactory是一个工具类,它提供了大量的方法,这个方法可用于不同的数据源(包括文件、流, 和字节数组)来解析、创建 Bitmap对象。
方法 | 说明 |
---|---|
decodeFile(String pathName, Options opts) | 从文件读取图片 |
decodeFile(String pathName) | 从文件读取图片 |
decodeFileDescriptor(FileDescriptor fd) | 从文件读取文件 与decodeFile不同的是这个直接调用JNI函数进行读取 效率比较高 |
decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts) | 同上 |
decodeStream(InputStream is) | 从输入流读取图片 |
decodeStream(InputStream is, Rect outPadding, Options opts) | 从输入流读取图片 |
decodeStream(InputStream is, Rect outPadding, Options opts) | 从资源文件读取图片 |
decodeResource(Resources res, int id) | 从资源文件读取图片 |
decodeResource(Resources res, int id, Options opts) | 从资源文件读取图片 |
decodeByteArray(byte[] data, int offset, int length) | 从数组读取图片 |
decodeByteArray(byte[] data, int offset, int length, Options opts) | 从数组读取图片 |
BitmapFactory提供的解析Bitmap的静态工厂方法主要有以下五种:
Bitmap decodeFile(...)
Bitmap decodeResource(...)
Bitmap decodeByteArray(...)
Bitmap decodeStream(...)
Bitmap decodeFileDescriptor(...)
其中常用的三个:decodeFile、decodeResource、decodeStream。decodeFile和decodeResource其实最终都是调用decodeStream方法来解析Bitmap,decodeStream的内部则是调用两个native方法解析Bitmap的:
nativeDecodeAsset()
nativeDecodeStream()
这两个native方法只是对应decodeFile、decodeResource和decodeStream来解析的,像decodeByteArray、decodeFileDescriptor也有专门的native方法负责解析Bitmap。
接下来就是看看这两个方法在解析Bitmap时究竟有什么区别decodeFile、decodeResource,查看后发现它们调用路径如下:
decodeFile->decodeStream
decodeResource->decodeResourceStream->decodeStream
decodeResource在解析时多调用了一个decodeResourceStream方法,而这个decodeResourceStream方法代码如下:
#decodeResource
public static Bitmap decodeResource(Resources res, int id, Options opts) {
validate(opts);
Bitmap bm = null;
InputStream is = null;
try {
final TypedValue value = new TypedValue();
is = res.openRawResource(id, value);
bm = decodeResourceStream(res, value, is, null, opts);
} catch (Exception e) {
/* do nothing.
If the exception happened on open, bm will be null.
If it happened on close, bm is still valid.
*/
} finally {
try {
if (is != null) is.close();
} catch (IOException e) {
// Ignore
}
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
return bm;
}
#decodeResourceStream
public static Bitmap decodeResourceStream(Resources res, TypedValue value,
InputStream is, Rect pad, Options opts) {
if (opts == null) {
opts = new Options();
}
if (opts.inDensity == 0 && value != null) {
final int density = value.density;
if (density == TypedValue.DENSITY_DEFAULT) {//图片在drawable目录下
opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
} else if (density != TypedValue.DENSITY_NONE) {//图片在非drawable目录下
opts.inDensity = density;
}
}
if (opts.inTargetDensity == 0 && res != null) {
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
}
return decodeStream(is, pad, opts);
}
阅读:BitmapFactory.Options中的inDensity和inTargetDensity。
它主要是对Options进行处理了,在得到opts.inDensity属性的前提下,如果我们没有对该属性设定值,那么将opts.inDensity=DisplayMetrics.DENSITY_DEFAULT;赋定这个默认的Density值,这个默认值为160,为标准的dpi比例,即在Density=160的设备上1dp=1px,这个方法中还有这么一行:
opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
对opts.inTargetDensity进行了赋值,该值为当前设备的densityDpi值,所以说在decodeResourceStream方法中主要做了两件事:
1、对opts.inDensity赋值,没有则赋默认值160
2、对opts.inTargetDensity赋值,没有则赋当前设备的densityDpi值
之后重点来了,之后参数将传入decodeStream方法,该方法中在调用native方法进行解析Bitmap后会调用这个方法setDensityFromOptions(bm, opts);:
Android 4.4之前#decodeStream
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
// we don't throw in this case, thus allowing the caller to only check
// the cache, and not force the image to be decoded.
if (is == null) {
return null;
}
// we need mark/reset to work properly
if (!is.markSupported()) {
is = new BufferedInputStream(is, DECODE_BUFFER_SIZE);
}
// so we can call reset() if a given codec gives up after reading up to
// this many bytes. FIXME: need to find out from the codecs what this
// value should be.
is.mark(1024);
Bitmap bm;
boolean finish = true;
if (is instanceof AssetManager.AssetInputStream) {
final int asset = ((AssetManager.AssetInputStream) is).getAssetInt();
if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
float scale = 1.0f;
int targetDensity = 0;
if (opts != null) {
final int density = opts.inDensity;
targetDensity = opts.inTargetDensity;
if (density != 0 && targetDensity != 0) {
scale = targetDensity / (float) density;
}
}
bm = nativeDecodeAsset(asset, outPadding, opts, true, scale);
if (bm != null && targetDensity != 0) bm.setDensity(targetDensity);
finish = false;
} else {
bm = nativeDecodeAsset(asset, outPadding, opts);
}
} else {
// pass some temp storage down to the native code. 1024 is made up,
// but should be large enough to avoid too many small calls back
// into is.read(...) This number is not related to the value passed
// to mark(...) above.
byte [] tempStorage = null;
if (opts != null) tempStorage = opts.inTempStorage;
if (tempStorage == null) tempStorage = new byte[16 * 1024];
if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
float scale = 1.0f;
int targetDensity = 0;
if (opts != null) {
final int density = opts.inDensity;
targetDensity = opts.inTargetDensity;
if (density != 0 && targetDensity != 0) {
scale = targetDensity / (float) density;
}
}
bm = nativeDecodeStream(is, tempStorage, outPadding, opts, true, scale);
if (bm != null && targetDensity != 0) bm.setDensity(targetDensity);
finish = false;
} else {
bm = nativeDecodeStream(is, tempStorage, outPadding, opts);
}
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
return finish ? finishDecode(bm, outPadding, opts) : bm;
}
#finishDecode
private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
if (bm == null || opts == null) {
return bm;
}
final int density = opts.inDensity;
if (density == 0) {
return bm;
}
bm.setDensity(density);
final int targetDensity = opts.inTargetDensity;
if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
return bm;
}
byte[] np = bm.getNinePatchChunk();
int[] lb = bm.getLayoutBounds();
final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
if (opts.inScaled || isNinePatch) {
float scale = targetDensity / (float) density;
if (scale != 1.0f) {
final Bitmap oldBitmap = bm;
bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
(int) (bm.getHeight() * scale + 0.5f), true);
if (bm != oldBitmap) oldBitmap.recycle();
if (isNinePatch) {
np = nativeScaleNinePatch(np, scale, outPadding);
bm.setNinePatchChunk(np);
}
if (lb != null) {
int[] newLb = new int[lb.length];
for (int i=0; i<lb.length; i++) {
newLb[i] = (int)((lb[i]*scale)+.5f);
}
bm.setLayoutBounds(newLb);
}
}
bm.setDensity(targetDensity);
}
return bm;
}
这里我们只需要关注,当decodeStream完成后,在设备密度不为0且不等于资源密度时,会执行finishDecode,在finishDecode中会调用createScaledBitmap重新创建bitmap并回收旧的bitmap,也就是说在java层有一个调整bitmap的逻辑。接下来看Android 4.4之后的decodeStream的相关源码:
Android 4.4之后#decodeStream
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
// we don't throw in this case, thus allowing the caller to only check
// the cache, and not force the image to be decoded.
if (is == null) {
return null;
}
validate(opts);
Bitmap bm = null;
Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
try {
if (is instanceof AssetManager.AssetInputStream) {
final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
bm = nativeDecodeAsset(asset, outPadding, opts);
} else {
bm = decodeStreamInternal(is, outPadding, opts);
}
if (bm == null && opts != null && opts.inBitmap != null) {
throw new IllegalArgumentException("Problem decoding into existing bitmap");
}
setDensityFromOptions(bm, opts);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
}
return bm;
}
#setDensityFromOptions
private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
if (outputBitmap == null || opts == null) return;
final int density = opts.inDensity;
if (density != 0) {
outputBitmap.setDensity(density);
final int targetDensity = opts.inTargetDensity;
if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
return;
}
byte[] np = outputBitmap.getNinePatchChunk();
final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
if (opts.inScaled || isNinePatch) {
outputBitmap.setDensity(targetDensity);
}
} else if (opts.inBitmap != null) {
// bitmap was reused, ensure density is reset
outputBitmap.setDensity(Bitmap.getDefaultDensity());
}
}
可以看到,decodeStream方法在执行完成后,会调用setDensityFromOptions方法,该方法主要就是把前面赋值过的两个属性inDensity和inTargetDensity给Bitmap进行赋值,不过并不是直接赋给Bitmap就完了,中间有个判断,当inDensity的值与inTargetDensity或与设备的屏幕Density不相等时,则将应用inTargetDensity的值,如果相等则应用inDensity的值。
在setDensityFromOptions方法中,我们没有看到根据密度调整Bitmap的操作,这是因为Android 4.4以后,为了节省内存,将这个操作放在了native方法中,也就是C层去做Bitmap的缩放。
所以总结来说,setDensityFromOptions方法就是把inTargetDensity的值赋给Bitmap,不过前提是opts.inScaled = true;
进过上面的分析,可以得出这样一个结论:
在不配置Options的情况下:
1、decodeFile、decodeStream在解析时不会对Bitmap进行一系列的屏幕适配,解析出来的将是原始大小的图。
2、decodeResource在解析时会对Bitmap根据当前设备屏幕像素密度densityDpi的值进行缩放适配操作,使得解析出来的Bitmap与当前设备的分辨率匹配,达到一个最佳的显示效果,并且Bitmap的大小将比原始的大。
BitmapFactory.decodeResource加载的图片可能会经过缩放,在Android 4.4之前,该缩放目前是放在java层做的,效率比较低,而且需要消耗java层的内存。因此,在Android 4.4之前,如果大量使用该接口加载图片,容易导致OOM错误;而在Android 4.4之后,这个操作放在了Native层,也就是C层去做,节省了Java层的内存空间,不会出现上述问题。
BitmapFactory.decodeStream不会对所加载的图片进行缩放,在Android 4.4之前,相比之下占用内存少,效率更高,但是decodeStream直接拿的图片来读取字节码了,不会根据机器的各种分辨率来自动适应,所以使用了decodeStream的时候,需要在hdpi和mdpi,ldpi中配置相应的图片资源,否则在不同分辨率机器上都是同样大小(像素点数量),显示出来的大小就不对了。
这两个接口各有用处,关于如何选择这两个方法,在Android 4.4之前,除了使用场景之外,还需要考虑性能方面,如果对性能要求较高,则应该使用decodeStream;如果对性能要求不高,且需要Android自带的图片自适应缩放功能,则可以使用decodeResource;在Android 4.4之后,则只需要根据具体的使用场景去考虑即可。
部分摘自:Android性能优化之Bitmap的内存优化。
建议阅读:安卓屏幕的尺寸信息&Android中图片大小、内存占用与drawable文件夹关系的研究与分析
从资源文件读取图片
Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.drawable.after19);
iv.setImageBitmap(bitmap);
从输入流中读取图片
FileInputStream fis=new FileInputStream(new File(getFilesDir()+"/psb.jpg"));
Bitmap bitmap= BitmapFactory.decodeStream(fis);
iv.setImageBitmap(bitmap);
从数组中读取图片
public static Bitmap readBitmapFromByteArray(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);
}
保存本地图片
public static void writeBitmapToFile(String filePath, Bitmap b, int quality) {
try {
File desFile = new File(filePath);
FileOutputStream fos = new FileOutputStream(desFile);
BufferedOutputStream bos = new BufferedOutputStream(fos);
b.compress(Bitmap.CompressFormat.JPEG, quality, bos);
bos.flush();
bos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
图片缩放
public static Bitmap bitmapScale(Bitmap bitmap, float scale) {
Matrix matrix = new Matrix();
matrix.postScale(scale, scale); // 长和宽放大缩小的比例
Bitmap resizeBmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
return resizeBmp;
}
裁剪
public Bitmap bitmapClip(Context context , int id , int x , int y){
Bitmap map = BitmapFactory.decodeResource(context.getResources(), id);
map = Bitmap.createBitmap(map, x, y, 120, 120);
return map;
}
参考链接