Glide原理(三):图片解析处理、ImageView保证大小详解手机开发

Glide 怎么判断解析图片的

Glide 怎么保证ImageView宽高?

Glide 怎么判断图片旋转角度


以上三个问题是我自己在做一个图片池遇到的问题,趁机好好学习Glide

Glide解码的类在 Downsampler中,它的注释上写着:

Downsamples, decodes, and rotates images according to their exif orientation.

根据图像文件采样、解码、旋转图像

有几个重要的类:
  • private final BitmapPool bitmapPool;

Bitmap池,是一个LruCache,内部保留着使用过的Bitmap,重复使用

  • private final DisplayMetrics displayMetrics;

屏幕信息

  • private final ArrayPool byteArrayPool;

同样是一个LruCache,最大是4MB,充当一个byte的buffer

  • private final List parsers;

图像的头部信息解析器,例如图像类型、旋转角度等信息;

ImageHeaderParser有两个实现类:DefaultImageHeaderParser、 ExifInterfaceImageHeaderParser

DefaultImageHeaderParser是Glide自定义的默认的头部解析器

ExifInterfaceImageHeaderParser是支持SDK 27的基于 exifInterface的解析器

  • private final HardwareConfigState hardwareConfigState = HardwareConfigState.getInstance();

目测跟硬件加速有关,具体还不清楚

计算图像大小、缩放、计算角度、解码、旋转
public Resource<Bitmap> decode(InputStream is, int requestedWidth, int requestedHeight, 
Options options, DecodeCallbacks callbacks) throws IOException {
 
// 以下具体是做一系列的BitmapFactory.Options的配置 
byte[] bytesForOptions = byteArrayPool.get(ArrayPool.STANDARD_BUFFER_SIZE_BYTES, byte[].class); 
BitmapFactory.Options bitmapFactoryOptions = getDefaultOptions(); // 获取Option 
bitmapFactoryOptions.inTempStorage = bytesForOptions; // 设定一个可重复使用的临时存储空间 
DecodeFormat decodeFormat = options.get(DECODE_FORMAT); 
DownsampleStrategy downsampleStrategy = options.get(DownsampleStrategy.OPTION); 
boolean fixBitmapToRequestedDimensions = options.get(FIX_BITMAP_SIZE_TO_REQUESTED_DIMENSIONS);  
boolean isHardwareConfigAllowed = 
options.get(ALLOW_HARDWARE_CONFIG) != null && options.get(ALLOW_HARDWARE_CONFIG); // 是否允许硬件加速 
try {
 
// 进行解码流程 
Bitmap result = decodeFromWrappedStreams(is, bitmapFactoryOptions, 
downsampleStrategy, decodeFormat, isHardwareConfigAllowed, requestedWidth, 
requestedHeight, fixBitmapToRequestedDimensions, callbacks); 
return BitmapResource.obtain(result, bitmapPool); 
} finally {
 
releaseOptions(bitmapFactoryOptions); 
byteArrayPool.put(bytesForOptions); 
} 
} 
// 真正解码的流程 
private Bitmap decodeFromWrappedStreams(InputStream is, 
BitmapFactory.Options options, DownsampleStrategy downsampleStrategy, 
DecodeFormat decodeFormat, boolean isHardwareConfigAllowed, int requestedWidth, 
int requestedHeight, boolean fixBitmapToRequestedDimensions, 
DecodeCallbacks callbacks) throws IOException {
 
//1. 计算图像大小 
int[] sourceDimensions = getDimensions(is, options, callbacks, bitmapPool); 
int sourceWidth = sourceDimensions[0]; 
int sourceHeight = sourceDimensions[1]; 
String sourceMimeType = options.outMimeType; 
if (sourceWidth == -1 || sourceHeight == -1) {
 
isHardwareConfigAllowed = false; 
} 
//2. 计算图像角度 
int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool); 
int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation); 
boolean isExifOrientationRequired = TransformationUtils.isExifOrientationRequired(orientation); 
// 设定图像需要的目标大小 
int targetWidth = requestedWidth == Target.SIZE_ORIGINAL ? sourceWidth : requestedWidth; 
int targetHeight = requestedHeight == Target.SIZE_ORIGINAL ? sourceHeight : requestedHeight; 
ImageType imageType = ImageHeaderParserUtils.getType(parsers, is, byteArrayPool); 
// 计算调整Option关于inSamplerSize、inScale、inDensity等参数 
calculateScaling( 
imageType, 
is, 
callbacks, 
bitmapPool, 
downsampleStrategy, 
degreesToRotate, 
sourceWidth, 
sourceHeight, 
targetWidth, 
targetHeight, 
options); 
calculateConfig( 
is, 
decodeFormat, 
isHardwareConfigAllowed, 
isExifOrientationRequired, 
options, 
targetWidth, 
targetHeight); 
// 重新设定图像最后需要的大小 
boolean isKitKatOrGreater = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 
if ((options.inSampleSize == 1 || isKitKatOrGreater) && shouldUsePool(imageType)) {
 
int expectedWidth; 
int expectedHeight; 
if (sourceWidth >= 0 && sourceHeight >= 0 
&& fixBitmapToRequestedDimensions && isKitKatOrGreater) {
 
expectedWidth = targetWidth; 
expectedHeight = targetHeight; 
} else {
 
float densityMultiplier = isScaling(options) 
? (float) options.inTargetDensity / options.inDensity : 1f; 
int sampleSize = options.inSampleSize; 
int downsampledWidth = (int) Math.ceil(sourceWidth / (float) sampleSize); 
int downsampledHeight = (int) Math.ceil(sourceHeight / (float) sampleSize); 
expectedWidth = Math.round(downsampledWidth * densityMultiplier); 
expectedHeight = Math.round(downsampledHeight * densityMultiplier); 
} 
// 如果不是image,或者BitmapFactory无法解析,expectedWidth/expectedHeight都会为-1 
if (expectedWidth > 0 && expectedHeight > 0) {
 
// 设置InBitmap可复用的bitmap 
setInBitmap(options, bitmapPool, expectedWidth, expectedHeight); 
} 
} 
// 3. 解码 
Bitmap downsampled = decodeStream(is, options, callbacks, bitmapPool); 
callbacks.onDecodeComplete(bitmapPool, downsampled); 
Bitmap rotated = null; 
if (downsampled != null) {
 
// 如果缩放过,bitmap的密度inTargetDensity, 等于设定bitmap密度为屏幕的密度 
downsampled.setDensity(displayMetrics.densityDpi); 
// 4.旋转图像 
rotated = TransformationUtils.rotateImageExif(bitmapPool, downsampled, orientation); 
if (!downsampled.equals(rotated)) {
 
// 加入缓存池 
bitmapPool.put(downsampled); 
} 
} 
return rotated; 
} 

