Skip to content

Android 3.0 ActionBar Class: Maintaining Compatibility with Pre-Android 3.0 Apps

My goal for version 2 of the Fake Name Generator app was to have a single APK that ran on Android 3.0 and Android 1.6-2.3 while being optimized for tablets. While the Android Compatibility Package allowed me to use the Fragments API on pre-3.0 versions of Android, it does not provide support for the ActionBar class. The ActionBar is paramount to having an app properly optimized for tablets! Thus, I had to get creative.

The problem: How to instantiate an object in Android 3.* that doesn’t exist on pre-Android 3.0 in an APK that will run on both pre-3.0 and 3.*?

The answer, as it turns out, is not very straightforward.

But wait, won’t a catch-able exception just be thrown if an class isn’t found? Nope. An exception is thrown, but it is not able to be caught. Why? Because of the Bytecode Verifier of course! Basically, the bytecode verifier is going to check if all the necessary classes exist before it tries to instantiate and object from a class. Normally, code that references a non-existent class wouldn’t even compile. However, the build target here is Android 3.0, where the ActionBar class does exist. But when it’s run on Android 2.3 and lower, well, uh oh, or more formally, a VerifyError is thrown, which we can’t catch.

So, how to solve it? Well, we need to make a wrapper class that checks if the ActionBar class is available in a static initalizer and throws an exception that we can catch if it is not.

Let’s take a look at aptly-named ActionBarWrapper class to start.

Remember, we can’t instantiate anything, so we need to use a static initalizer to check if the ActionBar class is available. Thus, we need a static method to force the static initalizer to be called. If you’re unfamiliar with static initalizers, read up. Essentially, all this needs to be is an empty static method. As long as it is called, the static initalizer will be called and that’s the target. So:

public static void isAvailable() {}

And that’s all!

Now, for the meat of the class, the static initalizer.

static {
   try {
      Class.forName("android.app.ActionBar");
   } catch (Exception e) {
      throw new RuntimeException(e);
   }
}

It is relatively simple. Use the Class.forName() function to check if the ActionBar class, android.app.ActionBar, exists. If it does, don’t do anything. If it doesn’t, throw a RuntimeException which lets the calling class know that the ActionBar class is not available.

Speaking of the calling class, let’s jump over there. The calling class in best practice should be your main class or the launching activity of your app so that a boolean denoting if the ActionBar class is available right from the get go. We’re going to take advantage of another static initalizer here.

static {
   try {
      ActionBarWrapper.isAvailable();
      isActionBarAvailable = true;
   } catch (Throwable t) {
      isActionBarAvailable = false;
   }
}

This part is relatively straightforward since all the hard work is in the ActionBarWrapper class. All that needs to be done here is to stick the call to isAvailable() in a try and catch and handle any possible exception by setting the isActionBarAvailable boolean to false. As already discussed, calling isAvailable() calls the static initalizer of the ActionBarWrapper class which checks if the ActionBar class is available. But how is the static initalizer of the main class called? The system will call it on application startup so you don’t have to worry about it.

The hard part is done! From here, we know whether or not the ActionBar class is available. BUT! We still can’t just try to initialize an ActionBar object directly. That will still result in a VerifyError being thrown. We can, however, initialize an instance of the ActionBarWrapper class though. The constructor for the ActionBarWrapper class is simple enough:

public ActionBarWrapper(Context context) {
   actionBar = ((Activity)context).getActionBar();
}

Here’s where we create an instance of the ActionBar class. Why can we do it here though? Patience!

With an ActionBarWrapper object created, a reference to an ActionBar object is now a member variable of our ActionBarWrapper object. So, all calls to the ActionBar object must go through the ActionBarWrapper object. Thus, any ActionBar method we wish to call, must be implemented in the ActionBarWrapper class. For my purposes, I only needed three methods, but you could add all of them if you wish.

public void setBackgroundDrawable(Drawable background) {
   actionBar.setBackgroundDrawable(background);
}
public void setDisplayShowTitleEnabled(boolean showTitle) {
   actionBar.setDisplayShowTitleEnabled(showTitle);
}

public void setDisplayUseLogoEnabled(boolean useLogo) {
   actionBar.setDisplayUseLogoEnabled(useLogo);
}

Let’s look at how this is implemented in the calling class.

if(isActionBarAvailable) {
   ActionBarWrapper actionBarWrapper = new ActionBarWrapper(this);
   actionBarWrapper.setBackgroundDrawable(getResources().getDrawable(R.drawable.logo_bg_repeat));
   actionBarWrapper.setDisplayShowTitleEnabled(false);
   actionBarWrapper.setDisplayUseLogoEnabled(true);
}

You can see how the ActionBar object is interacted with. Any methods from the ActionBar class must be called through ActionBarWrapper.

And that’s all! You now have an application that can use an Android 3.0 API on Android 2.* and Android 3.0 without crashing. You can easily change this for any Android API by modifying the Class.forname call in ActionBarWrapper although you’ll probably want to rename the wrapper class as well and add some wrapper functions.

Lastly, let’s take a quick look at why this solution works. The ActionBarWrapper never initializes an ActionBar object until after it has checked whether or not it exists. By doing this check in static initializers, we avoid ever referencing the ActionBar class until we are sure it exists. But why can’t we just directly initialize an ActionBar object in the main class after we determined it exists? Because the bytecode verifier will throw a fit if it is running on a version of Android without the ActionBar class. You see, as soon as an object is initialized, the bytecode verifier is going to check all the code to make sure that calls to classes referenced in the object are valid. On Android 2.*, this will fail and result in a VerifyError. This is also the same reason that we cannot catch a VerifyError. The key here is the static initializers. These perform the check before the object is instantiated and prevent the bytecode verifier from throwing that dreaded VerifyError.

The complete listing of ActionBarWrapper is below.

public class ActionBarWrapper {
	private ActionBar actionBar;

	// Check if android.app.ActionBar exists and throw an error if not
	static {
		try {
			Class.forName("android.app.ActionBar");
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	// A static function that can be called to force the static
	// initialization of this class
	public static void isAvailable() {}

	public ActionBarWrapper(Context context) {
		actionBar = ((Activity)context).getActionBar();
	}

	// Wrapper functions
	public void setBackgroundDrawable(Drawable background) {
		actionBar.setBackgroundDrawable(background);
	}

	public void setDisplayShowTitleEnabled(boolean showTitle) {
		actionBar.setDisplayShowTitleEnabled(showTitle);
	}

	public void setDisplayUseLogoEnabled(boolean useLogo) {
		actionBar.setDisplayUseLogoEnabled(useLogo);
	}
}
Read more from Uncategorized
4 Comments Post a comment
  1. Oct 8 2011

    Very innovative Shane! This really illustrates the depth of your knowledge of programming, and specifically Android development.
    You also walk through the code in a clear and easy to understand manner. I think it’s very professional. Keep up the excellent work!

    Reply
    • Oct 8 2011

      Thank you Gage! This particular problem had me stumped for a week over the summer. Hacking together a solution took a while, but it ended up being pretty interesting albeit not very elegant.

      Reply
  2. Jul 15 2012

    Very interesting solution! Thank you for sharing this; I’m working on adding Gingerbread support to my Holo-themed app, and this was very helpful.

    Reply
    • shane
      Jul 15 2012

      Glad you found it useful!

      Reply

Leave a comment.

(required)
(required)

Note: HTML is allowed. Your email address will never be published and is only used for notification of comment replies by the blog owner.