Fragmentation Talk @JAX – April 19th, 2012

At this years JAX conference in Mainz, Andreas will give a talk about fragmentation on the Android platform.

Reserve the date:

JAX, Rheingoldhalle, Mainz – 19.04.2012 | 16:00 – 17:00

The talk covers all facets of fragmentation seen on the Android platform including the correct antidote to cure them.

The main fragmentation issues are:

  • Hardware features
  • API levels
  • Screen sizes / resolution
  • tablet vs. phone dev
  • issues with the support package (fragments backport)
Posted in event, talk | Tagged , , | Leave a comment

The (lack of) usability of the ninepatch tool

If you’re into Android development (which you are, since you’re reading this), you know the draw9patch tool which comes with the Android SDK.  For reference, a couple of links:
The 9patch concept
The draw9patch tool

In the last days I used it quite a lot and I’m really frustrated with its usability, or lack thereof. Here are a couple of things that could have been implemented better, to put it mildly:

  • When drawing the border I must click exactly on the 1-pixel border, which is hard, especially with larger images. I would like to be able to simply drag a selection rectangle around the border area which I want to paint black, and not go pixel-hunting.
  • Most images have some symmetry, and I want them to remain symmetric after resizing. For that I need again to go pixel-peeping, to make sure I have the same number of black pixels on both sides of the central axis. Some simple alignment tools would make this a lot easier.
  • When you edit buttons, you’ll have 2-3 images with the same dimension and minor variations, representing the states of the buttons. I want to be able to copy the 9patch markings from one image to the other, ensuring that they resize consistently. Right now you need to do it by pixel-counting.
  • Last but not least, it shouldn’t be so hard for the tool to remember the directory where I saved my last image, and open the Save dialog with that directory selected. It kinda sucks having to navigate from my home directory all the way to the project directory for every file.

So that’s my rant, I had to get it out. Now, since draw9patch is open-source, I’d better get down and code some of these improvements myself instead of bitching about them. Maybe we’ll even hold a Hackergarten for it!

Posted in rant | Tagged , , , | Leave a comment

Maps + Fragments + Support Package = headache

The development goals of the Android version of Wikihood were:

Write a single app (.apk), that …

  • supports API-Level 4-14
  • supports both phones and tablets gracefully
  • uses fragments to achieve the above

Trying to achieve this I ran into the following problems:

Actionbar

There is no Actionbar implementation prior 3.0 …

Solution: use the Actionbar Compatibility example code or better one of the open libraries out there like http://actionbarsherlock.com/.

Fragments + Map

To actually make use of fragments you need to work with the Support (aka compatibility) package which backports fragment functionality prior 3.0. This is all fine, but in combination with a MapView trouble arises:

http://code.google.com/p/android/issues/detail?id=15347

“Solutions”

One “solution” mentioned on the Internet is to use a hack, which makes FragmentActivity extend MapActivity:

https://github.com/petedoyle/android-support-v4-googlemaps

While the hack at a first glance seems harmless, it sooner or later leads to nasty problems like: http://code.google.com/p/android/issues/detail?id=3756

You can fix that by having separate processes for every single (map)activity … but do you really want that??

You can also find obscur “solutions” as:

http://code.google.com/apis/maps/articles/android_v3.html

Finally I ended up abusing TabHost, see:

https://github.com/inazaruk/examples/tree/master/MapFragmentExample

which seems to work fine so far. The only thing that is worrying me is:

android.app.LocalActivityManager is deprecated

[see also comment below from inazaruk]

Nesting Fragments

Besides the MapFragment problem, I ran into obscur exceptions when trying to nest fragments. I implemented a Fragment with a ViewPager using a FragmentAdapter …

I works somehow, but when you try to manage (show/hide) your fragments you run into problems you can’t solve …

Various posts on stackoverlow indicate that nesting fragments is generally not supported … yet. The official documentation does not mention this anywhere.

Posted in does and don'ts | Tagged , , , , | 4 Comments

Android Hackergarten II

On January 12th, there’ll be a second Android Hackergarten event in Basel at Canoo Engineering AG. The plan is not to finish coding until the Hackergarten Android app is releases to the Marketplace!

Andrei and I will be there, join us!

Posted in event | Tagged , | Leave a comment

Large bitmaps? Be sure to recycle them!

When using large-ish bitmaps you might run into a strange OutOfMemoryError being thrown on BitmapFactory.decodeBitmap. I know I did. Of course, if the image is simply too large for your heap, there’s nothing strange about the OutOfMemory – you’ve really run out of it. In my case however, ddms showed only 3MB of heap being used at the time the error got thrown. How could that be?

