Android: Offload cover path unmangling to another thread

This commit is contained in:
Charles Lombardo 2022-11-04 13:02:21 -04:00
parent 1d07332657
commit 6eb5013359
4 changed files with 151 additions and 121 deletions

View File

@ -2,6 +2,7 @@
package org.dolphinemu.dolphinemu.adapters;
import android.app.Activity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
@ -27,14 +28,16 @@ public final class GameAdapter extends RecyclerView.Adapter<GameAdapter.GameView
View.OnLongClickListener
{
private List<GameFile> mGameFiles;
private Activity mActivity;
/**
* Initializes the adapter's observer, which watches for changes to the dataset. The adapter will
* display no data until swapDataSet is called.
*/
public GameAdapter()
public GameAdapter(Activity activity)
{
mGameFiles = new ArrayList<>();
mActivity = activity;
}
/**
@ -70,7 +73,7 @@ public final class GameAdapter extends RecyclerView.Adapter<GameAdapter.GameView
{
Context context = holder.itemView.getContext();
GameFile gameFile = mGameFiles.get(position);
GlideUtils.loadGameCover(holder, holder.binding.imageGameScreen, gameFile);
GlideUtils.loadGameCover(holder, holder.binding.imageGameScreen, gameFile, mActivity);
if (GameFileCacheManager.findSecondDisc(gameFile) != null)
{

View File

@ -50,7 +50,7 @@ public final class GameRowPresenter extends Presenter
GameFile gameFile = (GameFile) item;
holder.imageScreenshot.setImageDrawable(null);
GlideUtils.loadGameCover(null, holder.imageScreenshot, gameFile);
GlideUtils.loadGameCover(null, holder.imageScreenshot, gameFile, null);
holder.cardParent.setTitleText(gameFile.getTitle());

View File

@ -65,7 +65,7 @@ public final class PlatformGamesFragment extends Fragment implements PlatformGam
int columns = getResources().getInteger(R.integer.game_grid_columns);
RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getActivity(), columns);
mAdapter = new GameAdapter();
mAdapter = new GameAdapter(requireActivity());
// Set theme color to the refresh animation's background
mSwipeRefresh.setProgressBackgroundColorSchemeColor(

View File

@ -2,11 +2,14 @@
package org.dolphinemu.dolphinemu.utils;
import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.ImageView;
@ -34,7 +37,9 @@ import java.util.concurrent.Executors;
public class GlideUtils
{
private static final ExecutorService executor = Executors.newSingleThreadExecutor();
private static final ExecutorService saveCoverExecutor = Executors.newSingleThreadExecutor();
private static final ExecutorService unmangleExecutor = Executors.newSingleThreadExecutor();
private static final Handler unmangleHandler = new Handler(Looper.getMainLooper());
public static void loadGameBanner(ImageView imageView, GameFile gameFile)
{
@ -61,7 +66,7 @@ public class GlideUtils
}
public static void loadGameCover(GameAdapter.GameViewHolder gameViewHolder, ImageView imageView,
GameFile gameFile)
GameFile gameFile, Activity activity)
{
if (BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal() && gameViewHolder != null)
{
@ -77,134 +82,156 @@ public class GlideUtils
gameViewHolder.binding.textGameCaption.setVisibility(View.GONE);
}
String customCoverPath = gameFile.getCustomCoverPath();
Uri customCoverUri = null;
boolean customCoverExists = false;
if (ContentHandler.isContentUri(customCoverPath))
unmangleExecutor.execute(() ->
{
try
String customCoverPath = gameFile.getCustomCoverPath();
Uri customCoverUri = null;
boolean customCoverExists = false;
if (ContentHandler.isContentUri(customCoverPath))
{
customCoverUri = ContentHandler.unmangle(customCoverPath);
customCoverExists = true;
try
{
customCoverUri = ContentHandler.unmangle(customCoverPath);
customCoverExists = true;
}
catch (FileNotFoundException | SecurityException ignored)
{
// Let customCoverExists remain false
}
}
catch (FileNotFoundException | SecurityException ignored)
else
{
// Let customCoverExists remain false
customCoverUri = Uri.parse(customCoverPath);
customCoverExists = new File(customCoverPath).exists();
}
}
else
{
customCoverUri = Uri.parse(customCoverPath);
customCoverExists = new File(customCoverPath).exists();
}
Context context = imageView.getContext();
File cover;
if (customCoverExists)
{
Glide.with(context)
.load(customCoverUri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.listener(new RequestListener<Drawable>()
{
@Override public boolean onLoadFailed(@Nullable GlideException e, Object model,
Target<Drawable> target, boolean isFirstResource)
{
GlideUtils.enableInnerTitle(gameViewHolder, imageView);
return false;
}
Context context = imageView.getContext();
boolean finalCustomCoverExists = customCoverExists;
Uri finalCustomCoverUri = customCoverUri;
@Override public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource, boolean isFirstResource)
{
GlideUtils.disableInnerTitle(gameViewHolder);
return false;
}
})
.into(imageView);
}
else if ((cover = new File(gameFile.getCoverPath(context))).exists())
{
Glide.with(context)
.load(cover)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.listener(new RequestListener<Drawable>()
{
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model,
Target<Drawable> target, boolean isFirstResource)
{
GlideUtils.enableInnerTitle(gameViewHolder, imageView);
return false;
}
File cover = new File(gameFile.getCoverPath(context));
boolean cachedCoverExists = cover.exists();
unmangleHandler.post(() ->
{
// We can't get a reference to the current activity in the TV version.
// Luckily it won't attempt to start loads on destroyed activities.
if (activity != null)
{
// We can't start an image load on a destroyed activity
if (activity.isDestroyed())
{
return;
}
}
@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource, boolean isFirstResource)
{
GlideUtils.disableInnerTitle(gameViewHolder);
return false;
}
})
.into(imageView);
}
else if (BooleanSetting.MAIN_USE_GAME_COVERS.getBooleanGlobal())
{
Glide.with(context)
.load(CoverHelper.buildGameTDBUrl(gameFile, CoverHelper.getRegion(gameFile)))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.listener(new RequestListener<Drawable>()
{
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model,
Target<Drawable> target, boolean isFirstResource)
{
GlideUtils.enableInnerTitle(gameViewHolder, imageView);
return false;
}
if (finalCustomCoverExists)
{
Glide.with(imageView)
.load(finalCustomCoverUri)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.error(R.drawable.no_banner)
.listener(new RequestListener<Drawable>()
{
@Override public boolean onLoadFailed(@Nullable GlideException e, Object model,
Target<Drawable> target, boolean isFirstResource)
{
GlideUtils.enableInnerTitle(gameViewHolder);
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource, boolean isFirstResource)
{
GlideUtils.disableInnerTitle(gameViewHolder);
return false;
}
})
.into(new CustomTarget<Drawable>()
{
@Override
public void onResourceReady(@NonNull Drawable resource,
@Nullable Transition<? super Drawable> transition)
{
Bitmap cover = ((BitmapDrawable) resource).getBitmap();
executor.execute(
() -> CoverHelper.saveCover(cover, gameFile.getCoverPath(context)));
imageView.setImageBitmap(cover);
}
@Override public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource, boolean isFirstResource)
{
GlideUtils.disableInnerTitle(gameViewHolder);
return false;
}
})
.into(imageView);
}
else if (cachedCoverExists)
{
Glide.with(imageView)
.load(cover)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.error(R.drawable.no_banner)
.listener(new RequestListener<Drawable>()
{
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model,
Target<Drawable> target, boolean isFirstResource)
{
GlideUtils.enableInnerTitle(gameViewHolder);
return false;
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder)
{
}
});
}
else
{
enableInnerTitle(gameViewHolder, imageView);
}
@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource, boolean isFirstResource)
{
GlideUtils.disableInnerTitle(gameViewHolder);
return false;
}
})
.into(imageView);
}
else if (BooleanSetting.MAIN_USE_GAME_COVERS.getBooleanGlobal())
{
Glide.with(context)
.load(CoverHelper.buildGameTDBUrl(gameFile, CoverHelper.getRegion(gameFile)))
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
.error(R.drawable.no_banner)
.listener(new RequestListener<Drawable>()
{
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model,
Target<Drawable> target, boolean isFirstResource)
{
GlideUtils.enableInnerTitle(gameViewHolder);
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model,
Target<Drawable> target, DataSource dataSource, boolean isFirstResource)
{
GlideUtils.disableInnerTitle(gameViewHolder);
return false;
}
})
.into(new CustomTarget<Drawable>()
{
@Override
public void onResourceReady(@NonNull Drawable resource,
@Nullable Transition<? super Drawable> transition)
{
Bitmap cover = ((BitmapDrawable) resource).getBitmap();
saveCoverExecutor.execute(
() -> CoverHelper.saveCover(cover, gameFile.getCoverPath(context)));
imageView.setImageBitmap(cover);
}
@Override
public void onLoadCleared(@Nullable Drawable placeholder)
{
}
});
}
else
{
Glide.with(imageView.getContext())
.load(R.drawable.no_banner)
.into(imageView);
enableInnerTitle(gameViewHolder);
}
});
});
}
private static void enableInnerTitle(GameAdapter.GameViewHolder gameViewHolder,
ImageView imageView)
private static void enableInnerTitle(GameAdapter.GameViewHolder gameViewHolder)
{
Glide.with(imageView.getContext())
.load(R.drawable.no_banner)
.into(imageView);
if (gameViewHolder != null && !BooleanSetting.MAIN_SHOW_GAME_TITLES.getBooleanGlobal())
{
gameViewHolder.binding.textGameTitleInner.setVisibility(View.VISIBLE);