Commit f21e0241 authored by Wei-Yin Chen (陳威尹)'s avatar Wei-Yin Chen (陳威尹) Committed by Commit Bot

Extract BitmapCache from ThumbnailProviderImpl

The in-memory bitmap cache can be reused elsewhere.

Bug: None
Change-Id: I71f593da22afe7eebb98c43d3f93240acf97d237
Reviewed-on: https://chromium-review.googlesource.com/1068126
Commit-Queue: Wei-Yin Chen (陳威尹) <wychen@chromium.org>
Reviewed-by: default avatarBernhard Bauer <bauerb@chromium.org>
Cr-Commit-Position: refs/heads/master@{#562993}
parent c03ce23c
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.chrome.browser;
import android.graphics.Bitmap;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.LruCache;
import org.chromium.base.DiscardableReferencePool;
import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils;
import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
/**
* In-memory cache of Bitmap.
*
* Bitmaps are cached in memory and shared across all instances of BitmapCache. There are two
* levels of caches: one static cache for deduplication (or canonicalization) of bitmaps, and one
* per-object cache for storing recently used bitmaps. The deduplication cache uses weak references
* to allow bitmaps to be garbage-collected once they are no longer in use. As long as there is at
* least one strong reference to a bitmap, it is not going to be GC'd and will therefore stay in the
* cache. This ensures that there is never more than one (reachable) copy of a bitmap in memory.
* The {@link RecentlyUsedCache} is limited in size and dropped under memory pressure, or when the
* object is destroyed.
*/
public class BitmapCache {
private final int mCacheSize;
/**
* Least-recently-used cache that falls back to the deduplication cache on misses.
* This propagates bitmaps that were only in the deduplication cache back into the LRU cache
* and also moves them to the front to ensure correct eviction order.
* Cache key is a pair of the filepath and the height/width of the thumbnail. Value is
* the thumbnail.
*/
private static class RecentlyUsedCache extends LruCache<String, Bitmap> {
private RecentlyUsedCache(int size) {
super(size);
}
@Override
protected Bitmap create(String key) {
WeakReference<Bitmap> cachedBitmap = sDeduplicationCache.get(key);
return cachedBitmap == null ? null : cachedBitmap.get();
}
@Override
protected int sizeOf(String key, Bitmap thumbnail) {
return thumbnail.getByteCount();
}
}
/**
* Discardable reference to the {@link RecentlyUsedCache} that can be dropped under memory
* pressure.
*/
private DiscardableReferencePool.DiscardableReference<RecentlyUsedCache> mBitmapCache;
/**
* The reference pool that contains the {@link #mBitmapCache}. Used to recreate a new cache
* after the old one has been dropped.
*/
private final DiscardableReferencePool mReferencePool;
/**
* Static cache used for deduplicating bitmaps. The key is a pair of file name and thumbnail
* size (as for the {@link #mBitmapCache}.
*/
private static Map<String, WeakReference<Bitmap>> sDeduplicationCache = new HashMap<>();
private static int sUsageCount;
/**
* Creates an instance of a {@link BitmapCache}.
*
* This constructor must be called on UI thread.
*
* @param referencePool The discardable reference pool. See
* {@link ChromeApplication#getReferencePool}.
* @param size The capacity of the cache in bytes.
*/
public BitmapCache(DiscardableReferencePool referencePool, int size) {
ThreadUtils.assertOnUiThread();
mReferencePool = referencePool;
mCacheSize = size;
mBitmapCache = referencePool.put(new RecentlyUsedCache(mCacheSize));
}
public Bitmap getBitmap(String key) {
ThreadUtils.assertOnUiThread();
Bitmap cachedBitmap = getBitmapCache().get(key);
assert cachedBitmap == null || !cachedBitmap.isRecycled();
maybeScheduleDeduplicationCache();
return cachedBitmap;
}
public void putBitmap(@NonNull String key, @Nullable Bitmap bitmap) {
ThreadUtils.assertOnUiThread();
if (bitmap == null) return;
if (!SysUtils.isLowEndDevice()) {
getBitmapCache().put(key, bitmap);
}
maybeScheduleDeduplicationCache();
sDeduplicationCache.put(key, new WeakReference<>(bitmap));
}
private RecentlyUsedCache getBitmapCache() {
RecentlyUsedCache bitmapCache = mBitmapCache.get();
if (bitmapCache == null) {
bitmapCache = new RecentlyUsedCache(mCacheSize);
mBitmapCache = mReferencePool.put(bitmapCache);
}
return bitmapCache;
}
private static void maybeScheduleDeduplicationCache() {
sUsageCount++;
// Amortized cost of automatic dedup work is constant.
if (sUsageCount < sDeduplicationCache.size()) return;
sUsageCount = 0;
scheduleDeduplicationCache();
}
private static void scheduleDeduplicationCache() {
Looper.myQueue().addIdleHandler(() -> {
compactDeduplicationCache();
return false;
});
}
/**
* Compacts the deduplication cache by removing all entries that have been cleared by the
* garbage collector.
*/
private static void compactDeduplicationCache() {
// Too many angle brackets for clang-format :-(
// clang-format off
for (Iterator<Map.Entry<String, WeakReference<Bitmap>>> it =
sDeduplicationCache.entrySet().iterator(); it.hasNext();) {
// clang-format on
if (it.next().getValue().get() == null) it.remove();
}
}
}
...@@ -5,36 +5,22 @@ ...@@ -5,36 +5,22 @@
package org.chromium.chrome.browser.widget; package org.chromium.chrome.browser.widget;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.os.Looper;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.support.v4.util.LruCache;
import android.text.TextUtils; import android.text.TextUtils;
import android.util.Pair;
import org.chromium.base.DiscardableReferencePool; import org.chromium.base.DiscardableReferencePool;
import org.chromium.base.SysUtils;
import org.chromium.base.ThreadUtils; import org.chromium.base.ThreadUtils;
import org.chromium.chrome.browser.BitmapCache;
import java.lang.ref.WeakReference;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Deque; import java.util.Deque;
import java.util.HashMap; import java.util.Locale;
import java.util.Iterator;
import java.util.Map;
/** /**
* Concrete implementation of {@link ThumbnailProvider}. * Concrete implementation of {@link ThumbnailProvider}.
* *
* Thumbnails are cached in memory and shared across all ThumbnailProviderImpls. There are two * Thumbnails are cached in {@link BitmapCache}.
* levels of caches: One static cache for deduplication (or canonicalization) of bitmaps, and one
* per-object cache for storing recently used bitmaps. The deduplication cache uses weak references
* to allow bitmaps to be garbage-collected once they are no longer in use. As long as there is at
* least one strong reference to a bitmap, it is not going to be GC'd and will therefore stay in the
* cache. This ensures that there is never more than one (reachable) copy of a bitmap in memory.
* The {@link RecentlyUsedCache} is limited in size and dropped under memory pressure, or when the
* object is destroyed.
*
* A queue of requests is maintained in FIFO order. * A queue of requests is maintained in FIFO order.
* *
* TODO(dfalcantara): Figure out how to send requests simultaneously to the utility process without * TODO(dfalcantara): Figure out how to send requests simultaneously to the utility process without
...@@ -44,55 +30,11 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag ...@@ -44,55 +30,11 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag
/** 5 MB of thumbnails should be enough for everyone. */ /** 5 MB of thumbnails should be enough for everyone. */
private static final int MAX_CACHE_BYTES = 5 * 1024 * 1024; private static final int MAX_CACHE_BYTES = 5 * 1024 * 1024;
/** private BitmapCache mBitmapCache;
* Least-recently-used cache that falls back to the deduplication cache on misses.
* This propagates bitmaps that were only in the deduplication cache back into the LRU cache
* and also moves them to the front to ensure correct eviction order.
* Cache key is a pair of the filepath and the height/width of the thumbnail. Value is
* the thumbnail.
*/
private static class RecentlyUsedCache extends LruCache<Pair<String, Integer>, Bitmap> {
private RecentlyUsedCache() {
super(MAX_CACHE_BYTES);
}
@Override
protected Bitmap create(Pair<String, Integer> key) {
WeakReference<Bitmap> cachedBitmap = sDeduplicationCache.get(key);
return cachedBitmap == null ? null : cachedBitmap.get();
}
@Override
protected int sizeOf(Pair<String, Integer> key, Bitmap thumbnail) {
return thumbnail.getByteCount();
}
}
/**
* Discardable reference to the {@link RecentlyUsedCache} that can be dropped under memory
* pressure.
*/
private DiscardableReferencePool.DiscardableReference<RecentlyUsedCache> mBitmapCache;
/**
* The reference pool that contains the {@link #mBitmapCache}. Used to recreate a new cache
* after the old one has been dropped.
*/
private final DiscardableReferencePool mReferencePool;
/**
* Static cache used for deduplicating bitmaps. The key is a pair of file name and thumbnail
* size (as for the {@link #mBitmapCache}.
*/
private static Map<Pair<String, Integer>, WeakReference<Bitmap>> sDeduplicationCache =
new HashMap<>();
/** Queue of files to retrieve thumbnails for. */ /** Queue of files to retrieve thumbnails for. */
private final Deque<ThumbnailRequest> mRequestQueue = new ArrayDeque<>(); private final Deque<ThumbnailRequest> mRequestQueue = new ArrayDeque<>();
/** The native side pointer that is owned and destroyed by the Java class. */
private long mNativeThumbnailProvider;
/** Request that is currently having its thumbnail retrieved. */ /** Request that is currently having its thumbnail retrieved. */
private ThumbnailRequest mCurrentRequest; private ThumbnailRequest mCurrentRequest;
...@@ -100,8 +42,7 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag ...@@ -100,8 +42,7 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag
public ThumbnailProviderImpl(DiscardableReferencePool referencePool) { public ThumbnailProviderImpl(DiscardableReferencePool referencePool) {
ThreadUtils.assertOnUiThread(); ThreadUtils.assertOnUiThread();
mReferencePool = referencePool; mBitmapCache = new BitmapCache(referencePool, MAX_CACHE_BYTES);
mBitmapCache = referencePool.put(new RecentlyUsedCache());
mStorage = ThumbnailDiskStorage.create(this); mStorage = ThumbnailDiskStorage.create(this);
} }
...@@ -156,17 +97,13 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag ...@@ -156,17 +97,13 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag
ThreadUtils.postOnUiThread(this::processNextRequest); ThreadUtils.postOnUiThread(this::processNextRequest);
} }
private RecentlyUsedCache getBitmapCache() { private String getKey(String contentId, int bitmapSizePx) {
RecentlyUsedCache bitmapCache = mBitmapCache.get(); return String.format(Locale.US, "id=%s, size=%d", contentId, bitmapSizePx);
if (bitmapCache == null) {
bitmapCache = new RecentlyUsedCache();
mBitmapCache = mReferencePool.put(bitmapCache);
}
return bitmapCache;
} }
private Bitmap getBitmapFromCache(String contentId, int bitmapSizePx) { private Bitmap getBitmapFromCache(String contentId, int bitmapSizePx) {
Bitmap cachedBitmap = getBitmapCache().get(Pair.create(contentId, bitmapSizePx)); String key = getKey(contentId, bitmapSizePx);
Bitmap cachedBitmap = mBitmapCache.getBitmap(key);
assert cachedBitmap == null || !cachedBitmap.isRecycled(); assert cachedBitmap == null || !cachedBitmap.isRecycled();
return cachedBitmap; return cachedBitmap;
} }
...@@ -174,14 +111,7 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag ...@@ -174,14 +111,7 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag
private void processNextRequest() { private void processNextRequest() {
ThreadUtils.assertOnUiThread(); ThreadUtils.assertOnUiThread();
if (mCurrentRequest != null) return; if (mCurrentRequest != null) return;
if (mRequestQueue.isEmpty()) { if (mRequestQueue.isEmpty()) return;
// If the request queue is empty, schedule compaction for when the main loop is idling.
Looper.myQueue().addIdleHandler(() -> {
compactDeduplicationCache();
return false;
});
return;
}
mCurrentRequest = mRequestQueue.poll(); mCurrentRequest = mRequestQueue.poll();
...@@ -217,7 +147,7 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag ...@@ -217,7 +147,7 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag
/** /**
* Called when thumbnail is ready, retrieved from memory cache or by * Called when thumbnail is ready, retrieved from memory cache or by
* {@link ThumbnailDiskStorage} or by {@link ThumbnailRequest#getThumbnail()}. * {@link ThumbnailDiskStorage} or by {@link ThumbnailRequest#getThumbnail}.
* @param contentId Content ID for the thumbnail retrieved. * @param contentId Content ID for the thumbnail retrieved.
* @param bitmap The thumbnail retrieved. * @param bitmap The thumbnail retrieved.
*/ */
...@@ -234,29 +164,12 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag ...@@ -234,29 +164,12 @@ public class ThumbnailProviderImpl implements ThumbnailProvider, ThumbnailStorag
// We set the key pair to contain the required size (maximum dimension (pixel) of the // We set the key pair to contain the required size (maximum dimension (pixel) of the
// smaller side) instead of the minimal dimension of the thumbnail so that future // smaller side) instead of the minimal dimension of the thumbnail so that future
// fetches of this thumbnail can recognise the key in the cache. // fetches of this thumbnail can recognise the key in the cache.
Pair<String, Integer> key = Pair.create(contentId, mCurrentRequest.getIconSize()); String key = getKey(contentId, mCurrentRequest.getIconSize());
if (!SysUtils.isLowEndDevice()) { mBitmapCache.putBitmap(key, bitmap);
getBitmapCache().put(key, bitmap);
}
sDeduplicationCache.put(key, new WeakReference<>(bitmap));
mCurrentRequest.onThumbnailRetrieved(contentId, bitmap); mCurrentRequest.onThumbnailRetrieved(contentId, bitmap);
} }
mCurrentRequest = null; mCurrentRequest = null;
processQueue(); processQueue();
} }
/**
* Compacts the deduplication cache by removing all entries that have been cleared by the
* garbage collector.
*/
private void compactDeduplicationCache() {
// Too many angle brackets for clang-format :-(
// clang-format off
for (Iterator<Map.Entry<Pair<String, Integer>, WeakReference<Bitmap>>> it =
sDeduplicationCache.entrySet().iterator(); it.hasNext();) {
// clang-format on
if (it.next().getValue().get() == null) it.remove();
}
}
} }
...@@ -19,6 +19,7 @@ chrome_java_sources = [ ...@@ -19,6 +19,7 @@ chrome_java_sources = [
"java/src/org/chromium/chrome/browser/AssistStatusHandler.java", "java/src/org/chromium/chrome/browser/AssistStatusHandler.java",
"java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java", "java/src/org/chromium/chrome/browser/BackgroundSyncLauncher.java",
"java/src/org/chromium/chrome/browser/BasicNativePage.java", "java/src/org/chromium/chrome/browser/BasicNativePage.java",
"java/src/org/chromium/chrome/browser/BitmapCache.java",
"java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java", "java/src/org/chromium/chrome/browser/BluetoothChooserDialog.java",
"java/src/org/chromium/chrome/browser/BrowserRestartActivity.java", "java/src/org/chromium/chrome/browser/BrowserRestartActivity.java",
"java/src/org/chromium/chrome/browser/ChromeActionModeCallback.java", "java/src/org/chromium/chrome/browser/ChromeActionModeCallback.java",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment