Add ImageViewBinder#detach/reattach and extra logs
See the ImageViewBinder javadoc.
Test: manual
Change-Id: I65dee21d0125990c2550b2cfcda7d6b79c86d041
diff --git a/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java b/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java
index 9b13438..ee50b72 100644
--- a/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java
+++ b/car-apps-common/src/com/android/car/apps/common/imaging/ImageViewBinder.java
@@ -27,7 +27,15 @@
import com.android.car.apps.common.R;
/**
- * Binds images to an image view.
+ * Binds images to an image view.<p/>
+ * While making a new image request (including passing a null {@link ImageBinder.ImageRef} in
+ * {@link #setImage}) will cancel the current image request (if any), RecyclerView doesn't
+ * always reuse all its views, causing multiple requests to not be canceled. On a slow network,
+ * those requests then take time to execute and can make it look like the application has
+ * stopped loading images if the user keeps browsing. To prevent that, override:
+ * {@link RecyclerView.Adapter#onViewDetachedFromWindow} and call {@link #maybeCancelLoading}
+ * {@link RecyclerView.Adapter#onViewAttachedToWindow} and call {@link #maybeRestartLoading}.
+ *
* @param <T> see {@link ImageRef}.
*/
public class ImageViewBinder<T extends ImageBinder.ImageRef> extends ImageBinder<T> {
@@ -36,6 +44,9 @@
private final ImageView mImageView;
private final boolean mFlagBitmaps;
+ private T mSavedRef;
+ private boolean mCancelled;
+
/** See {@link ImageViewBinder} and {@link ImageBinder}. */
public ImageViewBinder(Size maxImageSize, @Nullable ImageView imageView) {
this(PlaceholderType.FOREGROUND, maxImageSize, imageView, false);
@@ -71,13 +82,39 @@
}
}
+ /**
+ * Loads a new {@link ImageRef}. The previous request (if any) will be canceled.
+ */
@Override
public void setImage(Context context, @Nullable T newRef) {
+ mSavedRef = newRef;
+ mCancelled = false;
if (mImageView != null) {
super.setImage(context, newRef);
}
}
+ /**
+ * Restarts the image loading request if {@link #setImage} was called with a valid reference
+ * that could not be loaded before {@link #maybeCancelLoading} was called.
+ */
+ public void maybeRestartLoading(Context context) {
+ if (mCancelled) {
+ setImage(context, mSavedRef);
+ }
+ }
+
+ /**
+ * Cancels the current loading request (if any) so it doesn't take cycles when the imageView
+ * doesn't need the image (like when the view was moved off screen).
+ */
+ public void maybeCancelLoading(Context context) {
+ mCancelled = true;
+ if (mImageView != null) {
+ super.setImage(context, null); // Call super to keep mSavedRef.
+ }
+ }
+
@Override
protected void prepareForNewBinding(Context context) {
mImageView.setImageBitmap(null);
diff --git a/car-apps-common/src/com/android/car/apps/common/imaging/LocalImageFetcher.java b/car-apps-common/src/com/android/car/apps/common/imaging/LocalImageFetcher.java
index dc10caf..e8b245e 100644
--- a/car-apps-common/src/com/android/car/apps/common/imaging/LocalImageFetcher.java
+++ b/car-apps-common/src/com/android/car/apps/common/imaging/LocalImageFetcher.java
@@ -62,6 +62,7 @@
private static final String TAG = "LocalImageFetcher";
private static final boolean L_WARN = Log.isLoggable(TAG, Log.WARN);
+ private static final boolean L_DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private static final int KB = 1024;
private static final int MB = KB * KB;
@@ -150,6 +151,9 @@
task = new ImageLoadingTask(context, key, mFlagRemoteImages);
mTasks.put(key, task);
task.executeOnExecutor(getThreadPool(packageName));
+ if (L_DEBUG) {
+ Log.d(TAG, "Added task " + key.mImageUri);
+ }
} else {
Log.e(TAG, "No package for " + key.mImageUri);
}
@@ -168,8 +172,10 @@
ImageLoadingTask task = mTasks.remove(key);
if (task != null) {
task.cancel(true);
- }
- if (L_WARN) {
+ if (L_DEBUG) {
+ Log.d(TAG, "Canceled task " + key.mImageUri);
+ }
+ } else if (L_WARN) {
Log.w(TAG, "cancelRequest missing task for: " + key);
}
}
@@ -325,6 +331,10 @@
@UiThread
@Override
protected void onPostExecute(Drawable drawable) {
+ if (L_DEBUG) {
+ Log.d(TAG, "onPostExecute canceled: " + isCancelled() + " drawable: " + drawable
+ + " " + mImageKey.mImageUri);
+ }
if (!isCancelled()) {
if (sInstance != null) {
sInstance.fulfilRequests(this, drawable);