DecodeTaskpublic class DecodeTask extends android.os.AsyncTask Decodes an image from either a file descriptor or input stream on a worker thread. After the
decode is complete, even if the task is cancelled, the result is placed in the given cache.
A {@link DecodeCallback} client may be notified on decode begin and completion.
This class uses {@link BitmapRegionDecoder} when possible to minimize unnecessary decoding
and allow bitmap reuse on Jellybean 4.1 and later.
GIFs are supported, but their decode does not reuse bitmaps at all. The resulting
{@link ReusableBitmap} will be marked as not reusable
({@link ReusableBitmap#isEligibleForPooling()} will return false). |
Fields Summary |
---|
private final RequestKey | mKey | private final DecodeOptions | mDecodeOpts | private final com.android.bitmap.RequestKey.FileDescriptorFactory | mFactory | private final DecodeCallback | mDecodeCallback | private final BitmapCache | mCache | private final BitmapFactory.Options | mOpts | private ReusableBitmap | mInBitmap | private static final boolean | CROP_DURING_DECODE | private static final String | TAG | public static final boolean | DEBUG |
Methods Summary |
---|
public void | cancel()
cancel(true);
mOpts.requestCancelDecode();
| public ReusableBitmap | decode()
if (isCancelled()) {
return null;
}
ReusableBitmap result = null;
ParcelFileDescriptor fd = null;
InputStream in = null;
try {
if (mFactory != null) {
Trace.beginSection("create fd");
fd = mFactory.createFileDescriptor();
Trace.endSection();
} else {
in = reset(in);
if (in == null) {
return null;
}
if (isCancelled()) {
return null;
}
}
final boolean isJellyBeanOrAbove = android.os.Build.VERSION.SDK_INT
>= android.os.Build.VERSION_CODES.JELLY_BEAN;
// This blocks during fling when the pool is empty. We block early to avoid jank.
if (isJellyBeanOrAbove) {
Trace.beginSection("poll for reusable bitmap");
mInBitmap = mCache.poll();
Trace.endSection();
}
if (isCancelled()) {
return null;
}
Trace.beginSection("get bytesize");
final long byteSize;
if (fd != null) {
byteSize = fd.getStatSize();
} else {
byteSize = -1;
}
Trace.endSection();
Trace.beginSection("get orientation");
final int orientation;
if (mKey.hasOrientationExif()) {
if (fd != null) {
// Creating an input stream from the file descriptor makes it useless
// afterwards.
Trace.beginSection("create orientation fd and stream");
final ParcelFileDescriptor orientationFd = mFactory.createFileDescriptor();
in = new AutoCloseInputStream(orientationFd);
Trace.endSection();
}
orientation = Exif.getOrientation(in, byteSize);
if (fd != null) {
try {
// Close the temporary file descriptor.
in.close();
} catch (IOException ignored) {
}
}
} else {
orientation = 0;
}
final boolean isNotRotatedOr180 = orientation == 0 || orientation == 180;
Trace.endSection();
if (orientation != 0) {
// disable inBitmap-- bitmap reuse doesn't work with different decode regions due
// to orientation
if (mInBitmap != null) {
mCache.offer(mInBitmap);
mInBitmap = null;
mOpts.inBitmap = null;
}
}
if (isCancelled()) {
return null;
}
if (fd == null) {
in = reset(in);
if (in == null) {
return null;
}
if (isCancelled()) {
return null;
}
}
Trace.beginSection("decodeBounds");
mOpts.inJustDecodeBounds = true;
if (fd != null) {
BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, mOpts);
} else {
BitmapFactory.decodeStream(in, null, mOpts);
}
Trace.endSection();
if (isCancelled()) {
return null;
}
// We want to calculate the sample size "as if" the orientation has been corrected.
final int srcW, srcH; // Orientation corrected.
if (isNotRotatedOr180) {
srcW = mOpts.outWidth;
srcH = mOpts.outHeight;
} else {
srcW = mOpts.outHeight;
srcH = mOpts.outWidth;
}
// BEGIN MANUAL-INLINE calculateSampleSize()
final float sz = Math
.min((float) srcW / mDecodeOpts.destW, (float) srcH / mDecodeOpts.destH);
final int sampleSize;
switch (mDecodeOpts.sampleSizeStrategy) {
case DecodeOptions.STRATEGY_TRUNCATE:
sampleSize = (int) sz;
break;
case DecodeOptions.STRATEGY_ROUND_UP:
sampleSize = (int) Math.ceil(sz);
break;
case DecodeOptions.STRATEGY_ROUND_NEAREST:
default:
sampleSize = (int) Math.pow(2, (int) (0.5 + (Math.log(sz) / Math.log(2))));
break;
}
mOpts.inSampleSize = Math.max(1, sampleSize);
// END MANUAL-INLINE calculateSampleSize()
mOpts.inJustDecodeBounds = false;
mOpts.inMutable = true;
if (isJellyBeanOrAbove && orientation == 0) {
if (mInBitmap == null) {
if (DEBUG) {
Log.e(TAG, "decode thread wants a bitmap. cache dump:\n"
+ mCache.toDebugString());
}
Trace.beginSection("create reusable bitmap");
mInBitmap = new ReusableBitmap(
Bitmap.createBitmap(mDecodeOpts.destW, mDecodeOpts.destH,
Bitmap.Config.ARGB_8888));
Trace.endSection();
if (isCancelled()) {
return null;
}
if (DEBUG) {
Log.e(TAG, "*** allocated new bitmap in decode thread: "
+ mInBitmap + " key=" + mKey);
}
} else {
if (DEBUG) {
Log.e(TAG, "*** reusing existing bitmap in decode thread: "
+ mInBitmap + " key=" + mKey);
}
}
mOpts.inBitmap = mInBitmap.bmp;
}
if (isCancelled()) {
return null;
}
if (fd == null) {
in = reset(in);
if (in == null) {
return null;
}
if (isCancelled()) {
return null;
}
}
Bitmap decodeResult = null;
final Rect srcRect = new Rect(); // Not orientation corrected. True coordinates.
if (CROP_DURING_DECODE) {
try {
Trace.beginSection("decodeCropped" + mOpts.inSampleSize);
// BEGIN MANUAL INLINE decodeCropped()
final BitmapRegionDecoder brd;
if (fd != null) {
brd = BitmapRegionDecoder
.newInstance(fd.getFileDescriptor(), true /* shareable */);
} else {
brd = BitmapRegionDecoder.newInstance(in, true /* shareable */);
}
final Bitmap bitmap;
if (isCancelled()) {
bitmap = null;
} else {
// We want to call calculateCroppedSrcRect() on the source rectangle "as
// if" the orientation has been corrected.
// Center the decode on the top 1/3.
BitmapUtils.calculateCroppedSrcRect(srcW, srcH, mDecodeOpts.destW,
mDecodeOpts.destH,
mDecodeOpts.destH, mOpts.inSampleSize, mDecodeOpts.verticalCenter,
true /* absoluteFraction */,
1f, srcRect);
if (DEBUG) {
System.out.println("rect for this decode is: " + srcRect
+ " srcW/H=" + srcW + "/" + srcH
+ " dstW/H=" + mDecodeOpts.destW + "/" + mDecodeOpts.destH);
}
// calculateCroppedSrcRect() gave us the source rectangle "as if" the
// orientation has been corrected. We need to decode the uncorrected
// source rectangle. Calculate true coordinates.
RectUtils.rotateRectForOrientation(orientation, new Rect(0, 0, srcW, srcH),
srcRect);
bitmap = brd.decodeRegion(srcRect, mOpts);
}
brd.recycle();
// END MANUAL INLINE decodeCropped()
decodeResult = bitmap;
} catch (IOException e) {
// fall through to below and try again with the non-cropping decoder
if (fd == null) {
in = reset(in);
if (in == null) {
return null;
}
if (isCancelled()) {
return null;
}
}
e.printStackTrace();
} finally {
Trace.endSection();
}
if (isCancelled()) {
return null;
}
}
//noinspection PointlessBooleanExpression
if (!CROP_DURING_DECODE || (decodeResult == null && !isCancelled())) {
try {
Trace.beginSection("decode" + mOpts.inSampleSize);
// disable inBitmap-- bitmap reuse doesn't work well below K
if (mInBitmap != null) {
mCache.offer(mInBitmap);
mInBitmap = null;
mOpts.inBitmap = null;
}
decodeResult = decode(fd, in);
} catch (IllegalArgumentException e) {
Log.e(TAG, "decode failed: reason='" + e.getMessage() + "' ss="
+ mOpts.inSampleSize);
if (mOpts.inSampleSize > 1) {
// try again with ss=1
mOpts.inSampleSize = 1;
decodeResult = decode(fd, in);
}
} finally {
Trace.endSection();
}
if (isCancelled()) {
return null;
}
}
if (decodeResult == null) {
return null;
}
if (mInBitmap != null) {
result = mInBitmap;
// srcRect is non-empty when using the cropping BitmapRegionDecoder codepath
if (!srcRect.isEmpty()) {
result.setLogicalWidth((srcRect.right - srcRect.left) / mOpts.inSampleSize);
result.setLogicalHeight(
(srcRect.bottom - srcRect.top) / mOpts.inSampleSize);
} else {
result.setLogicalWidth(mOpts.outWidth);
result.setLogicalHeight(mOpts.outHeight);
}
} else {
// no mInBitmap means no pooling
result = new ReusableBitmap(decodeResult, false /* reusable */);
if (isNotRotatedOr180) {
result.setLogicalWidth(decodeResult.getWidth());
result.setLogicalHeight(decodeResult.getHeight());
} else {
result.setLogicalWidth(decodeResult.getHeight());
result.setLogicalHeight(decodeResult.getWidth());
}
}
result.setOrientation(orientation);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fd != null) {
try {
fd.close();
} catch (IOException ignored) {
}
}
if (in != null) {
try {
in.close();
} catch (IOException ignored) {
}
}
// Put result in cache, regardless of null. The cache will handle null results.
mCache.put(mKey, result);
if (result != null) {
result.acquireReference();
if (DEBUG) {
Log.d(TAG, "placed result in cache: key=" + mKey + " bmp="
+ result + " cancelled=" + isCancelled());
}
} else if (mInBitmap != null) {
if (DEBUG) {
Log.d(TAG, "placing failed/cancelled bitmap in pool: key="
+ mKey + " bmp=" + mInBitmap);
}
mCache.offer(mInBitmap);
}
}
return result;
| private android.graphics.Bitmap | decode(android.os.ParcelFileDescriptor fd, java.io.InputStream in)
final Bitmap result;
if (fd != null) {
result = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor(), null, mOpts);
} else {
result = BitmapFactory.decodeStream(in, null, mOpts);
}
return result;
| protected ReusableBitmap | doInBackground(java.lang.Void params)
// enqueue the 'onDecodeBegin' signal on the main thread
publishProgress();
return decode();
| protected void | onCancelled(ReusableBitmap result)
mDecodeCallback.onDecodeCancel(mKey);
if (result == null) {
return;
}
result.releaseReference();
if (mInBitmap == null) {
// not reusing bitmaps: can recycle immediately
result.bmp.recycle();
}
| public void | onPostExecute(ReusableBitmap result)
mDecodeCallback.onDecodeComplete(mKey, result);
| protected void | onProgressUpdate(java.lang.Void values)
mDecodeCallback.onDecodeBegin(mKey);
| private java.io.InputStream | reset(java.io.InputStream in)Return an input stream that can be read from the beginning using the most efficient way,
given an input stream that may or may not support reset(), or given null.
The returned input stream may or may not be the same stream.
Trace.beginSection("create stream");
if (in == null) {
in = mKey.createInputStream();
} else if (in.markSupported()) {
in.reset();
} else {
try {
in.close();
} catch (IOException ignored) {
}
in = mKey.createInputStream();
}
Trace.endSection();
return in;
|
|