I’m trying to implement support for Library projects in Robolectric (android unit testing framework). I have the framework loading all resources for the library projects and have tested this to work just fine. The process is pretty simple, from the RobolectricConfig I read in the project.properties and look for android.library.reference.x values in a loop, and recurse through each project.
The tricky part has to do with resolving R references at runtime when doing so from the Library Project codebase. Lets say for example that we have an application with 1 library project like so:
com.example.app which depends on library com.example.lib
Both projects have resources in them. Under the com.example.app project we have:
gen/com.example.app.R
gen/com.example.lib.R
com.example.app.R is a superset of com.example.lib.R as it contains all of com.example.lib.R’s definitions and then those that have been added because of its own resources. I originally thought they were identical but I was wrong, they are in fact different. However for R’s inner classes ((R.string, R.color, R.attr, etc) the name/values map the same as they corresponding values in com.example.app.R in the app project. The library project’s R classes values do not map the same though as the one in the application project.
As a simplification, lets say they look like this:
package com.example.app;
public final class R {
public static final class string {
public static final int a = 0x7f050001;
public static final int a = 0x7f050002;
public static final int c = 0x7f050003;
}
}
//this file is in the application project
package com.example.lib;
public final class R {
public static final class string {
public static final int c = 0x7f050003;
}
}
Then, under the library project there is gen/com.example.lib.R:
//this file is in the library project
package com.example.lib;
public final class R {
public static final class string {
public static final int c = 0x7f040003;
}
}
So what happened is that the library has c defined in an string.xml and the project does not. The value for R.string.c in the library’s values are not the same as the application’s R values (c = 0x7f050003 in app and c = 0x7f040003 in library). I understand that the library doesn’t know about the application thus it’s own R class can’t possibly generate values that are the same, and thus the application class must have its values re-mapped. What I want to know is how could I possibly, at runtime, use the com.example.lib.R values that are defined in the application project as opposed to those defined in the library’s com.example.lib.R class?
What fails is that when the Robolectric Test runner is running my test, when it gets into the library project codebase I do a lookup for string c, as so:
resources.getString(R.string.c);
So what I get when I do the lookup is 0x7f040003 instead of 0x7f050003. It seems most values map exactly 0x10000 higher but that does not always work out so I can’t rely on that.
What I don’t understand is how come if we have 2 classes with the same package name and the one from the application comes first in the classpath (which I verified at runtime by printing out System.getProperty(“java.class.path”) at runtime), why does the library project still use its own com.example.lib.R definitions rather than the equivalent version in the application project? They both have the exact same canonical name.
I imagine something in the class loader says that this class (lets call its MyLibraryActivity)
only knows about the com.example.lib.R class that it depends on, and loads that one. Perhaps the security manager or something makes that decision? I have no idea really. But I would hope that somehow I could change this behavior so that library project resource lookups from within the library project could resolve to the version in the application project (since after all, it is in the class path). Maybe there is a way I can force load this class ahead of time so the system doesn’t try to reload it?
I understand that there is no dependency between the library project and the application project and I definitely don’t want to add that dependancy, however I’d like the values at runtime to come in from the application’s R class instead of the library R class.
Anyone have any idea why this happens and also if there is a way for me to force the application R class to be the one that the application project references?
— Update —
After some thought, I believe the problem is that the java compiler is inlining these values since they are final static variables and thus there would be no way to fix the problem with class loading changes.
Another idea I had at one point was to load all of the library project’s R classes, and map the variables to the application projects variables in the R class. Whenever getValue was called I would somehow (at runtime) analyze the stack to determine who the caller was and which R class was associated with that caller. From there I could determine what the associated ID was from the application project and the lookup would work as expected.
Anyone have an idea if this is possible? I know how to get the stack trace at runtime and could put in some logic to figure out who the caller was, but figuring out which R class is associated with that caller seems complicated, slow, and probably error prone.
— Update Again —
What about doing something like using http://www.csg.is.titech.ac.jp/~chiba/javassist/ to modify the class file at runtime, substituting the values where appropriate! This would be amazing but probably pretty difficult.
Android library project doesn’t run on itself, it is always compiled indirectly, by referencing the library in the dependent application and building that application.
The only purpose of R.java generated in library project’s gen folder is to accomplish the library project itself, so that IDE won’t pop any compile error when seeing code like
resources.getString(R.string.c);in the library project. The library project’s R.java is more like a temp file, it will never get pushed into application project’s apk file.Think the library project as two parts, the pure Java source (my-lib/src/) and Android resources (my-lib/res/). The pure Java source is compiled and end up as my-lib/bin/my-lib.jar (note that there is no R.class file in this so-called temporary jar), when you reference the library project in the application project, it does add my-lib.jar as a dependency in the application project (in Eclipse Package Explorer, check out
my-app -> Android Dependencies -> my-lib.jar). The Android resources are globally merged and compiled later when ADT think it is time to do so, usually when application project is built for run/debug.The R java files that get compiled and packed into the final apk are those under application project’s gen folder, in the apk file, they are injected under the corresponding package, like so:
The inconsistent R value generated between library and application project is not a problem, this is guaranteed by the SDK design. For you Robolectric pull request, always use R files from application project accordingly.
Most of info I mentioned can be found in Library Projects & Development considerations section from official dev guide.