/* * Copyright (C) 2013 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.bitmap; import android.util.Log; import android.util.LruCache; import com.android.bitmap.ReusableBitmap.NullReusableBitmap; import com.android.bitmap.util.Trace; /** * This subclass provides custom pool behavior. The pool can be set to block on {@link #poll()} if * nothing can be returned. This is useful if you know you will incur high costs upon receiving * nothing from the pool, and you do not want to incur those costs at the critical moment when the * UI is animating. * * This subclass provides custom cache behavior. Null values can be cached. Later, * when the same key is used to retrieve the value, a {@link NullReusableBitmap} singleton will * be returned. */ public class UnrefedBitmapCache extends UnrefedPooledCache<RequestKey, ReusableBitmap> implements BitmapCache { private boolean mBlocking = false; private final Object mLock = new Object(); private LruCache<RequestKey, NullReusableBitmap> mNullRequests; private final static boolean DEBUG = DecodeTask.DEBUG; private final static String TAG = UnrefedBitmapCache.class.getSimpleName(); public UnrefedBitmapCache(final int targetSizeBytes, final float nonPooledFraction, final int nullCapacity) { super(targetSizeBytes, nonPooledFraction); if (nullCapacity > 0) { mNullRequests = new LruCache<RequestKey, NullReusableBitmap>(nullCapacity); } } /** * Declare that {@link #poll()} should now block until it can return something. */ @Override public void setBlocking(final boolean blocking) { synchronized (mLock) { if (DEBUG) { Log.d(TAG, String.format("AltBitmapCache: block %b", blocking)); } mBlocking = blocking; if (!mBlocking) { // no longer blocking. Notify every thread. mLock.notifyAll(); } } } @Override protected int sizeOf(final ReusableBitmap value) { return value.getByteCount(); } /** * If {@link #setBlocking(boolean)} has been called with true, this method will block until a * resource is available. * @return an available resource, or null if none are available. Null will never be returned * until blocking is set to false. */ @Override public ReusableBitmap poll() { ReusableBitmap bitmap; synchronized (mLock) { while ((bitmap = super.poll()) == null && mBlocking) { if (DEBUG) { Log.d(TAG, String.format( "AltBitmapCache: %s waiting", Thread.currentThread().getName())); } Trace.beginSection("sleep"); try { // block mLock.wait(); if (DEBUG) { Log.d(TAG, String.format("AltBitmapCache: %s notified", Thread.currentThread().getName())); } } catch (InterruptedException ignored) { } Trace.endSection(); } } return bitmap; } @Override public void offer(final ReusableBitmap value) { synchronized (mLock) { super.offer(value); if (DEBUG) { Log.d(TAG, "AltBitmapCache: offer +1"); } // new resource gained. Notify one thread. mLock.notify(); } } @Override public ReusableBitmap get(final RequestKey key, final boolean incrementRefCount) { if (mNullRequests != null && mNullRequests.get(key) != null) { return NullReusableBitmap.getInstance(); } return super.get(key, incrementRefCount); } /** * Note: The cache only supports same-sized bitmaps. */ @Override public ReusableBitmap put(final RequestKey key, final ReusableBitmap value) { if (mNullRequests != null && (value == null || value == NullReusableBitmap.getInstance())) { mNullRequests.put(key, NullReusableBitmap.getInstance()); return null; } return super.put(key, value); } }