OSDN Git Service

Reuse bitmap for all micro thumb images to prevent GC.
authorOwen Lin <owenlin@google.com>
Wed, 7 Mar 2012 09:39:56 +0000 (17:39 +0800)
committerOwen Lin <owenlin@google.com>
Wed, 14 Mar 2012 07:48:54 +0000 (15:48 +0800)
Change-Id: I27d3002e5bb745a597f52962fe24744c8329441c

15 files changed:
gallerycommon/src/com/android/gallery3d/common/BitmapUtils.java
gallerycommon/src/com/android/gallery3d/common/Utils.java
src/com/android/gallery3d/app/AbstractGalleryActivity.java
src/com/android/gallery3d/data/DecodeUtils.java
src/com/android/gallery3d/data/ImageCacheRequest.java
src/com/android/gallery3d/data/ImageCacheService.java
src/com/android/gallery3d/data/LocalImage.java
src/com/android/gallery3d/data/LocalVideo.java
src/com/android/gallery3d/data/MediaItem.java
src/com/android/gallery3d/data/MtpImage.java
src/com/android/gallery3d/data/UriImage.java
src/com/android/gallery3d/ui/AbstractDisplayItem.java
src/com/android/gallery3d/ui/AlbumSetSlidingWindow.java
src/com/android/gallery3d/ui/AlbumSlidingWindow.java
src/com/android/gallery3d/ui/BitmapPool.java [new file with mode: 0644]

