Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c301e09c authored by Shen Lin's avatar Shen Lin
Browse files

Introduce trimMemory to AppIconCacheManager

This CL adds a method to trim lrucache size dynamically based on the
level of onLowMemory to avoid calling evictAll to frequently, and to make cache resize more reasonable.

Bug: 259630755
Test: atest AppIconCacheManagerTest -c

Change-Id: I1b9692a0fad8c13722da4906cf844232707e74b2
parent 140a69e9
Loading
Loading
Loading
Loading
+25 −1
Original line number Original line Diff line number Diff line
@@ -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);
            }
        }
    }
}
}
+70 −0
Original line number Original line Diff line number Diff line
@@ -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();
    }
}
}