Skip to content

Commit

Permalink
Download home v2: Increase thumbnail cache size.
Browse files Browse the repository at this point in the history
On Android we use an in-memory thumbnail cache to improve view recycling
performance and use a disk cache to avoid costly backend operations like
decoding images, videos etc.

For download home v2, the thumbnail size is much larger than the old
download home, thus we need to tweak the size of cache. 90% percentile
of users have around 15 media thumbnails. The size of bitmap varies from
under 1MB to around 5MB. The disk cache uses PNG to compress, the
compression rate is around 1/3.

This CL does the following:

1. The size of the thumbnail for high dpi devices are scaled down to
mdpi, which can save memory/disk.

2. For in-memory cache size, download home v2 uses 15MB for high end
devices. In-memory cache size is now passed in from owner of
ThumbnailProviderImpl.

3. Disk cache size is increased. This should be shared by multiple
owners of ThumbnailProviderImpl, or the owner with smaller disk cache
size may remove files unexpectedly used by other owners.

Bug: 87292
Change-Id: If7cb700a60a8d119bca136b1a7d1fe7feb787d8b
Reviewed-on: https://chromium-review.googlesource.com/c/1296546
Commit-Queue: Xing Liu <xingliu@chromium.org>
Reviewed-by: David Trainor <dtrainor@chromium.org>
Reviewed-by: Shakti Sahu <shaktisahu@chromium.org>
Cr-Commit-Position: refs/heads/master@{#602957}
  • Loading branch information
Xing Liu authored and Commit Bot committed Oct 26, 2018
1 parent dfd6942 commit b859fdb
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

package org.chromium.chrome.browser.download.home;

import static org.chromium.chrome.browser.util.ConversionUtils.BYTES_PER_MEGABYTE;

import org.chromium.base.ContextUtils;
import org.chromium.base.SysUtils;
import org.chromium.chrome.browser.ChromeFeatureList;
Expand Down Expand Up @@ -32,6 +34,17 @@ public class DownloadManagerUiConfig {
*/
public final boolean useNewDownloadPathThumbnails;

/**
* The in-memory thumbnail size in bytes.
*/
public final int inMemoryThumbnailCacheSizeBytes;

/**
* The maximum thumbnail scale factor, thumbnail on higher dpi devices will downscale the
* quality to this level.
*/
public final float maxThumbnailScaleFactor;

/**
* The time interval during which a download update is considered recent enough to show
* in Just Now section.
Expand All @@ -46,6 +59,8 @@ private DownloadManagerUiConfig(Builder builder) {
supportFullWidthImages = builder.mSupportFullWidthImages;
useNewDownloadPath = builder.mUseNewDownloadPath;
useNewDownloadPathThumbnails = builder.mUseNewDownloadPathThumbnails;
inMemoryThumbnailCacheSizeBytes = builder.mInMemoryThumbnailCacheSizeBytes;
maxThumbnailScaleFactor = builder.mMaxThumbnailScaleFactor;
justNowThresholdSeconds = builder.mJustNowThresholdSeconds;
}

Expand All @@ -56,12 +71,16 @@ public static class Builder {
/** Default value for threshold time interval to show up in Just Now section. */
private static final int JUST_NOW_THRESHOLD_SECONDS_DEFAULT = 30 * 60;

private static final int IN_MEMORY_THUMBNAIL_CACHE_SIZE_BYTES = 15 * BYTES_PER_MEGABYTE;

private boolean mIsOffTheRecord;
private boolean mIsSeparateActivity;
private boolean mUseGenericViewTypes;
private boolean mSupportFullWidthImages;
private boolean mUseNewDownloadPath;
private boolean mUseNewDownloadPathThumbnails;
private int mInMemoryThumbnailCacheSizeBytes = IN_MEMORY_THUMBNAIL_CACHE_SIZE_BYTES;
private float mMaxThumbnailScaleFactor = 1.f; /* mdpi scale factor. */
private long mJustNowThresholdSeconds;

public Builder() {
Expand Down Expand Up @@ -101,6 +120,16 @@ public Builder setUseNewDownloadPathThumbnails(boolean useNewDownloadPathThumbna
return this;
}

public Builder setInMemoryThumbnailCacheSizeBytes(int inMemoryThumbnailCacheSizeBytes) {
mInMemoryThumbnailCacheSizeBytes = inMemoryThumbnailCacheSizeBytes;
return this;
}

public Builder setMaxThumbnailScaleFactor(float maxThumbnailScaleFactor) {
mMaxThumbnailScaleFactor = maxThumbnailScaleFactor;
return this;
}

public DownloadManagerUiConfig build() {
return new DownloadManagerUiConfig(this);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@
import android.graphics.Bitmap;

import org.chromium.base.Callback;
import org.chromium.base.ContextUtils;
import org.chromium.chrome.browser.widget.ThumbnailProvider;
import org.chromium.chrome.browser.widget.ThumbnailProvider.ThumbnailRequest;
import org.chromium.chrome.browser.widget.ThumbnailProviderImpl;
import org.chromium.components.offline_items_collection.OfflineContentProvider;
import org.chromium.components.offline_items_collection.OfflineItem;
import org.chromium.components.offline_items_collection.OfflineItemVisuals;
import org.chromium.components.offline_items_collection.VisualsCallback;
import org.chromium.ui.display.DisplayAndroid;

/**
* Glue class responsible for connecting the current downloads and {@link OfflineContentProvider}
Expand All @@ -28,11 +30,15 @@ public class ThumbnailRequestGlue implements ThumbnailRequest {

/** Creates a {@link ThumbnailRequestGlue} instance. */
public ThumbnailRequestGlue(OfflineContentProviderGlue provider, OfflineItem item,
int iconWidthPx, int iconHeightPx, VisualsCallback callback) {
int iconWidthPx, int iconHeightPx, float maxThumbnailScaleFactor,
VisualsCallback callback) {
mProvider = provider;
mItem = item;
mIconWidthPx = iconWidthPx;
mIconHeightPx = iconHeightPx;

// Scale the thumbnail quality to mdpi for high dpi devices.
mIconWidthPx = downscaleThumbnailSize(iconWidthPx, maxThumbnailScaleFactor);
mIconHeightPx = downscaleThumbnailSize(iconHeightPx, maxThumbnailScaleFactor);

mCallback = callback;
}

Expand Down Expand Up @@ -90,4 +96,18 @@ public boolean getThumbnail(Callback<Bitmap> callback) {
}
});
}

/**
* Returns size in pixel used by the thumbnail request, considering dip scale factor.
* @param currentSize The current size before considering the dip scale factor.
* @param maxScaleFactor The maximum scale factor we expected to show as the thumbnail. Device
* with higher scale factor will be downscaled to this level.
*/
private int downscaleThumbnailSize(int currentSize, float maxScaleFactor) {
DisplayAndroid display =
DisplayAndroid.getNonMultiDisplay(ContextUtils.getApplicationContext());
float scale = display.getDipScale();
if (scale <= maxScaleFactor) return currentSize;
return (int) (maxScaleFactor * currentSize / scale);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ public interface ShareController {
private final ThumbnailProvider mThumbnailProvider;
private final MediatorSelectionObserver mSelectionObserver;
private final SelectionDelegate<ListItem> mSelectionDelegate;
private final DownloadManagerUiConfig mUiConfig;

private final OffTheRecordOfflineItemFilter mOffTheRecordFilter;
private final InvalidStateOfflineItemFilter mInvalidStateFilter;
Expand Down Expand Up @@ -140,6 +141,7 @@ public DateOrderedListMediator(OfflineContentProvider provider, ShareController
mModel = model;
mDeleteController = deleteController;
mSelectionDelegate = selectionDelegate;
mUiConfig = config;

mSource = new OfflineItemSource(mProvider);
mOffTheRecordFilter = new OffTheRecordOfflineItemFilter(config.isOffTheRecord, mSource);
Expand All @@ -154,7 +156,8 @@ public DateOrderedListMediator(OfflineContentProvider provider, ShareController

mSearchFilter.addObserver(new EmptyStateObserver(mSearchFilter, dateOrderedListObserver));
mThumbnailProvider = new ThumbnailProviderImpl(
((ChromeApplication) ContextUtils.getApplicationContext()).getReferencePool());
((ChromeApplication) ContextUtils.getApplicationContext()).getReferencePool(),
config.inMemoryThumbnailCacheSizeBytes);
mSelectionObserver = new MediatorSelectionObserver(selectionDelegate);

mModel.getProperties().set(ListProperties.ENABLE_ITEM_ANIMATIONS, true);
Expand Down Expand Up @@ -358,8 +361,8 @@ private Runnable getVisuals(
return () -> {};
}

ThumbnailRequest request =
new ThumbnailRequestGlue(mProvider, item, iconWidthPx, iconHeightPx, callback);
ThumbnailRequest request = new ThumbnailRequestGlue(mProvider, item, iconWidthPx,
iconHeightPx, mUiConfig.maxThumbnailScaleFactor, callback);
mThumbnailProvider.getThumbnail(request);
return () -> mThumbnailProvider.cancelRetrieval(request);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
public class ThumbnailDiskStorage implements ThumbnailGeneratorCallback {
private static final String TAG = "ThumbnailStorage";
private static final int MAX_CACHE_BYTES =
ConversionUtils.BYTES_PER_MEGABYTE; // Max disk cache size is 1MB.
5 * ConversionUtils.BYTES_PER_MEGABYTE; // Max disk cache size is 5MB.

// LRU cache of a pair of thumbnail's contentID and size. The order is based on the sequence of
// add and get with the most recent at the end. The order at initialization (i.e. browser
Expand All @@ -76,6 +76,9 @@ public class ThumbnailDiskStorage implements ThumbnailGeneratorCallback {

private ThumbnailStorageDelegate mDelegate;

// Maximum size in bytes for the disk cache.
private final int mMaxCacheBytes;

// Number of bytes used in disk for cache.
@VisibleForTesting
long mSizeBytes;
Expand Down Expand Up @@ -174,10 +177,12 @@ protected Void doInBackground() {
}

@VisibleForTesting
ThumbnailDiskStorage(ThumbnailStorageDelegate delegate, ThumbnailGenerator thumbnailGenerator) {
ThumbnailDiskStorage(ThumbnailStorageDelegate delegate, ThumbnailGenerator thumbnailGenerator,
int maxCacheSizeBytes) {
ThreadUtils.assertOnUiThread();
mDelegate = delegate;
mThumbnailGenerator = thumbnailGenerator;
mMaxCacheBytes = maxCacheSizeBytes;
new InitTask().executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
}

Expand All @@ -188,7 +193,7 @@ protected Void doInBackground() {
* @return An instance of {@link ThumbnailDiskStorage}.
*/
public static ThumbnailDiskStorage create(ThumbnailStorageDelegate delegate) {
return new ThumbnailDiskStorage(delegate, new ThumbnailGenerator());
return new ThumbnailDiskStorage(delegate, new ThumbnailGenerator(), MAX_CACHE_BYTES);
}

/**
Expand Down Expand Up @@ -391,12 +396,12 @@ Bitmap getFromDisk(String contentId, int iconSizePx) {
}

/**
* Trim the cache to stay under the MAX_CACHE_BYTES limit by removing the oldest entries.
* Trim the cache to stay under the max cache size by removing the oldest entries.
*/
@VisibleForTesting
void trim() {
ThreadUtils.assertOnBackgroundThread();
while (mSizeBytes > MAX_CACHE_BYTES) {
while (mSizeBytes > mMaxCacheBytes) {
removeFromDiskHelper(sDiskLruCache.iterator().next());
}
}
Expand Down Expand Up @@ -476,10 +481,9 @@ private String getThumbnailFilePath(String contentId, int iconSizePx) {
* Get directory for thumbnail entries in the designated app (internal) cache directory.
* The directory's name must be unique.
* @param context The application's context.
* @param uniqueName The name of the thumbnail directory. Must be unique.
* @return The path to the thumbnail cache directory.
*/
private static File getDiskCacheDir(Context context, String thumbnailDirName) {
return new File(context.getCacheDir().getPath() + File.separator + thumbnailDirName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,18 @@
* duplicating work to decode the same image for two different requests.
*/
public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorageDelegate {
/** 5 MB of thumbnails should be enough for everyone. */
private static final int MAX_CACHE_BYTES = 5 * ConversionUtils.BYTES_PER_MEGABYTE;
/** Default in-memory thumbnail cache size. */
private static final int DEFAULT_MAX_CACHE_BYTES = 5 * ConversionUtils.BYTES_PER_MEGABYTE;

/**
* Helper object to store in the LruCache when we don't really need a value but can't use null.
*/
private static final Object NO_BITMAP_PLACEHOLDER = new Object();

/**
* An in-memory LRU cache used to cache bitmaps, mostly improve performance for scrolling, when
* the view is recycled and needs a new thumbnail.
*/
private BitmapCache mBitmapCache;

/**
Expand All @@ -57,9 +61,22 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag

private ThumbnailDiskStorage mStorage;

/**
* Constructor to build the thumbnail provider with default thumbnail cache size.
* @param referencePool The application's reference pool.
*/
public ThumbnailProviderImpl(DiscardableReferencePool referencePool) {
this(referencePool, DEFAULT_MAX_CACHE_BYTES);
}

/**
* Constructor to build the thumbnail provider.
* @param referencePool The application's reference pool.
* @param bitmapCacheSizeByte The size in bytes of the in-memory LRU bitmap cache.
*/
public ThumbnailProviderImpl(DiscardableReferencePool referencePool, int bitmapCacheSizeByte) {
ThreadUtils.assertOnUiThread();
mBitmapCache = new BitmapCache(referencePool, MAX_CACHE_BYTES);
mBitmapCache = new BitmapCache(referencePool, bitmapCacheSizeByte);
mStorage = ThumbnailDiskStorage.create(this);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import org.chromium.base.ThreadUtils;
import org.chromium.base.test.util.UrlUtils;
import org.chromium.chrome.browser.util.ConversionUtils;
import org.chromium.chrome.test.ChromeJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.Criteria;
import org.chromium.content_public.browser.test.util.CriteriaHelper;
Expand All @@ -42,6 +43,7 @@ public class ThumbnailDiskStorageTest {
private static final Bitmap BITMAP2 = BitmapFactory.decodeFile(FILE_PATH2);
private static final int ICON_WIDTH1 = 50;
private static final int ICON_WIDTH2 = 70;
private static final int TEST_MAX_CACHE_BYTES = 1 * ConversionUtils.BYTES_PER_MEGABYTE;

private static final long TIMEOUT_MS = 10000;
private static final long INTERVAL_MS = 500;
Expand Down Expand Up @@ -98,9 +100,9 @@ private static class TestThumbnailDiskStorage extends ThumbnailDiskStorage {
// Accessed by test and UI threads.
public final AtomicBoolean initialized = new AtomicBoolean();

public TestThumbnailDiskStorage(
TestThumbnailStorageDelegate delegate, TestThumbnailGenerator thumbnailGenerator) {
super(delegate, thumbnailGenerator);
public TestThumbnailDiskStorage(TestThumbnailStorageDelegate delegate,
TestThumbnailGenerator thumbnailGenerator, int maxCacheSizeBytes) {
super(delegate, thumbnailGenerator, maxCacheSizeBytes);
}

@Override
Expand Down Expand Up @@ -159,8 +161,9 @@ public void setUp() throws Exception {
ThreadUtils.runOnUiThreadBlocking(new Runnable() {
@Override
public void run() {
mTestThumbnailDiskStorage = new TestThumbnailDiskStorage(
mTestThumbnailStorageDelegate, mTestThumbnailGenerator);
mTestThumbnailDiskStorage =
new TestThumbnailDiskStorage(mTestThumbnailStorageDelegate,
mTestThumbnailGenerator, TEST_MAX_CACHE_BYTES);
// Clear the disk cache so that cached entries from previous runs won't show up.
mTestThumbnailDiskStorage.clear();
}
Expand Down

0 comments on commit b859fdb

Please sign in to comment.