OSDN Git Service

Replace various BitmapPools with a smarter unified pool
authorBobby Georgescu <georgescu@google.com>
Tue, 19 Feb 2013 23:49:14 +0000 (15:49 -0800)
committerBobby Georgescu <georgescu@google.com>
Fri, 22 Feb 2013 20:56:23 +0000 (12:56 -0800)
Make all of gallery use a single shared pool, and pave the
way for making the pool more adaptive based on the current
workload.

Change-Id: Ia32561ad50b1b9716ebe2fd32a7bf02737685dac

19 files changed:
src/android/util/Pools.java [new file with mode: 0644]
src/com/android/gallery3d/app/AbstractGalleryActivity.java
src/com/android/gallery3d/app/PhotoDataAdapter.java
src/com/android/gallery3d/data/BitmapPool.java [deleted file]
src/com/android/gallery3d/data/DecodeUtils.java
src/com/android/gallery3d/data/ImageCacheRequest.java
src/com/android/gallery3d/data/MediaItem.java
src/com/android/gallery3d/ingest/IngestService.java
src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java
src/com/android/gallery3d/ui/AlbumLabelMaker.java
src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
src/com/android/gallery3d/ui/AlbumSlidingWindow.java
src/com/android/gallery3d/ui/BitmapLoader.java
src/com/android/gallery3d/ui/BitmapTileProvider.java
src/com/android/gallery3d/ui/TileImageView.java
src/com/android/gallery3d/ui/TileImageViewAdapter.java
src/com/android/gallery3d/ui/TiledScreenNail.java
src/com/android/photos/data/GalleryBitmapPool.java [new file with mode: 0644]
src/com/android/photos/data/SparseArrayBitmapPool.java [new file with mode: 0644]

diff --git a/src/android/util/Pools.java b/src/android/util/Pools.java
new file mode 100644 (file)
index 0000000..40bab1e
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2009 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 android.util;
+
+/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ * <pre>
+ * public class MyPooledClass {
+ *
+ *     private static final SynchronizedPool<MyPooledClass> sPool =
+ *             new SynchronizedPool<MyPooledClass>(10);
+ *
+ *     public static MyPooledClass obtain() {
+ *         MyPooledClass instance = sPool.acquire();
+ *         return (instance != null) ? instance : new MyPooledClass();
+ *     }
+ *
+ *     public void recycle() {
+ *          // Clear state if needed.
+ *          sPool.release(this);
+ *     }
+ *
+ *     . . .
+ * }
+ * </pre>
+ *
+ * @hide
+ */
+public final class Pools {
+
+    /**
+     * Interface for managing a pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static interface Pool<T> {
+
+        /**
+         * @return An instance from the pool if such, null otherwise.
+         */
+        public T acquire();
+
+        /**
+         * Release an instance to the pool.
+         *
+         * @param instance The instance to release.
+         * @return Whether the instance was put in the pool.
+         *
+         * @throws IllegalStateException If the instance is already in the pool.
+         */
+        public boolean release(T instance);
+    }
+
+    private Pools() {
+        /* do nothing - hiding constructor */
+    }
+
+    /**
+     * Simple (non-synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SimplePool<T> implements Pool<T> {
+        private final Object[] mPool;
+
+        private int mPoolSize;
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SimplePool(int maxPoolSize) {
+            if (maxPoolSize <= 0) {
+                throw new IllegalArgumentException("The max pool size must be > 0");
+            }
+            mPool = new Object[maxPoolSize];
+        }
+
+        @Override
+        @SuppressWarnings("unchecked")
+        public T acquire() {
+            if (mPoolSize > 0) {
+                final int lastPooledIndex = mPoolSize - 1;
+                T instance = (T) mPool[lastPooledIndex];
+                mPool[lastPooledIndex] = null;
+                mPoolSize--;
+                return instance;
+            }
+            return null;
+        }
+
+        @Override
+        public boolean release(T instance) {
+            if (isInPool(instance)) {
+                throw new IllegalStateException("Already in the pool!");
+            }
+            if (mPoolSize < mPool.length) {
+                mPool[mPoolSize] = instance;
+                mPoolSize++;
+                return true;
+            }
+            return false;
+        }
+
+        private boolean isInPool(T instance) {
+            for (int i = 0; i < mPoolSize; i++) {
+                if (mPool[i] == instance) {
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Synchronized) pool of objects.
+     *
+     * @param <T> The pooled type.
+     */
+    public static class SynchronizedPool<T> extends SimplePool<T> {
+        private final Object mLock = new Object();
+
+        /**
+         * Creates a new instance.
+         *
+         * @param maxPoolSize The max pool size.
+         *
+         * @throws IllegalArgumentException If the max pool size is less than zero.
+         */
+        public SynchronizedPool(int maxPoolSize) {
+            super(maxPoolSize);
+        }
+
+        @Override
+        public T acquire() {
+            synchronized (mLock) {
+                return super.acquire();
+            }
+        }
+
+        @Override
+        public boolean release(T element) {
+            synchronized (mLock) {
+                return super.release(element);
+            }
+        }
+    }
+}
\ No newline at end of file
index c9cce81..d960942 100644 (file)
@@ -38,9 +38,9 @@ import android.view.WindowManager;
 
 import com.android.gallery3d.R;
 import com.android.gallery3d.common.ApiHelper;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DataManager;
 import com.android.gallery3d.data.MediaItem;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.ui.GLRoot;
 import com.android.gallery3d.ui.GLRootView;
 import com.android.gallery3d.util.LightCycleHelper.PanoramaViewHelper;
