From 11cfeeddedd61d3c4aa40945fef0bc2d87c7559d Mon Sep 17 00:00:00 2001 From: Bobby Georgescu Date: Tue, 19 Feb 2013 15:49:14 -0800 Subject: Replace various BitmapPools with a smarter unified pool 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 --- .../gallery3d/app/AbstractGalleryActivity.java | 10 +- .../android/gallery3d/app/PhotoDataAdapter.java | 5 +- src/com/android/gallery3d/data/BitmapPool.java | 96 -------------- src/com/android/gallery3d/data/DecodeUtils.java | 43 +++--- .../android/gallery3d/data/ImageCacheRequest.java | 10 +- src/com/android/gallery3d/data/MediaItem.java | 24 ---- .../android/gallery3d/ingest/IngestService.java | 1 - .../gallery3d/ingest/data/MtpBitmapFetch.java | 15 +-- src/com/android/gallery3d/ui/AlbumLabelMaker.java | 17 +-- .../gallery3d/ui/AlbumSetSlidingWindow.java | 13 -- .../android/gallery3d/ui/AlbumSlidingWindow.java | 9 +- src/com/android/gallery3d/ui/BitmapLoader.java | 6 +- .../android/gallery3d/ui/BitmapTileProvider.java | 7 +- src/com/android/gallery3d/ui/TileImageView.java | 21 +-- .../android/gallery3d/ui/TileImageViewAdapter.java | 8 +- src/com/android/gallery3d/ui/TiledScreenNail.java | 16 +-- src/com/android/photos/data/GalleryBitmapPool.java | 128 ++++++++++++++++++ .../android/photos/data/SparseArrayBitmapPool.java | 146 +++++++++++++++++++++ 18 files changed, 332 insertions(+), 243 deletions(-) delete mode 100644 src/com/android/gallery3d/data/BitmapPool.java create mode 100644 src/com/android/photos/data/GalleryBitmapPool.java create mode 100644 src/com/android/photos/data/SparseArrayBitmapPool.java (limited to 'src/com') diff --git a/src/com/android/gallery3d/app/AbstractGalleryActivity.java b/src/com/android/gallery3d/app/AbstractGalleryActivity.java index c9cce811a..d96094245 100644 --- a/src/com/android/gallery3d/app/AbstractGalleryActivity.java +++ b/src/com/android/gallery3d/app/AbstractGalleryActivity.java @@ -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(); diff --git a/src/com/android/gallery3d/app/PhotoDataAdapter.java b/src/com/android/gallery3d/app/PhotoDataAdapter.java index faff14674..d409315d1 100644 --- a/src/com/android/gallery3d/app/PhotoDataAdapter.java +++ b/src/com/android/gallery3d/app/PhotoDataAdapter.java @@ -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 index 5bc6d672b..000000000 --- a/src/com/android/gallery3d/data/BitmapPool.java +++ /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 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(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(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; - } -} diff --git a/src/com/android/gallery3d/data/DecodeUtils.java b/src/com/android/gallery3d/data/DecodeUtils.java index 4d3c99653..fa709157d 100644 --- a/src/com/android/gallery3d/data/DecodeUtils.java +++ b/src/com/android/gallery3d/data/DecodeUtils.java @@ -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); } } diff --git a/src/com/android/gallery3d/data/ImageCacheRequest.java b/src/com/android/gallery3d/data/ImageCacheRequest.java index 3f937e365..475614962 100644 --- a/src/com/android/gallery3d/data/ImageCacheRequest.java +++ b/src/com/android/gallery3d/data/ImageCacheRequest.java @@ -60,13 +60,11 @@ abstract class ImageCacheRequest implements Job { 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()); diff --git a/src/com/android/gallery3d/data/MediaItem.java b/src/com/android/gallery3d/data/MediaItem.java index 19084d41e..59ea86551 100644 --- a/src/com/android/gallery3d/data/MediaItem.java +++ b/src/com/android/gallery3d/data/MediaItem.java @@ -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(); } } } diff --git a/src/com/android/gallery3d/ingest/IngestService.java b/src/com/android/gallery3d/ingest/IngestService.java index 5e0ca0b68..fa421e771 100644 --- a/src/com/android/gallery3d/ingest/IngestService.java +++ b/src/com/android/gallery3d/ingest/IngestService.java @@ -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); } } diff --git a/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java index 46a2051be..5e1fb34fd 100644 --- a/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java +++ b/src/com/android/gallery3d/ingest/data/MtpBitmapFetch.java @@ -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); diff --git a/src/com/android/gallery3d/ui/AlbumLabelMaker.java b/src/com/android/gallery3d/ui/AlbumLabelMaker.java index 6eeeec045..da1cac0bd 100644 --- a/src/com/android/gallery3d/ui/AlbumLabelMaker.java +++ b/src/com/android/gallery3d/ui/AlbumLabelMaker.java @@ -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 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); } } diff --git a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java index d5a15b4ac..8149df4b3 100644 --- a/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java +++ b/src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java @@ -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() { @@ -428,12 +426,6 @@ public class AlbumSetSlidingWindow implements AlbumSetDataLoader.DataListener { mMediaItem = item; } - @Override - protected void recycleBitmap(Bitmap bitmap) { - BitmapPool pool = MediaItem.getMicroThumbPool(); - if (pool != null) pool.recycle(bitmap); - } - @Override protected Future submitBitmapTask(FutureListener l) { return mThreadPool.submit(mMediaItem.requestImage( @@ -504,11 +496,6 @@ public class AlbumSetSlidingWindow implements AlbumSetDataLoader.DataListener { mTitle, String.valueOf(mTotalCount), mSourceType), l); } - @Override - protected void recycleBitmap(Bitmap bitmap) { - mLabelMaker.recycleLabel(bitmap); - } - @Override protected void onLoadComplete(Bitmap bitmap) { mHandler.obtainMessage(MSG_UPDATE_ALBUM_ENTRY, this).sendToTarget(); diff --git a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java index 8cd2cf500..fec7d1e92 100644 --- a/src/com/android/gallery3d/ui/AlbumSlidingWindow.java +++ b/src/com/android/gallery3d/ui/AlbumSlidingWindow.java @@ -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; @@ -295,12 +294,6 @@ public class AlbumSlidingWindow implements AlbumDataLoader.DataListener { mItem = item; } - @Override - protected void recycleBitmap(Bitmap bitmap) { - BitmapPool pool = MediaItem.getMicroThumbPool(); - if (pool != null) pool.recycle(bitmap); - } - @Override protected Future submitBitmapTask(FutureListener l) { return mThreadPool.submit( diff --git a/src/com/android/gallery3d/ui/BitmapLoader.java b/src/com/android/gallery3d/ui/BitmapLoader.java index 4f07cc047..a708a90f3 100644 --- a/src/com/android/gallery3d/ui/BitmapLoader.java +++ b/src/com/android/gallery3d/ui/BitmapLoader.java @@ -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 { 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 { 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 { } abstract protected Future submitBitmapTask(FutureListener l); - abstract protected void recycleBitmap(Bitmap bitmap); abstract protected void onLoadComplete(Bitmap bitmap); } diff --git a/src/com/android/gallery3d/ui/BitmapTileProvider.java b/src/com/android/gallery3d/ui/BitmapTileProvider.java index c3466e7fe..e1a8b7644 100644 --- a/src/com/android/gallery3d/ui/BitmapTileProvider.java +++ b/src/com/android/gallery3d/ui/BitmapTileProvider.java @@ -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 { diff --git a/src/com/android/gallery3d/ui/TileImageView.java b/src/com/android/gallery3d/ui/TileImageView.java index f1c31e49d..3185c7598 100644 --- a/src/com/android/gallery3d/ui/TileImageView.java +++ b/src/com/android/gallery3d/ui/TileImageView.java @@ -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); } diff --git a/src/com/android/gallery3d/ui/TileImageViewAdapter.java b/src/com/android/gallery3d/ui/TileImageViewAdapter.java index 0d20b0757..0c1f66d0c 100644 --- a/src/com/android/gallery3d/ui/TileImageViewAdapter.java +++ b/src/com/android/gallery3d/ui/TileImageViewAdapter.java @@ -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; } } diff --git a/src/com/android/gallery3d/ui/TiledScreenNail.java b/src/com/android/gallery3d/ui/TiledScreenNail.java index ab24f5b6c..860e230bb 100644 --- a/src/com/android/gallery3d/ui/TiledScreenNail.java +++ b/src/com/android/gallery3d/ui/TiledScreenNail.java @@ -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 index 000000000..cddc160fd --- /dev/null +++ b/src/com/android/photos/data/GalleryBitmapPool.java @@ -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 mSharedNodePool = new SimplePool(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 index 000000000..851259056 --- /dev/null +++ b/src/com/android/photos/data/SparseArrayBitmapPool.java @@ -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 mStore = new SparseArray(); + private int mSizeBytes = 0; + + private Pool 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 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); + } +} -- cgit v1.2.3