这里说下Bitmap Options的几个参数:

  • inSamplerSize:采样率,2的幂次方
  • inDesity: 位图使用的像素密度
  • inTargetDesity: 设备的屏幕密度
  • inScale: 是否需要放缩位图

inScale设置为true时,且inDenstiy和inTargetDensity也不为0时,位图将在加载时(解码)时放缩去匹配inTargetDensity,在绘制到canvas时不会依赖图像系统放缩。

看完上面的流程,Glide可以说把复用机制用到了极致了,不仅用缓存池,还有上了inBitmap等参数。

这里我只关心几个问题:

1. 计算图像大小
  private static int[] getDimensions(InputStream is, BitmapFactory.Options options, 
DecodeCallbacks decodeCallbacks, BitmapPool bitmapPool) throws IOException {
 
options.inJustDecodeBounds = true; 
decodeStream(is, options, decodeCallbacks, bitmapPool); 
options.inJustDecodeBounds = false; 
return new int[] {
 options.outWidth, options.outHeight }; 
} 

跟日常我们使用的差不多,也是用inJustDecodeBounds参数,不多说

2. 计算图像角度
    int orientation = ImageHeaderParserUtils.getOrientation(parsers, is, byteArrayPool); 
int degreesToRotate = TransformationUtils.getExifOrientationDegrees(orientation); 

以上说了parsers有两种类型: DefaultImageHeaderParser、 ExifInterfaceImageHeaderParser

DefaultImageHeaderParser是Glide自定义的默认的头部解析器,自己解析头部信息

ExifInterfaceImageHeaderParser 是利用ExifInterface接口,比较有借鉴意义:

// ExifInterfaceImageHeaderParser 
public int getOrientation(@NonNull InputStream is, @NonNull ArrayPool byteArrayPool) 
throws IOException {
 
ExifInterface exifInterface = new ExifInterface(is); 
int result = exifInterface.getAttributeInt( 
ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); 
if (result == ExifInterface.ORIENTATION_UNDEFINED) {
 
return ImageHeaderParser.UNKNOWN_ORIENTATION; 
} 
return result; 
} 
// TransformationUtils 
public static int getExifOrientationDegrees(int exifOrientation) {
 
final int degreesToRotate; 
switch (exifOrientation) {
 
case ExifInterface.ORIENTATION_TRANSPOSE: 
case ExifInterface.ORIENTATION_ROTATE_90: 
degreesToRotate = 90; 
break; 
case ExifInterface.ORIENTATION_ROTATE_180: 
case ExifInterface.ORIENTATION_FLIP_VERTICAL: 
degreesToRotate = 180; 
break; 
case ExifInterface.ORIENTATION_TRANSVERSE: 
case ExifInterface.ORIENTATION_ROTATE_270: 
degreesToRotate = 270; 
break; 
default: 
degreesToRotate = 0; 
break; 
} 
return degreesToRotate; 
} 
3. 解码
private static Bitmap decodeStream(InputStream is, BitmapFactory.Options options, 
DecodeCallbacks callbacks, BitmapPool bitmapPool) throws IOException {
 
if (options.inJustDecodeBounds) {
 
is.mark(MARK_POSITION); 
} else {
 
callbacks.onObtainBounds(); 
} 
String outMimeType = options.outMimeType; 
final Bitmap result; 
TransformationUtils.getBitmapDrawableLock().lock(); 
try {
 
result = BitmapFactory.decodeStream(is, null, options); 
} catch (IllegalArgumentException e) {
 
... 
} 
if (options.inJustDecodeBounds) {
 
is.reset(); 
} 
return result; 
} 

