/** * Copyright (c) 2017-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. */ package com.facebook.litho.reference; import java.util.concurrent.atomic.AtomicInteger; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.support.annotation.Nullable; import android.support.v4.content.res.ResourcesCompat; import android.support.v4.util.LruCache; import android.support.v4.util.Pools; import android.util.StateSet; import static android.os.Build.VERSION.SDK_INT; import static android.os.Build.VERSION_CODES.HONEYCOMB; /** * A cache that holds Drawables retreived from Android {@link android.content.res.Resources} for * each resId this class keeps a {@link android.support.v4.util.Pools.SynchronizedPool} of * DRAWABLES_POOL_MAX_ITEMS. The cache has a maximum capacity of DRAWABLES_MAX_ENTRIES. When the * cache is full it starts clearing memory deleting the less recently used pool of resources */ class DrawableResourcesCache { private static final int DRAWABLES_MAX_ENTRIES = 200; private static final int DRAWABLES_POOL_MAX_ITEMS = 10; private final LruCache<Integer, SimplePoolWithCount<Drawable>> mDrawableCache; DrawableResourcesCache() { mDrawableCache = new LruCache<Integer, SimplePoolWithCount<Drawable>>(DRAWABLES_MAX_ENTRIES) { @Override protected int sizeOf(Integer key, SimplePoolWithCount<Drawable> value) { return value.getPoolSize(); } }; } /** * @deprecated use {@link #get(int, Resources, Resources.Theme)} */ @Deprecated public @Nullable Drawable get(int resId, Resources resources) { return get(resId, resources, null); } public @Nullable Drawable get(int resId, Resources resources, @Nullable Resources.Theme theme) { SimplePoolWithCount<Drawable> drawablesPool = mDrawableCache.get(resId); if (drawablesPool == null) { drawablesPool = new SimplePoolWithCount<>(DRAWABLES_POOL_MAX_ITEMS); mDrawableCache.put(resId, drawablesPool); } Drawable drawable = drawablesPool.acquire(); if (drawable == null) { drawable = ResourcesCompat.getDrawable(resources, resId, theme); } // We never want this pool to remain empty otherwise we would risk to resolve a new drawable // when get is called again. So if the pool is about to drain we just put a new Drawable in it // to keep it warm. if (drawable != null && drawablesPool.getPoolSize() == 0) { drawablesPool.release(drawable.getConstantState().newDrawable()); } return drawable; } public void release(Drawable drawable, int resId) { SimplePoolWithCount<Drawable> drawablesPool = mDrawableCache.get(resId); if (drawablesPool == null) { drawablesPool = new SimplePoolWithCount<>(DRAWABLES_POOL_MAX_ITEMS); mDrawableCache.put(resId, drawablesPool); } // Reset a stateful drawable, and its animations, before being released. if (drawable.isStateful()) { drawable.setState(StateSet.WILD_CARD); if (SDK_INT >= HONEYCOMB) { drawable.jumpToCurrentState(); } } drawablesPool.release(drawable); } private static class SimplePoolWithCount<T> extends Pools.SynchronizedPool<T> { private AtomicInteger mPoolSize; public SimplePoolWithCount(int maxPoolSize) { super(maxPoolSize); mPoolSize = new AtomicInteger(0); } @Override public T acquire() { T item = super.acquire(); if (item != null) { mPoolSize.decrementAndGet(); } return item; } @Override public boolean release(T instance) { boolean added = super.release(instance); if (added) { mPoolSize.incrementAndGet(); } return added; } public int getPoolSize() { return mPoolSize.get(); } } }