Monday, August 26, 2013

Android Gradle - Building Unique Build Variants

The new build system for Android, based upon Gradle, is a major improvement over the original build system. The introduction of build types and product flavors to create unique applications from the same source code is very powerful. Moreover, Gradle is more pleasurable and easy to use than ant, especially if you execute custom build logic. For these reasons, the new build system has solved most of my build woes.

Unfortunately, many of the APIs were not written with this new, scalable build system in mind. This means that build variants (the product of a buildType and productFlavor) generated from a relatively complex codebase do not yield the expected results. Let's start with a concrete example.

The Problem


Let's say that your application has some pretty standard productFlavors and buildTypes defined in your build.gradle file.
    productFlavors {
        free {
            packageName "com.gradle.build.example.free"
        }

        paid {
            packageName "com.gradle.build.example.paid"
        }
    }

    buildTypes {
        alpha {
            packageNameSuffix ".alpha"
        }

        beta {
            packageNameSuffix ".beta"
        }
    }


Everything is fine and dandy. You are able to assemble 4 APKs from this configuration: freeAlpha, freeBeta, paidAlpha, paidBeta. By specifying packageNameSuffix in the buildType, each of these APKs can be installed alongside each other on a device. This is great for testing and even better if you release both a free and paid version of your app to the Play Store. This is precisely what Gradle Android aims to solve!

Now let's consider the case where your application defines a ContentProvider in the AndroidManifest. If you try to install these APKs, you'll receive the following error:
Installation error: INSTALL_FAILED_CONFLICTING_PROVIDER
As it currently stands, Android Gradle does not have a good solution for this problem. There is a similar problem if you are using Google Maps v2. Google issues API keys that are created based upon your application package name and the signing key. The install doesn't fail, but you have to make sure to pick the correct key otherwise your map will not load. I'm sure the Android API is full of other components that behave similarly. The new build system is great at creating the union of all resources required for a given buildVariant. But when you need to define a unique identifier for a specific buildVariant, there aren't any simple solutions.

A Solution


An approach that I've taken to automating this selection of "unique identifiers" for a given build variant boils down to dynamically generating the override identifiers and writing this to a standard Android resource folder. Then any buildType that would like to take advantage of these unique identifiers can refer to this res directory in its buildType config. Because code speaks louder than words; I've pushed a sample project to: Gradle-Build-Example.

android.applicationVariants.all { variant ->
    variant.mergeResources.dependsOn {
        overrideMapsKey(variant)
        overrideContentProviderAuthority(variant)
    }
}
For the ContentProvider authority strings, I make sure to define these strings as a resource in its own file. I then iterate over the XML tree and append a unique suffix to the end of each String resource and write this to the override resource folder. The best part about this solution is that it is fully automated. If you add a new ContentProvider, just make sure to add it's authority String in the file that gets parsed and overridden. If you add a new buildType or a new productFlavor, it will just work.

For the maps keys, I store them in an associative array in the build.gradle file, keyed off the packageName. The correct maps key will be selected, written to the override file and thus your map tiles will load. I recommend viewing the source code for the full implementation.

Update: The Google Maps v2 key assertion is incorrect. You can assign a set of packageName/signing-key pairs to a given Google Maps v2 key. I would recommend updating your Google maps key to support any new packageName/signing-key pairs you introduce. The example remains, however, for other components that may benefit this strategy.

Solving the problem in this way was done for a few reasons:

1. Scalability. Adding new productFlavors or buildTypes or Android API components that must adhere to this "uniqueness" principle is relatively painless.

2. Automation. Humans aren't required to go in and change the code to replace the Google Maps debug key with the production key when build a release APK, for instance.

3. Intuitive. The overrides all go into one resource folder, so it is easy to see which values are being selected.

Here are some things that I am not so happy about with this solution:

1. Task execution. I think this is mostly because I am still trying to grok Gradle in general, but, using android.applicationVariants.all { variant -> variant..dependsOn myCustomTask} does not seem like the "Gradle" way to run custom code. I've discovered that my customTask gets invoked even when running "./gradlew tasks". This is not what I expect.

2. Location of files. Generating these override files into the src tree is less than ideal. It can be easily mitigated from a dev standpoint by adding this generated file to your .gitignore. It would be preferable if these values could just be added into the standard Gradle build/res folder. This should be possible, I just don't know enough about Gradle Android yet to know how to do something like that.

I hope this post has helped with some of your build problems! Also, I would be very curious to hear from others on how you've solved this problem. And for reference, this sample was built using Android Gradle 0.5.6.

tl;dr: The new Android build system is great. Unfortunately it does not work perfectly. Here is my approach to solving some of its limitations.

Source: https://github.com/bradmcmanus/Gradle-Build-Example

Update: It has come to my attention that Google Maps v2 keys can be associated with a set of packageName/signing key pairs. In lieu of this fact, I would recommend updating your Google Maps v2 key to support any new packageName/signing key pair you introduce. The example remains, however, for other components that may benefit this strategy.

