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/tech/app/6251.html