[关闭]
@946898963 2020-03-09T09:29:42.000000Z 字数 10448 阅读 719

BitmapFactory

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的静态工厂方法主要有以下五种:

  1. Bitmap decodeFile(...)
  2. Bitmap decodeResource(...)
  3. Bitmap decodeByteArray(...)
  4. Bitmap decodeStream(...)
  5. Bitmap decodeFileDescriptor(...)

其中常用的三个:decodeFile、decodeResource、decodeStream。decodeFile和decodeResource其实最终都是调用decodeStream方法来解析Bitmap,decodeStream的内部则是调用两个native方法解析Bitmap的:

  1. nativeDecodeAsset()
  2. nativeDecodeStream()

这两个native方法只是对应decodeFile、decodeResource和decodeStream来解析的,像decodeByteArray、decodeFileDescriptor也有专门的native方法负责解析Bitmap。

接下来就是看看这两个方法在解析Bitmap时究竟有什么区别decodeFile、decodeResource,查看后发现它们调用路径如下:

  1. decodeFile->decodeStream
  2. decodeResource->decodeResourceStream->decodeStream

decodeResource在解析时多调用了一个decodeResourceStream方法,而这个decodeResourceStream方法代码如下:

#decodeResource

  1. public static Bitmap decodeResource(Resources res, int id, Options opts) {
  2. validate(opts);
  3. Bitmap bm = null;
  4. InputStream is = null;
  5. try {
  6. final TypedValue value = new TypedValue();
  7. is = res.openRawResource(id, value);
  8. bm = decodeResourceStream(res, value, is, null, opts);
  9. } catch (Exception e) {
  10. /* do nothing.
  11. If the exception happened on open, bm will be null.
  12. If it happened on close, bm is still valid.
  13. */
  14. } finally {
  15. try {
  16. if (is != null) is.close();
  17. } catch (IOException e) {
  18. // Ignore
  19. }
  20. }
  21. if (bm == null && opts != null && opts.inBitmap != null) {
  22. throw new IllegalArgumentException("Problem decoding into existing bitmap");
  23. }
  24. return bm;
  25. }

#decodeResourceStream

  1. public static Bitmap decodeResourceStream(Resources res, TypedValue value,
  2. InputStream is, Rect pad, Options opts) {
  3. if (opts == null) {
  4. opts = new Options();
  5. }
  6. if (opts.inDensity == 0 && value != null) {
  7. final int density = value.density;
  8. if (density == TypedValue.DENSITY_DEFAULT) {//图片在drawable目录下
  9. opts.inDensity = DisplayMetrics.DENSITY_DEFAULT;
  10. } else if (density != TypedValue.DENSITY_NONE) {//图片在非drawable目录下
  11. opts.inDensity = density;
  12. }
  13. }
  14. if (opts.inTargetDensity == 0 && res != null) {
  15. opts.inTargetDensity = res.getDisplayMetrics().densityDpi;
  16. }
  17. return decodeStream(is, pad, opts);
  18. }

阅读:BitmapFactory.Options中的inDensity和inTargetDensity

它主要是对Options进行处理了,在得到opts.inDensity属性的前提下,如果我们没有对该属性设定值,那么将opts.inDensity=DisplayMetrics.DENSITY_DEFAULT;赋定这个默认的Density值,这个默认值为160,为标准的dpi比例,即在Density=160的设备上1dp=1px,这个方法中还有这么一行:

  1. opts.inTargetDensity = res.getDisplayMetrics().densityDpi;

对opts.inTargetDensity进行了赋值,该值为当前设备的densityDpi值,所以说在decodeResourceStream方法中主要做了两件事:

  1. 1、对opts.inDensity赋值,没有则赋默认值160
  2. 2、对opts.inTargetDensity赋值,没有则赋当前设备的densityDpi

之后重点来了,之后参数将传入decodeStream方法,该方法中在调用native方法进行解析Bitmap后会调用这个方法setDensityFromOptions(bm, opts);:

