From 98638186b565caf12e746274d8112068c379f951 Mon Sep 17 00:00:00 2001 From: Cameron Gutman Date: Thu, 26 Feb 2015 21:05:33 -0500 Subject: [PATCH] Use weak references to allow the image views to be garbage collected while a load is in progress --- .../com/limelight/grid/AppGridAdapter.java | 92 +++++++++++++------ 1 file changed, 63 insertions(+), 29 deletions(-) diff --git a/app/src/main/java/com/limelight/grid/AppGridAdapter.java b/app/src/main/java/com/limelight/grid/AppGridAdapter.java index 668e9b30..b4e87227 100644 --- a/app/src/main/java/com/limelight/grid/AppGridAdapter.java +++ b/app/src/main/java/com/limelight/grid/AppGridAdapter.java @@ -13,18 +13,21 @@ import com.limelight.grid.assets.MemoryAssetLoader; import com.limelight.grid.assets.NetworkAssetLoader; import com.limelight.nvstream.http.ComputerDetails; +import java.lang.ref.WeakReference; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; import java.util.Collection; import java.util.Collections; import java.util.Comparator; +import java.util.Iterator; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class AppGridAdapter extends GenericGridAdapter { private final Activity activity; private final CachedAppAssetLoader loader; - private final ConcurrentHashMap loadingTuples = new ConcurrentHashMap<>(); + private final ConcurrentHashMap, CachedAppAssetLoader.LoaderTuple> loadingTuples = new ConcurrentHashMap<>(); private final ConcurrentHashMap backgroundLoadingTuples = new ConcurrentHashMap<>(); public AppGridAdapter(Activity activity, boolean listMode, boolean small, ComputerDetails computer, String uniqueId) throws KeyManagementException, NoSuchAlgorithmException { @@ -79,33 +82,49 @@ public class AppGridAdapter extends GenericGridAdapter { private final CachedAppAssetLoader.LoadListener imageViewLoadListener = new CachedAppAssetLoader.LoadListener() { @Override public void notifyLongLoad(Object object) { - final ImageView view = (ImageView) object; + final WeakReference viewRef = (WeakReference) object; - activity.runOnUiThread(new Runnable() { - @Override - public void run() { - view.setImageResource(R.drawable.image_loading); - fadeInImage(view); - } - }); - } - - @Override - public void notifyLoadComplete(Object object, final Bitmap bitmap) { - final ImageView view = (ImageView) object; - - loadingTuples.remove(view); - - // Just leave the loading icon in place - if (bitmap == null) { + // If the view isn't there anymore, don't bother scheduling on the UI thread + if (viewRef.get() == null) { return; } activity.runOnUiThread(new Runnable() { @Override public void run() { - view.setImageBitmap(bitmap); - fadeInImage(view); + ImageView view = viewRef.get(); + if (view != null) { + view.setImageResource(R.drawable.image_loading); + fadeInImage(view); + } + } + }); + } + + @Override + public void notifyLoadComplete(Object object, final Bitmap bitmap) { + final WeakReference viewRef = (WeakReference) object; + + loadingTuples.remove(viewRef); + + // Just leave the loading icon in place + if (bitmap == null) { + return; + } + + // If the view isn't there anymore, don't bother scheduling on the UI thread + if (viewRef.get() == null) { + return; + } + + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + ImageView view = viewRef.get(); + if (view != null) { + view.setImageBitmap(bitmap); + fadeInImage(view); + } } }); } @@ -121,23 +140,38 @@ public class AppGridAdapter extends GenericGridAdapter { } }; + private void reapLoaderTuples(ImageView view) { + // Poor HashMap doesn't deserve this... + Iterator, CachedAppAssetLoader.LoaderTuple>> i = loadingTuples.entrySet().iterator(); + while (i.hasNext()) { + Map.Entry, CachedAppAssetLoader.LoaderTuple> entry = i.next(); + ImageView imageView = entry.getKey().get(); + + // Remove tuples that refer to this view or no view + if (imageView == null || imageView == view) { + // FIXME: There's a small chance that this can race if we've already gone down + // the path to notification but haven't been notified yet + entry.getValue().cancel(); + + // Remove it from the tuple list + i.remove(); + } + } + } + public boolean populateImageView(final ImageView imgView, final AppView.AppObject obj) { // Cancel pending loads on this image view - CachedAppAssetLoader.LoaderTuple tuple = loadingTuples.remove(imgView); - if (tuple != null) { - // FIXME: There's a small chance that this can race if we've already gone down - // the path to notification but haven't been notified yet - tuple.cancel(); - } + reapLoaderTuples(imgView); // Clear existing contents of the image view imgView.setAlpha(0.0f); // Start loading the bitmap - tuple = loader.loadBitmapWithContext(obj.app, imgView, imageViewLoadListener); + WeakReference viewRef = new WeakReference<>(imgView); + CachedAppAssetLoader.LoaderTuple tuple = loader.loadBitmapWithContext(obj.app, viewRef, imageViewLoadListener); if (tuple != null) { // The load was issued asynchronously - loadingTuples.put(imgView, tuple); + loadingTuples.put(viewRef, tuple); } return true; }