summaryrefslogtreecommitdiff
path: root/src/com/android/bitmap/UnrefedBitmapCache.java
blob: f30b8fc618046b57065aaeb4987f8bd3af34a337 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/*
 * 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);
    }
}