Android 4.4之前#decodeStream

  1. public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
  2. // we don't throw in this case, thus allowing the caller to only check
  3. // the cache, and not force the image to be decoded.
  4. if (is == null) {
  5. return null;
  6. }
  7. // we need mark/reset to work properly
  8. if (!is.markSupported()) {
  9. is = new BufferedInputStream(is, DECODE_BUFFER_SIZE);
  10. }
  11. // so we can call reset() if a given codec gives up after reading up to
  12. // this many bytes. FIXME: need to find out from the codecs what this
  13. // value should be.
  14. is.mark(1024);
  15. Bitmap bm;
  16. boolean finish = true;
  17. if (is instanceof AssetManager.AssetInputStream) {
  18. final int asset = ((AssetManager.AssetInputStream) is).getAssetInt();
  19. if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
  20. float scale = 1.0f;
  21. int targetDensity = 0;
  22. if (opts != null) {
  23. final int density = opts.inDensity;
  24. targetDensity = opts.inTargetDensity;
  25. if (density != 0 && targetDensity != 0) {
  26. scale = targetDensity / (float) density;
  27. }
  28. }
  29. bm = nativeDecodeAsset(asset, outPadding, opts, true, scale);
  30. if (bm != null && targetDensity != 0) bm.setDensity(targetDensity);
  31. finish = false;
  32. } else {
  33. bm = nativeDecodeAsset(asset, outPadding, opts);
  34. }
  35. } else {
  36. // pass some temp storage down to the native code. 1024 is made up,
  37. // but should be large enough to avoid too many small calls back
  38. // into is.read(...) This number is not related to the value passed
  39. // to mark(...) above.
  40. byte [] tempStorage = null;
  41. if (opts != null) tempStorage = opts.inTempStorage;
  42. if (tempStorage == null) tempStorage = new byte[16 * 1024];
  43. if (opts == null || (opts.inScaled && opts.inBitmap == null)) {
  44. float scale = 1.0f;
  45. int targetDensity = 0;
  46. if (opts != null) {
  47. final int density = opts.inDensity;
  48. targetDensity = opts.inTargetDensity;
  49. if (density != 0 && targetDensity != 0) {
  50. scale = targetDensity / (float) density;
  51. }
  52. }
  53. bm = nativeDecodeStream(is, tempStorage, outPadding, opts, true, scale);
  54. if (bm != null && targetDensity != 0) bm.setDensity(targetDensity);
  55. finish = false;
  56. } else {
  57. bm = nativeDecodeStream(is, tempStorage, outPadding, opts);
  58. }
  59. }
  60. if (bm == null && opts != null && opts.inBitmap != null) {
  61. throw new IllegalArgumentException("Problem decoding into existing bitmap");
  62. }
  63. return finish ? finishDecode(bm, outPadding, opts) : bm;
  64. }

#finishDecode

  1. private static Bitmap finishDecode(Bitmap bm, Rect outPadding, Options opts) {
  2. if (bm == null || opts == null) {
  3. return bm;
  4. }
  5. final int density = opts.inDensity;
  6. if (density == 0) {
  7. return bm;
  8. }
  9. bm.setDensity(density);
  10. final int targetDensity = opts.inTargetDensity;
  11. if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
  12. return bm;
  13. }
  14. byte[] np = bm.getNinePatchChunk();
  15. int[] lb = bm.getLayoutBounds();
  16. final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
  17. if (opts.inScaled || isNinePatch) {
  18. float scale = targetDensity / (float) density;
  19. if (scale != 1.0f) {
  20. final Bitmap oldBitmap = bm;
  21. bm = Bitmap.createScaledBitmap(oldBitmap, (int) (bm.getWidth() * scale + 0.5f),
  22. (int) (bm.getHeight() * scale + 0.5f), true);
  23. if (bm != oldBitmap) oldBitmap.recycle();
  24. if (isNinePatch) {
  25. np = nativeScaleNinePatch(np, scale, outPadding);
  26. bm.setNinePatchChunk(np);
  27. }
  28. if (lb != null) {
  29. int[] newLb = new int[lb.length];
  30. for (int i=0; i<lb.length; i++) {
  31. newLb[i] = (int)((lb[i]*scale)+.5f);
  32. }
  33. bm.setLayoutBounds(newLb);
  34. }
  35. }
  36. bm.setDensity(targetDensity);
  37. }
  38. return bm;
  39. }

这里我们只需要关注,当decodeStream完成后,在设备密度不为0且不等于资源密度时,会执行finishDecode,在finishDecode中会调用createScaledBitmap重新创建bitmap并回收旧的bitmap,也就是说在java层有一个调整bitmap的逻辑。接下来看Android 4.4之后的decodeStream的相关源码:

Android 4.4之后#decodeStream

  1. public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) {
  2. // we don't throw in this case, thus allowing the caller to only check
  3. // the cache, and not force the image to be decoded.
  4. if (is == null) {
  5. return null;
  6. }
  7. validate(opts);
  8. Bitmap bm = null;
  9. Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap");
  10. try {
  11. if (is instanceof AssetManager.AssetInputStream) {
  12. final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset();
  13. bm = nativeDecodeAsset(asset, outPadding, opts);
  14. } else {
  15. bm = decodeStreamInternal(is, outPadding, opts);
  16. }
  17. if (bm == null && opts != null && opts.inBitmap != null) {
  18. throw new IllegalArgumentException("Problem decoding into existing bitmap");
  19. }
  20. setDensityFromOptions(bm, opts);
  21. } finally {
  22. Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS);
  23. }
  24. return bm;
  25. }

