mirror of
https://github.com/moonlight-stream/moonlight-android.git
synced 2025-07-20 11:33:06 +00:00
Use a separate executor for network loads to avoid stalling cached loads. Optimize background cache fill loads.
This commit is contained in:
parent
56c8a9e6fe
commit
899387caa1
@ -13,16 +13,17 @@ import com.limelight.nvstream.http.NvApp;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.ref.WeakReference;
|
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_DISK_LOADS = 4;
|
||||||
|
private static final int MAX_CONCURRENT_NETWORK_LOADS = 4;
|
||||||
private static final int MAX_CONCURRENT_CACHE_LOADS = 2;
|
private static final int MAX_CONCURRENT_CACHE_LOADS = 2;
|
||||||
|
|
||||||
private static final int MAX_PENDING_CACHE_LOADS = 100;
|
private static final int MAX_PENDING_CACHE_LOADS = 100;
|
||||||
private static final int MAX_PENDING_FOREGROUND_LOADS = 30;
|
private static final int MAX_PENDING_NETWORK_LOADS = 40;
|
||||||
|
private static final int MAX_PENDING_DISK_LOADS = 40;
|
||||||
|
|
||||||
private final ThreadPoolExecutor cacheExecutor = new ThreadPoolExecutor(
|
private final ThreadPoolExecutor cacheExecutor = new ThreadPoolExecutor(
|
||||||
MAX_CONCURRENT_CACHE_LOADS, MAX_CONCURRENT_CACHE_LOADS,
|
MAX_CONCURRENT_CACHE_LOADS, MAX_CONCURRENT_CACHE_LOADS,
|
||||||
@ -31,9 +32,15 @@ public class CachedAppAssetLoader {
|
|||||||
new ThreadPoolExecutor.DiscardOldestPolicy());
|
new ThreadPoolExecutor.DiscardOldestPolicy());
|
||||||
|
|
||||||
private final ThreadPoolExecutor foregroundExecutor = new ThreadPoolExecutor(
|
private final ThreadPoolExecutor foregroundExecutor = new ThreadPoolExecutor(
|
||||||
MAX_CONCURRENT_FOREGROUND_LOADS, MAX_CONCURRENT_FOREGROUND_LOADS,
|
MAX_CONCURRENT_DISK_LOADS, MAX_CONCURRENT_DISK_LOADS,
|
||||||
Long.MAX_VALUE, TimeUnit.DAYS,
|
Long.MAX_VALUE, TimeUnit.DAYS,
|
||||||
new LinkedBlockingQueue<Runnable>(MAX_PENDING_FOREGROUND_LOADS),
|
new LinkedBlockingQueue<Runnable>(MAX_PENDING_DISK_LOADS),
|
||||||
|
new ThreadPoolExecutor.DiscardOldestPolicy());
|
||||||
|
|
||||||
|
private final ThreadPoolExecutor networkExecutor = new ThreadPoolExecutor(
|
||||||
|
MAX_CONCURRENT_NETWORK_LOADS, MAX_CONCURRENT_NETWORK_LOADS,
|
||||||
|
Long.MAX_VALUE, TimeUnit.DAYS,
|
||||||
|
new LinkedBlockingQueue<Runnable>(MAX_PENDING_NETWORK_LOADS),
|
||||||
new ThreadPoolExecutor.DiscardOldestPolicy());
|
new ThreadPoolExecutor.DiscardOldestPolicy());
|
||||||
|
|
||||||
private final ComputerDetails computer;
|
private final ComputerDetails computer;
|
||||||
@ -63,9 +70,14 @@ public class CachedAppAssetLoader {
|
|||||||
|
|
||||||
public void cancelForegroundLoads() {
|
public void cancelForegroundLoads() {
|
||||||
Runnable r;
|
Runnable r;
|
||||||
|
|
||||||
while ((r = foregroundExecutor.getQueue().poll()) != null) {
|
while ((r = foregroundExecutor.getQueue().poll()) != null) {
|
||||||
foregroundExecutor.remove(r);
|
foregroundExecutor.remove(r);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
while ((r = networkExecutor.getQueue().poll()) != null) {
|
||||||
|
networkExecutor.remove(r);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void freeCacheMemory() {
|
public void freeCacheMemory() {
|
||||||
@ -73,8 +85,6 @@ public class CachedAppAssetLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Bitmap doNetworkAssetLoad(LoaderTuple tuple, LoaderTask task) {
|
private Bitmap doNetworkAssetLoad(LoaderTuple tuple, LoaderTask task) {
|
||||||
Bitmap bmp;
|
|
||||||
|
|
||||||
// Try 3 times
|
// Try 3 times
|
||||||
for (int i = 0; i < 3; i++) {
|
for (int i = 0; i < 3; i++) {
|
||||||
// Check again whether we've been cancelled or the image view is gone
|
// Check again whether we've been cancelled or the image view is gone
|
||||||
@ -87,10 +97,13 @@ public class CachedAppAssetLoader {
|
|||||||
// Write the stream straight to disk
|
// Write the stream straight to disk
|
||||||
diskLoader.populateCacheWithStream(tuple, in);
|
diskLoader.populateCacheWithStream(tuple, in);
|
||||||
|
|
||||||
// Read it back scaled
|
// If there's a task associated with this load, we should return the bitmap
|
||||||
bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
if (task != null) {
|
||||||
if (bmp != null) {
|
return diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
||||||
return bmp;
|
}
|
||||||
|
else {
|
||||||
|
// Otherwise it's a background load and we return nothing
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,11 +120,13 @@ public class CachedAppAssetLoader {
|
|||||||
|
|
||||||
private class LoaderTask extends AsyncTask<LoaderTuple, Void, Bitmap> {
|
private class LoaderTask extends AsyncTask<LoaderTuple, Void, Bitmap> {
|
||||||
private final WeakReference<ImageView> imageViewRef;
|
private final WeakReference<ImageView> imageViewRef;
|
||||||
private LoaderTuple tuple;
|
private final boolean diskOnly;
|
||||||
private boolean loadFinished;
|
|
||||||
|
|
||||||
public LoaderTask(ImageView imageView) {
|
private LoaderTuple tuple;
|
||||||
imageViewRef = new WeakReference<ImageView>(imageView);
|
|
||||||
|
public LoaderTask(ImageView imageView, boolean diskOnly) {
|
||||||
|
this.imageViewRef = new WeakReference<ImageView>(imageView);
|
||||||
|
this.diskOnly = diskOnly;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -120,22 +135,23 @@ public class CachedAppAssetLoader {
|
|||||||
|
|
||||||
// Check whether it has been cancelled or the image view is gone
|
// Check whether it has been cancelled or the image view is gone
|
||||||
if (isCancelled() || imageViewRef.get() == null) {
|
if (isCancelled() || imageViewRef.get() == null) {
|
||||||
System.out.println("Cancelled or no image view in doInBackground");
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Bitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
Bitmap bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
||||||
if (bmp == null) {
|
if (bmp == null) {
|
||||||
// Report progress to display the placeholder
|
if (!diskOnly) {
|
||||||
publishProgress();
|
|
||||||
|
|
||||||
// Try to load the asset from the network
|
// Try to load the asset from the network
|
||||||
bmp = doNetworkAssetLoad(tuple, this);
|
bmp = doNetworkAssetLoad(tuple, this);
|
||||||
|
} else {
|
||||||
|
// Report progress to display the placeholder and spin
|
||||||
|
// off the network-capable task
|
||||||
|
publishProgress();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache the bitmap
|
// Cache the bitmap
|
||||||
if (bmp != null) {
|
if (bmp != null) {
|
||||||
loadFinished = true;
|
|
||||||
memoryLoader.populateCache(tuple, bmp);
|
memoryLoader.populateCache(tuple, bmp);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,25 +160,20 @@ public class CachedAppAssetLoader {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onProgressUpdate(Void... nothing) {
|
protected void onProgressUpdate(Void... nothing) {
|
||||||
// Do nothing if the load has already completed
|
|
||||||
if (loadFinished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do nothing if cancelled
|
// Do nothing if cancelled
|
||||||
if (isCancelled()) {
|
if (isCancelled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final ImageView imageView = imageViewRef.get();
|
|
||||||
if (imageView != null) {
|
|
||||||
// If the current loader task for this view isn't us, do nothing
|
// If the current loader task for this view isn't us, do nothing
|
||||||
if (getLoaderTask(imageView) != this) {
|
final ImageView imageView = imageViewRef.get();
|
||||||
return;
|
if (getLoaderTask(imageView) == this) {
|
||||||
}
|
// Set off another loader task on the network executor
|
||||||
|
LoaderTask task = new LoaderTask(imageView, false);
|
||||||
// Show the placeholder by setting alpha to 1.0
|
AsyncDrawable asyncDrawable = new AsyncDrawable(imageView.getResources(), placeholderBitmap, task);
|
||||||
imageView.setAlpha(1.0f);
|
imageView.setAlpha(1.0f);
|
||||||
|
imageView.setImageDrawable(asyncDrawable);
|
||||||
|
task.executeOnExecutor(networkExecutor, tuple);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,12 +185,7 @@ public class CachedAppAssetLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final ImageView imageView = imageViewRef.get();
|
final ImageView imageView = imageViewRef.get();
|
||||||
if (imageView != null) {
|
if (getLoaderTask(imageView) == this) {
|
||||||
// If the current loader task for this view isn't us, do nothing
|
|
||||||
if (getLoaderTask(imageView) != this) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the bitmap
|
// Set the bitmap
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
imageView.setImageBitmap(bitmap);
|
imageView.setImageBitmap(bitmap);
|
||||||
@ -206,6 +212,10 @@ public class CachedAppAssetLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static LoaderTask getLoaderTask(ImageView imageView) {
|
private static LoaderTask getLoaderTask(ImageView imageView) {
|
||||||
|
if (imageView == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final Drawable drawable = imageView.getDrawable();
|
final Drawable drawable = imageView.getDrawable();
|
||||||
|
|
||||||
// If our drawable is in play, get the loader task
|
// If our drawable is in play, get the loader task
|
||||||
@ -249,20 +259,13 @@ public class CachedAppAssetLoader {
|
|||||||
cacheExecutor.execute(new Runnable() {
|
cacheExecutor.execute(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
Bitmap bmp;
|
|
||||||
|
|
||||||
// Check if the image is cached on disk
|
// Check if the image is cached on disk
|
||||||
bmp = diskLoader.loadBitmapFromCache(tuple, (int) scalingDivider);
|
if (diskLoader.checkCacheExists(tuple)) {
|
||||||
if (bmp == null) {
|
return;
|
||||||
// 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
|
// Try to load the asset from the network and cache result on disk
|
||||||
// because it's not loaded into any image views or cached in memory
|
doNetworkAssetLoad(tuple, null);
|
||||||
if (bmp != null) {
|
|
||||||
bmp.recycle();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -270,14 +273,6 @@ public class CachedAppAssetLoader {
|
|||||||
public void populateImageView(NvApp app, ImageView view) {
|
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
|
|
||||||
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
|
||||||
if (bmp != null) {
|
|
||||||
// Show the bitmap immediately
|
|
||||||
view.setImageBitmap(bmp);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's already a task in progress for this view,
|
// If there's already a task in progress for this view,
|
||||||
// cancel it. If the task is already loading the same image,
|
// cancel it. If the task is already loading the same image,
|
||||||
// we return and let that load finish.
|
// we return and let that load finish.
|
||||||
@ -285,9 +280,18 @@ public class CachedAppAssetLoader {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// First, try the memory cache in the current context
|
||||||
|
Bitmap bmp = memoryLoader.loadBitmapFromCache(tuple);
|
||||||
|
if (bmp != null) {
|
||||||
|
// Show the bitmap immediately
|
||||||
|
view.setAlpha(1.0f);
|
||||||
|
view.setImageBitmap(bmp);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If it's not in memory, create an async task to load it. This task will be attached
|
// If it's not in memory, create an async task to load it. This task will be attached
|
||||||
// via AsyncDrawable to this view.
|
// via AsyncDrawable to this view.
|
||||||
final LoaderTask task = new LoaderTask(view);
|
final LoaderTask task = new LoaderTask(view, true);
|
||||||
final AsyncDrawable asyncDrawable = new AsyncDrawable(view.getResources(), placeholderBitmap, task);
|
final AsyncDrawable asyncDrawable = new AsyncDrawable(view.getResources(), placeholderBitmap, task);
|
||||||
view.setAlpha(0.0f);
|
view.setAlpha(0.0f);
|
||||||
view.setImageDrawable(asyncDrawable);
|
view.setImageDrawable(asyncDrawable);
|
||||||
|
@ -19,6 +19,10 @@ public class DiskAssetLoader {
|
|||||||
this.cacheDir = cacheDir;
|
this.cacheDir = cacheDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean checkCacheExists(CachedAppAssetLoader.LoaderTuple tuple) {
|
||||||
|
return CacheHelper.cacheFileExists(cacheDir, "boxart", tuple.computer.uuid.toString(), tuple.app.getAppId() + ".png");
|
||||||
|
}
|
||||||
|
|
||||||
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
|
public Bitmap loadBitmapFromCache(CachedAppAssetLoader.LoaderTuple tuple, int sampleSize) {
|
||||||
InputStream in = null;
|
InputStream in = null;
|
||||||
Bitmap bmp = null;
|
Bitmap bmp = null;
|
||||||
|
@ -7,7 +7,7 @@ import com.limelight.LimeLog;
|
|||||||
|
|
||||||
public class MemoryAssetLoader {
|
public class MemoryAssetLoader {
|
||||||
private static final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
private static final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
||||||
private static final LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(maxMemory / 12) {
|
private static final LruCache<String, Bitmap> memoryCache = new LruCache<String, Bitmap>(maxMemory / 16) {
|
||||||
@Override
|
@Override
|
||||||
protected int sizeOf(String key, Bitmap bitmap) {
|
protected int sizeOf(String key, Bitmap bitmap) {
|
||||||
// Sizeof returns kilobytes
|
// Sizeof returns kilobytes
|
||||||
|
@ -30,6 +30,10 @@ public class CacheHelper {
|
|||||||
return f;
|
return f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean cacheFileExists(File root, String... path) {
|
||||||
|
return openPath(false, root, path).exists();
|
||||||
|
}
|
||||||
|
|
||||||
public static InputStream openCacheFileForInput(File root, String... path) throws FileNotFoundException {
|
public static InputStream openCacheFileForInput(File root, String... path) throws FileNotFoundException {
|
||||||
return new BufferedInputStream(new FileInputStream(openPath(false, root, path)));
|
return new BufferedInputStream(new FileInputStream(openPath(false, root, path)));
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user