Sunday, February 26, 2012

Loading Bitmaps from the Gallery

A common intent to use is requesting a photo from the Gallery. The request itself is pretty easy. Here's the code.
final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/*");
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
activity.startActivityForResult(intent, requestCode);
Getting the data back in Android 4.0 is almost just as simple, thanks to ContentProviders.
final InputStream is = context.getContentResolver().openInputStream(intent.getData());
final Bitmap imageData = BitmapFactory.decodeStream(is, null, options);
is.close();
This is kinda amazing considering that the Gallery does all sorts of complicated stuff to provide a good user experience as well as handling the management of the content itself. Remember, the gallery doesn't just handle Camera pictures, it pictures from other folders of the phone, albums from Picasa/Google+ and other pluggable data sources.

Unfortunately, if you support earlier versions you'll need to do more work because these abstractions weren't covered up as well back then. You'll need code for different Uri schemes that are returned from intent.getData().

  1. http:// or https:// - Download the file from the internet and read the file locally.
  2. file:// - Load the file via FileInputStream rather than the suggested getContentResolver.openInputStream.
  3. Complete garbage - Nothing you can do. The file failed to load, this can happen when the phone is low on memory, acting weird, data corruption, picture is too ugly, etc.
For a sample app doing the minimum is fine. For apps posted on the market there's a lot more things to handle before you can safely load the picture into memory...

Considerations for Robust Bitmap Loading:
  1. Code very defensively. You're reading user data which can be error prone. Catch exceptions when appropriate. Handle bad read permissions, handle network errors, and OutOfMemoryErrors. Lots of things can go wrong.
  2. Never load on the UI thread. It can take over 10 seconds to get the data if it isn't stored on the phone yet. Use AsyncTask. Tell the user that stuff is loading.
  3. Pictures can be large. Always check the size metrics before loading a graphic into memory. Use BitmapFactory.Options.inJustDecodeBounds. If the image file's metrics are too large then downsample the image using BitmapFactory.Options.inSampleSize.
  4. Bitmaps that have non-powers of 2 (NPOT) sizes are a bit troublesome for hardware accelerated views (relevant to Android 3.0 and higher). NPOT images can take up a lot more memory and the errors are subtle. Check Logcat if images aren't appearing. Try downsampling, resizing, or cropping the bitmap.
  5. In Android 3.0, images are loaded into Java-native byte arrays which means they count towards your VM budget. Android 2.3 and lower used native code memory which isn't tracked. This masked some memory load problems. Use Eclipse Memory Analyzer (MAT) to detect large bitmaps.
The sample code in this article is not enough for a robust implementation of loading bitmaps. It doesn't take into account all the considerations listed above. There's some code in the android-beryl project that does this for you. You'll want to checkout BitmapWrapper.create().

If you want a start-to-finish for handling the intent result of the gallery then look into, Gallery.java.

Beryl Gallery Intent Handler Code
// Sample code to call Beryl's async intent launch system to load a bitmap from the gallery.
// It's kinda complicated but it does a lot of stuff in the background for you.
// Just drop this code in whatever Fragment or Activity you want to get picture data from
// and implement acquirePicture() callback method and everything should work.

private static final int ACTIVITYRESULT_CHOOSEPICTURE = 100;

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);

  IActivityResultHandler handler = null;
  
  // If receiving result from Gallery.
  if(requestCode == ACTIVITYRESULT_CHOOSEPICTURE) {
    handler = new Gallery.GetImageResult() {
    
      // These callbacks are run on the UI thread.
      // They handle the different results of loading the bitmap.
      public void onResultCompleted() {
        if(this.bitmapResult.isAvailable()) {
          acquirePicture(this.bitmapResult.get());
        }
        
        this.bitmapResult = null;
      }

      public void onResultCanceled() {
        this.bitmapResult = null;
      }

      public void onResultCustomCode(int resultCode) {
        this.bitmapResult = null;
      }
    };
  }
  
  if(handler != null) {
    final ActivityResultTask resultTask = 
       new ActivityResultTask(intentLauncher, handler, resultCode, data);
    resultTask.execute();
  }
}

// Request Gallery for a picture.
public void selectPictureFromGallery() {
    if(checkStorageState()) {
      intentLauncher.beginStartActivity(new Gallery.GetImage(), ACTIVITYRESULT_CHOOSEPICTURE);
    }
  }

// Proxy for launching the gallery.
// Needed since we can be launching from a Fragment or an Activity.
final ActivityIntentLauncher.IActivityLauncherProxy launcherProxy = 
  new ActivityIntentLauncher.IActivityLauncherProxy() {
  public Activity getContext() {
    return WorkerFragment.this.getActivity();
  }

  public void startActivityForResult(Intent intent, int requestCode) {
    WorkerFragment.this.startActivityForResult(intent, requestCode);
  }

  public void startActivity(Intent intent) {
    WorkerFragment.this.startActivity(intent);
  }

  public void onStartActivityFailed(Intent intent) {
    WorkerFragment.this.showToast(R.string.nolocal_generic_error);
  }

  public void onStartActivityForResultFailed(Intent intent, int requestCode) {
    WorkerFragment.this.showToast(R.string.nolocal_generic_error);
  }
};

Friday, February 10, 2012

How to Quickly Support Ice Cream Sandwich

Android 4.0 (Ice Cream Sandwich) has been released for a few months now and a lot of apps still don't support it. A quick look at the Platform Versions page reveals only about 5% have Honeycomb or ICS at this time. While that may not be a lot, there's an interesting trend where users with newer versions of Android download more apps.

I personally am in the process of updating all of my apps to ICS and for the most part it's pretty simple. Below are some steps to quickly get the most benefits of ICS in the shortest time possible.

Download the Latest SDKs and Developer Tools
The first thing your should do is make sure your IDE and SDKs are up-to-date.
  1. Open Eclipse
  2. Help > Check for Updates (If ADT has an update install it.)
  3. Window > Android SDK Manager
  4. Check all the newest SDKs and new Android Tools and download them. Screenshot below shows 4.0.3.
Android SDK Manager with 4.0.3 and the latest SDK tools installed. 

Update the Project SDK
  1. Right click the Project folder name in Eclipse and select Properties.
  2. Go to the Android section and check the latest version. This gives you access to the latest APIs. Don't worry you can still target older versions.

Project Properties, Android 4.0.3 selected.
Tip: If you start using the new APIs you'll need to make sure that you don't call them while running on older versions of Android. A great way to do this is to temporarily switch the target SDK version in the project settings to the lowest version. Then check to see if any of the compile errors can be hit on those versions.

Set the Target Versions in the AndroidManifest.xml
Open the AndroidManifest.xml and change the uses-sdk tag to match the version of the SDK you selected. Android 4.0.3 has an API level of 15. The example below shows an app that supports 4.0.3 down to Android 1.6, API level 4.

Changing the sdkTargetVersion to 14 or higher will take you out of compatibility mode. This means certain key features will be turned on. Like the Action Bar replacing the menu button.

Consider Dropping Older Versions
Likewise holding on to older version can be a drag. At this time Android 2.1 is a good minimum version to support for maximum coverage (98%). Supporting Android 2.2 (90%)  as the minimum version is also acceptable too.

Enable Hardware Accelerated Rendering
Android 3.0 introduced hardware accelerated rendering. While this won't make your apps faster, it will make animations smoother. Do this at the application level in the manifest.

Hardware accelerated rendering is awesome but it does have its drawbacks. This is one thing to consider while testing your app. Certain features require more memory or actually draw slower when using 2D-accelerated drawing. If this is the case, you can disable hardware acceleration at the application, activity, and view levels. Check out this article for more information.

Use the New Themes
In general, it's a good idea to have a theme associated with your app even if it's just an alias to the stock theme. This gives you the ability to make minor tweaks to the look and feel. Android 3.0 introduced the Holographic theme, (@android:style/Theme.Holo) This is the standard Android theme going forward and it is a requirement for anyone that has the Android Market to have this theme on their device. To enable it create these XML files.
res/values/themes.xml

  
res/values-v11/themes.xml

  
AndroidManifest.xml

De-Lint your App
The latest release of the ADT introduced Eclipse integration with the android lint tool. This tool is nice for finding bugs and bad practices with your app. A few things it finds is unused resources, over draw performance issues, and even edge case bugs that cause weird crashes on some phones.

To run the tool right click on the Project Folder > Android Tools > Run Lint: Check for Common Errors menu.

The issues will appear as warnings some as errors. Fix the ones of importance. I'd give higher priority over possible crash and performance bugs.

Fix JNI References
This isn't for most developers but if you write native code check out this article. Essentially, Dalvik moves objects around if you're running with a targetSdkVersion="14" or higher. The goal is to improve memory usage. CheckJNI is a tool that has been out for a while that finds bad practices that can cause crashes in ICS. This is a painful process that you'll need to do eventually. Unless you always stuck to the best practices.

Apart from the JNI References, following these steps is the fast track way to support ICS. It should probably take 1-3 days' worth of work to get an ICS apk ready for the market. While most of your user base won't get these enhancements the most important share of your users will. Lastly, don't upload the apk until you've tested it on the versions you support and you feel like it meets the level of quality you want.

Saturday, February 4, 2012

In App Screenshots

One of the new highly touted features in Ice Cream Sandwich is screenshot support. This allows a user to take a snapshot of their screen and send it to anyone. Obviously this is a valuable debugging tool as users can send how an app looked when it did something weird.

It is possible to partially support screenshots within your own app. This is essentially done by getting the content root view of your Activity and rendering it against a Canvas.

The code below does just that. (Source excerpt from the android-beryl library.)
public class Screenshot {
  private final View view;
  /** Create snapshots based on the view and its children. */
  public Screenshot(View root) {
    this.view = root;
  }
  /** Create snapshot handler that captures the root of the whole activity. */
  public Screenshot(Activity activity) {
    final View contentView = activity.findViewById(android.R.id.content);
    this.view = contentView.getRootView();
  }
  /** Take a snapshot of the view. */
  public Bitmap snap() {
    Bitmap bitmap = Bitmap.createBitmap(this.view.getWidth(), this.view.getHeight(), Config.ARGB_8888);
    Canvas canvas = new Canvas(bitmap);
    view.draw(canvas);
    return bitmap;
  }
}