#setDensityFromOptions

  1. private static void setDensityFromOptions(Bitmap outputBitmap, Options opts) {
  2. if (outputBitmap == null || opts == null) return;
  3. final int density = opts.inDensity;
  4. if (density != 0) {
  5. outputBitmap.setDensity(density);
  6. final int targetDensity = opts.inTargetDensity;
  7. if (targetDensity == 0 || density == targetDensity || density == opts.inScreenDensity) {
  8. return;
  9. }
  10. byte[] np = outputBitmap.getNinePatchChunk();
  11. final boolean isNinePatch = np != null && NinePatch.isNinePatchChunk(np);
  12. if (opts.inScaled || isNinePatch) {
  13. outputBitmap.setDensity(targetDensity);
  14. }
  15. } else if (opts.inBitmap != null) {
  16. // bitmap was reused, ensure density is reset
  17. outputBitmap.setDensity(Bitmap.getDefaultDensity());
  18. }
  19. }

可以看到,decodeStream方法在执行完成后,会调用setDensityFromOptions方法,该方法主要就是把前面赋值过的两个属性inDensity和inTargetDensity给Bitmap进行赋值,不过并不是直接赋给Bitmap就完了,中间有个判断,当inDensity的值与inTargetDensity或与设备的屏幕Density不相等时,则将应用inTargetDensity的值,如果相等则应用inDensity的值。

在setDensityFromOptions方法中,我们没有看到根据密度调整Bitmap的操作,这是因为Android 4.4以后,为了节省内存,将这个操作放在了native方法中,也就是C层去做Bitmap的缩放。

  1. 所以总结来说,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文件夹关系的研究与分析

BitmapFactory.cpp

从资源文件读取图片

  1. Bitmap bitmap= BitmapFactory.decodeResource(getResources(),R.drawable.after19);
  2. iv.setImageBitmap(bitmap);

从输入流中读取图片

  1. FileInputStream fis=new FileInputStream(new File(getFilesDir()+"/psb.jpg"));
  2. Bitmap bitmap= BitmapFactory.decodeStream(fis);
  3. iv.setImageBitmap(bitmap);

从数组中读取图片

  1. public static Bitmap readBitmapFromByteArray(byte[] data, int width, int height) {
  2. BitmapFactory.Options options = new BitmapFactory.Options();
  3. options.inJustDecodeBounds = true;
  4. BitmapFactory.decodeByteArray(data, 0, data.length, options);
  5. float srcWidth = options.outWidth;
  6. float srcHeight = options.outHeight;
  7. int inSampleSize = 1;
  8. if (srcHeight > height || srcWidth > width) {
  9. if (srcWidth > srcHeight) {
  10. inSampleSize = Math.round(srcHeight / height);
  11. } else {
  12. inSampleSize = Math.round(srcWidth / width);
  13. }
  14. }
  15. options.inJustDecodeBounds = false;
  16. options.inSampleSize = inSampleSize;
  17. return BitmapFactory.decodeByteArray(data, 0, data.length, options);
  18. }

BitmapFactory.Option

BitmapFactory.Option

常用操作

保存本地图片

  1. public static void writeBitmapToFile(String filePath, Bitmap b, int quality) {
  2. try {
  3. File desFile = new File(filePath);
  4. FileOutputStream fos = new FileOutputStream(desFile);
  5. BufferedOutputStream bos = new BufferedOutputStream(fos);
  6. b.compress(Bitmap.CompressFormat.JPEG, quality, bos);
  7. bos.flush();
  8. bos.close();
  9. } catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. }

图片缩放

  1. public static Bitmap bitmapScale(Bitmap bitmap, float scale) {
  2. Matrix matrix = new Matrix();
  3. matrix.postScale(scale, scale); // 长和宽放大缩小的比例
  4. Bitmap resizeBmp = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
  5. return resizeBmp;
  6. }

裁剪

  1. public Bitmap bitmapClip(Context context , int id , int x , int y){
  2. Bitmap map = BitmapFactory.decodeResource(context.getResources(), id);
  3. map = Bitmap.createBitmap(map, x, y, 120, 120);
  4. return map;
  5. }

参考链接

Android Bitmap最全面详解

深入理解Android Bitmap的各种操作

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注