After much digging and searching, it turns out that the memory for bitmaps is allocated natively and not as part of the heap. The memory limit the OS imposes on your process is however for heap and non-heap memory together. So I was actually running out of non-heap memory! Time for a memory-profiling session, during which to my disbelief I did not find any memory-leak in my java code.

More digging / searching / banging head on the table happened.

Finally I found a good explanation on Markus Kohler’s blog. It appears that deallocating non-heap memory is a rather difficult task for the Android GC, and that it actually takes two runs of the GC to release things such as memory used by bitmaps.

With this in mind I found the recycle() on the Bitmap class, which frees the memory used natively for storing the pixel data for it. You would call it when your bitmaps are no longer needed – a good time would be during onDestroy(). That solved my problem, and it may solve yours as well!

Relevant links:

http://www.mail-archive.com/android-developers@googlegroups.com/msg82996.html
http://kohlerm.blogspot.com/2010/02/android-memory-usage-analysis-slides.html

Posted in does and don'ts | Tagged , , | Leave a comment

Us in Android360

Yesterday a new issue of the german android 360 magazine was released featuring an article about fragmentation of the Android platform by Andrei and me.

Our article is primarily a wrap up of our workshop on the topic and provides you with all the knowledge to successfully tackle fragmentation in the Android eco system.

The new issue furtermore covers testing and continuous integration, a wrap up of Ice Cream Sandwich and many more interesting articles.

Posted in publication | Tagged , , | Leave a comment

Displaying images with style using a NinePatch frame

Every eye-catchy Android app features nicely styled visual content.
When it comes to displaying images, framing every image with a decent style adds to the overall visual impression.

Surely you can paint an image frame manually implementing a custom drawable and doing the painting yourself, but there is an easier solution to that: use a NinePatch background image for your ImageView instead.

Have a look at the Honeycomb Gallery samples .

Honeycomb Gallery Example

And this is how it works:

Define your ImageView with a custom background drawable:

<ImageView
  android:id="@+id/image"       
  android:background="@drawable/picture_frame"       
  android:layout_width="wrap_content"       
  android:layout_height="wrap_content"       
  android:layout_gravity="center"       
  android:duplicateParentState="true"/>

Implement the background drawable (res/drawable/picture_frame.xml) as follows (supports selection state):

<!--?xml version="1.0" encoding="utf-8"?-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">   
  <item android:state_selected="true" android:drawable="@drawable/picture_frame_selected" />
  <item android:state_pressed="true" android:drawable="@drawable/picture_frame_pressed" />
  <item android:drawable="@drawable/picture_frame_default" />
</selector>

Finally create the different NinePatch images

res/drawable/picture_frame_default.9.png
res/drawable/picture_frame_selected.9.png
res/drawable/picture_frame_pressed.9.png

with your preferred frame style.

See the sample doc for a useful example.

For an alternative implementation which actually draws frames have a look at Romain Guy’s Shelves demo app.

Posted in does and don'ts, ui | Tagged , , | Leave a comment

Camera preview done right

A task that is (or should be) rather simple is to show a camera preview in your App. There is a very nice example that comes with the Android SDK  - however, it has a couple of problems: first, it is written for Gingerbread (2.3) – meaning you cannot use it if your app also targets older versions (which it has to). Second, it only works in landscape orientation, so your preview will be rotated if you hold your phone in portrait mode.

Here’s my solution, which builds on the example from Google, but also supports older phones, and takes into account the device orientation.One thing to note is that you need to extract the camera handling in 2 separate classes, one for Froyo (2.2) and older, one for Gingerbread (2.3) and newer. The reason is the Camera.CameraInfo class, which was introduced with 2.3 – if your class has at least an import referring to the CameraInfo  class, you’ll get runtime exceptions when running on an older platform. That’s why we need the CameraPreviewProvider interface, along with its 2 implementations.

Here’s the CameraPreview component.

public class CameraPreview extends SurfaceView implements SurfaceHolder.Callback {
  SurfaceHolder holder;
  Camera camera;
  private int lastReportedWidth;
  private int lastReportedHeight;

  private static final String TAG = "CameraPreviewLog";
  private CameraPreviewProvider previewProvider;

