OSDN Git Service

Reduce memory footprint of session bitmap cache.
authorAlan Newberger <alann@google.com>
Thu, 19 Feb 2015 23:51:01 +0000 (15:51 -0800)
committerAlan Newberger <alann@google.com>
Sat, 21 Feb 2015 02:44:12 +0000 (18:44 -0800)
Convert storage bitmap cache to an LRU cache to have a fixed
upper bound on usage. Evict bitmaps after a session is complete.

Bug: 19124117
Change-Id: I4a5dd9f15c3b986601c4a92477edf9346f190de1

src/com/android/camera/CameraActivity.java
src/com/android/camera/Storage.java
src/com/android/camera/data/PhotoItem.java
src/com/android/camera/data/SessionItem.java
src/com/android/camera/session/CaptureSession.java
src/com/android/camera/session/CaptureSessionImpl.java
src/com/android/camera/session/CaptureSessionManager.java
src/com/android/camera/session/CaptureSessionManagerImpl.java
src/com/android/camera/session/PlaceholderManager.java
src/com/android/camera/session/SessionNotifier.java

index a8aab82..78213d3 100644 (file)
@@ -42,7 +42,6 @@ import android.os.AsyncTask;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
-import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Message;
 import android.provider.MediaStore;
@@ -907,7 +906,7 @@ public class CameraActivity extends QuickActivity
                     // Make the PhotoItem aware of the session placeholder, to
                     // allow it to make a smooth transition to its content.
                     newData.setSessionPlaceholderBitmap(
-                            Storage.getPlacerHolderForSession(sessionUri));
+                            Storage.getPlaceholderForSession(sessionUri));
 
                     final int pos = mDataAdapter.findByContentUri(sessionUri);
                     if (pos == -1) {
index b313436..7cf3885 100644 (file)
@@ -27,12 +27,14 @@ import android.os.StatFs;
 import android.provider.MediaStore.Images;
 import android.provider.MediaStore.Images.ImageColumns;
 import android.provider.MediaStore.MediaColumns;
+import android.util.LruCache;
 
 import com.android.camera.data.FilmstripItemData;
 import com.android.camera.debug.Log;
 import com.android.camera.exif.ExifInterface;
 import com.android.camera.util.ApiHelper;
 import com.android.camera.util.Size;
+import com.google.common.base.Optional;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -61,7 +63,14 @@ public class Storage {
     private static final String GOOGLE_COM = "google.com";
     private static HashMap<Uri, Uri> sSessionsToContentUris = new HashMap<>();
     private static HashMap<Uri, Uri> sContentUrisToSessions = new HashMap<>();
-    private static HashMap<Uri, Bitmap> sSessionsToPlaceholderBitmap = new HashMap<>();
+    private static LruCache<Uri, Bitmap> sSessionsToPlaceholderBitmap =
+            // 20MB cache as an upper bound for session bitmap storage
+            new LruCache<Uri, Bitmap>(20 * 1024 * 1024) {
+                @Override
+                protected int sizeOf(Uri key, Bitmap value) {
+                    return value.getByteCount();
+                }
+            };
     private static HashMap<Uri, Point> sSessionsToSizes = new HashMap<>();
     private static HashMap<Uri, Integer> sSessionsToPlaceholderVersions = new HashMap<>();
 
@@ -201,6 +210,15 @@ public class Storage {
     }
 
     /**
+     * Remove a placeholder from in memory storage.
+     */
+    public static void removePlaceholder(Uri uri) {
+        sSessionsToSizes.remove(uri);
+        sSessionsToPlaceholderBitmap.remove(uri);
+        sSessionsToPlaceholderVersions.remove(uri);
+    }
+
+    /**
      * Add or replace placeholder for a new image that does not exist yet.
      *
      * @param uri the uri of the placeholder to replace, or null if this is a
@@ -209,6 +227,7 @@ public class Storage {
      * @return A URI used to reference this placeholder
      */
     public static void replacePlaceholder(Uri uri, Bitmap placeholder) {
+        Log.v(TAG, "session bitmap cache size: " + sSessionsToPlaceholderBitmap.size());
         Point size = new Point(placeholder.getWidth(), placeholder.getHeight());
         sSessionsToSizes.put(uri, size);
         sSessionsToPlaceholderBitmap.put(uri, placeholder);
@@ -226,7 +245,7 @@ public class Storage {
     public static Uri addEmptyPlaceholder(@Nonnull Size size) {
         Uri uri = generateUniquePlaceholderUri();
         sSessionsToSizes.put(uri, new Point(size.getWidth(), size.getHeight()));
-        sSessionsToPlaceholderBitmap.put(uri, null /* Bitmap */);
+        sSessionsToPlaceholderBitmap.remove(uri);
         Integer currentVersion = sSessionsToPlaceholderVersions.get(uri);
         sSessionsToPlaceholderVersions.put(uri, currentVersion == null ? 0 : currentVersion + 1);
         return uri;
@@ -394,10 +413,10 @@ public class Storage {
      * Returns the jpeg bytes for a placeholder session
      *
      * @param uri the session uri to look up
-     * @return The jpeg bytes or null
+     * @return The bitmap or null
      */
-    public static Bitmap getPlacerHolderForSession(Uri uri) {
-        return sSessionsToPlaceholderBitmap.get(uri);
+    public static Optional<Bitmap> getPlaceholderForSession(Uri uri) {
+        return Optional.fromNullable(sSessionsToPlaceholderBitmap.get(uri));
     }
 
     /**
@@ -407,7 +426,7 @@ public class Storage {
      * @param uri the session uri to look up.
      * @return the current version int.
      */
-    public static int getPlacerHolderVersionForSession(Uri uri) {
+    public static int getPlaceholderVersionForSession(Uri uri) {
         return sSessionsToPlaceholderVersions.get(uri);
     }
 
index ea47786..d09270a 100644 (file)
@@ -63,7 +63,7 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
 
     private final PhotoItemFactory mPhotoItemFactory;
 
-    private Bitmap mSessionPlaceholderBitmap;
+    private Optional<Bitmap> mSessionPlaceholderBitmap = Optional.absent();
 
     public PhotoItem(Context context, GlideFilmstripManager manager, FilmstripItemData data,
           PhotoItemFactory photoItemFactory) {
@@ -78,7 +78,7 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
      *
      * @param sessionPlaceholderBitmap a Bitmap to set as a placeholder
      */
-    public void setSessionPlaceholderBitmap(Bitmap sessionPlaceholderBitmap) {
+    public void setSessionPlaceholderBitmap(Optional<Bitmap> sessionPlaceholderBitmap) {
         mSessionPlaceholderBitmap = sessionPlaceholderBitmap;
     }
 
@@ -153,7 +153,7 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
     @Override
     public void recycle(@Nonnull View view) {
         Glide.clear(view);
-        mSessionPlaceholderBitmap = null;
+        mSessionPlaceholderBitmap = Optional.absent();
     }
 
     @Override
@@ -199,9 +199,9 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
 
         // If we have a non-null placeholder, use that and do NOT ever render a
         // tiny thumbnail to prevent un-intended "flash of low resolution image"
-        if (mSessionPlaceholderBitmap != null) {
+        if (mSessionPlaceholderBitmap.isPresent()) {
             return request.placeholder(new BitmapDrawable(mContext.getResources(),
-                  mSessionPlaceholderBitmap));
+                  mSessionPlaceholderBitmap.get()));
         }
 
         // If we do not have a placeholder bitmap, render a thumbnail with
@@ -223,7 +223,7 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
         final Bitmap bitmap;
 
         if (getAttributes().isRendering()) {
-            bitmap = Storage.getPlacerHolderForSession(data.getUri());
+            return Storage.getPlaceholderForSession(data.getUri());
         } else {
 
             FileInputStream stream;
@@ -259,8 +259,8 @@ public class PhotoItem extends FilmstripItemBase<FilmstripItemData> {
                         data.getDimensions().getHeight(),
                         (int) (dim.x * 0.7f), (int) (dim.y * 0.7),
                         data.getOrientation(), MAX_PEEK_BITMAP_PIXELS);
-        }
 
-        return Optional.fromNullable(bitmap);
+            return Optional.fromNullable(bitmap);
+        }
     }
 }
index deb7427..ef9ce4a 100644 (file)
@@ -83,9 +83,9 @@ public class SessionItem implements FilmstripItem {
             imageView.setTag(R.id.mediadata_tag_viewtype, getItemViewType().ordinal());
         }
 
-        Bitmap placeholder = Storage.getPlacerHolderForSession(mData.getUri());
-        if (placeholder != null) {
-            imageView.setImageBitmap(placeholder);
+        Optional<Bitmap> placeholder = Storage.getPlaceholderForSession(mData.getUri());
+        if (placeholder.isPresent()) {
+            imageView.setImageBitmap(placeholder.get());
         } else {
             imageView.setImageResource(GlideFilmstripManager.DEFAULT_PLACEHOLDER_RESOURCE);
         }
@@ -149,7 +149,7 @@ public class SessionItem implements FilmstripItem {
 
     @Override
     public Optional<Bitmap> generateThumbnail(int boundingWidthPx, int boundingHeightPx) {
-        return Optional.fromNullable(Storage.getPlacerHolderForSession(mUri));
+        return Storage.getPlaceholderForSession(mUri);
     }
 
     @Override
index 1e83699..4ecceea 100644 (file)
@@ -158,16 +158,25 @@ public interface CaptureSession {
     public StackSaver getStackSaver();
 
     /**
-     * Finishes the session.
+     * Finishes the session. Resources may be held during notification of
+     * finished state, {@link #finalize()} must be called to fully complete
+     * the session.
      */
     public void finish();
 
     /**
-     * Finish the session and indicate it failed.
+     * Finish the session and indicate it failed. Resources may be held during
+     * notification of finished state, {@link #finalize()} must be called to
+     * fully complete the session.
      */
     public void finishWithFailure(CharSequence reason);
 
     /**
+     * All processing complete, finalize the session and remove any resources.
+     */
+    public void finalize();
+
+    /**
      * Returns the file to where the final output of this session should be
      * stored. This is only available after startSession has been called and
      * will become unavailable after finish() was called.
index a8a396a..c99398c 100644 (file)
@@ -22,13 +22,13 @@ import android.location.Location;
 import android.net.Uri;
 import android.os.AsyncTask;
 
-import com.android.camera.Storage;
 import com.android.camera.app.MediaSaver;
 import com.android.camera.data.FilmstripItemData;
 import com.android.camera.debug.Log;
 import com.android.camera.exif.ExifInterface;
 import com.android.camera.util.FileUtil;
 import com.android.camera.util.Size;
+import com.google.common.base.Optional;
 
 import java.io.File;
 import java.io.IOException;
@@ -204,8 +204,11 @@ public class CaptureSessionImpl implements CaptureSession {
         mUri = mPlaceHolderSession.outputUri;
         mSessionManager.putSession(mUri, this);
         mSessionNotifier.notifyTaskQueued(mUri);
-        Bitmap placeholderBitmap = Storage.getPlacerHolderForSession(mUri);
-        onCaptureIndicatorUpdate(placeholderBitmap, 0);
+        Optional<Bitmap> placeholderBitmap =
+                mPlaceholderManager.getPlaceholder(mPlaceHolderSession);
+        if (placeholderBitmap.isPresent()) {
+            onCaptureIndicatorUpdate(placeholderBitmap.get(), 0);
+        }
     }
 
     @Override
@@ -221,7 +224,7 @@ public class CaptureSessionImpl implements CaptureSession {
     @Override
     public synchronized void cancel() {
         if (isStarted()) {
-            mSessionNotifier.removeSession(mUri.toString());
+            mSessionManager.removeSession(mUri);
         }
     }
 
@@ -238,8 +241,7 @@ public class CaptureSessionImpl implements CaptureSession {
         mContentUri = mPlaceholderManager.finishPlaceholder(mPlaceHolderSession, mLocation,
                 orientation, exif, data, width, height, FilmstripItemData.MIME_TYPE_JPEG);
 
-        mSessionNotifier.removeSession(mUri.toString());
-        mSessionNotifier.notifyTaskDone(mPlaceHolderSession.outputUri);
+        mSessionNotifier.notifyTaskDone(mUri);
     }
 
     @Override
@@ -337,9 +339,8 @@ public class CaptureSessionImpl implements CaptureSession {
                     "Cannot call finish without calling startSession first.");
         }
         mProgressMessage = reason;
-        mSessionNotifier.removeSession(mUri.toString());
-        mSessionManager.putErrorMessage(mPlaceHolderSession.outputUri, reason);
-        mSessionNotifier.notifyTaskFailed(mPlaceHolderSession.outputUri, reason);
+        mSessionManager.putErrorMessage(mUri, reason);
+        mSessionNotifier.notifyTaskFailed(mUri, reason);
     }
 
     @Override
@@ -354,6 +355,10 @@ public class CaptureSessionImpl implements CaptureSession {
         mProgressListeners.remove(listener);
     }
 
+    @Override
+    public void finalize() {
+        mPlaceholderManager.removePlaceholder(mPlaceHolderSession);
+    }
 
     private void onCaptureIndicatorUpdate(Bitmap indicator, int rotationDegrees) {
         mSessionNotifier.notifySessionCaptureIndicatorAvailable(indicator, rotationDegrees);
index dc13fe2..210be88 100644 (file)
@@ -97,6 +97,13 @@ public interface CaptureSessionManager {
     public void putSession(Uri sessionUri, CaptureSession session);
 
     /**
+     * Removes the session with the given uri from the manager. This may not
+     * remove temporary in memory resources from the session itself, see
+     * {@link CaptureSession#finalize()} to complete session removal.
+     */
+    public CaptureSession removeSession(Uri sessionUri);
+
+    /**
      * Removes a previously added listener from receiving further capture
      * session updates.
      */
index ac4d319..e7084fd 100644 (file)
@@ -61,13 +61,6 @@ import java.util.Map;
 public class CaptureSessionManagerImpl implements CaptureSessionManager {
 
     private final class SessionNotifierImpl implements SessionNotifier {
-        @Override
-        public void removeSession(String sessionUri) {
-            synchronized (mSessions) {
-                mSessions.remove(sessionUri);
-            }
-        }
-
         /**
          * Notifies all task listeners that the task with the given URI has been
          * queued.
@@ -100,6 +93,7 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
                             listener.onSessionDone(uri);
                         }
                     }
+                    finalizeSession(uri);
                 }
             });
         }
@@ -118,6 +112,7 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
                             listener.onSessionFailed(uri, reason);
                         }
                     }
+                    finalizeSession(uri);
                 }
             });
         }
@@ -235,6 +230,7 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
         mMainHandler = mainHandler;
     }
 
+    @Override
     public CaptureSession createNewSession(String title, long sessionStartMillis, Location location) {
         return mSessionFactory.createNewSession(this, mSessionNotifier, title, sessionStartMillis,
                 location);
@@ -255,6 +251,13 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
     }
 
     @Override
+    public CaptureSession removeSession(Uri sessionUri) {
+        synchronized (mSessions) {
+            return mSessions.remove(sessionUri.toString());
+        }
+    }
+
+    @Override
     public void addSessionListener(SessionListener listener) {
         synchronized (mTaskListeners) {
             mTaskListeners.add(listener);
@@ -310,4 +313,19 @@ public class CaptureSessionManagerImpl implements CaptureSessionManager {
             }
         });
     }
+
+    /**
+     * When done with a session, remove it from internal map and finalize it.
+     *
+     * @param uri Uri of the session to remove and finalize
+     */
+    private void finalizeSession(Uri uri) {
+        CaptureSession session;
+        synchronized (mSessions) {
+            session = removeSession(uri);
+        }
+        if (session != null) {
+            session.finalize();
+        }
+    }
 }
index 4f283ee..fa19975 100644 (file)
@@ -30,6 +30,7 @@ import com.android.camera.debug.Log;
 import com.android.camera.exif.ExifInterface;
 import com.android.camera.util.CameraUtil;
 import com.android.camera.util.Size;
+import com.google.common.base.Optional;
 
 /**
  * Handles placeholders in filmstrip that show up temporarily while a final
@@ -132,8 +133,8 @@ public class PlaceholderManager {
     public Uri finishPlaceholder(Session session, Location location, int orientation,
             ExifInterface exif, byte[] jpeg, int width, int height, String mimeType) {
         Uri resultUri = Storage.updateImage(session.outputUri, mContext.getContentResolver(),
-                session.outputTitle,
-                session.time, location, orientation, exif, jpeg, width, height, mimeType);
+                session.outputTitle, session.time, location, orientation, exif, jpeg, width,
+                height, mimeType);
         CameraUtil.broadcastNewPicture(mContext, resultUri);
         return resultUri;
     }
@@ -150,6 +151,25 @@ public class PlaceholderManager {
     }
 
     /**
+     * Retrieve the placeholder for a given session.
+     *
+     * @param session the session for which to retrieve bitmap placeholder
+     */
+    public Optional<Bitmap> getPlaceholder(Session session) {
+        return Storage.getPlaceholderForSession(session.outputUri);
+    }
+
+
+    /**
+     * Remove the placeholder for a given session.
+     *
+     * @param session the session for which to remove the bitmap placeholder.
+     */
+    public void removePlaceholder(Session session) {
+        Storage.removePlaceholder(session.outputUri);
+    }
+
+    /**
      * Create a new session instance from the given URI by querying the media
      * store.
      * <p>
index a3c84b2..8d508eb 100644 (file)
@@ -24,9 +24,6 @@ import android.net.Uri;
  * status of session.
  */
 interface SessionNotifier {
-    /** Removes the session since it's either done or cancelled. */
-    void removeSession(String sessionUri);
-
     /** A new task has been queued. */
     void notifyTaskQueued(final Uri uri);