Android Annotations that Inject Bytecode Boilerplate

Java annotations are of great utility for android developers when using an appropriate library, generally providing more succinct code. There are two popular choices that provide annotations for boilerplate android code that I want to briefly discuss, RoboGuice and AndroidAnnotations. Each of these projects approaches the issue of processing annotations differently. Afterwards, I’ll look at a new project, DroidCook, that uses bytecode injection.

RoboGuice

RoboGuice handles annotations during runtime, which is generally reviled on mobile as a cause for degraded performance and in this case can lead to extremely obscured stack traces. Nevertheless, RoboGuice accomplishes this by extending Android classes to provide the needed functionality of an annotation at key points during the activity’s life cycle, which you then must subclass. With the need to subclass RoboGuice classes instead of Android’s default classes, complications arise with the lack of multiple inheritance in Java and other 3rd party Android libraries requiring the same. Consider ActionbarSherlock, to use this and RoboGuice requires manual class mashing as can be seen here: RoboSherlockActivity.java With many more classes to support, such work becomes a drudge while receiving bug fixes in a given library becomes a manual process of updating the mash, and this is only considering two libraries. What if we wanted to use RoboGuice, ActionBarSherlock, and SlidingMenu? I suppose we could design a SlidingRoboSherlockActivity class, but seriously?

AndroidAnnotations

The approach taken here I think was rather clever. Instead of requiring one to subclass an AndroidAnnotation class, you instead insert a new build step during compilation where the library subclasses your class to provide necessary functionality, generating source code on the fly. This means that it plays nice with other libraries and doesn’t need to perform runtime reflection. There’s only one, awkward, catch. Due to the complication of generating source on the fly during an android build, there’s no way around the fact that you now need to start referencing this subclass generated for you. So if you have MainActivity, then you need to reference MainActivity_ in your manifest, in your intents, everywhere. So you’re now bound to this contract to use classes that do not actually “exist”. I actually set out to solve this originally in the library but could find no way around it, which led me to the following.

DroidCook

DroidCook explores post-compilation annotation processing to provide bytecode injection into compiled class files.

Here’s how it works:

  • Annotate classes, methods, and fields.
  • During post-compile of a build and before conversion to dex format, annotations will be read from compiled java classes and be used to inject boilerplate into the class file.

For example, if we want to produce code like:


    TextView mTextView; 
    public void onCreate(Bundle savedState) { 
        super.onCreate(savedState); 
        mTextView = (TextView) findViewById(R.id.text_view); 
        updateText(); 
    } void updateText() { 
        mTextView.setText("Hello, World!"); 
    } 

we could simply write:


    @ViewById
    TextView mTextView; 
    @OnCreate void updateText() { 
        mTextView.setText("Hello, World!"); 
    } 

Once compiled, DroidCook will read in the bytecode of the above and transform it to resemble that of the longer form. There are some additional benefits to bytecode injection here. For example, first take note of the updateText method above annotated with @OnCreate. If you’re familiar with android, you’ll know that onCreate always accepts a single Bundle argument. Since we’re actually inserting the call directly into the onCreate of the compiled class, we can inspect the callee to see if it accepts any arguments. For example:


    @OnCreate
    void setupToolbar() {
    } 
    @OnCreate void setupUserInput(Bundle savedState) {
    } 

Here, we have annotated two methods to be called during onCreate. The setupUserInput method will have the savedState Bundle passed in as an argument by DroidCook when generating the bytecode while setupToolbar will be called without the argument. DroidCook also loads classes during run and will check how the class is assignable to determine how it should be processed. So if the class extends SherlockActivity from ActionBarSherlock, then it is assignable as an android Activity. This means one can freely use standard android classes, subclasses, or libraries with subclasses and there’s no need for additional annotations to identify what it is. DroidCook is still in an early phase but checkout the README.md to get started and see a list of what’s currently supported.

Daniel is the lead Android guy. He does Python while eating lunch and uses Linux all day. Needless to say, he's our primary open source contributor. Loves Go, maintainer for Debian, plays Guitar. Not so much on the foreign languages, but does signal processing as a hobby

Leave a comment

Add your comment here