index 75ae25f..a671ed2 100644 (file)
@@ -32,7 +32,7 @@ import java.lang.reflect.Method;
 
 public class BitmapUtils {
     private static final String TAG = "BitmapUtils";
-    private static final int COMPRESS_JPEG_QUALITY = 90;
+    private static final int DEFAULT_JPEG_QUALITY = 90;
     public static final int UNCONSTRAINED = -1;
 
     private BitmapUtils(){}
@@ -94,7 +94,7 @@ public class BitmapUtils {
                 : initialSize / 8 * 8;
     }
 
-    // Fin the min x that 1 / x <= scale
+    // Find the min x that 1 / x >= scale
     public static int computeSampleSizeLarger(float scale) {
         int initialSize = (int) FloatMath.floor(1f / scale);
         if (initialSize <= 1) return 1;
@@ -104,7 +104,7 @@ public class BitmapUtils {
                 : initialSize / 8 * 8;
     }
 
-    // Find the max x that 1 / x >= scale.
+    // Find the max x that 1 / x <= scale.
     public static int computeSampleSize(float scale) {
         Utils.assertTrue(scale > 0);
         int initialSize = Math.max(1, (int) FloatMath.ceil(1 / scale));
@@ -146,27 +146,15 @@ public class BitmapUtils {
         return resizeBitmapByScale(bitmap, scale, recycle);
     }
 
-    // Resize the bitmap if each side is >= targetSize * 2
-    public static Bitmap resizeDownIfTooBig(
-            Bitmap bitmap, int targetSize, boolean recycle) {
-        int srcWidth = bitmap.getWidth();
-        int srcHeight = bitmap.getHeight();
-        float scale = Math.max(
-                (float) targetSize / srcWidth, (float) targetSize / srcHeight);
-        if (scale > 0.5f) return bitmap;
-        return resizeBitmapByScale(bitmap, scale, recycle);
-    }
-
-    public static Bitmap resizeDownAndCropCenter(Bitmap bitmap, int size,
-            boolean recycle) {
+    public static Bitmap resizeAndCropCenter(Bitmap bitmap, int size, boolean recycle) {
         int w = bitmap.getWidth();
         int h = bitmap.getHeight();
-        int minSide = Math.min(w, h);
-        if (w == h && minSide <= size) return bitmap;
-        size = Math.min(size, minSide);
+        if (w == size && h == size) return bitmap;
+
+        // scale the image so that the shorter side equals to the target;
+        // the longer side will be center-cropped.
+        float scale = (float) size / Math.min(w,  h);
 
-        float scale = Math.max((float) size / bitmap.getWidth(),
-                (float) size / bitmap.getHeight());
         Bitmap target = Bitmap.createBitmap(size, size, getConfig(bitmap));
         int width = Math.round(scale * bitmap.getWidth());
         int height = Math.round(scale * bitmap.getHeight());
@@ -247,11 +235,14 @@ public class BitmapUtils {
         return null;
     }
 
-    public static byte[] compressBitmap(Bitmap bitmap) {
-        ByteArrayOutputStream os = new ByteArrayOutputStream();
-        bitmap.compress(Bitmap.CompressFormat.JPEG,
-                COMPRESS_JPEG_QUALITY, os);
-        return os.toByteArray();
+    public static byte[] compressToBytes(Bitmap bitmap) {
+        return compressToBytes(bitmap, DEFAULT_JPEG_QUALITY);
+    }
+
+    public static byte[] compressToBytes(Bitmap bitmap, int quality) {
+        ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
+        bitmap.compress(CompressFormat.JPEG, quality, baos);
+        return baos.toByteArray();
     }
 
     public static boolean isSupportedByRegionDecoder(String mimeType) {
@@ -266,10 +257,4 @@ public class BitmapUtils {
         mimeType = mimeType.toLowerCase();
         return mimeType.equals("image/jpeg");
     }
-
-    public static byte[] compressToBytes(Bitmap bitmap, int quality) {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream(65536);
-        bitmap.compress(CompressFormat.JPEG, quality, baos);
-        return baos.toByteArray();
-    }
 }
index bbe2a5d..391b225 100644 (file)
 
 package com.android.gallery3d.common;
 
-import android.app.PendingIntent;
 import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.database.Cursor;
 import android.os.Build;
-import android.os.Environment;
-import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
-import android.os.StatFs;
 import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.Closeable;
 import java.io.InterruptedIOException;
-import java.util.Random;
 
 public class Utils {
     private static final String TAG = "Utils";
@@ -336,4 +331,9 @@ public class Utils {
         int length = Math.min(s.length(), MASK_STRING.length());
         return IS_DEBUG_BUILD ? s : MASK_STRING.substring(0, length);
     }
+
+    // This method should be ONLY used for debugging.
+    public static void debug(String message, Object ... args) {
+        Log.v(DEBUG_TAG, String.format(message, args));
+    }
 }
index f3e60ce..7d9d72b 100644 (file)
 
 package com.android.gallery3d.app;
 
-import com.android.gallery3d.R;
-import com.android.gallery3d.data.DataManager;
-import com.android.gallery3d.data.ImageCacheService;
-import com.android.gallery3d.ui.GLRoot;
-import com.android.gallery3d.ui.GLRootView;
-import com.android.gallery3d.ui.PositionRepository;
-import com.android.gallery3d.util.ThreadPool;
-
-import android.app.ActionBar;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
@@ -39,6 +30,13 @@ import android.os.Bundle;
 import android.view.Window;
 import android.view.WindowManager;
 
+import com.android.gallery3d.R;
+import com.android.gallery3d.data.DataManager;
+import com.android.gallery3d.ui.BitmapPool;
+import com.android.gallery3d.ui.GLRoot;
+import com.android.gallery3d.ui.GLRootView;
+import com.android.gallery3d.util.ThreadPool;
+
 public class AbstractGalleryActivity extends Activity implements GalleryActivity {
     @SuppressWarnings("unused")
     private static final String TAG = "AbstractGalleryActivity";
@@ -177,6 +175,7 @@ public class AbstractGalleryActivity extends Activity implements GalleryActivity
         } finally {
             mGLRootView.unlockRenderThread();
         }
+        BitmapPool.clear();
     }
 
     @Override
index 969fd98..319458a 100644 (file)
 
 package com.android.gallery3d.data;
 
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.ThreadPool.CancelListener;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
-import android.content.ContentResolver;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapFactory.Options;
 import android.graphics.BitmapRegionDecoder;
-import android.graphics.Rect;
-import android.net.Uri;
-import android.os.ParcelFileDescriptor;
+import android.util.FloatMath;
+
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.ThreadPool.CancelListener;
+import com.android.gallery3d.util.ThreadPool.JobContext;
 
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -40,27 +37,29 @@ public class DecodeUtils {
 
     private static class DecodeCanceller implements CancelListener {
         Options mOptions;
+
         public DecodeCanceller(Options options) {
             mOptions = options;
         }
+
+        @Override
         public void onCancel() {
             mOptions.requestCancelDecode();
         }
     }
 
-    public static Bitmap requestDecode(JobContext jc, FileDescriptor fd, Options options) {
+    public static Bitmap decode(JobContext jc, FileDescriptor fd, Options options) {
         if (options == null) options = new Options();
         jc.setCancelListener(new DecodeCanceller(options));
         return ensureGLCompatibleBitmap(
                 BitmapFactory.decodeFileDescriptor(fd, null, options));
     }
 
-    public static Bitmap requestDecode(JobContext jc, byte[] bytes,
-            Options options) {
-        return requestDecode(jc, bytes, 0, bytes.length, options);
+    public static Bitmap decode(JobContext jc, byte[] bytes, Options options) {
+        return decode(jc, bytes, 0, bytes.length, options);
     }
 
-    public static Bitmap requestDecode(JobContext jc, byte[] bytes, int offset,
+    public static Bitmap decode(JobContext jc, byte[] bytes, int offset,
             int length, Options options) {
         if (options == null) options = new Options();
         jc.setCancelListener(new DecodeCanceller(options));
@@ -68,13 +67,13 @@ public class DecodeUtils {
                 BitmapFactory.decodeByteArray(bytes, offset, length, options));
     }
 
-    public static Bitmap requestDecode(JobContext jc, final String filePath,
-            Options options, int targetSize) {
+    public static Bitmap decodeThumbnail(
+            JobContext jc, String filePath, Options options, int targetSize, int type) {
         FileInputStream fis = null;
         try {
             fis = new FileInputStream(filePath);
             FileDescriptor fd = fis.getFD();
-            return requestDecode(jc, fd, options, targetSize);
+            return decodeThumbnail(jc, fd, options, targetSize, type);
         } catch (Exception ex) {
             Log.w(TAG, ex);
             return null;
@@ -83,8 +82,8 @@ public class DecodeUtils {
         }
     }
 
-    public static Bitmap requestDecode(JobContext jc, FileDescriptor fd,
-            Options options, int targetSize) {
+    public static Bitmap decodeThumbnail(
+            JobContext jc, FileDescriptor fd, Options options, int targetSize, int type) {
         if (options == null) options = new Options();
         jc.setCancelListener(new DecodeCanceller(options));
 
@@ -92,14 +91,40 @@ public class DecodeUtils {
         BitmapFactory.decodeFileDescriptor(fd, null, options);
         if (jc.isCancelled()) return null;
 
-        options.inSampleSize = BitmapUtils.computeSampleSizeLarger(
-                options.outWidth, options.outHeight, targetSize);
+        int w = options.outWidth;
+        int h = options.outHeight;
+
+        if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
+            // We center-crop the original image as it's micro thumbnail. In this case,
+            // we want to make sure the shorter side >= "targetSize".
+            float scale = (float) targetSize / Math.min(w, h);
+            options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
+
+            // For an extremely wide image, e.g. 300x30000, we may got OOM when decoding
+            // it for TYPE_MICROTHUMBNAIL. So we add a max number of pixels limit here.
+            final int MAX_PIXEL_COUNT = 640000; // 400 x 1600
+            if ((w / options.inSampleSize) * (h / options.inSampleSize) > MAX_PIXEL_COUNT) {
+                options.inSampleSize = BitmapUtils.computeSampleSize(
+                        FloatMath.sqrt((float) MAX_PIXEL_COUNT / (w * h)));
+            }
+        } else {
+            // For screen nail, we only want to keep the longer side >= targetSize.
+            float scale = (float) targetSize / Math.max(w, h);
+            options.inSampleSize = BitmapUtils.computeSampleSizeLarger(scale);
+        }
+
         options.inJustDecodeBounds = false;
 
         Bitmap result = BitmapFactory.decodeFileDescriptor(fd, null, options);
-        // We need to resize down if the decoder does not support inSampleSize.
-        // (For example, GIF images.)
-        result = BitmapUtils.resizeDownIfTooBig(result, targetSize, true);
+        if (result == null) return null;
+
+        // We need to resize down if the decoder does not support inSampleSize
+        // (For example, GIF images)
+        float scale = (float) targetSize / (type == MediaItem.TYPE_MICROTHUMBNAIL
+                ? Math.min(result.getWidth(), result.getHeight())
+                : Math.max(result.getWidth(), result.getHeight()));
+
+        if (scale <= 0.5) result = BitmapUtils.resizeBitmapByScale(result, scale, true);
         return ensureGLCompatibleBitmap(result);
     }
 
@@ -110,7 +135,7 @@ public class DecodeUtils {
      * Note: The returned image may be resized down. However, both width and height must be
      * larger than the <code>targetSize</code>.
      */
-    public static Bitmap requestDecodeIfBigEnough(JobContext jc, byte[] data,
+    public static Bitmap decodeIfBigEnough(JobContext jc, byte[] data,
             Options options, int targetSize) {
         if (options == null) options = new Options();
         jc.setCancelListener(new DecodeCanceller(options));
@@ -138,7 +163,7 @@ public class DecodeUtils {
         return newBitmap;
     }
 
-    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
+    public static BitmapRegionDecoder createBitmapRegionDecoder(
             JobContext jc, byte[] bytes, int offset, int length,
             boolean shareable) {
         if (offset < 0 || length <= 0 || offset + length > bytes.length) {
@@ -156,7 +181,7 @@ public class DecodeUtils {
         }
     }
 
-    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
+    public static BitmapRegionDecoder createBitmapRegionDecoder(
             JobContext jc, String filePath, boolean shareable) {
         try {
             return BitmapRegionDecoder.newInstance(filePath, shareable);
@@ -166,7 +191,7 @@ public class DecodeUtils {
         }
     }
 
-    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
+    public static BitmapRegionDecoder createBitmapRegionDecoder(
             JobContext jc, FileDescriptor fd, boolean shareable) {
         try {
             return BitmapRegionDecoder.newInstance(fd, shareable);
@@ -176,7 +201,7 @@ public class DecodeUtils {
         }
     }
 
-    public static BitmapRegionDecoder requestCreateBitmapRegionDecoder(
+    public static BitmapRegionDecoder createBitmapRegionDecoder(
             JobContext jc, InputStream is, boolean shareable) {
         try {
             return BitmapRegionDecoder.newInstance(is, shareable);
index 104ff48..64dfc9f 100644 (file)
 
 package com.android.gallery3d.data;
 
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+
 import com.android.gallery3d.app.GalleryApp;
 import com.android.gallery3d.common.BitmapUtils;
 import com.android.gallery3d.data.ImageCacheService.ImageData;
+import com.android.gallery3d.ui.BitmapPool;
 import com.android.gallery3d.util.ThreadPool.Job;
 import com.android.gallery3d.util.ThreadPool.JobContext;
 
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
-
 abstract class ImageCacheRequest implements Job<Bitmap> {
     private static final String TAG = "ImageCacheRequest";
 
@@ -53,8 +54,14 @@ abstract class ImageCacheRequest implements Job<Bitmap> {
         if (data != null) {
             BitmapFactory.Options options = new BitmapFactory.Options();
             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
-            Bitmap bitmap = DecodeUtils.requestDecode(jc, data.mData,
-                    data.mOffset, data.mData.length - data.mOffset, options);
+            Bitmap bitmap;
+            if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
+                bitmap = BitmapPool.decode(jc, BitmapPool.TYPE_MICRO_THUMB,
+                        data.mData, data.mOffset, data.mData.length - data.mOffset, options);
+            } else {
+                bitmap = DecodeUtils.decode(jc,
+                        data.mData, data.mOffset, data.mData.length - data.mOffset, options);
+            }
             if (bitmap == null && !jc.isCancelled()) {
                 Log.w(TAG, "decode cached failed " + debugTag);
             }
@@ -69,15 +76,13 @@ abstract class ImageCacheRequest implements Job<Bitmap> {
             }
 
             if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
-                bitmap = BitmapUtils.resizeDownAndCropCenter(bitmap,
-                        mTargetSize, true);
+                bitmap = BitmapUtils.resizeAndCropCenter(bitmap, mTargetSize, true);
             } else {
-                bitmap = BitmapUtils.resizeDownBySideLength(bitmap,
-                        mTargetSize, true);
+                bitmap = BitmapUtils.resizeDownBySideLength(bitmap, mTargetSize, true);
             }
             if (jc.isCancelled()) return null;
 
-            byte[] array = BitmapUtils.compressBitmap(bitmap);
+            byte[] array = BitmapUtils.compressToBytes(bitmap);
             if (jc.isCancelled()) return null;
 
             cacheService.putImageData(mPath, mType, array);
index 3adce13..fdc2774 100644 (file)
 
 package com.android.gallery3d.data;
 
+import android.content.Context;
+
 import com.android.gallery3d.common.BlobCache;
 import com.android.gallery3d.common.Utils;
 import com.android.gallery3d.util.CacheManager;
 import com.android.gallery3d.util.GalleryUtils;
 
-import android.content.Context;
-
 import java.io.IOException;
 import java.nio.ByteBuffer;
 
@@ -33,7 +33,7 @@ public class ImageCacheService {
     private static final String IMAGE_CACHE_FILE = "imgcache";
     private static final int IMAGE_CACHE_MAX_ENTRIES = 5000;
     private static final int IMAGE_CACHE_MAX_BYTES = 200 * 1024 * 1024;
-    private static final int IMAGE_CACHE_VERSION = 3;
+    private static final int IMAGE_CACHE_VERSION = 4;
 
     private BlobCache mCache;
 
index fa3ece3..4f2797e 100644 (file)
 
 package com.android.gallery3d.data;
 
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.util.GalleryUtils;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-import com.android.gallery3d.util.UpdateHelper;
-
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
@@ -35,6 +28,13 @@ import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Images.ImageColumns;
 import android.util.Log;
 
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.util.GalleryUtils;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+import com.android.gallery3d.util.UpdateHelper;
+
 import java.io.File;
 import java.io.IOException;
 
@@ -159,14 +159,15 @@ public class LocalImage extends LocalMediaItem {
 
         LocalImageRequest(GalleryApp application, Path path, int type,
                 String localFilePath) {
-            super(application, path, type, getTargetSize(type));
+            super(application, path, type, MediaItem.getTargetSize(type));
             mLocalFilePath = localFilePath;
         }
 
         @Override
-        public Bitmap onDecodeOriginal(JobContext jc, int type) {
+        public Bitmap onDecodeOriginal(JobContext jc, final int type) {
             BitmapFactory.Options options = new BitmapFactory.Options();
             options.inPreferredConfig = Bitmap.Config.ARGB_8888;
+            int targetSize = MediaItem.getTargetSize(type);
 
             // try to decode from JPEG EXIF
             if (type == MediaItem.TYPE_MICROTHUMBNAIL) {
@@ -181,25 +182,13 @@ public class LocalImage extends LocalMediaItem {
                     Log.w(TAG, "fail to get exif thumb", t);
                 }
                 if (thumbData != null) {
-                    Bitmap bitmap = DecodeUtils.requestDecodeIfBigEnough(
-                            jc, thumbData, options, getTargetSize(type));
+                    Bitmap bitmap = DecodeUtils.decodeIfBigEnough(
+                            jc, thumbData, options, targetSize);
                     if (bitmap != null) return bitmap;
                 }
             }
-            return DecodeUtils.requestDecode(
-                    jc, mLocalFilePath, options, getTargetSize(type));
-        }
-    }
 
-    static int getTargetSize(int type) {
-        switch (type) {
-            case TYPE_THUMBNAIL:
-                return THUMBNAIL_TARGET_SIZE;
-            case TYPE_MICROTHUMBNAIL:
-                return MICROTHUMBNAIL_TARGET_SIZE;
-            default:
-                throw new RuntimeException(
-                    "should only request thumb/microthumb from cache");
+            return DecodeUtils.decodeThumbnail(jc, mLocalFilePath, options, targetSize, type);
         }
     }
 
@@ -217,8 +206,7 @@ public class LocalImage extends LocalMediaItem {
         }
 
         public BitmapRegionDecoder run(JobContext jc) {
-            return DecodeUtils.requestCreateBitmapRegionDecoder(
-                    jc, mLocalFilePath, false);
+            return DecodeUtils.createBitmapRegionDecoder(jc, mLocalFilePath, false);
         }
     }
 
index d68072b..c45949e 100644 (file)
@@ -147,7 +147,7 @@ public class LocalVideo extends LocalMediaItem {
 
         LocalVideoRequest(GalleryApp application, Path path, int type,
                 String localFilePath) {
-            super(application, path, type, LocalImage.getTargetSize(type));
+            super(application, path, type, MediaItem.getTargetSize(type));
             mLocalFilePath = localFilePath;
         }
 
index 1361232..b682c2d 100644 (file)
 
 package com.android.gallery3d.data;
 
-import com.android.gallery3d.util.ThreadPool.Job;
-
 import android.graphics.Bitmap;
 import android.graphics.BitmapRegionDecoder;
 
+import com.android.gallery3d.util.ThreadPool.Job;
+
 // MediaItem represents an image or a video item.
 public abstract class MediaItem extends MediaObject {
     // NOTE: These type numbers are stored in the image cache, so it should not
@@ -89,4 +89,16 @@ public abstract class MediaItem extends MediaObject {
     // Returns 0, 0 if the information is not available.
     public abstract int getWidth();
     public abstract int getHeight();
+
+    public static int getTargetSize(int type) {
+        switch (type) {
+            case TYPE_THUMBNAIL:
+                return THUMBNAIL_TARGET_SIZE;
+            case TYPE_MICROTHUMBNAIL:
+                return MICROTHUMBNAIL_TARGET_SIZE;
+            default:
+                throw new RuntimeException(
+                    "should only request thumb/microthumb from cache");
+        }
+    }
 }
index 211b2f2..bdbaecd 100644 (file)
@@ -84,7 +84,7 @@ public class MtpImage extends MediaItem {
                     Log.w(TAG, "decoding thumbnail failed");
                     return null;
                 }
-                return DecodeUtils.requestDecode(jc, thumbnail, null);
+                return DecodeUtils.decode(jc, thumbnail, null);
             }
         };
     }
@@ -95,7 +95,7 @@ public class MtpImage extends MediaItem {
             public BitmapRegionDecoder run(JobContext jc) {
                 byte[] bytes = mMtpContext.getMtpClient().getObject(
                         UsbDevice.getDeviceName(mDeviceId), mObjectId, mObjectSize);
-                return DecodeUtils.requestCreateBitmapRegionDecoder(
+                return DecodeUtils.createBitmapRegionDecoder(
                         jc, bytes, 0, bytes.length, false);
             }
         };
index 8f91cc0..05850bb 100644 (file)
 
 package com.android.gallery3d.data;
 
-import com.android.gallery3d.app.GalleryApp;
-import com.android.gallery3d.common.BitmapUtils;
-import com.android.gallery3d.common.Utils;
-import com.android.gallery3d.util.ThreadPool.CancelListener;
-import com.android.gallery3d.util.ThreadPool.Job;
-import com.android.gallery3d.util.ThreadPool.JobContext;
-
 import android.content.ContentResolver;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.Config;
@@ -32,6 +25,13 @@ import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.webkit.MimeTypeMap;
 
+import com.android.gallery3d.app.GalleryApp;
+import com.android.gallery3d.common.BitmapUtils;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.util.ThreadPool.CancelListener;
+import com.android.gallery3d.util.ThreadPool.Job;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.InputStream;
@@ -180,7 +180,7 @@ public class UriImage extends MediaItem {
     private class RegionDecoderJob implements Job<BitmapRegionDecoder> {
         public BitmapRegionDecoder run(JobContext jc) {
             if (!prepareInputFile(jc)) return null;
-            BitmapRegionDecoder decoder = DecodeUtils.requestCreateBitmapRegionDecoder(
+            BitmapRegionDecoder decoder = DecodeUtils.createBitmapRegionDecoder(
                     jc, mFileDescriptor.getFileDescriptor(), false);
             mWidth = decoder.getWidth();
             mHeight = decoder.getHeight();
@@ -195,25 +195,24 @@ public class UriImage extends MediaItem {
             mType = type;
         }
 
+        @Override
         public Bitmap run(JobContext jc) {
             if (!prepareInputFile(jc)) return null;
-            int targetSize = LocalImage.getTargetSize(mType);
+            int targetSize = MediaItem.getTargetSize(mType);
             Options options = new Options();
             options.inPreferredConfig = Config.ARGB_8888;
-            Bitmap bitmap = DecodeUtils.requestDecode(jc,
-                    mFileDescriptor.getFileDescriptor(), options, targetSize);
+            Bitmap bitmap = DecodeUtils.decodeThumbnail(jc,
+                    mFileDescriptor.getFileDescriptor(), options, targetSize, mType);
+
             if (jc.isCancelled() || bitmap == null) {
                 return null;
             }
 
             if (mType == MediaItem.TYPE_MICROTHUMBNAIL) {
-                bitmap = BitmapUtils.resizeDownAndCropCenter(bitmap,
-                        targetSize, true);
+                bitmap = BitmapUtils.resizeAndCropCenter(bitmap, targetSize, true);
             } else {
-                bitmap = BitmapUtils.resizeDownBySideLength(bitmap,
-                        targetSize, true);
+                bitmap = BitmapUtils.resizeDownBySideLength(bitmap, targetSize, true);
             }
-
             return bitmap;
         }
     }
index 1f8165f..2547ebf 100644 (file)
 
 package com.android.gallery3d.ui;
 
-import com.android.gallery3d.data.MediaItem;
-
 import android.graphics.Bitmap;
 
+import com.android.gallery3d.data.MediaItem;
+
 public abstract class AbstractDisplayItem extends DisplayItem {
 
     private static final String TAG = "AbstractDisplayItem";
@@ -44,6 +44,7 @@ public abstract class AbstractDisplayItem extends DisplayItem {
 
     protected void updateImage(Bitmap bitmap, boolean isCancelled) {
         if (mRecycling) {
+            recycleBitmap(bitmap);
             return;
         }
 
@@ -96,7 +97,10 @@ public abstract class AbstractDisplayItem extends DisplayItem {
 
     public void recycle() {
         if (!inState(STATE_UPDATING | STATE_CANCELING)) {
-            if (mBitmap != null) mBitmap = null;
+            if (mBitmap != null) {
+                recycleBitmap(mBitmap);
+                mBitmap = null;
+            }
         } else {
             mRecycling = true;
             cancelImageRequest();
@@ -110,4 +114,5 @@ public abstract class AbstractDisplayItem extends DisplayItem {
     abstract protected void startLoadBitmap();
     abstract protected void cancelLoadBitmap();
     abstract protected void onBitmapAvailable(Bitmap bitmap);
+    abstract protected void recycleBitmap(Bitmap bitmap);
 }
index b02f19e..5bead62 100644 (file)
@@ -352,6 +352,11 @@ public class AlbumSetSlidingWindow implements AlbumSetView.ModelListener {
         }
 
         @Override
+        protected void recycleBitmap(Bitmap bitmap) {
+            BitmapPool.recycle(BitmapPool.TYPE_MICRO_THUMB, bitmap);
+        }
+
+        @Override
         protected void onBitmapAvailable(Bitmap bitmap) {
             if (isActiveSlot(mSlotIndex)) {
                 --mActiveRequestCount;
index 8d61f93..142a309 100644 (file)
@@ -293,6 +293,15 @@ public class AlbumSlidingWindow implements AlbumView.ModelListener {
         }
 
         @Override
+        protected void recycleBitmap(Bitmap bitmap) {
+            // if mCacheThumbSize > 0, we will keep images in cache so that
+            // we cannot recycle the bitmap
+            if (mCacheThumbSize == 0) {
+                BitmapPool.recycle(BitmapPool.TYPE_MICRO_THUMB, bitmap);
+            }
+        }
+
+        @Override
         protected void onBitmapAvailable(Bitmap bitmap) {
             boolean isActiveSlot = isActiveSlot(mSlotIndex);
             if (isActiveSlot) {
diff --git a/src/com/android/gallery3d/ui/BitmapPool.java b/src/com/android/gallery3d/ui/BitmapPool.java
new file mode 100644 (file)
index 0000000..e910aec
--- /dev/null
@@ -0,0 +1,113 @@
+// Copyright 2012 Google Inc. All Rights Reserved.
+
+package com.android.gallery3d.ui;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapFactory.Options;
+
+import com.android.gallery3d.data.DecodeUtils;
+import com.android.gallery3d.data.MediaItem;
+import com.android.gallery3d.util.ThreadPool.JobContext;
+
+import java.io.FileDescriptor;
+import java.util.ArrayList;
+
+public class BitmapPool {
+    private static final String TAG = "BitmapPool";
+
+    public static final int TYPE_MICRO_THUMB = 0;
+    private static final int TYPE_COUNT = 1;
+    private static final int POOL_SIZE = 16;
+    private static final int EXPECTED_WIDTH[] = {MediaItem.MICROTHUMBNAIL_TARGET_SIZE};
+    private static final int EXPECTED_HEIGHT[] = {MediaItem.MICROTHUMBNAIL_TARGET_SIZE};
+
+    @SuppressWarnings("unchecked")
+    private static final ArrayList<Bitmap> sPools[] = new ArrayList[TYPE_COUNT];
+    static {
+        for (int i = 0; i < TYPE_COUNT; ++i) {
+            sPools[i] = new ArrayList<Bitmap>();
+        }
+    }
+
+    private BitmapPool() {
+    }
+
+    public static Bitmap getBitmap(int type) {
+        ArrayList<Bitmap> list = sPools[type];
+        synchronized (list) {
+            int size = list.size();
+            return size > 0 ? list.remove(size - 1) : null;
+        }
+    }
+
+    public static void recycle(int type, Bitmap bitmap) {
+        if (bitmap == null) return;
+        if ((bitmap.getWidth() != EXPECTED_WIDTH[type])
+                || (bitmap.getHeight() != EXPECTED_HEIGHT[type])) {
+            bitmap.recycle();
+            return;
+        }
+        ArrayList<Bitmap> list = sPools[type];
+        synchronized (list) {
+            if (list.size() < POOL_SIZE) list.add(bitmap);
+        }
+    }
+
+    public static void clear() {
+        for (int i = 0; i < TYPE_COUNT; ++i) {
+            ArrayList<Bitmap> list = sPools[i];
+            synchronized (list) {
+                list.clear();
+            }
+        }
+    }
+
+    public static Bitmap decode(JobContext jc, int type,
+            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) ? getBitmap(type) : null;
+        try {
+            Bitmap bitmap = DecodeUtils.decode(jc, data, offset, length, options);
+            if (options.inBitmap != null && options.inBitmap != bitmap) {
+                recycle(type, bitmap);
+                options.inBitmap = null;
+            }
+            return bitmap;
+        } catch (IllegalArgumentException e) {
+            if (options.inBitmap == null) throw e;
+
+            Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
+            recycle(type, options.inBitmap);
+            options.inBitmap = null;
+            return DecodeUtils.decode(jc, data, offset, length, options);
+        }
+    }
+
+    // This is the same as the method above except the source data comes
+    // from a file descriptor instead of a byte array.
+    public static Bitmap decode(int type,
+            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) ? getBitmap(type) : null;
+        try {
+            Bitmap bitmap = DecodeUtils.decode(jc, fileDescriptor, options);
+            if (options.inBitmap != null&& options.inBitmap != bitmap) {
+                recycle(type, bitmap);
+                options.inBitmap = null;
+            }
+            return bitmap;
+        } catch (IllegalArgumentException e) {
+            if (options.inBitmap == null) throw e;
+
+            Log.w(TAG, "decode fail with a given bitmap, try decode to a new bitmap");
+            recycle(type, options.inBitmap);
+            options.inBitmap = null;
+            return DecodeUtils.decode(jc, fileDescriptor, options);
+        }
+    }
+}