  public CameraPreview(Context context, AttributeSet attrs) {
    super(context, attrs);
    holder = getHolder();
    holder.addCallback(this);
    holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
      previewProvider = new CameraPreviewGingerbread();
    } else {
      previewProvider = new CameraPreviewFroyo();
    }
  }

  public void surfaceCreated(SurfaceHolder holder) {
    preparePreview();
  }

  public void surfaceDestroyed(SurfaceHolder holder) {
    stopPreview();
  }

  private void preparePreview() {
    try {
      camera = previewProvider.openCamera();
      camera.setPreviewDisplay(holder);
    } catch (Exception exception) {
      if (camera != null) {
        camera.release();
      }
      camera = null;
    }
  }

  private void stopPreview() {
    if (camera != null) {
      camera.stopPreview();
      camera.release();
      camera = null;
    }
  }

  public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
    this.lastReportedWidth = w;
    this.lastReportedHeight = h;
    if (camera != null) {
      startPreview();
    }
  }

  private void startPreview() {
    Display display = ((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
    previewProvider.startPreview(camera, lastReportedWidth,
    lastReportedHeight, display);
  }

}

And the CameraPreviewProvider interface

interface CameraPreviewProvider {
  Camera openCamera();
  void startPreview(Camera camera, int previewWidth, int previewHeight, Display display);
}

The implementation for Froyo (2.2) and older

class CameraPreviewFroyo implements CameraPreviewProvider {

  public Camera openCamera() {
    return Camera.open();
  }

  public void startPreview(Camera camera, int previewWidth, int previewHeight, Display display) {
    Camera.Parameters parameters = camera.getParameters();
    List supportedPreviewSizes = parameters.getSupportedPreviewSizes();
    Size optimalPreviewSize = CameraPreviewFroyo.getOptimalPreviewSize(supportedPreviewSizes, previewWidth, previewHeight);
    if (optimalPreviewSize != null) {
      parameters.setPreviewSize(optimalPreviewSize.width, optimalPreviewSize.height);
      camera.setParameters(parameters);
      camera.startPreview();
    }
  }

  static Size getOptimalPreviewSize(List sizes, int w, int h) {
    final double ASPECT_TOLERANCE = 0.1;
    final double MAX_DOWNSIZE = 1.5;

    double targetRatio = (double) w / h;
    if (sizes == null) return null;

    Size optimalSize = null;
    double minDiff = Double.MAX_VALUE;

    int targetHeight = h;

    // Try to find an size match aspect ratio and size
    for (Size size : sizes) {
      double ratio = (double) size.width / size.height;
      double downsize = (double) size.width / w;
      if (downsize > MAX_DOWNSIZE) {
        //if the preview is a lot larger than our display surface ignore it
        //reason - on some phones there is not enough heap available to show the larger preview sizes
        continue;
      }
      if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) continue;
      if (Math.abs(size.height - targetHeight) < minDiff) {
        optimalSize = size;
        minDiff = Math.abs(size.height - targetHeight);
      }
    }
    // Cannot find the one match the aspect ratio, ignore the requirement
    //keep the max_downsize requirement
    if (optimalSize == null) {
      minDiff = Double.MAX_VALUE;
      for (Size size : sizes) {
        double downsize = (double) size.width / w;
        if (downsize > MAX_DOWNSIZE) {
          continue;
        }
        if (Math.abs(size.height - targetHeight) < minDiff) {
          optimalSize = size;
          minDiff = Math.abs(size.height - targetHeight);
        }
      }
    }
    //everything else failed, just take the closest match
    if (optimalSize == null) {
      minDiff = Double.MAX_VALUE;
      for (Size size : sizes) {
        if (Math.abs(size.height - targetHeight) < minDiff) {
          optimalSize = size;
          minDiff = Math.abs(size.height - targetHeight);
        }
      }
    }
    return optimalSize;
  }

}

And the implementation for Gingerbread (2.3) and newer.

class CameraPreviewGingerbread implements CameraPreviewProvider {

  private int cameraId = -1;
  private int cameraOrientation;

  public Camera openCamera() {
    CameraInfo cameraInfo = new CameraInfo();
    for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
      Camera.getCameraInfo(i, cameraInfo);
      if (cameraInfo.facing == CameraInfo.CAMERA_FACING_BACK) {
        cameraId = i;
        cameraOrientation = cameraInfo.orientation;
        return Camera.open(cameraId);
      }
    }
    return null;
  }

  public void startPreview(Camera camera, int previewWidth, int previewHeight, Display display) {
    int displayRotation = display.getRotation();
    Camera.Parameters parameters = camera.getParameters();
    List supportedPreviewSizes = parameters.getSupportedPreviewSizes();
    Size optimalPreviewSize = CameraPreviewFroyo.getOptimalPreviewSize(supportedPreviewSizes, previewWidth, previewHeight);
    if (optimalPreviewSize != null) {
      int width = optimalPreviewSize.width, height = optimalPreviewSize.height;
      int degrees = 0;
      switch (displayRotation) {
        case Surface.ROTATION_0:
          degrees = 0;
        break;
        case Surface.ROTATION_90:
          degrees = 90;
        break;
        case Surface.ROTATION_180:
          degrees = 180;
        break;
        case Surface.ROTATION_270:
          degrees = 270;
        break;
      }
      int result = (cameraOrientation - degrees + 360) % 360;
      camera.setDisplayOrientation(result);
      parameters.setPreviewSize(width, height);

      camera.setParameters(parameters);
      camera.startPreview();
    }
  }

}

