diff --git a/MyTracks/src/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java b/MyTracks/src/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java index 3f1de531a..8cdaa71e5 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java +++ b/MyTracks/src/com/google/android/apps/mytracks/fragments/MarkerDetailFragment.java @@ -28,7 +28,6 @@ import com.google.android.apps.mytracks.util.StatsUtils; import com.google.android.maps.mytracks.R; import android.content.Intent; -import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.support.v4.app.Fragment; @@ -66,7 +65,6 @@ public class MarkerDetailFragment extends Fragment { private MyTracksProviderUtils myTracksProviderUtils; private Waypoint waypoint; - private Bitmap bitmap; @Override public void onCreate(Bundle savedInstanceState) { @@ -97,13 +95,6 @@ public class MarkerDetailFragment extends Fragment { update(); } - public void onPause() { - super.onPause(); - if (bitmap != null) { - bitmap.recycle(); - } - } - @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.marker_detail, menu); @@ -180,8 +171,7 @@ public class MarkerDetailFragment extends Fragment { int displayWidth = defaultDisplay.getWidth(); @SuppressWarnings("deprecation") int displayHeight = defaultDisplay.getHeight(); - bitmap = PhotoUtils.setImageVew( - imageView, Uri.parse(photoUrl), displayWidth, displayHeight); + PhotoUtils.setImageVew(imageView, Uri.parse(photoUrl), displayWidth, displayHeight); } TextView name = (TextView) getView().findViewById(R.id.marker_detail_waypoint_name); diff --git a/MyTracks/src/com/google/android/apps/mytracks/services/tasks/BitmapLoader.java b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/BitmapLoader.java new file mode 100644 index 000000000..2513e4d7b --- /dev/null +++ b/MyTracks/src/com/google/android/apps/mytracks/services/tasks/BitmapLoader.java @@ -0,0 +1,172 @@ +/* + * Copyright 2013 Google Inc. + * + * 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.google.android.apps.mytracks.services.tasks; + +import com.google.android.apps.mytracks.util.PhotoUtils; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Matrix; +import android.media.ExifInterface; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.Log; +import android.widget.ImageView; + +import java.io.IOException; +import java.lang.ref.WeakReference; + +/** + * A bitmap loader. + * + * @author Jimmy Shih + */ +public class BitmapLoader extends AsyncTask { + private static final String TAG = BitmapLoader.class.getSimpleName(); + + private final WeakReference imageViewReference; + private final Uri uri; + private final int displayWidth; + private final int displayHeight; + + public BitmapLoader(ImageView imageView, Uri uri, int displayWidth, int displayHeight) { + + // Use a WeakReference to ensure the ImageView can be garbage collected + imageViewReference = new WeakReference(imageView); + this.uri = uri; + this.displayWidth = displayWidth; + this.displayHeight = displayHeight; + } + + public Uri getUri() { + return uri; + } + + @Override + protected Bitmap doInBackground(Void... params) { + + // Get the image dimensions + BitmapFactory.Options options = new BitmapFactory.Options(); + + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(uri.getPath(), options); + + // Set targetWidth and targetHeight + int targetWidth = displayWidth; + int targetHeight = displayHeight; + if (targetHeight == 0) { + targetHeight = (int) (targetWidth * ((float) options.outHeight / (float) options.outWidth)); + } + + // Set imageWidth and imageHeight based on image rotation + int rotation = getRotation(); + int imageWidth; + int imageHeight; + + if (rotation == 0 || rotation == 180) { + imageWidth = options.outWidth; + imageHeight = options.outHeight; + } else { + imageWidth = options.outHeight; + imageHeight = options.outWidth; + } + + // Get a scaled down version of the image + options.inJustDecodeBounds = false; + options.inSampleSize = getInSampleSize(imageWidth, imageHeight, targetWidth, targetHeight); + options.inPurgeable = true; + + Bitmap scaledBitmap = BitmapFactory.decodeFile(uri.getPath(), options); + + // Get the final bitmap after rotating the scaled down image + Bitmap bitmap; + if (rotation == 0) { + bitmap = scaledBitmap; + } else { + Matrix matrix = new Matrix(); + matrix.postRotate(rotation); + bitmap = Bitmap.createBitmap( + scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); + scaledBitmap.recycle(); + } + return bitmap; + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (isCancelled()) { + bitmap = null; + } + // If imageView is still around, set bitmap + if (imageViewReference != null && bitmap != null) { + ImageView imageView = imageViewReference.get(); + if (imageView != null) { + BitmapLoader bitmapLoader = PhotoUtils.getBitmapLoader(imageView); + if (this == bitmapLoader) { + imageView.setImageBitmap(bitmap); + } + } + } + } + + private int getRotation() { + try { + ExifInterface exifInterface = new ExifInterface(uri.getPath()); + switch (exifInterface.getAttributeInt( + ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) { + case ExifInterface.ORIENTATION_ROTATE_90: + return 90; + case ExifInterface.ORIENTATION_ROTATE_180: + return 180; + case ExifInterface.ORIENTATION_ROTATE_270: + return 270; + default: + return 0; + } + } catch (IOException e) { + Log.e(TAG, "Unable to get photo orientation", e); + return 0; + } + } + + /** + * Gets the in sample size. + * + * @param imageWidth the image width + * @param imageHeight the image height + * @param targetWidth the target width + * @param targetHeight the target height + */ + private int getInSampleSize(int imageWidth, int imageHeight, int targetWidth, int targetHeight) { + float widthRatio = 1; + if (imageWidth > targetWidth) { + widthRatio = (float) imageWidth / (float) targetWidth; + } + + float heightRatio = 1; + if (imageHeight > targetHeight) { + heightRatio = (float) imageHeight / (float) targetHeight; + } + + /* + * To fit within the target area, return the larger sample ratio so the + * image will not be larger than the target dimensions. Use Math.floor to + * not under sample. + */ + return (int) Math.floor(Math.max(widthRatio, heightRatio)); + } +} diff --git a/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java b/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java index f29f96fec..80da1d43d 100644 --- a/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java +++ b/MyTracks/src/com/google/android/apps/mytracks/util/PhotoUtils.java @@ -16,15 +16,12 @@ package com.google.android.apps.mytracks.util; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Matrix; -import android.media.ExifInterface; +import com.google.android.apps.mytracks.services.tasks.BitmapLoader; + import android.net.Uri; -import android.util.Log; import android.widget.ImageView; -import java.io.IOException; +import java.lang.ref.WeakReference; /** * Utilities for photos. @@ -33,8 +30,6 @@ import java.io.IOException; */ public class PhotoUtils { - private static final String TAG = PhotoUtils.class.getSimpleName(); - private PhotoUtils() {} /** @@ -45,108 +40,55 @@ public class PhotoUtils { * @param displayWidth the display width * @param displayHeight the display height */ - public static Bitmap setImageVew( + public static void setImageVew( ImageView imageView, Uri uri, int displayWidth, int displayHeight) { - - // Get the image dimensions - BitmapFactory.Options options = new BitmapFactory.Options(); - - options.inJustDecodeBounds = true; - BitmapFactory.decodeFile(uri.getPath(), options); - - // Set targetWidth and targetHeight - int targetWidth = displayWidth; - int targetHeight = displayHeight; - if (targetHeight == 0) { - targetHeight = (int) (targetWidth * ((float) options.outHeight / (float) options.outWidth)); + if (cancelBitmapLoader(imageView, uri)) { + BitmapLoader bitmapLoader = new BitmapLoader(imageView, uri, displayWidth, displayHeight); + WeakReference bitmapLoaderReference = new WeakReference( + bitmapLoader); + imageView.setTag(bitmapLoaderReference); + bitmapLoader.execute(); } - - // Set imageWidth and imageHeight based on image rotation - int rotation = getRotation(uri); - int imageWidth; - int imageHeight; - - if (rotation == 0 || rotation == 180) { - imageWidth = options.outWidth; - imageHeight = options.outHeight; - } else { - imageWidth = options.outHeight; - imageHeight = options.outWidth; - } - - // Get a scaled down version of the image - options.inJustDecodeBounds = false; - options.inSampleSize = getInSampleSize(imageWidth, imageHeight, targetWidth, targetHeight); - options.inPurgeable = true; - - Bitmap scaledBitmap = BitmapFactory.decodeFile(uri.getPath(), options); - - // Get the final bitmap after rotating the scaled down image - Bitmap bitmap; - if (rotation == 0) { - bitmap = scaledBitmap; - } else { - Matrix matrix = new Matrix(); - matrix.postRotate(rotation); - bitmap = Bitmap.createBitmap( - scaledBitmap, 0, 0, scaledBitmap.getWidth(), scaledBitmap.getHeight(), matrix, true); - scaledBitmap.recycle(); - } - - imageView.setImageBitmap(bitmap); - return bitmap; } /** - * Gets the image rotation + * Gets the image view bitmap loader. * - * @param uri the image uri + * @param imageView the image view */ - private static int getRotation(Uri uri) { - try { - ExifInterface exifInterface = new ExifInterface(uri.getPath()); - switch (exifInterface.getAttributeInt( - ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)) { - case ExifInterface.ORIENTATION_ROTATE_90: - return 90; - case ExifInterface.ORIENTATION_ROTATE_180: - return 180; - case ExifInterface.ORIENTATION_ROTATE_270: - return 270; - default: - return 0; + public static BitmapLoader getBitmapLoader(ImageView imageView) { + if (imageView != null) { + Object object = imageView.getTag(); + if (object instanceof WeakReference) { + @SuppressWarnings("unchecked") + WeakReference bitmapLoaderReference = (WeakReference) object; + return bitmapLoaderReference.get(); } - } catch (IOException e) { - Log.e(TAG, "Unable to get photo orientation", e); - return 0; } + return null; } /** - * Gets the in sample size. + * Cancels the image view bitmap loader. * - * @param imageWidth the image width - * @param imageHeight the image height - * @param targetWidth the target width - * @param targetHeight the target height + * @param imageView the image view + * @param uri the uri + * @return false if the bitmap loader shouldn't be canceled. True if there is + * no bitmap loader or the bitmap loader is cancelled. */ - private static int getInSampleSize( - int imageWidth, int imageHeight, int targetWidth, int targetHeight) { - float widthRatio = 1; - if (imageWidth > targetWidth) { - widthRatio = (float) imageWidth / (float) targetWidth; - } + private static boolean cancelBitmapLoader(ImageView imageView, Uri uri) { + BitmapLoader bitmapLoaderAsyncTask = getBitmapLoader(imageView); - float heightRatio = 1; - if (imageHeight > targetHeight) { - heightRatio = (float) imageHeight / (float) targetHeight; + if (bitmapLoaderAsyncTask != null) { + if (bitmapLoaderAsyncTask.getUri().equals(uri)) { + // same bitmap loader is already in progress, don't cancel + return false; + } else { + // cancel previous bitmap loader + bitmapLoaderAsyncTask.cancel(true); + } } - - /* - * To fit within the target area, return the larger sample ratio so the - * image will not be larger than the target dimensions. Use Math.floor to - * not under sample. - */ - return (int) Math.floor(Math.max(widthRatio, heightRatio)); + // imageview has no bitmap loader, or an existing bitmap loader is cancelled + return true; } }