Glide的解码也是利用了BitmapFactory.decodeStream()。

这里可以学到的是:通过mark和reset可以复用InputStream,这里的InputStream是Glide自己实现的 RecyclableBufferedInputStream

但是FileInputStream是不支持mark和reset的,不过你可以使用BufferedInputStream,它提供“缓冲功能”以及支持“mark()标记”和“reset()重置方法”。本质上通过一个内部缓冲区数组实现的,将输入流的数据分批的填入到缓冲区中,当缓冲区中的数据被读完之后,输入流会再次填充数据缓冲区,如此反复,直到读完。

4.旋转图像
public static Bitmap rotateImageExif(@NonNull BitmapPool pool, @NonNull Bitmap inBitmap, 
int exifOrientation) {
 
// 判断是否需要旋转 
if (!isExifOrientationRequired(exifOrientation)) {
 
return inBitmap; 
} 
final Matrix matrix = new Matrix(); 
initializeMatrixForRotation(exifOrientation, matrix); 
// From Bitmap.createBitmap. 
final RectF newRect = new RectF(0, 0, inBitmap.getWidth(), inBitmap.getHeight()); 
matrix.mapRect(newRect); 
final int newWidth = Math.round(newRect.width()); 
final int newHeight = Math.round(newRect.height()); 
Bitmap.Config config = getNonNullConfig(inBitmap); 
// 从缓存池获取一个bitmap 
Bitmap result = pool.get(newWidth, newHeight, config); 
matrix.postTranslate(-newRect.left, -newRect.top); 
// 绘制一个新的bitmap 
applyMatrix(inBitmap, result, matrix); 
return result; 
} 
// 判断是否需要旋转 
public static boolean isExifOrientationRequired(int exifOrientation) {
 
switch (exifOrientation) {
 
case ExifInterface.ORIENTATION_FLIP_HORIZONTAL: 
case ExifInterface.ORIENTATION_ROTATE_180: 
case ExifInterface.ORIENTATION_FLIP_VERTICAL: 
case ExifInterface.ORIENTATION_TRANSPOSE: 
case ExifInterface.ORIENTATION_ROTATE_90: 
case ExifInterface.ORIENTATION_TRANSVERSE: 
case ExifInterface.ORIENTATION_ROTATE_270: 
return true; 
default: 
return false; 
} 
} 
// 利用 matrix 绘制一个新的bitmap 
private static void applyMatrix(@NonNull Bitmap inBitmap, @NonNull Bitmap targetBitmap, 
Matrix matrix) {
 
BITMAP_DRAWABLE_LOCK.lock(); 
try {
 
Canvas canvas = new Canvas(targetBitmap); 
canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT); 
clear(canvas); 
} finally {
 
BITMAP_DRAWABLE_LOCK.unlock(); 
} 
} 

以上注释蛮清晰简单的,不做过多解释

Glide 怎么保证ImageView宽高?

《Glide原理解析(一):加载流程分析》 的SingleRequest的begin()方法里,如果知道了Target的Size就会调用onSizeReady(),如果不知道就会调用Target.getSize()

    if (Util.isValidDimensions(overrideWidth, overrideHeight)) {
 
// 已经拿到了size,可以开始异步请求 
onSizeReady(overrideWidth, overrideHeight); 
} else {
 
// 从Target中获取目标的size,最后会走到onSizeReady() 
target.getSize(this); 
} 

我们看一下 ViewTarget.getSize()方法

  // ViewTarget 
public void getSize(@NonNull SizeReadyCallback cb) {
 
sizeDeterminer.getSize(cb); 
} 

sizeDeterminer 是负责获取target的size

// SizeDeterminer 
void getSize(@NonNull SizeReadyCallback cb) {
 
int currentWidth = getTargetWidth(); 
int currentHeight = getTargetHeight(); 
if (isViewStateAndSizeValid(currentWidth, currentHeight)) {
 
// 已经拿到了size,回调出去 
cb.onSizeReady(currentWidth, currentHeight); 
return; 
} 
// 添加一个回调 
if (!cbs.contains(cb)) {
 
cbs.add(cb); 
} 
if (layoutListener == null) {
 
ViewTreeObserver observer = view.getViewTreeObserver(); 
layoutListener = new SizeDeterminerLayoutListener(this); 
// 利用了ViewTreeObserver 
observer.addOnPreDrawListener(layoutListener); 
} 
} 

到这里不需要继续往下看了。

其实就是Glide利用了ViewTreeObserver.OnPreDrawListener,添加到View中,当view绘制之前就会通知Glide。

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/6251.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论