This has been tested on a variety of devices and it works correctly, however if you find any problems on other devices, please do let me know about them. One device where it does not work 100% correctly is the Sony Ericsson E10i / Xperia Mini – there the preview appears rotated, if the phone is held in portrait mode. However, there is no api that allows us to rotate the preview, as it runs with Eclair (2.1).

When showing the Camera Preview it’s best to use a full-screen activity, because then your preview dimensions will be closer to the native camera resolution. That’s why the default Camera app runs fullscreen.

You can download the complete source code as an Eclipse project here: CameraPreview

Posted in tutorials | Tagged , | Leave a comment

Shipping a Database with your App

You might decide to ship a database with your Android App. Why would you want to do that? Well, maybe you want your App to run offline, when the user has no data connectivity. Or maybe you don’t want to have the overhead of running a server component.
Let’s see what solutions we have at our disposal:

1. Include the sql script as a resource, run it the first time your app is opened.

Advantage: Size is kept small, the sql script will compress well, being a text file.
Disadvantage: Running the script may need quite a lot of time, especially if you have full-text indices.
I won’t detail this too much, it’s quite straightforward, but make sure not to run it in the UI Thread, and provide a way for the user to know what’s going on (i.e. a progress bar). It would be useful to test it on a slower device, to make sure the user doesn’t fall asleep before your scripts finish running.

2.Include the pre-populated database as an asset with your APK.

Advantage: Speed – no time is wasted inserting records.
Disadvantage: Size – your APK will be larger, as it now includes the database.
I’ll detail a bit this approach, since I’ve used it already.

One problem is that you cannot open the database directly from the assets, you need to copy it first to the /databases/ folder of your App. This means the installed size of your App will be even larger, as it contains the database two times.

You have to be careful with another aspect – your .sqlite asset will be compressed, and there is a size restriction to that. You won’t get an error when packaging the APK, the user will get it when your code tries to open the asset. He’ll get the following error:

Data exceeds UNCOMPRESS_DATA_MAX

And he won’t be able to use your app at all, since installing the db failed, which will make him/her very angry and give your app a bad rating. The limit seems to be device-specific – I’ve had the error for an 8.6 MB asset, but I’ve seen people get it for 1MB assets.

The solution is not to compress your database – you need to change its file extension (for example to .png). See this post for a detailed explanation: http://ponystyle.com/blog/2010/03/26/dealing-with-asset-compression-in-android-apps/ . Which again, increases your APK size even more.

For a very good code example of copying the database, see this discussion: http://stackoverflow.com/questions/513084/how-to-ship-an-android-application-with-a-database

Conclusion:

So it basically comes down to a compromise between APK size and setup speed. Choose whichever fits your app best. A variation would be to host your sql script or the pre-populated database on a server, and download it on the first use. This solves the APK size issue, but it brings a host of other stuff to solve – for example, what if the user does not have internet connectivity when first starting your app, or what if the connection is very slow or gets interrupted while you’re downloading?
I would’ve expected Android to provide a standard and documented way of including a database with an App, I think it’s a fairly common use-case. That’s currently not the case, and it might not change in the near future – see Dianne Hackborn’s post here: http://www.mail-archive.com/android-developers@googlegroups.com/msg16527.html.

Posted in does and don'ts | Tagged , , , | Leave a comment

Android Hackergarten, Basel, Sept. 28th

Never heard about Hackergarten yet?

It started as a bunch of hackers located in Basel contributing every now and then to open source projects during an evening event and having fun. The Hackergarten idea is now spreading quickly around the world. Check the website and mailing list.

On September 28th, there’ll the next Hackergarten event in Basel at Canoo Engineering AG. The plan is to collaboratively write an Android app for Hackergarten events!

Andrei and I will be there! Join us

Posted in event | Tagged , | Leave a comment