From 9c6ac19d4a3d39b7c2992060957920118ff56a65 Mon Sep 17 00:00:00 2001 From: Mark Wei Date: Thu, 24 Oct 2013 14:51:54 -0700 Subject: Relax BasicBitmapView to allow non-BasicBitmapDrawables to be assigned to it. Rename BasicBitmapView to BitmapDrawableImageView. Allow asynchronous creating of file. Change-Id: I0407bf0bf36ae92ce45d2175121a15483f8f72f2 --- src/com/android/bitmap/DecodeTask.java | 33 ++++--- src/com/android/bitmap/RequestKey.java | 101 +++++++++++++++++--- .../bitmap/drawable/BasicBitmapDrawable.java | 58 +++++++++--- .../bitmap/drawable/ExtendedBitmapDrawable.java | 7 +- src/com/android/bitmap/view/BasicImageView.java | 94 ------------------- .../bitmap/view/BitmapDrawableImageView.java | 103 +++++++++++++++++++++ 6 files changed, 262 insertions(+), 134 deletions(-) delete mode 100644 src/com/android/bitmap/view/BasicImageView.java create mode 100644 src/com/android/bitmap/view/BitmapDrawableImageView.java (limited to 'src/com/android/bitmap') diff --git a/src/com/android/bitmap/DecodeTask.java b/src/com/android/bitmap/DecodeTask.java index c90d772..3bb1252 100644 --- a/src/com/android/bitmap/DecodeTask.java +++ b/src/com/android/bitmap/DecodeTask.java @@ -25,6 +25,7 @@ import android.os.ParcelFileDescriptor; import android.os.ParcelFileDescriptor.AutoCloseInputStream; import android.util.Log; +import com.android.bitmap.RequestKey.FileDescriptorFactory; import com.android.bitmap.util.BitmapUtils; import com.android.bitmap.util.Exif; import com.android.bitmap.util.RectUtils; @@ -49,9 +50,9 @@ public class DecodeTask extends AsyncTask { private final RequestKey mKey; private final DecodeOptions mDecodeOpts; + private final FileDescriptorFactory mFactory; private final DecodeCallback mDecodeCallback; private final BitmapCache mCache; - private final BitmapFactory.Options mOpts = new BitmapFactory.Options(); private ReusableBitmap mInBitmap = null; @@ -85,17 +86,20 @@ public class DecodeTask extends AsyncTask { } /** - * Create new DecodeTask. - * - * @param requestKey The request to decode, also the key to use for the cache. - * @param decodeOpts The decode options. - * @param callback The callback to notify of decode state changes. - * @param cache The cache and pool. - */ + * 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. + */ public DecodeTask(RequestKey requestKey, DecodeOptions decodeOpts, - DecodeCallback callback, BitmapCache cache) { + FileDescriptorFactory factory, DecodeCallback callback, BitmapCache cache) { mKey = requestKey; mDecodeOpts = decodeOpts; + mFactory = factory; mDecodeCallback = callback; mCache = cache; } @@ -131,14 +135,15 @@ public class DecodeTask extends AsyncTask { } Trace.beginSection("create fd and stream"); - fd = mKey.createFd(); - Trace.endSection(); - if (fd == null) { + if (mFactory != null) { + fd = mFactory.createFileDescriptor(); + } else { in = reset(in); if (in == null) { return null; } } + Trace.endSection(); Trace.beginSection("get bytesize"); final long byteSize; @@ -155,8 +160,8 @@ public class DecodeTask extends AsyncTask { if (fd != null) { // Creating an input stream from the file descriptor makes it useless // afterwards. - Trace.beginSection("create fd and stream"); - final ParcelFileDescriptor orientationFd = mKey.createFd(); + Trace.beginSection("create orientation fd and stream"); + final ParcelFileDescriptor orientationFd = mFactory.createFileDescriptor(); in = new AutoCloseInputStream(orientationFd); Trace.endSection(); } diff --git a/src/com/android/bitmap/RequestKey.java b/src/com/android/bitmap/RequestKey.java index 0e92164..d9c15ae 100644 --- a/src/com/android/bitmap/RequestKey.java +++ b/src/com/android/bitmap/RequestKey.java @@ -23,32 +23,111 @@ import java.io.InputStream; /** * The decode task uses this class to get input to decode. You must implement at least one of - * {@link #createFd()} or {@link #createInputStream()}. {@link DecodeTask} will prioritize - * {@link #createFd()} before falling back to {@link #createInputStream()}. - *

- * Objects of this type will also serve as cache keys to fetch cached data for {@link PooledCache}s, - * so they must implement {@link #equals(Object)} and {@link #hashCode()}. + * {@link #createFileDescriptorFactoryAsync(RequestKey, Callback)} or {@link #createInputStream()}. + * {@link DecodeTask} will prioritize + * {@link #createFileDescriptorFactoryAsync(RequestKey, Callback)} before falling back to + * {@link #createInputStream()}. + * *

* Clients of this interface must also implement {@link #equals(Object)} and {@link #hashCode()} as * this object will be used as a cache key. + * + *

+ * The following is a high level view of the interactions between RequestKey and the rest of the + * system. + * + * BasicBitmapDrawable + * UI Thread + * ++ + * bind() || Background Thread + * |+-------------------->+ + * || createFDFasync() || + * || || Download from url + * || || Cache on disk + * || || + * || vv + * |<--------------------+x + * || FDFcreated() + * || + * || + * || DecodeTask + * || AsyncTask Thread + * |+-------------------->+ + * || new().execute() || + * || || Decode from FDF + * || || or createInputStream() + * || || + * || vv + * |<--------------------+x + * || onDecodeComplete() + * vv + * invalidate() xx */ - public interface RequestKey { + /** - * Create an {@link ParcelFileDescriptor} for a local file stored on the device. This method - * will be called first; if it returns null, {@link #createInputStream()} will be called. + * Create an {@link FileDescriptorFactory} for a local file stored on the device and pass it to + * the given callback. This method will be called first; if it returns null, + * {@link #createInputStream()} will be called. + * + * This method must be called from the UI thread. + * + * @param key The key to create a FileDescriptorFactory for. This key will be passed to the + * callback so it can check whether the key has changed. + * @param callback The callback to notify once the FileDescriptorFactory has been created. Do + * not invoke the callback directly from this method. Instead, create a handler + * and post a Runnable. + * + * @return If the client will attempt to create a FileDescriptorFactory, return a Cancelable + * object to cancel the asynchronous task. If the client wants to create an InputStream instead, + * return null. The callback must be notified if and only if the client returns a Cancelable + * object and not null. */ - public ParcelFileDescriptor createFd() throws IOException; + public Cancelable createFileDescriptorFactoryAsync(RequestKey key, Callback callback); /** - * Create an {@link InputStream} for a file. This method will be called if {@link #createFd()} - * returns null. + * Create an {@link InputStream} for the source. This method will be called if + * {@link #createFileDescriptorFactoryAsync(RequestKey, Callback)} returns null. + * + * This method can be called from any thread. */ public InputStream createInputStream() throws IOException; /** * Return true if the image source may have be oriented in either portrait or landscape, and * will need to be automatically re-oriented based on accompanying Exif metadata. + * + * This method can be called from any thread. */ public boolean hasOrientationExif() throws IOException; + + /** + * Callback for creating the {@link FileDescriptorFactory} asynchronously. + */ + public interface Callback { + + /** + * Notifies that the {@link FileDescriptorFactory} has been created. This must be called on + * the UI thread. + * @param key The key that the FileDescriptorFactory was created for. The callback should + * check that the key has not changed. + * @param factory The FileDescriptorFactory to decode from. + */ + void fileDescriptorFactoryCreated(RequestKey key, FileDescriptorFactory factory); + } + + public interface FileDescriptorFactory { + ParcelFileDescriptor createFileDescriptor(); + } + + /** + * Interface for a background task that is cancelable. + */ + public interface Cancelable { + + /** + * Cancel the background task. This must be called on the UI thread. + */ + void cancel(); + } } \ No newline at end of file diff --git a/src/com/android/bitmap/drawable/BasicBitmapDrawable.java b/src/com/android/bitmap/drawable/BasicBitmapDrawable.java index b172373..a7cfb48 100644 --- a/src/com/android/bitmap/drawable/BasicBitmapDrawable.java +++ b/src/com/android/bitmap/drawable/BasicBitmapDrawable.java @@ -27,14 +27,16 @@ import android.util.Log; import com.android.bitmap.BitmapCache; import com.android.bitmap.DecodeTask; +import com.android.bitmap.DecodeTask.DecodeCallback; import com.android.bitmap.DecodeTask.DecodeOptions; import com.android.bitmap.NamedThreadFactory; import com.android.bitmap.RequestKey; +import com.android.bitmap.RequestKey.Cancelable; +import com.android.bitmap.RequestKey.FileDescriptorFactory; import com.android.bitmap.ReusableBitmap; import com.android.bitmap.util.BitmapUtils; import com.android.bitmap.util.RectUtils; import com.android.bitmap.util.Trace; -import com.android.bitmap.view.BasicImageView; import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; @@ -49,11 +51,12 @@ import java.util.concurrent.TimeUnit; *

* If being used with a long-lived cache (static cache, attached to the Application instead of the * Activity, etc) then make sure to call {@link BasicBitmapDrawable#unbind()} at the appropriate - * times so the cache has accurate unref counts. The {@link BasicImageView} class has been created - * to do the appropriate unbind operation when the view is detached from the window. + * times so the cache has accurate unref counts. The + * {@link com.android.bitmap.view.BitmapDrawableImageView} class has been created to do the + * appropriate unbind operation when the view is detached from the window. */ -public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCallback, - Drawable.Callback { +public class BasicBitmapDrawable extends Drawable implements DecodeCallback, + Drawable.Callback, RequestKey.Callback { protected static Rect sRect; protected RequestKey mCurrKey; @@ -64,6 +67,7 @@ public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCa private final float mDensity; private ReusableBitmap mBitmap; private DecodeTask mTask; + private Cancelable mCreateFileDescriptorFactoryTask; private int mDecodeWidth; private int mDecodeHeight; @@ -107,7 +111,7 @@ public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCa public void setDecodeDimensions(int w, int h) { mDecodeWidth = w; mDecodeHeight = h; - decode(); + loadFileDescriptorFactory(); } public void unbind() { @@ -136,6 +140,10 @@ public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCa mTask.cancel(); mTask = null; } + if (mCreateFileDescriptorFactoryTask != null) { + mCreateFileDescriptorFactoryTask.cancel(); + mCreateFileDescriptorFactoryTask = null; + } if (key == null) { invalidateSelf(); @@ -151,7 +159,7 @@ public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCa Log.d(TAG, String.format("CACHE HIT key=%s", mCurrKey)); } } else { - decode(); + loadFileDescriptorFactory(); if (DEBUG) { Log.d(TAG, String.format( "CACHE MISS key=%s\ncache=%s", mCurrKey, mCache.toDebugString())); @@ -247,15 +255,41 @@ public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCa invalidateSelf(); } - private void decode() { - final int bufferW; - final int bufferH; - + private void loadFileDescriptorFactory() { if (mCurrKey == null) { return; } + if (mDecodeWidth == 0 || mDecodeHeight == 0) { + return; + } + // Create file descriptor if request supports it. + mCreateFileDescriptorFactoryTask = mCurrKey + .createFileDescriptorFactoryAsync(mCurrKey, this); + if (mCreateFileDescriptorFactoryTask == null) { + // Use input stream if request does not. + decode(null); + } + } + + @Override + public void fileDescriptorFactoryCreated(final RequestKey key, + final FileDescriptorFactory factory) { + if (mCreateFileDescriptorFactoryTask == null) { + // Cancelled. + return; + } + mCreateFileDescriptorFactoryTask = null; + + if (key.equals(mCurrKey)) { + decode(factory); + } + } + + private void decode(final FileDescriptorFactory factory) { Trace.beginSection("decode"); + final int bufferW; + final int bufferH; if (mLimitDensity) { final float scale = Math.min(1f, (float) MAX_BITMAP_DENSITY / DisplayMetrics.DENSITY_DEFAULT @@ -276,7 +310,7 @@ public class BasicBitmapDrawable extends Drawable implements DecodeTask.DecodeCa } final DecodeOptions opts = new DecodeOptions(bufferW, bufferH, VERTICAL_CENTER, DecodeOptions.STRATEGY_ROUND_NEAREST); - mTask = new DecodeTask(mCurrKey, opts, this, mCache); + mTask = new DecodeTask(mCurrKey, opts, factory, this, mCache); mTask.executeOnExecutor(EXECUTOR); Trace.endSection(); } diff --git a/src/com/android/bitmap/drawable/ExtendedBitmapDrawable.java b/src/com/android/bitmap/drawable/ExtendedBitmapDrawable.java index 61efa12..59b65bc 100644 --- a/src/com/android/bitmap/drawable/ExtendedBitmapDrawable.java +++ b/src/com/android/bitmap/drawable/ExtendedBitmapDrawable.java @@ -32,11 +32,11 @@ import android.util.DisplayMetrics; import android.util.Log; import android.view.animation.LinearInterpolator; -import com.android.bitmap.DecodeTask.DecodeOptions; -import com.android.bitmap.R; import com.android.bitmap.BitmapCache; import com.android.bitmap.DecodeAggregator; import com.android.bitmap.DecodeTask; +import com.android.bitmap.DecodeTask.DecodeOptions; +import com.android.bitmap.R; import com.android.bitmap.RequestKey; import com.android.bitmap.ReusableBitmap; import com.android.bitmap.util.BitmapUtils; @@ -390,7 +390,8 @@ public class ExtendedBitmapDrawable extends Drawable implements DecodeTask.Decod setLoadState(LOAD_STATE_NOT_YET_LOADED); final DecodeOptions opts = new DecodeOptions(bufferW, bufferH, VERTICAL_CENTER, DecodeOptions.STRATEGY_ROUND_NEAREST); - mTask = new DecodeTask(mCurrKey, opts, this, mCache); + // TODO: file is null because we expect this class to extend BasicBitmapDrawable soon. + mTask = new DecodeTask(mCurrKey, opts, null /* file */, this, mCache); mTask.executeOnExecutor(EXECUTOR); Trace.endSection(); } diff --git a/src/com/android/bitmap/view/BasicImageView.java b/src/com/android/bitmap/view/BasicImageView.java deleted file mode 100644 index 3e48af5..0000000 --- a/src/com/android/bitmap/view/BasicImageView.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2013 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.bitmap.view; - -import android.content.Context; -import android.graphics.Bitmap; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.util.AttributeSet; -import android.widget.ImageView; - -import com.android.bitmap.drawable.BasicBitmapDrawable; - -/** - * A helpful ImageView replacement that can generally be used in lieu of ImageView. - * BasicImageView has logic to unbind its BasicBitmapDrawable when it is detached from the window. - */ -public class BasicImageView extends ImageView { - private BasicBitmapDrawable mDrawable; - - public BasicImageView(final Context context) { - this(context, null); - } - - public BasicImageView(final Context context, final AttributeSet attrs) { - this(context, attrs, 0); - } - - public BasicImageView(final Context context, final AttributeSet attrs, final int defStyle) { - super(context, attrs, defStyle); - } - - /** - * Set the given BasicBitmapDrawable as the source for this BasicImageView. - * @param drawable The source drawable. - */ - public void setDrawable(BasicBitmapDrawable drawable) { - super.setImageDrawable(drawable); - mDrawable = drawable; - } - - public BasicBitmapDrawable getDrawable() { - return mDrawable; - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - - mDrawable.unbind(); - } - - @Override - public void setImageDrawable(final Drawable drawable) { - throw new UnsupportedOperationException( - "BasicImageView is only compatible with BasicBitmapDrawable. Use setDrawable() " - + "instead."); - } - - @Override - public void setImageResource(final int resId) { - throw new UnsupportedOperationException( - "BasicImageView is only compatible with BasicBitmapDrawable. Use setDrawable() " - + "instead."); - } - - @Override - public void setImageURI(final Uri uri) { - throw new UnsupportedOperationException( - "BasicImageView is only compatible with BasicBitmapDrawable. Use setDrawable() " - + "instead."); - } - - @Override - public void setImageBitmap(final Bitmap bm) { - throw new UnsupportedOperationException( - "BasicImageView is only compatible with BasicBitmapDrawable. Use setDrawable() " - + "instead."); - } -} diff --git a/src/com/android/bitmap/view/BitmapDrawableImageView.java b/src/com/android/bitmap/view/BitmapDrawableImageView.java new file mode 100644 index 0000000..a55b864 --- /dev/null +++ b/src/com/android/bitmap/view/BitmapDrawableImageView.java @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bitmap.view; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.util.AttributeSet; +import android.widget.ImageView; + +import com.android.bitmap.drawable.BasicBitmapDrawable; + +/** + * A helpful ImageView replacement that can generally be used in lieu of ImageView. + * BitmapDrawableImageView has logic to unbind its BasicBitmapDrawable when it is detached from the + * window. + */ +public class BitmapDrawableImageView extends ImageView { + private BasicBitmapDrawable mDrawable; + + public BitmapDrawableImageView(final Context context) { + this(context, null); + } + + public BitmapDrawableImageView(final Context context, final AttributeSet attrs) { + this(context, attrs, 0); + } + + public BitmapDrawableImageView(final Context context, final AttributeSet attrs, + final int defStyle) { + super(context, attrs, defStyle); + } + + /** + * Get the source BasicBitmapDrawable for this BitmapDrawableImageView. + * @return The source drawable. + */ + public BasicBitmapDrawable getBasicBitmapDrawable() { + return mDrawable; + } + + /** + * Set the given BasicBitmapDrawable as the source for this BitmapDrawableImageView. + * @param drawable The source drawable. + */ + public void setBasicBitmapDrawable(BasicBitmapDrawable drawable) { + super.setImageDrawable(drawable); + unbindDrawable(); + mDrawable = drawable; + } + + private void unbindDrawable() { + if (mDrawable != null) { + mDrawable.unbind(); + mDrawable = null; + } + } + + @Override + public void setImageResource(final int resId) { + super.setImageResource(resId); + unbindDrawable(); + } + + @Override + public void setImageURI(final Uri uri) { + super.setImageURI(uri); + unbindDrawable(); + } + + @Override + public void setImageDrawable(final Drawable drawable) { + super.setImageDrawable(drawable); + unbindDrawable(); + } + + @Override + public void setImageBitmap(final Bitmap bm) { + super.setImageBitmap(bm); + unbindDrawable(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + unbindDrawable(); + } +} -- cgit v1.2.3