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