mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 11:33:06 +00:00
Use AsyncTasks and attached Drawables to track background image loading
This commit is contained in:
parent
fc8ce5e4b9
commit
896288a40b
@ -1,7 +1,7 @@
|
|||||||
package com.limelight.grid;
|
package com.limelight.grid;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.BitmapFactory;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
@ -14,27 +14,18 @@ import com.limelight.grid.assets.MemoryAssetLoader;
|
|||||||
import com.limelight.grid.assets.NetworkAssetLoader;
|
import com.limelight.grid.assets.NetworkAssetLoader;
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
||||||
private final Activity activity;
|
|
||||||
|
|
||||||
private static final int ART_WIDTH_PX = 300;
|
private static final int ART_WIDTH_PX = 300;
|
||||||
private static final int SMALL_WIDTH_DP = 100;
|
private static final int SMALL_WIDTH_DP = 100;
|
||||||
private static final int LARGE_WIDTH_DP = 150;
|
private static final int LARGE_WIDTH_DP = 150;
|
||||||
|
|
||||||
private final CachedAppAssetLoader loader;
|
private final CachedAppAssetLoader loader;
|
||||||
private final ConcurrentHashMap<WeakReference<ImageView>, CachedAppAssetLoader.LoaderTuple> loadingTuples = new ConcurrentHashMap<>();
|
|
||||||
private final ConcurrentHashMap<Object, CachedAppAssetLoader.LoaderTuple> backgroundLoadingTuples = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
public AppGridAdapter(Activity activity, boolean listMode, boolean small, ComputerDetails computer, String uniqueId) throws KeyManagementException, NoSuchAlgorithmException {
|
public AppGridAdapter(Activity activity, boolean listMode, boolean small, ComputerDetails computer, String uniqueId) throws KeyManagementException, NoSuchAlgorithmException {
|
||||||
super(activity, listMode ? R.layout.simple_row : (small ? R.layout.app_grid_item_small : R.layout.app_grid_item), R.drawable.image_loading);
|
super(activity, listMode ? R.layout.simple_row : (small ? R.layout.app_grid_item_small : R.layout.app_grid_item), R.drawable.image_loading);
|
||||||
@ -56,26 +47,20 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
}
|
}
|
||||||
LimeLog.info("Art scaling divisor: " + scalingDivisor);
|
LimeLog.info("Art scaling divisor: " + scalingDivisor);
|
||||||
|
|
||||||
this.activity = activity;
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
|
options.inSampleSize = (int) scalingDivisor;
|
||||||
|
|
||||||
this.loader = new CachedAppAssetLoader(computer, scalingDivisor,
|
this.loader = new CachedAppAssetLoader(computer, scalingDivisor,
|
||||||
new NetworkAssetLoader(context, uniqueId),
|
new NetworkAssetLoader(context, uniqueId),
|
||||||
new MemoryAssetLoader(), new DiskAssetLoader(context.getCacheDir()));
|
new MemoryAssetLoader(),
|
||||||
}
|
new DiskAssetLoader(context.getCacheDir()),
|
||||||
|
BitmapFactory.decodeResource(activity.getResources(),
|
||||||
private static void cancelTuples(ConcurrentHashMap<?, CachedAppAssetLoader.LoaderTuple> map) {
|
R.drawable.image_loading, options));
|
||||||
Collection<CachedAppAssetLoader.LoaderTuple> tuples = map.values();
|
|
||||||
|
|
||||||
for (CachedAppAssetLoader.LoaderTuple tuple : tuples) {
|
|
||||||
tuple.cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
map.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancelQueuedOperations() {
|
public void cancelQueuedOperations() {
|
||||||
cancelTuples(loadingTuples);
|
loader.cancelForegroundLoads();
|
||||||
cancelTuples(backgroundLoadingTuples);
|
loader.cancelBackgroundLoads();
|
||||||
|
|
||||||
loader.freeCacheMemory();
|
loader.freeCacheMemory();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,14 +74,10 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addApp(AppView.AppObject app) {
|
public void addApp(AppView.AppObject app) {
|
||||||
// Queue a request to fetch this bitmap in the background
|
// Queue a request to fetch this bitmap into cache
|
||||||
Object tupleKey = new Object();
|
loader.queueCacheLoad(app.app);
|
||||||
CachedAppAssetLoader.LoaderTuple tuple =
|
|
||||||
loader.loadBitmapWithContextInBackground(app.app, tupleKey, backgroundLoadListener);
|
|
||||||
if (tuple != null) {
|
|
||||||
backgroundLoadingTuples.put(tupleKey, tuple);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Add the app to our sorted list
|
||||||
itemList.add(app);
|
itemList.add(app);
|
||||||
sortList();
|
sortList();
|
||||||
}
|
}
|
||||||
@ -105,100 +86,9 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
itemList.remove(app);
|
itemList.remove(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final CachedAppAssetLoader.LoadListener imageViewLoadListener = new CachedAppAssetLoader.LoadListener() {
|
public boolean populateImageView(ImageView imgView, AppView.AppObject obj) {
|
||||||
@Override
|
// Let the cached asset loader handle it
|
||||||
public void notifyLongLoad(Object object) {
|
loader.populateImageView(obj.app, imgView);
|
||||||
final WeakReference<ImageView> viewRef = (WeakReference<ImageView>) object;
|
|
||||||
|
|
||||||
// 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.setImageResource(R.drawable.image_loading);
|
|
||||||
fadeInImage(view);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyLoadComplete(Object object, final Bitmap bitmap) {
|
|
||||||
final WeakReference<ImageView> viewRef = (WeakReference<ImageView>) 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private final CachedAppAssetLoader.LoadListener backgroundLoadListener = new CachedAppAssetLoader.LoadListener() {
|
|
||||||
@Override
|
|
||||||
public void notifyLongLoad(Object object) {}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void notifyLoadComplete(Object object, final Bitmap bitmap) {
|
|
||||||
backgroundLoadingTuples.remove(object);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
private void reapLoaderTuples(ImageView view) {
|
|
||||||
// Poor HashMap doesn't deserve this...
|
|
||||||
Iterator<Map.Entry<WeakReference<ImageView>, CachedAppAssetLoader.LoaderTuple>> i = loadingTuples.entrySet().iterator();
|
|
||||||
while (i.hasNext()) {
|
|
||||||
Map.Entry<WeakReference<ImageView>, 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
|
|
||||||
reapLoaderTuples(imgView);
|
|
||||||
|
|
||||||
// Clear existing contents of the image view
|
|
||||||
imgView.setAlpha(0.0f);
|
|
||||||
|
|
||||||
// Start loading the bitmap
|
|
||||||
WeakReference<ImageView> viewRef = new WeakReference<>(imgView);
|
|
||||||
CachedAppAssetLoader.LoaderTuple tuple = loader.loadBitmapWithContext(obj.app, viewRef, imageViewLoadListener);
|
|
||||||
if (tuple != null) {
|
|
||||||
// The load was issued asynchronously
|
|
||||||
loadingTuples.put(viewRef, tuple);
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -222,8 +112,4 @@ public class AppGridAdapter extends GenericGridAdapter<AppView.AppObject> {
|
|||||||
// No overlay
|
// No overlay
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void fadeInImage(ImageView view) {
|
|
||||||
view.animate().alpha(1.0f).setDuration(100).start();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,58 +1,85 @@
|
|||||||
package com.limelight.grid.assets;
|
package com.limelight.grid.assets;
|
||||||
|
|
||||||
|
import android.content.res.Resources;
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
|
import android.graphics.drawable.BitmapDrawable;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
|
||||||
import com.limelight.nvstream.http.ComputerDetails;
|
import com.limelight.nvstream.http.ComputerDetails;
|
||||||
import com.limelight.nvstream.http.NvApp;
|
import com.limelight.nvstream.http.NvApp;
|
||||||
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
import java.util.concurrent.LinkedBlockingQueue;
|
||||||
|
import java.util.concurrent.RejectedExecutionException;
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
import java.util.concurrent.ThreadPoolExecutor;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class CachedAppAssetLoader {
|
public class CachedAppAssetLoader {
|
||||||
|
private static final int MAX_CONCURRENT_FOREGROUND_LOADS = 8;
|
||||||
|
private static final int MAX_CONCURRENT_CACHE_LOADS = 2;
|
||||||
|
|
||||||
|
private static final int MAX_PENDING_CACHE_LOADS = 100;
|
||||||
|
private static final int MAX_PENDING_FOREGROUND_LOADS = 30;
|
||||||
|
|
||||||
|
private final ThreadPoolExecutor cacheExecutor = new ThreadPoolExecutor(
|
||||||
|
MAX_CONCURRENT_CACHE_LOADS, MAX_CONCURRENT_CACHE_LOADS,
|
||||||
|
Long.MAX_VALUE, TimeUnit.DAYS,
|
||||||
|
new LinkedBlockingQueue<Runnable>(MAX_PENDING_CACHE_LOADS),
|
||||||
|
new ThreadPoolExecutor.DiscardOldestPolicy());
|
||||||
|
|
||||||
|
private final ThreadPoolExecutor foregroundExecutor = new ThreadPoolExecutor(
|
||||||
|
MAX_CONCURRENT_FOREGROUND_LOADS, MAX_CONCURRENT_FOREGROUND_LOADS,
|
||||||
|
Long.MAX_VALUE, TimeUnit.DAYS,
|
||||||
|
new LinkedBlockingQueue<Runnable>(MAX_PENDING_FOREGROUND_LOADS),
|
||||||
|
new ThreadPoolExecutor.DiscardOldestPolicy());
|
||||||
|
|
||||||
private final ComputerDetails computer;
|
private final ComputerDetails computer;
|
||||||
private final double scalingDivider;
|
private final double scalingDivider;
|
||||||
private final ThreadPoolExecutor foregroundExecutor = new ThreadPoolExecutor(8, 8, Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue<Runnable>());
|
|
||||||
private final ThreadPoolExecutor backgroundExecutor = new ThreadPoolExecutor(2, 2, Long.MAX_VALUE, TimeUnit.DAYS, new LinkedBlockingQueue<Runnable>());
|
|
||||||
private final NetworkAssetLoader networkLoader;
|
private final NetworkAssetLoader networkLoader;
|
||||||
private final MemoryAssetLoader memoryLoader;
|
private final MemoryAssetLoader memoryLoader;
|
||||||
private final DiskAssetLoader diskLoader;
|
private final DiskAssetLoader diskLoader;
|
||||||
|
private final Bitmap placeholderBitmap;
|
||||||
|
|
||||||
public CachedAppAssetLoader(ComputerDetails computer, double scalingDivider,
|
public CachedAppAssetLoader(ComputerDetails computer, double scalingDivider,
|
||||||
NetworkAssetLoader networkLoader, MemoryAssetLoader memoryLoader,
|
NetworkAssetLoader networkLoader, MemoryAssetLoader memoryLoader,
|
||||||
DiskAssetLoader diskLoader) {
|
DiskAssetLoader diskLoader, Bitmap placeholderBitmap) {
|
||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
this.scalingDivider = scalingDivider;
|
this.scalingDivider = scalingDivider;
|
||||||
|
|
||||||
this.networkLoader = networkLoader;
|
this.networkLoader = networkLoader;
|
||||||
this.memoryLoader = memoryLoader;
|
this.memoryLoader = memoryLoader;
|
||||||
this.diskLoader = diskLoader;
|
this.diskLoader = diskLoader;
|
||||||
|
this.placeholderBitmap = placeholderBitmap;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelBackgroundLoads() {
|
||||||
|
Runnable r;
|
||||||
|
while ((r = cacheExecutor.getQueue().poll()) != null) {
|
||||||
|
cacheExecutor.remove(r);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void cancelForegroundLoads() {
|
||||||
|
Runnable r;
|
||||||
|
while ((r = foregroundExecutor.getQueue().poll()) != null) {
|
||||||
|
foregroundExecutor.remove(r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void freeCacheMemory() {
|
public void freeCacheMemory() {
|
||||||
memoryLoader.clearCache();
|
memoryLoader.clearCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Runnable createLoaderRunnable(final LoaderTuple tuple, final Object context, final LoadListener listener) {
|
private Bitmap doNetworkAssetLoad(LoaderTuple tuple, LoaderTask task) {
|
||||||
return new Runnable() {
|
Bitmap bmp;
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
// Abort if we've been cancelled
|
|
||||||
if (tuple.cancelled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
// Try 3 times
|
||||||
if (bmp == null) {
|
for (int i = 0; i < 3; i++) {
|
||||||
// Notify the listener that this may take a while
|
// Check again whether we've been cancelled or the image view is gone
|
||||||
listener.notifyLongLoad(context);
|
if (task != null && (task.isCancelled() || task.imageViewRef.get() == null)) {
|
||||||
|
return null;
|
||||||
// Try 5 times maximum
|
|
||||||
for (int i = 0; i < 5; i++) {
|
|
||||||
// Check again whether we've been cancelled
|
|
||||||
if (tuple.cancelled) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
InputStream in = networkLoader.getBitmapStream(tuple);
|
InputStream in = networkLoader.getBitmapStream(tuple);
|
||||||
@ -63,7 +90,7 @@ public class CachedAppAssetLoader {
|
|||||||
// Read it back scaled
|
// Read it back scaled
|
||||||
bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
||||||
if (bmp != null) {
|
if (bmp != null) {
|
||||||
break;
|
return bmp;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,78 +98,221 @@ public class CachedAppAssetLoader {
|
|||||||
try {
|
try {
|
||||||
Thread.sleep((int) (1000 + (Math.random() * 500)));
|
Thread.sleep((int) (1000 + (Math.random() * 500)));
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
break;
|
return null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LoaderTask extends AsyncTask<LoaderTuple, Void, Bitmap> {
|
||||||
|
private final WeakReference<ImageView> imageViewRef;
|
||||||
|
private LoaderTuple tuple;
|
||||||
|
private boolean loadFinished;
|
||||||
|
|
||||||
|
public LoaderTask(ImageView imageView) {
|
||||||
|
imageViewRef = new WeakReference<ImageView>(imageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Bitmap doInBackground(LoaderTuple... params) {
|
||||||
|
tuple = params[0];
|
||||||
|
|
||||||
|
// Check whether it has been cancelled or the image view is gone
|
||||||
|
if (isCancelled() || imageViewRef.get() == null) {
|
||||||
|
System.out.println("Cancelled or no image view in doInBackground");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
||||||
|
if (bmp == null) {
|
||||||
|
// Report progress to display the placeholder
|
||||||
|
publishProgress();
|
||||||
|
|
||||||
|
// Try to load the asset from the network
|
||||||
|
bmp = doNetworkAssetLoad(tuple, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache the bitmap
|
||||||
if (bmp != null) {
|
if (bmp != null) {
|
||||||
// Populate the memory cache
|
loadFinished = true;
|
||||||
memoryLoader.populateCache(tuple, bmp);
|
memoryLoader.populateCache(tuple, bmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check one last time whether we've been cancelled
|
return bmp;
|
||||||
synchronized (tuple) {
|
}
|
||||||
if (tuple.cancelled) {
|
|
||||||
|
@Override
|
||||||
|
protected void onProgressUpdate(Void... nothing) {
|
||||||
|
// Do nothing if the load has already completed
|
||||||
|
if (loadFinished) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
tuple.notified = true;
|
// Do nothing if cancelled
|
||||||
|
if (isCancelled()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ImageView imageView = imageViewRef.get();
|
||||||
|
if (imageView != null) {
|
||||||
|
// If the current loader task for this view isn't us, do nothing
|
||||||
|
if (getLoaderTask(imageView) != this) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show the placeholder by setting alpha to 1.0
|
||||||
|
imageView.setAlpha(1.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Call the load complete callback (possible with a null bitmap)
|
@Override
|
||||||
listener.notifyLoadComplete(context, bmp);
|
protected void onPostExecute(Bitmap bitmap) {
|
||||||
}
|
// Do nothing if cancelled
|
||||||
};
|
if (isCancelled()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoaderTuple loadBitmapWithContext(NvApp app, Object context, LoadListener listener) {
|
final ImageView imageView = imageViewRef.get();
|
||||||
return loadBitmapWithContext(app, context, listener, false);
|
if (imageView != null) {
|
||||||
|
// If the current loader task for this view isn't us, do nothing
|
||||||
|
if (getLoaderTask(imageView) != this) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LoaderTuple loadBitmapWithContextInBackground(NvApp app, Object context, LoadListener listener) {
|
// Set the bitmap
|
||||||
return loadBitmapWithContext(app, context, listener, true);
|
if (bitmap != null) {
|
||||||
|
imageView.setImageBitmap(bitmap);
|
||||||
}
|
}
|
||||||
|
|
||||||
private LoaderTuple loadBitmapWithContext(NvApp app, Object context, LoadListener listener, boolean background) {
|
// Show the view
|
||||||
|
imageView.setAlpha(1.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static class AsyncDrawable extends BitmapDrawable {
|
||||||
|
private final WeakReference<LoaderTask> loaderTaskReference;
|
||||||
|
|
||||||
|
public AsyncDrawable(Resources res, Bitmap bitmap,
|
||||||
|
LoaderTask loaderTask) {
|
||||||
|
super(res, bitmap);
|
||||||
|
loaderTaskReference = new WeakReference<LoaderTask>(loaderTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LoaderTask getLoaderTask() {
|
||||||
|
return loaderTaskReference.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LoaderTask getLoaderTask(ImageView imageView) {
|
||||||
|
final Drawable drawable = imageView.getDrawable();
|
||||||
|
|
||||||
|
// If our drawable is in play, get the loader task
|
||||||
|
if (drawable instanceof AsyncDrawable) {
|
||||||
|
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
|
||||||
|
return asyncDrawable.getLoaderTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean cancelPendingLoad(LoaderTuple tuple, ImageView imageView) {
|
||||||
|
final LoaderTask loaderTask = getLoaderTask(imageView);
|
||||||
|
|
||||||
|
// Check if any task was pending for this image view
|
||||||
|
if (loaderTask != null && !loaderTask.isCancelled()) {
|
||||||
|
final LoaderTuple taskTuple = loaderTask.tuple;
|
||||||
|
|
||||||
|
// Cancel the task if it's not already loading the same data
|
||||||
|
if (taskTuple == null || !taskTuple.equals(tuple)) {
|
||||||
|
loaderTask.cancel(true);
|
||||||
|
} else {
|
||||||
|
// It's already loading what we want
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow the load to proceed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void queueCacheLoad(NvApp app) {
|
||||||
|
final LoaderTuple tuple = new LoaderTuple(computer, app);
|
||||||
|
|
||||||
|
if (memoryLoader.loadBitmapFromCache(tuple) != null) {
|
||||||
|
// It's in memory which means it must also be on disk
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Queue a fetch in the cache executor
|
||||||
|
cacheExecutor.execute(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
Bitmap bmp;
|
||||||
|
|
||||||
|
// Check if the image is cached on disk
|
||||||
|
bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
||||||
|
if (bmp == null) {
|
||||||
|
// Try to load the asset from the network and cache on disk
|
||||||
|
bmp = doNetworkAssetLoad(tuple, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the bitmap was loaded, recycle it immediately. We can do this
|
||||||
|
// because it's not loaded into any image views or cached in memory
|
||||||
|
if (bmp != null) {
|
||||||
|
bmp.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void populateImageView(NvApp app, ImageView view) {
|
||||||
LoaderTuple tuple = new LoaderTuple(computer, app);
|
LoaderTuple tuple = new LoaderTuple(computer, app);
|
||||||
|
|
||||||
// First, try the memory cache in the current context
|
// First, try the memory cache in the current context
|
||||||
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
||||||
if (bmp != null) {
|
if (bmp != null) {
|
||||||
// The caller never sees our tuple in this case
|
// Show the bitmap immediately
|
||||||
listener.notifyLoadComplete(context, bmp);
|
view.setImageBitmap(bmp);
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If it's not in memory, throw this in our executor
|
// If there's already a task in progress for this view,
|
||||||
if (background) {
|
// cancel it. If the task is already loading the same image,
|
||||||
backgroundExecutor.execute(createLoaderRunnable(tuple, context, listener));
|
// we return and let that load finish.
|
||||||
|
if (!cancelPendingLoad(tuple, view)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
foregroundExecutor.execute(createLoaderRunnable(tuple, context, listener));
|
// If it's not in memory, create an async task to load it. This task will be attached
|
||||||
}
|
// via AsyncDrawable to this view.
|
||||||
return tuple;
|
final LoaderTask task = new LoaderTask(view);
|
||||||
|
final AsyncDrawable asyncDrawable = new AsyncDrawable(view.getResources(), placeholderBitmap, task);
|
||||||
|
view.setAlpha(0.0f);
|
||||||
|
view.setImageDrawable(asyncDrawable);
|
||||||
|
|
||||||
|
// Run the task on our foreground executor
|
||||||
|
task.executeOnExecutor(foregroundExecutor, tuple);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class LoaderTuple {
|
public class LoaderTuple {
|
||||||
public final ComputerDetails computer;
|
public final ComputerDetails computer;
|
||||||
public final NvApp app;
|
public final NvApp app;
|
||||||
|
|
||||||
public boolean notified;
|
|
||||||
public boolean cancelled;
|
|
||||||
|
|
||||||
public LoaderTuple(ComputerDetails computer, NvApp app) {
|
public LoaderTuple(ComputerDetails computer, NvApp app) {
|
||||||
this.computer = computer;
|
this.computer = computer;
|
||||||
this.app = app;
|
this.app = app;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean cancel() {
|
@Override
|
||||||
synchronized (this) {
|
public boolean equals(Object o) {
|
||||||
cancelled = true;
|
if (!(o instanceof LoaderTuple)) {
|
||||||
return !notified;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LoaderTuple other = (LoaderTuple) o;
|
||||||
|
return computer.uuid.equals(other.computer.uuid) && app.getAppId() == other.app.getAppId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -150,13 +320,4 @@ public class CachedAppAssetLoader {
|
|||||||
return "("+computer.uuid+", "+app.getAppId()+")";
|
return "("+computer.uuid+", "+app.getAppId()+")";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface LoadListener {
|
|
||||||
// Notifies that the load didn't hit any cache and is about to be dispatched
|
|
||||||
// over the network
|
|
||||||
public void notifyLongLoad(Object context);
|
|
||||||
|
|
||||||
// Bitmap may be null if the load failed
|
|
||||||
public void notifyLoadComplete(Object context, Bitmap bitmap);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -10,8 +10,6 @@ import com.limelight.nvstream.http.NvHTTP;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
|
|
||||||
public class NetworkAssetLoader {
|
public class NetworkAssetLoader {
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user