From b19eb1f91d7f64a955e6866f9c8eb44db0d9664c Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 9 Feb 2018 13:36:09 +0100 Subject: [PATCH 1/6] use default disk cache for UIL, CleanCacheService handles cleanup No need to slow down UIL by making it do a cache check since CleanCacheService already does that in a low priority background service. The default FileNameGenerator just uses imageUri.hashCode() so its safe an faster than ours. So just use the default. Also, no need to set threadPriority() since we are using the default --- .../java/org/fdroid/fdroid/FDroidApp.java | 32 ------------------- 1 file changed, 32 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 3306a32e9..de4d3a758 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -32,17 +32,13 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Configuration; -import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.StrictMode; -import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Log; import android.view.WindowManager; import android.widget.Toast; -import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiskCache; -import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import info.guardianproject.netcipher.NetCipher; @@ -59,7 +55,6 @@ import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.InstalledAppProviderService; import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; -import org.fdroid.fdroid.data.SanitizedFile; import org.fdroid.fdroid.installer.ApkFileProvider; import org.fdroid.fdroid.installer.InstallHistoryService; import org.fdroid.fdroid.net.ImageLoaderForUIL; @@ -387,34 +382,7 @@ public class FDroidApp extends Application { ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()) .imageDownloader(new ImageLoaderForUIL(getApplicationContext())) - .diskCache(new LimitedAgeDiskCache( - Utils.getImageCacheDir(this), - null, - new FileNameGenerator() { - @NonNull - @Override - public String generate(String imageUri) { - if (TextUtils.isEmpty(imageUri)) { - return "null"; - } - - String fileNameToSanitize; - Uri uri = Uri.parse(imageUri); - if (TextUtils.isEmpty(uri.getPath())) { - // files with URL like "drawable://213083835209" used by the category backgrounds - fileNameToSanitize = imageUri.replaceAll("[:/]", ""); - } else { - fileNameToSanitize = uri.getPath().replace("/", "-"); - } - - return SanitizedFile.sanitizeFileName(fileNameToSanitize); - } - }, - // 30 days in secs: 30*24*60*60 = 2592000 - 2592000) - ) .threadPoolSize(4) - .threadPriority(Thread.NORM_PRIORITY - 2) // Default is NORM_PRIORITY - 1 .build(); ImageLoader.getInstance().init(config); From 2a3aaacf2347679f30e2c8feffb92f25bb882c8b Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 23 Mar 2018 17:46:38 +0100 Subject: [PATCH 2/6] set UIL DisplayImageOptions at startup Instead of setting the same thing at each place its used, this puts all the settings in one place. For the most part, they are the same everywhere. This makes it a lot easier to optimize how UIL works since all the settings are in one place. --- .../java/org/fdroid/fdroid/AppDetails2.java | 2 +- .../java/org/fdroid/fdroid/FDroidApp.java | 1 + .../org/fdroid/fdroid/NotificationHelper.java | 17 +++---- .../main/java/org/fdroid/fdroid/Utils.java | 45 ++++++++++++++----- .../views/InstallConfirmActivity.java | 16 ++----- .../views/AppDetailsRecyclerViewAdapter.java | 16 +------ .../fdroid/views/ScreenShotsActivity.java | 18 ++++---- .../views/ScreenShotsRecyclerViewAdapter.java | 14 +++--- .../views/apps/AppListItemController.java | 40 ++++++++--------- .../views/categories/AppCardController.java | 11 ++--- .../views/categories/CategoryController.java | 8 +--- .../fdroid/views/swap/SwapAppsView.java | 9 +--- 12 files changed, 85 insertions(+), 112 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java index afe3d8e7b..554016be4 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails2.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails2.java @@ -173,7 +173,7 @@ public class AppDetails2 extends AppCompatActivity // Load the feature graphic, if present final FeatureImage featureImage = (FeatureImage) findViewById(R.id.feature_graphic); - DisplayImageOptions displayImageOptions = Utils.getImageLoadingOptions().build(); + DisplayImageOptions displayImageOptions = Utils.getRepoAppDisplayImageOptions(); String featureGraphicUrl = app.getFeatureGraphicUrl(this); featureImage.loadImageAndDisplay(ImageLoader.getInstance(), displayImageOptions, featureGraphicUrl, app.iconUrl); diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index de4d3a758..a64a73284 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -382,6 +382,7 @@ public class FDroidApp extends Application { ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()) .imageDownloader(new ImageLoaderForUIL(getApplicationContext())) + .defaultDisplayImageOptions(Utils.getDefaultDisplayImageOptionsBuilder().build()) .threadPoolSize(4) .build(); ImageLoader.getInstance().init(config); diff --git a/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java b/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java index 7d9a5d9d6..927b5626b 100644 --- a/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java @@ -21,10 +21,8 @@ import android.text.Spanned; import android.text.TextUtils; import android.text.style.StyleSpan; import android.view.View; -import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.assist.FailReason; -import com.nostra13.universalimageloader.core.assist.ImageScaleType; import com.nostra13.universalimageloader.core.assist.ImageSize; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.utils.DiskCacheUtils; @@ -54,7 +52,6 @@ class NotificationHelper { private final Context context; private final NotificationManagerCompat notificationManager; private final AppUpdateStatusManager appUpdateStatusManager; - private final DisplayImageOptions displayImageOptions; private final ArrayList updates = new ArrayList<>(); private final ArrayList installed = new ArrayList<>(); @@ -62,12 +59,6 @@ class NotificationHelper { this.context = context; appUpdateStatusManager = AppUpdateStatusManager.getInstance(context); notificationManager = NotificationManagerCompat.from(context); - displayImageOptions = new DisplayImageOptions.Builder() - .cacheInMemory(true) - .cacheOnDisk(true) - .imageScaleType(ImageScaleType.NONE) - .bitmapConfig(Bitmap.Config.RGB_565) - .build(); IntentFilter filter = new IntentFilter(); filter.addAction(AppUpdateStatusManager.BROADCAST_APPSTATUS_LIST_CHANGED); @@ -506,7 +497,8 @@ class NotificationHelper { Bitmap iconLarge = null; if (TextUtils.isEmpty(entry.app.iconUrl)) { return null; - } else if (entry.status == AppUpdateStatusManager.Status.Downloading || entry.status == AppUpdateStatusManager.Status.Installing) { + } else if (entry.status == AppUpdateStatusManager.Status.Downloading + || entry.status == AppUpdateStatusManager.Status.Installing) { Bitmap bitmap = Bitmap.createBitmap(largeIconSize.x, largeIconSize.y, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); Drawable downloadIcon = ContextCompat.getDrawable(context, R.drawable.ic_notification_download); @@ -516,10 +508,11 @@ class NotificationHelper { } return bitmap; } else if (DiskCacheUtils.findInCache(entry.app.iconUrl, ImageLoader.getInstance().getDiskCache()) != null) { - iconLarge = ImageLoader.getInstance().loadImageSync(entry.app.iconUrl, new ImageSize(largeIconSize.x, largeIconSize.y), displayImageOptions); + iconLarge = ImageLoader.getInstance().loadImageSync( + entry.app.iconUrl, new ImageSize(largeIconSize.x, largeIconSize.y)); } else { // Load it for later! - ImageLoader.getInstance().loadImage(entry.app.iconUrl, new ImageSize(largeIconSize.x, largeIconSize.y), displayImageOptions, new ImageLoadingListener() { + ImageLoader.getInstance().loadImage(entry.app.iconUrl, new ImageSize(largeIconSize.x, largeIconSize.y), new ImageLoadingListener() { AppUpdateStatusManager.AppUpdateStatus entry; diff --git a/app/src/main/java/org/fdroid/fdroid/Utils.java b/app/src/main/java/org/fdroid/fdroid/Utils.java index bb3873309..a0480f461 100644 --- a/app/src/main/java/org/fdroid/fdroid/Utils.java +++ b/app/src/main/java/org/fdroid/fdroid/Utils.java @@ -92,6 +92,9 @@ public final class Utils { "%.0f B", "%.0f KiB", "%.1f MiB", "%.2f GiB", }; + private static DisplayImageOptions.Builder defaultDisplayImageOptionsBuilder; + private static DisplayImageOptions repoAppDisplayImageOptions; + public static final String FALLBACK_ICONS_DIR = "/icons/"; /* @@ -369,15 +372,33 @@ public final class Utils { return new Locale(languageTag); } - public static DisplayImageOptions.Builder getImageLoadingOptions() { - return new DisplayImageOptions.Builder() - .cacheInMemory(true) - .cacheOnDisk(true) - .imageScaleType(ImageScaleType.NONE) - .showImageOnLoading(R.drawable.ic_repo_app_default) - .showImageForEmptyUri(R.drawable.ic_repo_app_default) - .displayer(new FadeInBitmapDisplayer(200, true, true, false)) - .bitmapConfig(Bitmap.Config.RGB_565); + public static DisplayImageOptions.Builder getDefaultDisplayImageOptionsBuilder() { + if (defaultDisplayImageOptionsBuilder == null) { + defaultDisplayImageOptionsBuilder = new DisplayImageOptions.Builder() + .cacheInMemory(true) + .cacheOnDisk(true) + .considerExifParams(false) + .bitmapConfig(Bitmap.Config.RGB_565) + .imageScaleType(ImageScaleType.NONE); + } + return defaultDisplayImageOptionsBuilder; + } + + /** + * Gets the {@link DisplayImageOptions} instance used to configure + * {@link com.nostra13.universalimageloader.core.ImageLoader} instances + * used to display app icons. It lazy loads a reusable static instance. + */ + public static DisplayImageOptions getRepoAppDisplayImageOptions() { + if (repoAppDisplayImageOptions == null) { + repoAppDisplayImageOptions = getDefaultDisplayImageOptionsBuilder() + .showImageOnLoading(R.drawable.ic_repo_app_default) + .showImageForEmptyUri(R.drawable.ic_repo_app_default) + .showImageOnFail(R.drawable.ic_repo_app_default) + .displayer(new FadeInBitmapDisplayer(200, true, true, false)) + .build(); + } + return repoAppDisplayImageOptions; } // this is all new stuff being added @@ -554,8 +575,10 @@ public final class Utils { } } - // Need this to add the unimplemented support for ordered and unordered - // lists to Html.fromHtml(). + /** + * Need this to add the unimplemented support for ordered and unordered + * lists to Html.fromHtml(). + */ public static class HtmlTagHandler implements Html.TagHandler { int listNum; diff --git a/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java b/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java index 5c3287074..f9f9791b8 100644 --- a/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/privileged/views/InstallConfirmActivity.java @@ -23,7 +23,6 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; import android.content.Intent; import android.content.pm.ApplicationInfo; -import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.FragmentActivity; @@ -36,11 +35,10 @@ import android.widget.Button; import android.widget.ImageView; import android.widget.TabHost; import android.widget.TextView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.ImageScaleType; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.ApkProvider; import org.fdroid.fdroid.data.App; @@ -71,15 +69,6 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel private App app; - private final DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder() - .cacheInMemory(true) - .cacheOnDisk(true) - .imageScaleType(ImageScaleType.NONE) - .showImageOnLoading(R.drawable.ic_repo_app_default) - .showImageForEmptyUri(R.drawable.ic_repo_app_default) - .bitmapConfig(Bitmap.Config.RGB_565) - .build(); - private void startInstallConfirm() { View appSnippet = findViewById(R.id.app_snippet); TextView appName = (TextView) appSnippet.findViewById(R.id.app_name); @@ -87,7 +76,8 @@ public class InstallConfirmActivity extends FragmentActivity implements OnCancel TabHost tabHost = (TabHost) findViewById(android.R.id.tabhost); appName.setText(app.name); - ImageLoader.getInstance().displayImage(app.iconUrl, appIcon, displayImageOptions); + ImageLoader.getInstance().displayImage(app.iconUrl, appIcon, + Utils.getRepoAppDisplayImageOptions()); tabHost.setup(); ViewPager viewPager = (ViewPager) findViewById(R.id.pager); diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java index d6dea4384..e31f174c1 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -6,7 +6,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.res.Resources; -import android.graphics.Bitmap; import android.net.Uri; import android.support.annotation.DrawableRes; import android.support.annotation.LayoutRes; @@ -34,11 +33,7 @@ import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; - -import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.ImageScaleType; - import org.fdroid.fdroid.Preferences; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; @@ -317,7 +312,6 @@ public class AppDetailsRecyclerViewAdapter final TextView progressLabel; final TextView progressPercent; final View progressCancel; - final DisplayImageOptions displayImageOptions; boolean descriptionIsExpanded; HeaderViewHolder(View view) { @@ -340,14 +334,6 @@ public class AppDetailsRecyclerViewAdapter progressLabel = (TextView) view.findViewById(R.id.progress_label); progressPercent = (TextView) view.findViewById(R.id.progress_percent); progressCancel = view.findViewById(R.id.progress_cancel); - displayImageOptions = new DisplayImageOptions.Builder() - .cacheInMemory(true) - .cacheOnDisk(true) - .imageScaleType(ImageScaleType.NONE) - .showImageOnLoading(R.drawable.ic_repo_app_default) - .showImageForEmptyUri(R.drawable.ic_repo_app_default) - .bitmapConfig(Bitmap.Config.RGB_565) - .build(); descriptionView.setMaxLines(MAX_LINES); descriptionView.setEllipsize(TextUtils.TruncateAt.MARQUEE); descriptionMoreView.setOnClickListener(new View.OnClickListener() { @@ -408,7 +394,7 @@ public class AppDetailsRecyclerViewAdapter } public void bindModel() { - ImageLoader.getInstance().displayImage(app.iconUrl, iconView, displayImageOptions); + ImageLoader.getInstance().displayImage(app.iconUrl, iconView, Utils.getRepoAppDisplayImageOptions()); titleView.setText(app.name); if (!TextUtils.isEmpty(app.authorName)) { authorView.setText(context.getString(R.string.by_author_format, app.authorName)); diff --git a/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsActivity.java b/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsActivity.java index d85988026..9d5aea335 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsActivity.java @@ -3,7 +3,6 @@ package org.fdroid.fdroid.views; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; -import android.graphics.Bitmap; import android.os.Build; import android.os.Bundle; import android.support.annotation.Nullable; @@ -16,18 +15,21 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; - import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.ImageScaleType; - import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.App; import org.fdroid.fdroid.data.AppProvider; /** - * Full screen view of an apps screenshots to swipe through. + * Full screen view of an apps screenshots to swipe through. This will always + * download the image, even if the user has said not to use "unmetered" networks, + * e.g. WiFi. That is because the user has to click on the thumbnail in + * {@link org.fdroid.fdroid.AppDetails2} in order to bring up this activity. + * That makes it a specific request for that image, rather than regular + * background loading. */ public class ScreenShotsActivity extends AppCompatActivity { @@ -114,13 +116,9 @@ public class ScreenShotsActivity extends AppCompatActivity { public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - DisplayImageOptions displayImageOptions = new DisplayImageOptions.Builder() - .cacheInMemory(true) - .cacheOnDisk(true) - .imageScaleType(ImageScaleType.NONE) + DisplayImageOptions displayImageOptions = Utils.getDefaultDisplayImageOptionsBuilder() .showImageOnLoading(R.drawable.screenshot_placeholder) .showImageForEmptyUri(R.drawable.screenshot_placeholder) - .bitmapConfig(Bitmap.Config.RGB_565) .build(); View rootView = inflater.inflate(R.layout.activity_screenshots_page, container, false); diff --git a/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsRecyclerViewAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsRecyclerViewAdapter.java index 1ce4aee02..a128f6d23 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/ScreenShotsRecyclerViewAdapter.java @@ -1,20 +1,20 @@ package org.fdroid.fdroid.views; import android.content.Context; -import android.graphics.Bitmap; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; - import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.ImageScaleType; - import org.fdroid.fdroid.R; +import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.App; +/** + * Loads and displays the small screenshots that are inline in {@link org.fdroid.fdroid.AppDetails2} + */ class ScreenShotsRecyclerViewAdapter extends RecyclerView.Adapter { private final String[] screenshots; private final DisplayImageOptions displayImageOptions; @@ -25,13 +25,9 @@ class ScreenShotsRecyclerViewAdapter extends RecyclerView.Adapter - *
  • app_list_item (see {@link StandardAppListItemController}
  • - *
  • updateable_app_list_status_item (see - * {@link org.fdroid.fdroid.views.updates.items.AppStatusListItemController}
  • - *
  • updateable_app_list_item (see - * {@link org.fdroid.fdroid.views.updates.items.UpdateableAppListItemController}
  • - *
  • installed_app_list_item (see {@link StandardAppListItemController}
  • + *
  • app_list_item (see {@link StandardAppListItemController}
  • + *
  • updateable_app_list_status_item (see + * {@link org.fdroid.fdroid.views.updates.items.AppStatusListItemController}
  • + *
  • updateable_app_list_item (see + * {@link org.fdroid.fdroid.views.updates.items.UpdateableAppListItemController}
  • + *
  • installed_app_list_item (see {@link StandardAppListItemController}
  • * - * + *

    * The state of the UI is defined in a dumb {@link AppListItemState} class, then applied to the UI * in the {@link #refreshView(App, AppUpdateStatus)} method. */ @@ -98,8 +95,6 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { @Nullable private final Button secondaryButton; - private final DisplayImageOptions displayImageOptions; - @Nullable private App currentApp; @@ -156,8 +151,6 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { cancelButton.setOnClickListener(onCancelDownload); } - displayImageOptions = Utils.getImageLoadingOptions().build(); - itemView.setOnClickListener(onAppClicked); } @@ -169,7 +162,7 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { public void bindModel(@NonNull App app) { currentApp = app; - ImageLoader.getInstance().displayImage(app.iconUrl, icon, displayImageOptions); + ImageLoader.getInstance().displayImage(app.iconUrl, icon, Utils.getRepoAppDisplayImageOptions()); // Figures out the current install/update/download/etc status for the app we are viewing. // Then, asks the view to update itself to reflect this status. @@ -193,7 +186,9 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { broadcastManager.registerReceiver(onStatusChanged, intentFilter); } - /** To be overridden if required */ + /** + * To be overridden if required + */ public boolean canDismiss() { return false; } @@ -214,6 +209,7 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { /** * Override to respond to the user swiping an app to dismiss it from the list. + * * @return Optionally return a description of what you did if it is not obvious to the user. It will be shown as * a {@link android.widget.Toast} for a {@link android.widget.Toast#LENGTH_SHORT} time. * @see #canDismiss() This must also be overriden and should return true. @@ -235,7 +231,7 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { /** * Queries the current state via {@link #getCurrentViewState(App, AppUpdateStatus)} * and then updates the relevant widgets depending on that state. - * + *

    * Should contain little to no business logic, this all belongs to * {@link #getCurrentViewState(App, AppUpdateStatus)}. * @@ -489,7 +485,8 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { intent.getParcelableExtra(Installer.EXTRA_USER_INTERACTION_PI); try { pendingIntent.send(); - } catch (PendingIntent.CanceledException ignored) { } + } catch (PendingIntent.CanceledException ignored) { + } } } }; @@ -503,8 +500,11 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { } } - /** To be overridden by subclasses if desired */ - protected void onSecondaryButtonPressed(@NonNull App app) { } + /** + * To be overridden by subclasses if desired + */ + protected void onSecondaryButtonPressed(@NonNull App app) { + } @SuppressWarnings("FieldCanBeLocal") private final View.OnClickListener onCancelDownload = new View.OnClickListener() { diff --git a/app/src/main/java/org/fdroid/fdroid/views/categories/AppCardController.java b/app/src/main/java/org/fdroid/fdroid/views/categories/AppCardController.java index b3c963da4..914285220 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/categories/AppCardController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/categories/AppCardController.java @@ -18,7 +18,6 @@ import android.text.TextUtils; import android.view.View; import android.widget.ImageView; import android.widget.TextView; -import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; @@ -70,7 +69,6 @@ public class AppCardController extends RecyclerView.ViewHolder private App currentApp; private final Activity activity; - private final DisplayImageOptions displayImageOptions; public AppCardController(Activity activity, View itemView) { super(itemView); @@ -83,8 +81,6 @@ public class AppCardController extends RecyclerView.ViewHolder featuredImage = (FeatureImage) itemView.findViewById(R.id.featured_image); newTag = (TextView) itemView.findViewById(R.id.new_tag); - displayImageOptions = Utils.getImageLoadingOptions().build(); - itemView.setOnClickListener(this); } @@ -117,7 +113,7 @@ public class AppCardController extends RecyclerView.ViewHolder } } - ImageLoader.getInstance().displayImage(app.iconUrl, icon, displayImageOptions, this); + ImageLoader.getInstance().displayImage(app.iconUrl, icon, Utils.getRepoAppDisplayImageOptions(), this); if (featuredImage != null) { featuredImage.setColour(ContextCompat.getColor(activity, R.color.fdroid_blue)); @@ -133,7 +129,7 @@ public class AppCardController extends RecyclerView.ViewHolder // feature image to be loaded). if (!TextUtils.isEmpty(app.featureGraphic)) { featuredImage.loadImageAndDisplay(ImageLoader.getInstance(), - displayImageOptions, app.getFeatureGraphicUrl(activity)); + Utils.getRepoAppDisplayImageOptions(), app.getFeatureGraphicUrl(activity)); } } } @@ -162,7 +158,8 @@ public class AppCardController extends RecyclerView.ViewHolder Pair iconTransitionPair = Pair.create((View) icon, activity.getString(R.string.transition_app_item_icon)); - @SuppressWarnings("unchecked") // the right type is passed as 2nd varargs arg: Pair + // unchecked since the right type is passed as 2nd varargs arg: Pair + @SuppressWarnings("unchecked") Bundle b = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, iconTransitionPair).toBundle(); activity.startActivity(intent, b); } else { diff --git a/app/src/main/java/org/fdroid/fdroid/views/categories/CategoryController.java b/app/src/main/java/org/fdroid/fdroid/views/categories/CategoryController.java index b4ca76bdc..371623c8e 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/categories/CategoryController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/categories/CategoryController.java @@ -5,7 +5,6 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.database.Cursor; -import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; @@ -23,9 +22,9 @@ import android.widget.FrameLayout; import android.widget.TextView; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; -import com.nostra13.universalimageloader.core.assist.ImageScaleType; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import org.fdroid.fdroid.R; +import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.AppProvider; import org.fdroid.fdroid.data.Schema; import org.fdroid.fdroid.views.apps.AppListActivity; @@ -67,11 +66,8 @@ public class CategoryController extends RecyclerView.ViewHolder implements Loade appCards.setAdapter(appCardsAdapter); appCards.addItemDecoration(new ItemDecorator(activity)); - displayImageOptions = new DisplayImageOptions.Builder() - .cacheInMemory(true) - .imageScaleType(ImageScaleType.NONE) + displayImageOptions = Utils.getDefaultDisplayImageOptionsBuilder() .displayer(new FadeInBitmapDisplayer(100, true, true, false)) - .bitmapConfig(Bitmap.Config.RGB_565) .build(); } diff --git a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java index 78547c6be..143f067a0 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java +++ b/app/src/main/java/org/fdroid/fdroid/views/swap/SwapAppsView.java @@ -36,10 +36,7 @@ import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; - -import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; - import org.fdroid.fdroid.BuildConfig; import org.fdroid.fdroid.R; import org.fdroid.fdroid.UpdateService; @@ -63,8 +60,6 @@ public class SwapAppsView extends ListView implements LoaderManager.LoaderCallbacks, SearchView.OnQueryTextListener { - private DisplayImageOptions displayImageOptions; - public SwapAppsView(Context context) { super(context); } @@ -113,8 +108,6 @@ public class SwapAppsView extends ListView implements // either reconnect with an existing loader or start a new one getActivity().getSupportLoaderManager().initLoader(LOADER_SWAPABLE_APPS, null, this); - displayImageOptions = Utils.getImageLoadingOptions().build(); - LocalBroadcastManager.getInstance(getActivity()).registerReceiver( pollForUpdatesReceiver, new IntentFilter(UpdateService.LOCAL_ACTION_STATUS)); @@ -354,7 +347,7 @@ public class SwapAppsView extends ListView implements nameView.setText(app.name); } - ImageLoader.getInstance().displayImage(app.iconUrl, iconView, displayImageOptions); + ImageLoader.getInstance().displayImage(app.iconUrl, iconView, Utils.getRepoAppDisplayImageOptions()); if (app.hasUpdates()) { btnInstall.setText(R.string.menu_upgrade); From 6e64438fa6dcdaeed0158da783749b2af51b2c05 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 23 Mar 2018 17:51:32 +0100 Subject: [PATCH 3/6] disable UIL image handling while scrolling This should speed up the scrolling, especially on older devices. --- .../fdroid/views/apps/AppListActivity.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java index 4476c5a8f..68f25b2c8 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListActivity.java @@ -18,6 +18,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; +import com.nostra13.universalimageloader.core.ImageLoader; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.AppProvider; @@ -124,6 +125,22 @@ public class AppListActivity extends AppCompatActivity implements LoaderManager. appView.setHasFixedSize(true); appView.setLayoutManager(new LinearLayoutManager(this)); appView.setAdapter(appAdapter); + appView.addOnScrollListener(new RecyclerView.OnScrollListener() { + private final ImageLoader imageLoader = ImageLoader.getInstance(); + + @Override + public void onScrollStateChanged(RecyclerView recyclerView, int newState) { + switch (newState) { + case RecyclerView.SCROLL_STATE_DRAGGING: + imageLoader.pause(); + break; + case RecyclerView.SCROLL_STATE_IDLE: + imageLoader.resume(); + break; + } + super.onScrollStateChanged(recyclerView, newState); + } + }); parseIntentForSearchQuery(); } From 504286d44b92187ef173a9fa586ae44b1706c571 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 26 Mar 2018 10:52:45 +0200 Subject: [PATCH 4/6] respond to onLowMemory/onTrimMemory by clearing UIL memory cache --- app/src/main/java/org/fdroid/fdroid/FDroidApp.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index a64a73284..460237f88 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -297,6 +297,20 @@ public class FDroidApp extends Application { atStartTime.edit().putString(lastLocaleKey, currentLocale).apply(); } + @Override + public void onTrimMemory(int level) { + super.onTrimMemory(level); + if (level >= TRIM_MEMORY_BACKGROUND) { + ImageLoader.getInstance().clearMemoryCache(); + } + } + + @Override + public void onLowMemory() { + super.onLowMemory(); + ImageLoader.getInstance().clearMemoryCache(); + } + @Override public void onCreate() { super.onCreate(); From b10fa425b5f1c43f72df76e621d2816ce0ece0da Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 26 Mar 2018 11:28:29 +0200 Subject: [PATCH 5/6] scale UIL parallelization based on amount of RAM the device comes with This uses the total RAM that the device comes with as a rough measure of the devices capabilities. That is then used to set how many parallel threads UIL can use. --- .../java/org/fdroid/fdroid/FDroidApp.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java index 460237f88..58b16de3f 100644 --- a/app/src/main/java/org/fdroid/fdroid/FDroidApp.java +++ b/app/src/main/java/org/fdroid/fdroid/FDroidApp.java @@ -397,7 +397,7 @@ public class FDroidApp extends Application { ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getApplicationContext()) .imageDownloader(new ImageLoaderForUIL(getApplicationContext())) .defaultDisplayImageOptions(Utils.getDefaultDisplayImageOptionsBuilder().build()) - .threadPoolSize(4) + .threadPoolSize(getThreadPoolSize()) .build(); ImageLoader.getInstance().init(config); @@ -470,6 +470,24 @@ public class FDroidApp extends Application { return false; } + /** + * Return the number of threads Universal Image Loader should use, based on + * the total RAM in the device. Devices with lots of RAM can do lots of + * parallel operations for fast icon loading. + */ + @TargetApi(16) + private int getThreadPoolSize() { + if (Build.VERSION.SDK_INT >= 16) { + ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo(); + if (activityManager != null) { + activityManager.getMemoryInfo(memInfo); + return (int) Math.max(1, Math.min(16, memInfo.totalMem / 256 / 1024 / 1024)); + } + } + return 2; + } + @TargetApi(18) private BluetoothAdapter getBluetoothAdapter() { // to use the new, recommended way of getting the adapter From 44fcfd36f98d2aeafbe3d165f253366cd6f766e1 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 26 Mar 2018 15:02:51 +0200 Subject: [PATCH 6/6] use FileInputStream for file:// URLs when UIL loads images This saves the levels of indirection that leads to a FileInputStream being created in LocalFileDownloader. Since there are already special cases for assets:// and drawable://, it seems a natural place to put the file:// case. Also, since this is used to load icons when scrolling through lists of apps, this is particularly sensitive to inefficient loading. This also removes custom code that UIL provides better. --- .../fdroid/fdroid/net/DownloaderFactory.java | 13 +---- .../fdroid/fdroid/net/ImageLoaderForUIL.java | 20 ++++--- .../fdroid/net/LocalFileDownloader.java | 52 ------------------- 3 files changed, 10 insertions(+), 75 deletions(-) delete mode 100644 app/src/main/java/org/fdroid/fdroid/net/LocalFileDownloader.java diff --git a/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java b/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java index 9c908e7a7..8e87b4347 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java +++ b/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java @@ -3,7 +3,6 @@ package org.fdroid.fdroid.net; import android.content.Context; import android.net.Uri; import android.support.v4.content.LocalBroadcastManager; - import org.fdroid.fdroid.data.Repo; import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.Schema; @@ -41,11 +40,9 @@ public class DownloaderFactory { localBroadcastManager = LocalBroadcastManager.getInstance(context); } - if (isBluetoothAddress(url)) { + if ("bluetooth".equalsIgnoreCase(url.getProtocol())) { String macAddress = url.getHost().replace("-", ":"); downloader = new BluetoothDownloader(macAddress, url, destFile); - } else if (isLocalFile(url)) { - downloader = new LocalFileDownloader(url, destFile); } else { final String[] projection = {Schema.RepoTable.Cols.USERNAME, Schema.RepoTable.Cols.PASSWORD}; Repo repo = RepoProvider.Helper.findByUrl(context, Uri.parse(url.toString()), projection); @@ -57,12 +54,4 @@ public class DownloaderFactory { } return downloader; } - - private static boolean isBluetoothAddress(URL url) { - return "bluetooth".equalsIgnoreCase(url.getProtocol()); - } - - private static boolean isLocalFile(URL url) { - return "file".equalsIgnoreCase(url.getProtocol()); - } } diff --git a/app/src/main/java/org/fdroid/fdroid/net/ImageLoaderForUIL.java b/app/src/main/java/org/fdroid/fdroid/net/ImageLoaderForUIL.java index 508d9df06..883c2d9d2 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/ImageLoaderForUIL.java +++ b/app/src/main/java/org/fdroid/fdroid/net/ImageLoaderForUIL.java @@ -1,15 +1,18 @@ package org.fdroid.fdroid.net; import android.content.Context; - import com.nostra13.universalimageloader.core.download.BaseImageDownloader; import java.io.IOException; import java.io.InputStream; /** - * Class used by the Universal Image Loader library (UIL) to fetch images for displaying in F-Droid. - * See {@link org.fdroid.fdroid.FDroidApp} for where this gets configured. + * Class used by the Universal Image Loader library (UIL) to fetch images for + * displaying in F-Droid. A custom subclass is needed since F-Droid's + * {@link HttpDownloader} provides support for Tor, proxying, and automatic + * mirror failover. + * + * @see org.fdroid.fdroid.FDroidApp#onCreate() for where this is setup */ public class ImageLoaderForUIL implements com.nostra13.universalimageloader.core.download.ImageDownloader { @@ -22,15 +25,10 @@ public class ImageLoaderForUIL implements com.nostra13.universalimageloader.core @Override public InputStream getStream(String imageUri, Object extra) throws IOException { switch (Scheme.ofUri(imageUri)) { - case ASSETS: - return context.getAssets().open(Scheme.ASSETS.crop(imageUri)); - - case DRAWABLE: - return new BaseImageDownloader(context).getStream(imageUri, extra); - - default: + case HTTP: + case HTTPS: return DownloaderFactory.create(context, imageUri).getInputStream(); } + return new BaseImageDownloader(context).getStream(imageUri, extra); } - } diff --git a/app/src/main/java/org/fdroid/fdroid/net/LocalFileDownloader.java b/app/src/main/java/org/fdroid/fdroid/net/LocalFileDownloader.java deleted file mode 100644 index d9d85e5d6..000000000 --- a/app/src/main/java/org/fdroid/fdroid/net/LocalFileDownloader.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.fdroid.fdroid.net; - -import org.fdroid.fdroid.Utils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - -public class LocalFileDownloader extends Downloader { - - private InputStream inputStream; - - LocalFileDownloader(URL url, File destFile) throws FileNotFoundException, MalformedURLException { - super(url, destFile); - } - - @Override - protected InputStream getDownloadersInputStream() throws IOException { - inputStream = new FileInputStream(new File(sourceUrl.getPath())); - return inputStream; - } - - @Override - protected void close() { - if (inputStream != null) { - Utils.closeQuietly(inputStream); - } - } - - @Override - public boolean hasChanged() { - return false; - } - - @Override - public int totalDownloadSize() { - return 0; - } - - @Override - public void download() throws IOException, InterruptedException { - if (new File(sourceUrl.getPath()).exists()) { - downloadFromStream(1024 * 50, false); - } else { - notFound = true; - } - } -}