Wednesday, July 20, 2011

Android Library Management for publishing multiple editions of an android app

[UPDATE 12/30/11: This post is a little out of date, I recently updated to r16 of the ADK, and eclipse 'Indigo' (3.7). I had to play around with the library settings to make the dex process happy, and the settings actually make a little more sense now. I'll make a new post]

Sorry for the long title, but I spent a frustratingly long time( hours ) doing something that should have been achievable in seconds.  My hopes are that I am able to document the steps needed here well enough that someone can save themselves some time.

As a last step before publishing, I needed to be able to quickly make two versions of Sedition, an Ad-supported version, and an Ad-free, paid version. In the Android market applications are distinguished from one another by the "package" property in your AndroidManifest.xml file. So simple, change your manifest name and re-build. Not so fast, the resources in your application, eg the files that get assigned an id in the infamous "R" class, are built to live in that class. So when you change your package, any code that references anything in your R class will break. For me this resulted in over 100 errors. I have some final static variables in that help me define code behavior for any given flavor of build. This was the closest I ever got to a #ifdef type mechanism that I mentioned a few months ago. Unfortunately, there is no solution for changing manifest files in a similar manner.

The conventional wisdom of the Web suggests moving your application into an android library, then make two new "Applications" that then link to the library, like so:





This is what I did. It should, by all accounts, have been quick. It wasn't, there were a ton of booby traps, and unless you have innate knowedge of the inner workings of the android build tools, it's a little bit of a mysterious beast. Here's what I did:



First off, I have to give credit to the most helpful search hit on the topic, and that was this blog post, especially the summary which made some important points I never found stated anywhere else:


  1. Source code and resources of all referenced Android Libraries are treated as if they were part of Android Application.
  2. Libraries’ AndroidManifest.xml files are ignored. Though this may change in future releases of Android SDK when manifest merging is added.
  3. Order of libraries determines who wins when identical resource names/ids are found across different libraries.

So far the only duplication I have had to do is the AndroidManifest.xml file itself

Ok, now on to a "recipe" of steps:

Before you begin, make sure your development directory structure is clean. Do not have overlapping project directories. As an example, I had an "external" folder to hold external libraries to my project. It was a subfolder of my main Sedition project. This worked fine for jar files, but file inter-dependent projects, it really confuses the build tools. Make all of your projects "peers", and in my case I make my Eclipse work space the parent to all the peer folders.


Step 1: Convert your original main application project to a library project. 
This is easy breazy: In eclipse, right-click your project and select "properties", then "Android". Check the "IsLibrary" check box and hit ok.


Step 2: Create a new Android Application Project.
This is the step you will do for however many build variants you want.


Step 3: Add a library reference in your new project to your original project.
Right click new project and click the properties menu, select the "Android" subsection, then in the library section, click add, then select the library of you original project. Hit ok.


Step 4: Add source folders for libraries to your new project.
Open the new projects' properties dialog as before, select "Java Build Path", then select the "Source Tab". Click "Link Source...". In the dialog box that comes up, next to "Linked Folder Location", click browse, and brwose to the "src" folder of you old main application. Eclipse will try to name this "src" by default which conflicts with your existing "src" project item, so rename it to something else. I did "main_src".


Hit finish, you should now see your library added as a source folder for you new project:


Step 5 : Add all the other 3rd party libraries your main application depended on.
All the Jar file libraries and other libraries used in your old main app need to be included in your new one. I had to add several jars, as well as the Facebook library for Android, which is not a jar lib, but an Android library much like the one we turned our main app into. This is in the "Java Build Path" section of the Project properties, but now click on the "Libraries" tab. 


Step 6 : Make Sure Project references are set.
Again, in the properties page, in the project references sub section, make sure you check the projects on which your new project depends.



Well, if all goes well, when Eclipse build, it will include your wrapped library in your shell of a front end. It's pretty easy to tell when it didn't work, just look at the size of your apk in your bin folder. It will be an anemic few kilobytes if it didn't work. 

A lot of these settings seem redundant or unclear so it took a bit of trial and error before I finally got it working. I should also mention that this was using  r11 and r12 of the Android SDK/ and matching eclipse tools with Eclipse Galileo. It could always change with new a tool. I update this is it no longer seems valid or mistakes are found. My purpose and hope is to save someone out there time.

Part of my confusion may stem from my C#/Visual Studio mindset, and a Jar/or a set of folders with compiled classes is far different from a .Net Assembly.

-P

1 comment:

  1. Nice overview thanks!

    I find it strange that the Android team and developer website does not recognize building multiple targets as a common requirement for developers.

    There are lot of techniques out there but not one of them overcomes duplication of some files like AndroidManifest.xml or Eclipse project settings

    ReplyDelete

I welcome you're thoughts. Keep it classy, think of the children.