Tuesday, November 29, 2011

Optimizing View Hierarchies

This blog post is a brain dump of my findings discovered while optimizing a ListView in Android. It covers ListView best practices, including the viewholder pattern, ListView row type, ListView footer. It also shows how to use layoutopt and hierarchyviewer from the Android Developer Tools (ADT) to help optimize your layouts.


I noticed that the scrolling in my ListView activity had some performance issues in regards to scrolling. There’s not a lot of complex stuff going on visually in the rows so scrolling should not really be an issue.  We already follow the ViewHolder pattern, where we take advantage of automatic ListView row recycling by the OS and make sure to not call findViewById on every view we manipulate and every call to getView() in the adapter. So it seemed like something else is the culprit. 


To help discover the problem, I decided to go under the hood and get a more tangible look at view hierarchy using hierarchyviewer from ADT. Note, if you have not used hierarchyviewer before, it is very useful in debugging and profiling your UI. Luckily, it happens to be easy to use, simply open a shell and run the hierarchyviewer command. A gui will launch that allows you to examine your application process and a tree of its views. It must be noted, that you android:debuggable attribute must be set to true in the manifest, and that hierarchyviewer can only access hierarchies from devices running a developer version of the OS, such as an emulator or a Nexus phone. This is what the view hierarchy for a single row of the ListView looked like:



I noticed that there was much cruft in the layout row file for the ListView. After investigation, it seemed that there could be a few improvements done to reduce this view hierarchy to something much more reasonable for the amount of data that is actually being shown. The real problem is that we were using the ListView improperly for our app’s functionality. Our ListView shows a basic layout for every row of the list, and expands to show a larger and more information-rich view when selected. As you can see from the hierarchy above, there are two main branches. The branch on the top contains the views for the expanded, special row. Our adapter contained logic to specify the visibility of the expanded views accordingly. This logic gets the job done, but a more lean and appropriate approach leverages the row type facility built in to Adapters.



By overriding getItemViewType() and getItemViewTypeCount() in my Adapter implementation, I was able to inflate and return different xml layouts conditionally. This effectively removed the top branch from the view hierarchy. Instead of having 12 layouts and 19 views in the first implementation, the hierarchy was reduced to 5 layouts and 8 views. Here is the improved version under hierarchyviewer:



The layout has improved, but it looks like improvements can still be made. The next improvement was implemented using knowledge of the power and ability of ListViews. I noticed that there was an extra padding view present in every row object. You may have run in to this problem before with ListViews: designer creates a slick mock for a list, and even provides the assets for the background the row. The background asset contains padding on only the top. This allows for nice, even spacing when the rows are lined up, one after the other. But, you still want there to be padding on the bottom after the list item (note: there can be a number of variations on this problem, but I have seemed to have seen this problem every time I work with a making a pretty ListView). This padding has to be added manually somehow. Originally this problem was being solved by including an extra view in the row, and then setting the visibility to VISIBLE if it happens to be the last row. Unfortunately, there are two horrible problem with this solution: 1. There include extra logic in the getView() method that does not need to be there. 2. This solution introduces an extraneous view, and level to the hierarchy to support it.


My solution to this problem leverages the footer facility of the ListView. In this manner, I have removed the padding View, and its containing layout from the row, and instead replaced it with a footer, which attaches to the bottom of the list and scrolls with it, boiling down to the same functionality. Now the hierarchy looks like this:


Wow! What an improvement. There are now only 5 levels, 4 layouts, and 7 views. It is looking pretty good, although, it looks like one more minor improvement can be made. The LinearLayout in the middle of the hierarchy does not look like it is doing much. Lets use layoutopt to confirm this fact. It seems that layoutopt is more of a proactive tool, telling me the user directly what is wrong, whereas hierarchyviewer is more a complementary tool to use while debugging that visualizes the views. To use layouopt, simply jump in the shell and run layoutopt with an argument of the file name. It spits out some useful information which confirm that this LinearLayout is useless, so I went ahead and removed it. Now, after all of the optimizations, it has only 4 levels, 3 layouts, and 7 views. The layout looks like this:




Now you are probably wondering why I went to all of this trouble just to reduce the view hierarchy. These are the two very important reasons I decided to go to this trouble:
  • Improved performance. Snappier app means happier customer. Reducing the hierarchy for most activities may not be the most beneficial way to spend your time, but the dividends are much more apparent in a row for a ListView. An optimized layout will directly affect the scrollability and usability of the list.
  • Cleaner code. By using built in ListView facilities, such as row view type and footers, the logic pertaining to the ListView became much cleaner. This means it will be easier to maintain, especially as new developers are brought on to work on the project.
I suppose the moral of this post it to make sure that you are properly using the API and tools given to you in effort to reduce to decrease headaches. And don’t be afraid to refactor!

Are you suffering from any of these pitfalls? I suggest running your application in hierarchyviewer to get a visualization of your views, especially if you have a ListView. You may be surprised at what you see!