@@ -222,16 +222,10 @@ public class AbstractGalleryActivity extends Activity implements GalleryContext
         } finally {
             mGLRootView.unlockRenderThread();
         }
-        clearBitmapPool(MediaItem.getMicroThumbPool());
-        clearBitmapPool(MediaItem.getThumbPool());
-
+        GalleryBitmapPool.getInstance().clear();
         MediaItem.getBytesBufferPool().clear();
     }
 
-    private static void clearBitmapPool(BitmapPool pool) {
-        if (pool != null) pool.clear();
-    }
-
     @Override
     protected void onDestroy() {
         super.onDestroy();
index faff146..d409315 100644 (file)
@@ -23,7 +23,6 @@ import android.os.Message;
 
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.ContentListener;
 import com.android.gallery3d.data.LocalMediaItem;
 import com.android.gallery3d.data.MediaItem;
@@ -550,8 +549,8 @@ public class PhotoDataAdapter implements PhotoPage.Model {
     }
 
     @Override
-    public Bitmap getTile(int level, int x, int y, int tileSize, BitmapPool pool) {
-        return mTileProvider.getTile(level, x, y, tileSize, pool);
+    public Bitmap getTile(int level, int x, int y, int tileSize) {
+        return mTileProvider.getTile(level, x, y, tileSize);
     }
 
     @Override
diff --git a/src/com/android/gallery3d/data/BitmapPool.java b/src/com/android/gallery3d/data/BitmapPool.java
deleted file mode 100644 (file)
index 5bc6d67..0000000
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2011 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.gallery3d.data;
-
-import android.graphics.Bitmap;
-
-import com.android.gallery3d.common.Utils;
-
-import java.util.ArrayList;
-
-public class BitmapPool {
-    @SuppressWarnings("unused")
-    private static final String TAG = "BitmapPool";
-
-    private final ArrayList<Bitmap> mPool;
-    private final int mPoolLimit;
-
-    // mOneSize is true if the pool can only cache Bitmap with one size.
-    private final boolean mOneSize;
-    private final int mWidth, mHeight;  // only used if mOneSize is true
-
-    // Construct a BitmapPool which caches bitmap with the specified size.
-    public BitmapPool(int width, int height, int poolLimit) {
-        mWidth = width;
-        mHeight = height;
-        mPoolLimit = poolLimit;
-        mPool = new ArrayList<Bitmap>(poolLimit);
-        mOneSize = true;
-    }
-
-    // Construct a BitmapPool which caches bitmap with any size;
-    public BitmapPool(int poolLimit) {
-        mWidth = -1;
-        mHeight = -1;
-        mPoolLimit = poolLimit;
-        mPool = new ArrayList<Bitmap>(poolLimit);
-        mOneSize = false;
-    }
-
-    // Get a Bitmap from the pool.
-    public synchronized Bitmap getBitmap() {
-        Utils.assertTrue(mOneSize);
-        int size = mPool.size();
-        return size > 0 ? mPool.remove(size - 1) : null;
-    }
-
-    // Get a Bitmap from the pool with the specified size.
-    public synchronized Bitmap getBitmap(int width, int height) {
-        Utils.assertTrue(!mOneSize);
-        for (int i = mPool.size() - 1; i >= 0; i--) {
-            Bitmap b = mPool.get(i);
-            if (b.getWidth() == width && b.getHeight() == height) {
-                return mPool.remove(i);
-            }
-        }
-        return null;
-    }
-
-    // Put a Bitmap into the pool, if the Bitmap has a proper size. Otherwise
-    // the Bitmap will be recycled. If the pool is full, an old Bitmap will be
-    // recycled.
-    public void recycle(Bitmap bitmap) {
-        if (bitmap == null) return;
-        if (mOneSize && ((bitmap.getWidth() != mWidth) ||
-                (bitmap.getHeight() != mHeight))) {
-            bitmap.recycle();
-            return;
-        }
-        synchronized (this) {
-            if (mPool.size() >= mPoolLimit) mPool.remove(0);
-            mPool.add(bitmap);
-        }
-    }
-
-    public synchronized void clear() {
-        mPool.clear();
-    }
-
-    public boolean isOneSize() {
-        return mOneSize;
-    }
-}
index 4d3c996..fa70915 100644 (file)
@@ -28,6 +28,7 @@ import android.util.FloatMath;
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.common.Utils;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.ui.Log;
 import com.android.gallery3d.util.ThreadPool.CancelListener;
 import com.android.gallery3d.util.ThreadPool.JobContext;
@@ -246,21 +247,17 @@ public class DecodeUtils {
     }
 
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static Bitmap decode(JobContext jc, byte[] data, int offset,
-            int length, BitmapFactory.Options options, BitmapPool pool) {
-        if (pool == null) {
-            return decode(jc, data, offset, length, options);
-        }
-
+    public static Bitmap decodeUsingPool(JobContext jc, byte[] data, int offset,
+            int length, BitmapFactory.Options options) {
         if (options == null) options = new BitmapFactory.Options();
         if (options.inSampleSize < 1) options.inSampleSize = 1;
         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
         options.inBitmap = (options.inSampleSize == 1)
-                ? findCachedBitmap(pool, jc, data, offset, length, options) : null;
+                ? findCachedBitmap(jc, data, offset, length, options) : null;
         try {
             Bitmap bitmap = decode(jc, data, offset, length, options);
             if (options.inBitmap != null && options.inBitmap != bitmap) {
-                pool.recycle(options.inBitmap);
+                GalleryBitmapPool.getInstance().put(options.inBitmap);
                 options.inBitmap = null;
             }
             return bitmap;
@@ -268,7 +265,7 @@ public class DecodeUtils {
             if (options.inBitmap == null) throw e;
 
             Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
-            pool.recycle(options.inBitmap);
+            GalleryBitmapPool.getInstance().put(options.inBitmap);
             options.inBitmap = null;
             return decode(jc, data, offset, length, options);
         }
@@ -277,21 +274,17 @@ public class DecodeUtils {
     // This is the same as the method above except the source data comes
     // from a file descriptor instead of a byte array.
     @TargetApi(Build.VERSION_CODES.HONEYCOMB)
-    public static Bitmap decode(JobContext jc,
-            FileDescriptor fileDescriptor, Options options, BitmapPool pool) {
-        if (pool == null) {
-            return decode(jc, fileDescriptor, options);
-        }
-
+    public static Bitmap decodeUsingPool(JobContext jc,
+            FileDescriptor fileDescriptor, Options options) {
         if (options == null) options = new BitmapFactory.Options();
         if (options.inSampleSize < 1) options.inSampleSize = 1;
         options.inPreferredConfig = Bitmap.Config.ARGB_8888;
         options.inBitmap = (options.inSampleSize == 1)
-                ? findCachedBitmap(pool, jc, fileDescriptor, options) : null;
+                ? findCachedBitmap(jc, fileDescriptor, options) : null;
         try {
             Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
             if (options.inBitmap != null && options.inBitmap != bitmap) {
-                pool.recycle(options.inBitmap);
+                GalleryBitmapPool.getInstance().put(options.inBitmap);
                 options.inBitmap = null;
             }
             return bitmap;
@@ -299,23 +292,21 @@ public class DecodeUtils {
             if (options.inBitmap == null) throw e;
 
             Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
-            pool.recycle(options.inBitmap);
+            GalleryBitmapPool.getInstance().put(options.inBitmap);
             options.inBitmap = null;
             return decode(jc, fileDescriptor, options);
         }
     }
 
-    private static Bitmap findCachedBitmap(BitmapPool pool, JobContext jc,
-            byte[] data, int offset, int length, Options options) {
-        if (pool.isOneSize()) return pool.getBitmap();
+    private static Bitmap findCachedBitmap(JobContext jc, byte[] data,
+            int offset, int length, Options options) {
         decodeBounds(jc, data, offset, length, options);
-        return pool.getBitmap(options.outWidth, options.outHeight);
+        return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight);
     }
 
-    private static Bitmap findCachedBitmap(BitmapPool pool, JobContext jc,
-            FileDescriptor fileDescriptor, Options options) {
-        if (pool.isOneSize()) return pool.getBitmap();
+    private static Bitmap findCachedBitmap(JobContext jc, FileDescriptor fileDescriptor,
+            Options options) {
         decodeBounds(jc, fileDescriptor, options);
-        return pool.getBitmap(options.outWidth, options.outHeight);
+        return GalleryBitmapPool.getInstance().get(options.outWidth, options.outHeight);
     }
 }
index 3f937e3..4756149 100644 (file)
@@ -60,13 +60,11 @@ abstract class ImageCacheRequest implements Job<Bitmap> {
                 options.inPreferredConfig = Bitmap.Config.ARGB_8888;
                 Bitmap bitmap;
                 if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
-                    bitmap = DecodeUtils.decode(jc,
-                            buffer.data, buffer.offset, buffer.length, options,
-                            MediaItem.getMicroThumbPool());
+                    bitmap = DecodeUtils.decodeUsingPool(jc,
+                            buffer.data, buffer.offset, buffer.length, options);
                 } else {
-                    bitmap = DecodeUtils.decode(jc,
-                            buffer.data, buffer.offset, buffer.length, options,
-                            MediaItem.getThumbPool());
+                    bitmap = DecodeUtils.decodeUsingPool(jc,
+                            buffer.data, buffer.offset, buffer.length, options);
                 }
                 if (bitmap == null && !jc.isCancelled()) {
                     Log.w(TAG, "decode cached failed " + debugTag());
index 19084d4..59ea865 100644 (file)
@@ -42,15 +42,10 @@ public abstract class MediaItem extends MediaObject {
     private static final int BYTESBUFFER_SIZE = 200 * 1024;
 
     private static int sMicrothumbnailTargetSize = 200;
-    private static BitmapPool sMicroThumbPool;
     private static final BytesBufferPool sMicroThumbBufferPool =
             new BytesBufferPool(BYTESBUFFE_POOL_SIZE, BYTESBUFFER_SIZE);
 
     private static int sThumbnailTargetSize = 640;
-    private static final BitmapPool sThumbPool =
-            ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY
-            ? new BitmapPool(4)
-            : null;
 
     // TODO: fix default value for latlng and change this.
     public static final double INVALID_LATLNG = 0f;
@@ -126,33 +121,14 @@ public abstract class MediaItem extends MediaObject {
         }
     }
 
-    public static BitmapPool getMicroThumbPool() {
-        if (ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY && sMicroThumbPool == null) {
-            initializeMicroThumbPool();
-        }
-        return sMicroThumbPool;
-    }
-
-    public static BitmapPool getThumbPool() {
-        return sThumbPool;
-    }
-
     public static BytesBufferPool getBytesBufferPool() {
         return sMicroThumbBufferPool;
     }
 
-    private static void initializeMicroThumbPool() {
-        sMicroThumbPool =
-                ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_FACTORY
-                ? new BitmapPool(sMicrothumbnailTargetSize, sMicrothumbnailTargetSize, 16)
-                : null;
-    }
-
     public static void setThumbnailSizes(int size, int microSize) {
         sThumbnailTargetSize = size;
         if (sMicrothumbnailTargetSize != microSize) {
             sMicrothumbnailTargetSize = microSize;
-            initializeMicroThumbPool();
         }
     }
 }
index 5e0ca0b..fa421e7 100644 (file)
@@ -187,7 +187,6 @@ public class IngestService extends Service implements ImportTask.Listener,
     public void deviceRemoved(MtpDevice device) {
         if (device == mDevice) {
             setDevice(null);
-            MtpBitmapFetch.onDeviceDisconnected(device);
         }
     }
 
index 46a2051..5e1fb34 100644 (file)
@@ -25,19 +25,14 @@ import android.util.DisplayMetrics;
 import android.view.WindowManager;
 
 import com.android.camera.Exif;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
-
-import java.util.ArrayList;
+import com.android.photos.data.GalleryBitmapPool;
 
 public class MtpBitmapFetch {
-    private static final int BITMAP_POOL_SIZE = 32;
-    private static BitmapPool sThumbnailPool = new BitmapPool(BITMAP_POOL_SIZE);
     private static int sMaxSize = 0;
 
     public static void recycleThumbnail(Bitmap b) {
         if (b != null) {
-            sThumbnailPool.recycle(b);
+            GalleryBitmapPool.getInstance().put(b);
         }
     }
 
@@ -48,7 +43,7 @@ public class MtpBitmapFetch {
         o.inJustDecodeBounds = true;
         BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.length, o);
         if (o.outWidth == 0 || o.outHeight == 0) return null;
-        o.inBitmap = sThumbnailPool.getBitmap(o.outWidth, o.outHeight);
+        o.inBitmap = GalleryBitmapPool.getInstance().get(o.outWidth, o.outHeight);
         o.inMutable = true;
         o.inJustDecodeBounds = false;
         o.inSampleSize = 1;
@@ -86,10 +81,6 @@ public class MtpBitmapFetch {
         return new BitmapWithMetadata(created, Exif.getOrientation(imageBytes));
     }
 
-    public static void onDeviceDisconnected(MtpDevice device) {
-        sThumbnailPool.clear();
-    }
-
     public static void configureForContext(Context context) {
         DisplayMetrics metrics = new DisplayMetrics();
         WindowManager wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
index 6eeeec0..da1cac0 100644 (file)
@@ -27,8 +27,8 @@ import android.text.TextPaint;
 import android.text.TextUtils;
 
 import com.android.gallery3d.R;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DataSourceType;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.util.ThreadPool;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 
@@ -41,7 +41,8 @@ public class AlbumLabelMaker {
     private final Context mContext;
 
     private int mLabelWidth;
-    private BitmapPool mBitmapPool;
+    private int mBitmapWidth;
+    private int mBitmapHeight;
 
     private final LazyLoadedBitmap mLocalSetIcon;
     private final LazyLoadedBitmap mPicasaIcon;
@@ -109,8 +110,8 @@ public class AlbumLabelMaker {
         if (mLabelWidth == width) return;
         mLabelWidth = width;
         int borders = 2 * BORDER_SIZE;
-        mBitmapPool = new BitmapPool(
-                width + borders, mSpec.labelBackgroundHeight + borders, 16);
+        mBitmapWidth = width + borders;
+        mBitmapHeight = mSpec.labelBackgroundHeight + borders;
     }
 
     public ThreadPool.Job<Bitmap> requestLabel(
@@ -152,7 +153,7 @@ public class AlbumLabelMaker {
 
             synchronized (this) {
                 labelWidth = mLabelWidth;
-                bitmap = mBitmapPool.getBitmap();
+                bitmap = GalleryBitmapPool.getInstance().get(mBitmapWidth, mBitmapHeight);
             }
 
             if (bitmap == null) {
@@ -200,10 +201,6 @@ public class AlbumLabelMaker {
     }
 
     public void recycleLabel(Bitmap label) {
-        mBitmapPool.recycle(label);
-    }
-
-    public void clearRecycledLabels() {
-        if (mBitmapPool != null) mBitmapPool.clear();
+        GalleryBitmapPool.getInstance().put(label);
     }
 }
index d5a15b4..8149df4 100644 (file)
@@ -23,7 +23,6 @@ import com.android.gallery3d.R;
 import com.android.gallery3d.app.AbstractGalleryActivity;
 import com.android.gallery3d.app.AlbumSetDataLoader;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DataSourceType;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
@@ -403,7 +402,6 @@ public class AlbumSetSlidingWindow implements AlbumSetDataLoader.DataListener {
         for (int i = mContentStart, n = mContentEnd; i < n; ++i) {
             freeSlotContent(i);
         }
-        mLabelMaker.clearRecycledLabels();
     }
 
     public void resume() {
@@ -429,12 +427,6 @@ public class AlbumSetSlidingWindow implements AlbumSetDataLoader.DataListener {
         }
 
         @Override
-        protected void recycleBitmap(Bitmap bitmap) {
-            BitmapPool pool = MediaItem.getMicroThumbPool();
-            if (pool != null) pool.recycle(bitmap);
-        }
-
-        @Override
         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
             return mThreadPool.submit(mMediaItem.requestImage(
                     MediaItem.TYPE_MICROTHUMBNAIL), l);
@@ -505,11 +497,6 @@ public class AlbumSetSlidingWindow implements AlbumSetDataLoader.DataListener {
         }
 
         @Override
-        protected void recycleBitmap(Bitmap bitmap) {
-            mLabelMaker.recycleLabel(bitmap);
-        }
-
-        @Override
         protected void onLoadComplete(Bitmap bitmap) {
             mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget();
         }
index 8cd2cf5..fec7d1e 100644 (file)
@@ -22,11 +22,10 @@ import android.os.Message;
 import com.android.gallery3d.app.AbstractGalleryActivity;
 import com.android.gallery3d.app.AlbumDataLoader;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.MediaItem;
 import com.android.gallery3d.data.MediaObject;
-import com.android.gallery3d.data.Path;
 import com.android.gallery3d.data.MediaObject.PanoramaSupportCallback;
+import com.android.gallery3d.data.Path;
 import com.android.gallery3d.glrenderer.Texture;
 import com.android.gallery3d.glrenderer.TiledTexture;
 import com.android.gallery3d.util.Future;
@@ -296,12 +295,6 @@ public class AlbumSlidingWindow implements AlbumDataLoader.DataListener {
         }
 
         @Override
-        protected void recycleBitmap(Bitmap bitmap) {
-            BitmapPool pool = MediaItem.getMicroThumbPool();
-            if (pool != null) pool.recycle(bitmap);
-        }
-
-        @Override
         protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l) {
             return mThreadPool.submit(
                     mItem.requestImage(MediaItem.TYPE_MICROTHUMBNAIL), this);
index 4f07cc0..a708a90 100644 (file)
@@ -18,6 +18,7 @@ package com.android.gallery3d.ui;
 
 import android.graphics.Bitmap;
 
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.util.Future;
 import com.android.gallery3d.util.FutureListener;
 
@@ -51,7 +52,7 @@ public abstract class BitmapLoader implements FutureListener<Bitmap> {
             mBitmap = future.get();
             if (mState == STATE_RECYCLED) {
                 if (mBitmap != null) {
-                    recycleBitmap(mBitmap);
+                    GalleryBitmapPool.getInstance().put(mBitmap);
                     mBitmap = null;
                 }
                 return; // don't call callback
@@ -84,7 +85,7 @@ public abstract class BitmapLoader implements FutureListener<Bitmap> {
     public synchronized void recycle() {
         mState = STATE_RECYCLED;
         if (mBitmap != null) {
-            recycleBitmap(mBitmap);
+            GalleryBitmapPool.getInstance().put(mBitmap);
             mBitmap = null;
         }
         if (mTask != null) mTask.cancel();
@@ -103,6 +104,5 @@ public abstract class BitmapLoader implements FutureListener<Bitmap> {
     }
 
     abstract protected Future<Bitmap> submitBitmapTask(FutureListener<Bitmap> l);
-    abstract protected void recycleBitmap(Bitmap bitmap);
     abstract protected void onLoadComplete(Bitmap bitmap);
 }
index c3466e7..e1a8b76 100644 (file)
@@ -21,7 +21,7 @@ import android.graphics.Bitmap.Config;
 import android.graphics.Canvas;
 
 import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.data.BitmapPool;
+import com.android.photos.data.GalleryBitmapPool;
 
 import java.util.ArrayList;
 
@@ -71,12 +71,11 @@ public class BitmapTileProvider implements TileImageView.TileSource {
     }
 
     @Override
-    public Bitmap getTile(int level, int x, int y, int tileSize,
-            BitmapPool pool) {
+    public Bitmap getTile(int level, int x, int y, int tileSize) {
         x >>= level;
         y >>= level;
 
-        Bitmap result = pool == null ? null : pool.getBitmap();
+        Bitmap result = GalleryBitmapPool.getInstance().get(tileSize, tileSize);
         if (result == null) {
             result = Bitmap.createBitmap(tileSize, tileSize, mConfig);
         } else {
index f1c31e4..3185c75 100644 (file)
@@ -27,10 +27,9 @@ import android.util.FloatMath;
 import android.view.WindowManager;
 
 import com.android.gallery3d.app.GalleryContext;
-import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
 import com.android.gallery3d.data.DecodeUtils;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.glrenderer.GLCanvas;
 import com.android.gallery3d.glrenderer.UploadedTexture;
 import com.android.gallery3d.util.Future;
@@ -50,8 +49,6 @@ public class TileImageView extends GLView {
     // TILE_SIZE must be 2^N
     private static int sTileSize;
 
-    private static BitmapPool sTilePool;
-
     /*
      *  This is the tile state in the CPU side.
      *  Life of a Tile:
@@ -143,8 +140,7 @@ public class TileImageView extends GLView {
         // still refers to the coordinate on the original image.
         //
         // The method would be called in another thread.
-        public Bitmap getTile(int level, int x, int y, int tileSize,
-                BitmapPool pool);
+        public Bitmap getTile(int level, int x, int y, int tileSize);
     }
 
     public static boolean isHighResolution(Context context) {
@@ -164,10 +160,6 @@ public class TileImageView extends GLView {
             } else {
                 sTileSize = 256;
             }
-            sTilePool =
-                    ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER
-                    ? new BitmapPool(sTileSize, sTileSize, 128)
-                    : null;
         }
     }
 
@@ -399,7 +391,6 @@ public class TileImageView extends GLView {
             }
         }
         setScreenNail(null);
-        if (sTilePool != null) sTilePool.clear();
     }
 
     public void prepareTextures() {
@@ -508,7 +499,7 @@ public class TileImageView extends GLView {
             if (tile.mTileState == STATE_RECYCLING) {
                 tile.mTileState = STATE_RECYCLED;
                 if (tile.mDecodedTile != null) {
-                    if (sTilePool != null) sTilePool.recycle(tile.mDecodedTile);
+                    GalleryBitmapPool.getInstance().put(tile.mDecodedTile);
                     tile.mDecodedTile = null;
                 }
                 mRecycledQueue.push(tile);
@@ -536,7 +527,7 @@ public class TileImageView extends GLView {
         }
         tile.mTileState = STATE_RECYCLED;
         if (tile.mDecodedTile != null) {
-            if (sTilePool != null) sTilePool.recycle(tile.mDecodedTile);
+            GalleryBitmapPool.getInstance().put(tile.mDecodedTile);
             tile.mDecodedTile = null;
         }
         mRecycledQueue.push(tile);
@@ -675,7 +666,7 @@ public class TileImageView extends GLView {
 
         @Override
         protected void onFreeBitmap(Bitmap bitmap) {
-            if (sTilePool != null) sTilePool.recycle(bitmap);
+            GalleryBitmapPool.getInstance().put(bitmap);
         }
 
         boolean decode() {
@@ -683,7 +674,7 @@ public class TileImageView extends GLView {
             // by (1 << mTilelevel) from a region in the original image.
             try {
                 mDecodedTile = DecodeUtils.ensureGLCompatibleBitmap(mModel.getTile(
-                        mTileLevel, mX, mY, sTileSize, sTilePool));
+                        mTileLevel, mX, mY, sTileSize));
             } catch (Throwable t) {
                 Log.w(TAG, "fail to decode tile", t);
             }
index 0d20b07..0c1f66d 100644 (file)
@@ -26,7 +26,7 @@ import android.graphics.Rect;
 
 import com.android.gallery3d.common.ApiHelper;
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
+import com.android.photos.data.GalleryBitmapPool;
 
 public class TileImageViewAdapter implements TileImageView.TileSource {
     private static final String TAG = "TileImageViewAdapter";
@@ -84,7 +84,7 @@ public class TileImageViewAdapter implements TileImageView.TileSource {
     // (44, 44, 256, 256) from the original photo and down sample it to 106.
     @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
     @Override
-    public Bitmap getTile(int level, int x, int y, int tileSize, BitmapPool pool) {
+    public Bitmap getTile(int level, int x, int y, int tileSize) {
         if (!ApiHelper.HAS_REUSING_BITMAP_IN_BITMAP_REGION_DECODER) {
             return getTileWithoutReusingBitmap(level, x, y, tileSize);
         }
@@ -106,7 +106,7 @@ public class TileImageViewAdapter implements TileImageView.TileSource {
                     .contains(wantRegion);
         }
 
-        Bitmap bitmap = pool == null ? null : pool.getBitmap();
+        Bitmap bitmap = GalleryBitmapPool.getInstance().get(tileSize, tileSize);
         if (bitmap != null) {
             if (needClear) bitmap.eraseColor(0);
         } else {
@@ -126,7 +126,7 @@ public class TileImageViewAdapter implements TileImageView.TileSource {
             }
         } finally {
             if (options.inBitmap != bitmap && options.inBitmap != null) {
-                if (pool != null) pool.recycle(options.inBitmap);
+                GalleryBitmapPool.getInstance().put(options.inBitmap);
                 options.inBitmap = null;
             }
         }
index ab24f5b..860e230 100644 (file)
@@ -20,8 +20,7 @@ import android.graphics.Bitmap;
 import android.graphics.RectF;
 
 import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.data.BitmapPool;
-import com.android.gallery3d.data.MediaItem;
+import com.android.photos.data.GalleryBitmapPool;
 import com.android.gallery3d.glrenderer.GLCanvas;
 import com.android.gallery3d.glrenderer.TiledTexture;
 
@@ -83,11 +82,6 @@ public class TiledScreenNail implements ScreenNail {
         mHeight = Math.round(scale * height);
     }
 
-    private static void recycleBitmap(BitmapPool pool, Bitmap bitmap) {
-        if (pool == null || bitmap == null) return;
-        pool.recycle(bitmap);
-    }
-
     // Combines the two ScreenNails.
     // Returns the used one and recycle the unused one.
     public ScreenNail combine(ScreenNail other) {
@@ -106,7 +100,7 @@ public class TiledScreenNail implements ScreenNail {
         mWidth = newer.mWidth;
         mHeight = newer.mHeight;
         if (newer.mTexture != null) {
-            recycleBitmap(MediaItem.getThumbPool(), mBitmap);
+            if (mBitmap != null) GalleryBitmapPool.getInstance().put(mBitmap);
             if (mTexture != null) mTexture.recycle();
             mBitmap = newer.mBitmap;
             mTexture = newer.mTexture;
@@ -143,8 +137,10 @@ public class TiledScreenNail implements ScreenNail {
             mTexture.recycle();
             mTexture = null;
         }
-        recycleBitmap(MediaItem.getThumbPool(), mBitmap);
-        mBitmap = null;
+        if (mBitmap != null) {
+            GalleryBitmapPool.getInstance().put(mBitmap);
+            mBitmap = null;
+        }
     }
 
     public static void disableDrawPlaceholder() {
diff --git a/src/com/android/photos/data/GalleryBitmapPool.java b/src/com/android/photos/data/GalleryBitmapPool.java
new file mode 100644 (file)
index 0000000..cddc160
--- /dev/null
@@ -0,0 +1,128 @@
+/*
+ * 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.photos.data;
+
+import android.graphics.Bitmap;
+import android.graphics.Point;
+import android.util.Pools.Pool;
+import android.util.Pools.SimplePool;
+
+import com.android.photos.data.SparseArrayBitmapPool.Node;
+
+public class GalleryBitmapPool {
+
+    private static final int CAPACITY_BYTES = 20971520;
+    private static final int POOL_INDEX_NONE = -1;
+    private static final int POOL_INDEX_SQUARE = 0;
+    private static final int POOL_INDEX_PHOTO = 1;
+    private static final int POOL_INDEX_MISC = 2;
+
+    private static final Point[] COMMON_PHOTO_ASPECT_RATIOS =
+        { new Point(4, 3), new Point(3, 2), new Point(16, 9) };
+
+    private int mCapacityBytes;
+    private SparseArrayBitmapPool [] mPools;
+    private Pool<Node> mSharedNodePool = new SimplePool<Node>(128);
+
+    private GalleryBitmapPool(int capacityBytes) {
+        mPools = new SparseArrayBitmapPool[3];
+        mPools[POOL_INDEX_SQUARE] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
+        mPools[POOL_INDEX_PHOTO] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
+        mPools[POOL_INDEX_MISC] = new SparseArrayBitmapPool(capacityBytes / 3, mSharedNodePool);
+        mCapacityBytes = capacityBytes;
+    }
+
+    private static GalleryBitmapPool sInstance;
+    public static GalleryBitmapPool getInstance() {
+        if (sInstance == null) {
+            sInstance = new GalleryBitmapPool(CAPACITY_BYTES);
+        }
+        return sInstance;
+    }
+
+    private SparseArrayBitmapPool getPoolForDimensions(int width, int height) {
+        int index = getPoolIndexForDimensions(width, height);
+        if (index == POOL_INDEX_NONE) {
+            return null;
+        } else {
+            return mPools[index];
+        }
+    }
+
+    private int getPoolIndexForDimensions(int width, int height) {
+        if (width <= 0 || height <= 0) {
+            return POOL_INDEX_NONE;
+        }
+        if (width == height) {
+            return POOL_INDEX_SQUARE;
+        }
+        int min, max;
+        if (width > height) {
+            min = height;
+            max = width;
+        } else {
+            min = width;
+            max = height;
+        }
+        for (Point ar : COMMON_PHOTO_ASPECT_RATIOS) {
+            if (min * ar.x == max * ar.y) {
+                return POOL_INDEX_PHOTO;
+            }
+        }
+        return POOL_INDEX_MISC;
+    }
+
+    public synchronized int getCapacity() {
+        return mCapacityBytes;
+    }
+
+    public synchronized int getSize() {
+        int total = 0;
+        for (SparseArrayBitmapPool p : mPools) {
+            total += p.getSize();
+        }
+        return total;
+    }
+
+    public Bitmap get(int width, int height) {
+        SparseArrayBitmapPool pool = getPoolForDimensions(width, height);
+        if (pool == null) {
+            return null;
+        } else {
+            return pool.get(width, height);
+        }
+    }
+
+    public boolean put(Bitmap b) {
+        if (b == null) {
+            return false;
+        }
+        SparseArrayBitmapPool pool = getPoolForDimensions(b.getWidth(), b.getHeight());
+        if (pool == null) {
+            b.recycle();
+            return false;
+        } else {
+            return pool.put(b);
+        }
+    }
+
+    public void clear() {
+        for (SparseArrayBitmapPool p : mPools) {
+            p.clear();
+        }
+    }
+}
diff --git a/src/com/android/photos/data/SparseArrayBitmapPool.java b/src/com/android/photos/data/SparseArrayBitmapPool.java
new file mode 100644 (file)
index 0000000..8512590
--- /dev/null
@@ -0,0 +1,146 @@
+/*
+ * 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.photos.data;
+
+import android.graphics.Bitmap;
+import android.util.SparseArray;
+
+import android.util.Pools.Pool;
+
+public class SparseArrayBitmapPool {
+
+    private static final int BITMAPS_TO_KEEP_AFTER_UNNEEDED_HINT = 4;
+    private int mCapacityBytes;
+    private SparseArray<Node> mStore = new SparseArray<Node>();
+    private int mSizeBytes = 0;
+
+    private Pool<Node> mNodePool;
+    private Node mPoolNodesHead = null;
+    private Node mPoolNodesTail = null;
+
+    protected static class Node {
+        Bitmap bitmap;
+        Node prevInBucket;
+        Node nextInBucket;
+        Node nextInPool;
+        Node prevInPool;
+    }
+
+    public SparseArrayBitmapPool(int capacityBytes, Pool<Node> nodePool) {
+        mCapacityBytes = capacityBytes;
+        mNodePool = nodePool;
+    }
+
+    public synchronized void setCapacity(int capacityBytes) {
+        mCapacityBytes = capacityBytes;
+        freeUpCapacity(0);
+    }
+
+    private void freeUpCapacity(int bytesNeeded) {
+        int targetSize = mCapacityBytes - bytesNeeded;
+        while (mPoolNodesTail != null && mSizeBytes > targetSize) {
+            unlinkAndRecycleNode(mPoolNodesTail, true);
+        }
+    }
+
+    private void unlinkAndRecycleNode(Node n, boolean recycleBitmap) {
+        // Remove the node from its spot in its bucket
+        if (n.prevInBucket != null) {
+            n.prevInBucket.nextInBucket = n.nextInBucket;
+        } else {
+            mStore.put(n.bitmap.getWidth(), n.nextInBucket);
+        }
+        if (n.nextInBucket != null) {
+            n.nextInBucket.prevInBucket = n.prevInBucket;
+        }
+
+        // Remove the node from its spot in the list of pool nodes
+        if (n.prevInPool != null) {
+            n.prevInPool.nextInPool = n.nextInPool;
+        } else {
+            mPoolNodesHead = n.nextInPool;
+        }
+        if (n.nextInPool != null) {
+            n.nextInPool.prevInPool = n.prevInPool;
+        } else {
+            mPoolNodesTail = n.prevInPool;
+        }
+
+        // Recycle the node
+        n.nextInBucket = null;
+        n.nextInPool = null;
+        n.prevInBucket = null;
+        n.prevInPool = null;
+        mSizeBytes -= n.bitmap.getByteCount();
+        if (recycleBitmap) n.bitmap.recycle();
+        n.bitmap = null;
+        mNodePool.release(n);
+    }
+
+    public synchronized int getCapacity() {
+        return mCapacityBytes;
+    }
+
+    public synchronized int getSize() {
+        return mSizeBytes;
+    }
+
+    public synchronized Bitmap get(int width, int height) {
+        Node cur = mStore.get(width);
+        while (cur != null) {
+            if (cur.bitmap.getHeight() == height) {
+                Bitmap b = cur.bitmap;
+                unlinkAndRecycleNode(cur, false);
+                return b;
+            }
+            cur = cur.nextInBucket;
+        }
+        return null;
+    }
+
+    public synchronized boolean put(Bitmap b) {
+        if (b == null) {
+            return false;
+        }
+        int bytes = b.getByteCount();
+        freeUpCapacity(bytes);
+        Node newNode = mNodePool.acquire();
+        if (newNode == null) {
+            newNode = new Node();
+        }
+        newNode.bitmap = b;
+        newNode.prevInBucket = null;
+        newNode.prevInPool = null;
+        newNode.nextInPool = mPoolNodesHead;
+        mPoolNodesHead = newNode;
+        int key = b.getWidth();
+        newNode.nextInBucket = mStore.get(key);
+        if (newNode.nextInBucket != null) {
+            newNode.nextInBucket.prevInBucket = newNode;
+        }
+        mStore.put(key, newNode);
+        if (newNode.nextInPool == null) {
+            mPoolNodesTail = newNode;
+        }
+        mSizeBytes += bytes;
+        return true;
+    }
+
+    public synchronized void clear() {
+        freeUpCapacity(mCapacityBytes);
+    }
+}