package me.xiaopan.sketch.cache; import android.annotation.TargetApi; import android.app.ActivityManager; import android.content.Context; import android.os.Build; import android.text.format.Formatter; import android.util.DisplayMetrics; import me.xiaopan.sketch.SLogType; import me.xiaopan.sketch.SLog; /** * A calculator that tries to intelligently determine cache sizes for a given device based on some constants and the * devices screen density, width, and height. */ public class MemorySizeCalculator { private static final String LOG_NAME = "MemorySizeCalculator"; // Visible for testing. static final int BYTES_PER_ARGB_8888_PIXEL = 4; static final int MEMORY_CACHE_TARGET_SCREENS = 3; static final int BITMAP_POOL_TARGET_SCREENS = 3; static final float MAX_SIZE_MULTIPLIER = 0.4f; static final float LOW_MEMORY_MAX_SIZE_MULTIPLIER = 0.33f; private final int bitmapPoolSize; private final int memoryCacheSize; private final Context context; interface ScreenDimensions { int getWidthPixels(); int getHeightPixels(); } public MemorySizeCalculator(Context context) { this(context, (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE), new DisplayMetricsScreenDimensions(context.getResources().getDisplayMetrics())); } // Visible for testing. MemorySizeCalculator(Context context, ActivityManager activityManager, ScreenDimensions screenDimensions) { context = context.getApplicationContext(); this.context = context; final int maxSize = getMaxSize(activityManager); final int screenSize = screenDimensions.getWidthPixels() * screenDimensions.getHeightPixels() * BYTES_PER_ARGB_8888_PIXEL; int targetPoolSize = screenSize * BITMAP_POOL_TARGET_SCREENS; int targetMemoryCacheSize = screenSize * MEMORY_CACHE_TARGET_SCREENS; if (targetMemoryCacheSize + targetPoolSize <= maxSize) { memoryCacheSize = targetMemoryCacheSize; bitmapPoolSize = BitmapPoolUtils.sdkSupportInBitmap() ? targetPoolSize : 0; } else { int part = Math.round((float) maxSize / (BITMAP_POOL_TARGET_SCREENS + MEMORY_CACHE_TARGET_SCREENS)); memoryCacheSize = part * MEMORY_CACHE_TARGET_SCREENS; bitmapPoolSize = BitmapPoolUtils.sdkSupportInBitmap() ? part * BITMAP_POOL_TARGET_SCREENS : 0; } if (SLogType.CACHE.isEnabled()) { SLog.d(SLogType.CACHE, LOG_NAME, "Calculated memory cache size: %s pool size: %s memory class limited? %s max size: %s memoryClass: %d isLowMemoryDevice: %s", toMb(memoryCacheSize), toMb(bitmapPoolSize), targetMemoryCacheSize + targetPoolSize > maxSize, toMb(maxSize), activityManager.getMemoryClass(), isLowMemoryDevice(activityManager)); } } /** * Returns the recommended memory cache size for the device it is run on in bytes. */ public int getMemoryCacheSize() { return memoryCacheSize; } /** * Returns the recommended bitmap pool size for the device it is run on in bytes. */ public int getBitmapPoolSize() { return bitmapPoolSize; } private static int getMaxSize(ActivityManager activityManager) { final int memoryClassBytes = activityManager.getMemoryClass() * 1024 * 1024; final boolean isLowMemoryDevice = isLowMemoryDevice(activityManager); return Math.round(memoryClassBytes * (isLowMemoryDevice ? LOW_MEMORY_MAX_SIZE_MULTIPLIER : MAX_SIZE_MULTIPLIER)); } private String toMb(int bytes) { return Formatter.formatFileSize(context, bytes); } @TargetApi(Build.VERSION_CODES.KITKAT) private static boolean isLowMemoryDevice(ActivityManager activityManager) { final int sdkInt = Build.VERSION.SDK_INT; return sdkInt < Build.VERSION_CODES.HONEYCOMB || (sdkInt >= Build.VERSION_CODES.KITKAT && activityManager.isLowRamDevice()); } private static class DisplayMetricsScreenDimensions implements ScreenDimensions { private final DisplayMetrics displayMetrics; public DisplayMetricsScreenDimensions(DisplayMetrics displayMetrics) { this.displayMetrics = displayMetrics; } @Override public int getWidthPixels() { return displayMetrics.widthPixels; } @Override public int getHeightPixels() { return displayMetrics.heightPixels; } } }