FileDocCategorySizeDatePackage
DecodeTask.javaAPI DocAndroid 5.1 API21699Thu Mar 12 22:22:50 GMT 2015com.android.bitmap

DecodeTask

public 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
Constructors Summary
public DecodeTask(RequestKey requestKey, DecodeOptions decodeOpts, com.android.bitmap.RequestKey.FileDescriptorFactory factory, DecodeCallback callback, BitmapCache cache)
Create new DecodeTask.

param
requestKey The request to decode, also the key to use for the cache.
param
decodeOpts The decode options.
param
factory The factory to obtain file descriptors to decode from. If this factory is null, then we will decode from requestKey.createInputStream().
param
callback The callback to notify of decode state changes.
param
cache The cache and pool.


                      
       
                                                        
          
                                          
            
                              
          
    

                                                                                             
        
                  
        mKey = requestKey;
        mDecodeOpts = decodeOpts;
        mFactory = factory;
        mDecodeCallback = callback;
        mCache = cache;
    
Methods Summary
public voidcancel()

        cancel(true);
        mOpts.requestCancelDecode();
    
public ReusableBitmapdecode()

        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.Bitmapdecode(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 ReusableBitmapdoInBackground(java.lang.Void params)

        // enqueue the 'onDecodeBegin' signal on the main thread
        publishProgress();

        return decode();
    
protected voidonCancelled(ReusableBitmap result)

        mDecodeCallback.onDecodeCancel(mKey);
        if (result == null) {
            return;
        }

        result.releaseReference();
        if (mInBitmap == null) {
            // not reusing bitmaps: can recycle immediately
            result.bmp.recycle();
        }
    
public voidonPostExecute(ReusableBitmap result)

        mDecodeCallback.onDecodeComplete(mKey, result);
    
protected voidonProgressUpdate(java.lang.Void values)

        mDecodeCallback.onDecodeBegin(mKey);
    
private java.io.InputStreamreset(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;