Thursday, February 2, 2012

File Size Matters

People love apps. Especially when they are simple and fast to get what they want done. There's a lot of work that goes into making an app's user experience as seamless as possible. Things ranging from subtle cues to picking sizes of buttons for Mr. Fat Fingers but one topic doesn't seem to get talked about, the file size.

While most users could care less how large an app is, after all they can fit 50,000 songs on their phone. What they care about is how long they have to wait to start using it. It is a critical part of the digital unboxing experience. And you know, everyone loves unboxing videos.

Many apps are downloaded on an impulse. Impulses tend to be short lived. Which means you probably only have seconds to gain the user's attention. The impulse starts before they hit the download button. Which means the time it takes to download the app takes away from the short time someone will be initially interested in your app.

Having bloated apps also have technical disadvantages too. If it's bad enough and you go over the 20MB limit you force users to download over WiFi which is an obstacle. Having resource downloads after the app is installed is preferred but is only acceptable for games.

Fortunately for Android does a lot of work during the compile process to keep package sizes small. There still some steps you can do to make sure that the package size is as small as possible:
  1. Make sure to obfuscate your app with ProGuard. ProGuard removes unused code, makes it faster, and more secure. The SDK now automatically turns this on. Learn the basics of the proguard.cfg file and use it to tighten your code.
  2. Use the Android Lint tool to find resources that are not used.
  3. Use XML shape drawables rather than bitmaps for simple shapes. These also scale very well.
  4. Don't use custom picture backgrounds, most of the time they cause overdraw problems and look ugly anyways.
  5. Use PNGs and use small set of colors for your images. Your app will look cleaner and leaner. Also, the aapt tool can optimize PNGs to 8-bit with palette when possible.
  6. When creating 9-patch drawables, don't apply padding in the png itself. Create a drawable that references the 9-patch and add the padding there.
Of course having a small package size isn't the only thing that's important but it is one of the first things that is, when it comes to the start-to-finish user experience.