При разработке приложений разработчик может столкнуться с такой проблемой, когда ему нужно очень часто работать с изображениями. Система каждый раз должна их загружать, обрабатывать, затем, после окончания использования, удалять, либо удалять тогда, когда это не нужно, с помощью сборщика мусора. Чтобы не выполнять все эти операции по кругу достаточно будет кэшировать готовые изображения в памяти устройства и по мере необходимости обращаться к ним. Для таких целей в Android отличным решением является использование кэш-памяти (LruCache) и дискового кэша (DiskLruCache). Оба этих способа основаны на использовании LRU-кэша.
LRU-кэшем называется стратегия кэширования, в которой используется политика Вытеснения давно неиспользуемых (Least Recently Used). То есть, когда кэш заполнен, и мы хотим добавить в него новые данные, из него автоматически будут удалены те данные, которые не использовались наиболее длительное время.
Класс LruCache был добавлен в Android начиная с версии Android 3,1 (API 12), но также доступен через библиотеку поддержки, начиная с версии Android 1.6. Основной его задачей является повторное использование объектов, создание которых затратно по ресурсам. Например, вы можете загрузить изображение из Интернета и поместить его в кэш, чтобы в будущем обращаться только к кэш-памяти.
В нашем приложении “Менеджер системных приложений” при прокрутке списка приложений метод onBindViewHolder() постоянно обращается к PackageManager для загрузки иконок приложений. Правильным решением будет после первой загрузки иконок помещать их в кэш-память, чтобы впоследствии обращаться только к ней.
Для этого создадим класс IconCache, наследующий от LruCache, который будет реализовывать размещение иконок в кэше и вызов их из него.
public class IconCache extends LruCache<String, Drawable> { public IconCache(int maxSize) { super(maxSize); } public Drawable getBitmapFromMemory(String key) { return this.get(key); } public void setBitmapToMemory(String key, Drawable drawable) { if (getBitmapFromMemory(key) == null) { this.put(key, drawable); Log.d("TEST", key + " добавлен в кэш"); } } }
Сохранение объектов происходит по ключу, в данном случае ключом будет выступать имя пакета приложения. Поэтому, когда мы будем запрашивать нужную иконку из кэша, нам будет достаточно знать только имя пакета.
При создании экземпляра LruCache в параметры необходимо передать размер кэша. Важно указать его верно, поскольку слишком маленький размер приведёт к тому, что операции с кэшем станут более затратными, чем текущие, а слишком большой – оставить мало памяти для других приложений на устройстве или даже вызвать исключение java.lang.outOfMemory. Поэтому при определении размера кэша важно сначала определить размер памяти устройства. Единого способа для этого нет, поэтому мы будем использовать класс метод getMemoryClass() класса ActivityManager.
int memClass = mainView.getMemoryClassFromActivity(); int cacheSize = 1024 * 1024 * memClass / 8; IconCache iconCache = new IconCache(cacheSize);
Поскольку в приложении используется MVP паттерн, инициализация происходит на уровне Presenter и затем экземпляр IconCache передаётся в фоновый поток, принимающий запросы на загрузку иконок. Подробнее о MVP можно почитать в предыдущей статье.
Затем, когда ViewHolder запрашивает иконку для приложения, в потоке проверяется, есть ли в кэше объект с данным именем пакета. Если он есть, то берём из кэша и возвращаем результат, а если нет – вызываем PackageManager, загружаем иконку, возвращаем её в главный поток и помещаем в кэш.
private void handleRequest(final T target) { final String packageName = mRequestMap.get(target); final Drawable icon; if (packageName != null) { try { if (iconCache.getBitmapFromMemory(packageName) == null) { icon = packageManager.getApplicationIcon(packageName); iconCache.setBitmapToMemory(packageName, icon); } else { icon = iconCache.getBitmapFromMemory(packageName); } mResponseHandler.post(new Runnable() { @Override public void run() { if (mRequestMap.get(target) == null || !mRequestMap.get(target).equals(packageName)) { return; } mRequestMap.remove(target); mGetIconThreadListener.onIconDownloaded(target, icon); } }); } catch (PackageManager.NameNotFoundException | NullPointerException e) { e.printStackTrace(); } } }
О том, как реализован фоновый поток, можно почитать в данной статье.
Таким образом, благодаря использованию LRU-кэша нам удалось избежать слишком частного повторения операций загрузки изображения и тем самым улучшить работу приложения.