diff --git a/samples/training/bitmapfun/BitmapFun/build.gradle b/samples/training/bitmapfun/BitmapFun/build.gradle index b12d5ccb2..25d609d91 100644 --- a/samples/training/bitmapfun/BitmapFun/build.gradle +++ b/samples/training/bitmapfun/BitmapFun/build.gradle @@ -13,12 +13,12 @@ repositories { } android { - compileSdkVersion 18 - buildToolsVersion "18.0.1" + compileSdkVersion 19 + buildToolsVersion "18.1.0" defaultConfig { minSdkVersion 7 - targetSdkVersion 18 + targetSdkVersion 19 } } diff --git a/samples/training/bitmapfun/BitmapFun/src/main/AndroidManifest.xml b/samples/training/bitmapfun/BitmapFun/src/main/AndroidManifest.xml index bc1c18057..9ca5cf50c 100644 --- a/samples/training/bitmapfun/BitmapFun/src/main/AndroidManifest.xml +++ b/samples/training/bitmapfun/BitmapFun/src/main/AndroidManifest.xml @@ -22,7 +22,7 @@ + android:targetSdkVersion="19" /> @@ -32,7 +32,8 @@ android:description="@string/app_description" android:hardwareAccelerated="true" android:icon="@drawable/ic_launcher" - android:label="@string/app_name" > + android:label="@string/app_name" + android:allowBackup="false"> > mReusableBitmaps; + private Set> mReusableBitmaps; /** * Create a new ImageCache object using the specified parameters. This should not be @@ -130,9 +133,18 @@ public class ImageCache { Log.d(TAG, "Memory cache created (size = " + mCacheParams.memCacheSize + ")"); } - // If we're running on Honeycomb or newer, then + // If we're running on Honeycomb or newer, create a set of reusable bitmaps that can be + // populated into the inBitmap field of BitmapFactory.Options. Note that the set is + // of SoftReferences which will actually not be very effective due to the garbage + // collector being aggressive clearing Soft/WeakReferences. A better approach + // would be to use a strongly references bitmaps, however this would require some + // balancing of memory usage between this set and the bitmap LruCache. It would also + // require knowledge of the expected size of the bitmaps. From Honeycomb to JellyBean + // the size would need to be precise, from KitKat onward the size would just need to + // be the upper bound (due to changes in how inBitmap can re-use bitmaps). if (Utils.hasHoneycomb()) { - mReusableBitmaps = new HashSet>(); + mReusableBitmaps = + Collections.synchronizedSet(new HashSet>()); } mMemoryCache = new LruCache(mCacheParams.memCacheSize) { @@ -152,7 +164,7 @@ public class ImageCache { if (Utils.hasHoneycomb()) { // We're running on Honeycomb or later, so add the bitmap - // to a SoftRefrence set for possible use with inBitmap later + // to a SoftReference set for possible use with inBitmap later mReusableBitmaps.add(new SoftReference(oldValue.getBitmap())); } } @@ -466,7 +478,7 @@ public class ImageCache { /** * Sets the memory cache size based on a percentage of the max available VM memory. * Eg. setting percent to 0.2 would set the memory cache to one fifth of the available - * memory. Throws {@link IllegalArgumentException} if percent is < 0.05 or > .8. + * memory. Throws {@link IllegalArgumentException} if percent is < 0.01 or > .8. * memCacheSize is stored in kilobytes instead of bytes as this will eventually be passed * to construct a LruCache which takes an int in its constructor. * @@ -477,9 +489,9 @@ public class ImageCache { * @param percent Percent of available app memory to use to size memory cache */ public void setMemCacheSizePercent(float percent) { - if (percent < 0.05f || percent > 0.8f) { + if (percent < 0.01f || percent > 0.8f) { throw new IllegalArgumentException("setMemCacheSizePercent - percent must be " - + "between 0.05 and 0.8 (inclusive)"); + + "between 0.01 and 0.8 (inclusive)"); } memCacheSize = Math.round(percent * Runtime.getRuntime().maxMemory() / 1024); } @@ -493,10 +505,33 @@ public class ImageCache { */ private static boolean canUseForInBitmap( Bitmap candidate, BitmapFactory.Options targetOptions) { - int width = targetOptions.outWidth / targetOptions.inSampleSize; - int height = targetOptions.outHeight / targetOptions.inSampleSize; - return candidate.getWidth() == width && candidate.getHeight() == height; + if (Utils.hasKitKat()) { + int width = targetOptions.outWidth / targetOptions.inSampleSize; + int height = targetOptions.outHeight / targetOptions.inSampleSize; + int byteCount = width * height * getBytesPerPixel(candidate.getConfig()); + return byteCount <= candidate.getAllocationByteCount(); + } + + return candidate.getWidth() == targetOptions.outWidth + && candidate.getHeight() == targetOptions.outHeight + && targetOptions.inSampleSize <= 1; + } + + /** + * Return the byte usage per pixel of a bitmap based on it's configuration. + * @param config The bitmap configuration. + * @return The byte usage per pixel. + */ + private static int getBytesPerPixel(Config config) { + if (config == Config.ARGB_8888) { + return 4; + } else if (config == Config.RGB_565) { + return 2; + } else if (config == Config.ALPHA_8) { + return 1; + } + return 1; } /** @@ -555,9 +590,16 @@ public class ImageCache { public static int getBitmapSize(BitmapDrawable value) { Bitmap bitmap = value.getBitmap(); + // From KitKat onward use getAllocationByteCount() as allocated bytes can potentially be + // larger than bitmap byte count. + if (Utils.hasKitKat()) { + return bitmap.getAllocationByteCount(); + } + if (Utils.hasHoneycombMR1()) { return bitmap.getByteCount(); } + // Pre HC-MR1 return bitmap.getRowBytes() * bitmap.getHeight(); } diff --git a/samples/training/bitmapfun/BitmapFun/src/main/java/com/example/android/bitmapfun/util/ImageResizer.java b/samples/training/bitmapfun/BitmapFun/src/main/java/com/example/android/bitmapfun/util/ImageResizer.java index 2a9d152d1..596a991e7 100644 --- a/samples/training/bitmapfun/BitmapFun/src/main/java/com/example/android/bitmapfun/util/ImageResizer.java +++ b/samples/training/bitmapfun/BitmapFun/src/main/java/com/example/android/bitmapfun/util/ImageResizer.java @@ -211,6 +211,8 @@ public class ImageResizer extends ImageWorker { Log.d(TAG, "Found bitmap to use for inBitmap"); } options.inBitmap = inBitmap; + } else { + Log.d(TAG, "Did NOT find bitmap to use for inBitmap"); } } } @@ -218,10 +220,8 @@ public class ImageResizer extends ImageWorker { /** * Calculate an inSampleSize for use in a {@link BitmapFactory.Options} object when decoding * bitmaps using the decode* methods from {@link BitmapFactory}. This implementation calculates - * the closest inSampleSize that will result in the final decoded bitmap having a width and - * height equal to or larger than the requested width and height. This implementation does not - * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but - * results in a larger bitmap which isn't as useful for caching purposes. + * the closest inSampleSize that is a power of 2 and will result in the final decoded bitmap + * having a width and height equal to or larger than the requested width and height. * * @param options An options object with out* params already populated (run through a decode* * method with inJustDecodeBounds==true @@ -238,13 +238,15 @@ public class ImageResizer extends ImageWorker { if (height > reqHeight || width > reqWidth) { - // Calculate ratios of height and width to requested height and width - final int heightRatio = Math.round((float) height / (float) reqHeight); - final int widthRatio = Math.round((float) width / (float) reqWidth); + final int halfHeight = height / 2; + final int halfWidth = width / 2; - // Choose the smallest ratio as inSampleSize value, this will guarantee a final image - // with both dimensions larger than or equal to the requested height and width. - inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; + // Calculate the largest inSampleSize value that is a power of 2 and keeps both + // height and width larger than the requested height and width. + while ((halfHeight / inSampleSize) > reqHeight + && (halfWidth / inSampleSize) > reqWidth) { + inSampleSize *= 2; + } // This offers some additional logic in case the image has a strange // aspect ratio. For example, a panorama may have a much larger @@ -252,13 +254,14 @@ public class ImageResizer extends ImageWorker { // end up being too large to fit comfortably in memory, so we should // be more aggressive with sample down the image (=larger inSampleSize). - final float totalPixels = width * height; + long totalPixels = width * height / inSampleSize; // Anything more than 2x the requested pixels we'll sample down further - final float totalReqPixelsCap = reqWidth * reqHeight * 2; + final long totalReqPixelsCap = reqWidth * reqHeight * 2; - while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { - inSampleSize++; + while (totalPixels > totalReqPixelsCap) { + inSampleSize *= 2; + totalPixels /= 2; } } return inSampleSize; diff --git a/samples/training/bitmapfun/BitmapFun/src/main/java/com/example/android/bitmapfun/util/Utils.java b/samples/training/bitmapfun/BitmapFun/src/main/java/com/example/android/bitmapfun/util/Utils.java index 52a99f9f2..81b856a2f 100644 --- a/samples/training/bitmapfun/BitmapFun/src/main/java/com/example/android/bitmapfun/util/Utils.java +++ b/samples/training/bitmapfun/BitmapFun/src/main/java/com/example/android/bitmapfun/util/Utils.java @@ -73,4 +73,8 @@ public class Utils { public static boolean hasJellyBean() { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN; } + + public static boolean hasKitKat() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + } } diff --git a/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-hdpi/ic_launcher.png b/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-hdpi/ic_launcher.png index 96a442e5b..75b3c9781 100644 Binary files a/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-hdpi/ic_launcher.png and b/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-hdpi/ic_launcher.png differ diff --git a/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-mdpi/ic_launcher.png b/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-mdpi/ic_launcher.png index 359047dfa..0c9c11af9 100644 Binary files a/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-mdpi/ic_launcher.png and b/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-mdpi/ic_launcher.png differ diff --git a/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-xhdpi/ic_launcher.png b/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-xhdpi/ic_launcher.png index 71c6d760f..7c5aeed04 100644 Binary files a/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-xhdpi/ic_launcher.png and b/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-xhdpi/ic_launcher.png differ diff --git a/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-xxhdpi/ic_launcher.png b/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..91b0f960b Binary files /dev/null and b/samples/training/bitmapfun/BitmapFun/src/main/res/drawable-xxhdpi/ic_launcher.png differ diff --git a/samples/training/bitmapfun/local.properties.sample b/samples/training/bitmapfun/local.properties.sample index b26452221..37317f492 100644 --- a/samples/training/bitmapfun/local.properties.sample +++ b/samples/training/bitmapfun/local.properties.sample @@ -4,4 +4,4 @@ # Location of the SDK. This is only used by Gradle. # For customization when using a Version Control System, please read the # header note. -sdk.dir=/usr/local/google/home/akoch/lib/android-sdk \ No newline at end of file +sdk.dir=/usr/local/lib/android-sdk \ No newline at end of file