Merge "Introduce trimMemory to AppIconCacheManager" am: 10ae330a97
Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/2308424 Change-Id: I9e4049722b4c341aa57d656ae572d6522de49f65 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -22,13 +22,16 @@ import android.os.UserHandle;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.LruCache;
|
import android.util.LruCache;
|
||||||
|
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache app icon for management.
|
* Cache app icon for management.
|
||||||
*/
|
*/
|
||||||
public class AppIconCacheManager {
|
public class AppIconCacheManager {
|
||||||
private static final String TAG = "AppIconCacheManager";
|
private static final String TAG = "AppIconCacheManager";
|
||||||
private static final float CACHE_RATIO = 0.1f;
|
private static final float CACHE_RATIO = 0.1f;
|
||||||
private static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
|
@VisibleForTesting
|
||||||
|
static final int MAX_CACHE_SIZE_IN_KB = getMaxCacheInKb();
|
||||||
private static final String DELIMITER = ":";
|
private static final String DELIMITER = ":";
|
||||||
private static AppIconCacheManager sAppIconCacheManager;
|
private static AppIconCacheManager sAppIconCacheManager;
|
||||||
private final LruCache<String, Drawable> mDrawableCache;
|
private final LruCache<String, Drawable> mDrawableCache;
|
||||||
@@ -109,4 +112,25 @@ public class AppIconCacheManager {
|
|||||||
private static int getMaxCacheInKb() {
|
private static int getMaxCacheInKb() {
|
||||||
return Math.round(CACHE_RATIO * Runtime.getRuntime().maxMemory() / 1024);
|
return Math.round(CACHE_RATIO * Runtime.getRuntime().maxMemory() / 1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears as much memory as possible.
|
||||||
|
*
|
||||||
|
* @see android.content.ComponentCallbacks2#onTrimMemory(int)
|
||||||
|
*/
|
||||||
|
public void trimMemory(int level) {
|
||||||
|
if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
|
||||||
|
// Time to clear everything
|
||||||
|
if (sAppIconCacheManager != null) {
|
||||||
|
sAppIconCacheManager.mDrawableCache.trimToSize(0);
|
||||||
|
}
|
||||||
|
} else if (level >= android.content.ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN
|
||||||
|
|| level == android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL) {
|
||||||
|
// Tough time but still affordable, clear half of the cache
|
||||||
|
if (sAppIconCacheManager != null) {
|
||||||
|
final int maxSize = sAppIconCacheManager.mDrawableCache.maxSize();
|
||||||
|
sAppIconCacheManager.mDrawableCache.trimToSize(maxSize / 2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,21 @@ import org.robolectric.RobolectricTestRunner;
|
|||||||
public class AppIconCacheManagerTest {
|
public class AppIconCacheManagerTest {
|
||||||
|
|
||||||
private static final String APP_PACKAGE_NAME = "com.test.app";
|
private static final String APP_PACKAGE_NAME = "com.test.app";
|
||||||
|
private static final String APP_PACKAGE_NAME1 = "com.test.app1";
|
||||||
|
private static final String APP_PACKAGE_NAME2 = "com.test.app2";
|
||||||
|
private static final String APP_PACKAGE_NAME3 = "com.test.app3";
|
||||||
private static final int APP_UID = 9999;
|
private static final int APP_UID = 9999;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Drawable mIcon;
|
private Drawable mIcon;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private Drawable mIcon1;
|
||||||
|
@Mock
|
||||||
|
private Drawable mIcon2;
|
||||||
|
@Mock
|
||||||
|
private Drawable mIcon3;
|
||||||
|
|
||||||
private AppIconCacheManager mAppIconCacheManager;
|
private AppIconCacheManager mAppIconCacheManager;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
@@ -48,6 +58,29 @@ public class AppIconCacheManagerTest {
|
|||||||
doReturn(10).when(mIcon).getIntrinsicHeight();
|
doReturn(10).when(mIcon).getIntrinsicHeight();
|
||||||
doReturn(10).when(mIcon).getIntrinsicWidth();
|
doReturn(10).when(mIcon).getIntrinsicWidth();
|
||||||
doReturn(mIcon).when(mIcon).mutate();
|
doReturn(mIcon).when(mIcon).mutate();
|
||||||
|
|
||||||
|
// Algorithm for trim memory test:
|
||||||
|
// The real maxsize is defined by AppIconCacheManager.MAX_CACHE_SIZE_IN_KB, and the size
|
||||||
|
// of each element is calculated as following:
|
||||||
|
// n * n * 4 / 1024
|
||||||
|
// In the testcase, we want to mock the maxsize of LruCache is 3, so the formula calculating
|
||||||
|
// the size of each element will be like:
|
||||||
|
// n * n * 4 / 1024 = maxsize / 3
|
||||||
|
// Thus, n = square_root(maxsize / 3 * 1024 / 4), which can be used as an icon size.
|
||||||
|
final int iconSize =
|
||||||
|
(int) Math.sqrt(AppIconCacheManager.MAX_CACHE_SIZE_IN_KB / 3f * 1024f / 4f);
|
||||||
|
|
||||||
|
doReturn(iconSize).when(mIcon1).getIntrinsicHeight();
|
||||||
|
doReturn(iconSize).when(mIcon1).getIntrinsicWidth();
|
||||||
|
doReturn(mIcon1).when(mIcon1).mutate();
|
||||||
|
|
||||||
|
doReturn(iconSize).when(mIcon2).getIntrinsicHeight();
|
||||||
|
doReturn(iconSize).when(mIcon2).getIntrinsicWidth();
|
||||||
|
doReturn(mIcon2).when(mIcon2).mutate();
|
||||||
|
|
||||||
|
doReturn(iconSize).when(mIcon3).getIntrinsicHeight();
|
||||||
|
doReturn(iconSize).when(mIcon3).getIntrinsicWidth();
|
||||||
|
doReturn(mIcon3).when(mIcon3).mutate();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@@ -106,4 +139,41 @@ public class AppIconCacheManagerTest {
|
|||||||
|
|
||||||
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
|
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME, APP_UID)).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void trimMemory_levelSatisfied_shouldNotCacheIcon() {
|
||||||
|
|
||||||
|
mAppIconCacheManager.put(APP_PACKAGE_NAME1, APP_UID, mIcon1);
|
||||||
|
mAppIconCacheManager.put(APP_PACKAGE_NAME2, APP_UID, mIcon2);
|
||||||
|
mAppIconCacheManager.put(APP_PACKAGE_NAME3, APP_UID, mIcon3);
|
||||||
|
|
||||||
|
// Expected to trim size to 0
|
||||||
|
final int level = android.content.ComponentCallbacks2.TRIM_MEMORY_BACKGROUND;
|
||||||
|
mAppIconCacheManager.trimMemory(level);
|
||||||
|
|
||||||
|
// None of the elements should be cached
|
||||||
|
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID)).isNull();
|
||||||
|
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
|
||||||
|
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void trimMemory_levelSatisfied_shouldCacheAtLeastHalf() {
|
||||||
|
|
||||||
|
mAppIconCacheManager.put(APP_PACKAGE_NAME1, APP_UID, mIcon1);
|
||||||
|
mAppIconCacheManager.put(APP_PACKAGE_NAME2, APP_UID, mIcon2);
|
||||||
|
mAppIconCacheManager.put(APP_PACKAGE_NAME3, APP_UID, mIcon3);
|
||||||
|
|
||||||
|
// Get the last element
|
||||||
|
mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID);
|
||||||
|
|
||||||
|
// Expected to trim size to half of it, which is int( 3 / 2 ) = 1
|
||||||
|
final int level = android.content.ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL;
|
||||||
|
mAppIconCacheManager.trimMemory(level);
|
||||||
|
|
||||||
|
// There should be only one cached element, which is the last recently used one
|
||||||
|
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME1, APP_UID)).isNotNull();
|
||||||
|
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME2, APP_UID)).isNull();
|
||||||
|
assertThat(mAppIconCacheManager.get(APP_PACKAGE_NAME3, APP_UID)).isNull();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user