简体   繁体   English

Android Robolectric Library类支持。 如何从应用程序项目加载库类R引用

[英]Android Robolectric Library class support. How to have library class R references load from application project

I'm trying to implement support for Library projects in Robolectric (android unit testing framework). 我正在尝试在Robolectric(android单元测试框架)中实现对Library项目的支持。 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. 这个过程非常简单,我在project.properties中读取的RobolectricConfig中查找了循环中的android.library.reference.x值,并逐步遍历每个项目。

The tricky part has to do with resolving R references at runtime when doing so from the Library Project codebase. 棘手的部分与在运行时从Library Project代码库中解析R引用有关。 Lets say for example that we have an application with 1 library project like so: 让我们说例如我们有一个带有1个库项目的应用程序,如下所示:

com.example.app which depends on library com.example.lib com.example.app ,它依赖于库com.example.lib

Both projects have resources in them. 这两个项目都有资源。 Under the com.example.app project we have: 在com.example.app项目下,我们有:

gen/com.example.app.R GEN / com.example.app.R

gen/com.example.lib.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. com.example.app.R是com.example.lib.R的超集,因为它包含所有com.example.lib.R的定义,然后包含由于其自身资源而添加的定义。 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. 但是对于R的内部类((R.string,R.color,R.attr等),名称/值的映射与应用程序项目中com.example.app.R中的对应值相同。库项目的R类值不会映射相同的应用程序项目中的值。

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 : 然后,在库项目下有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. 那么所发生的事情是该库在string.xml中定义了c而项目没有。 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). 库值中R.string.c的值与应用程序的R值不同(app中c = 0x7f050003,库中c = 0x7f040003)。 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. 我知道库不知道应用程序,因此它自己的R类不可能生成相同的值,因此应用程序类必须重新映射其值。 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? 我想知道的是,我怎么可能在运行时使用应用程序项目中定义的com.example.lib.R值而不是库的com.example.lib.R类中定义的值?

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: 失败的是,当Robolectric Test运行器运行我的测试时,当它进入库项目代码库时,我会查找字符串c ,如下所示:

 resources.getString(R.string.c);

So what I get when I do the lookup is 0x7f040003 instead of 0x7f050003. 所以当我进行查找时得到的是0x7f040003而不是0x7f050003。 It seems most values map exactly 0x10000 higher but that does not always work out so I can't rely on that. 似乎大多数值都精确地映射了0x10000,但这并不总是有效,所以我不能依赖它。

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? 我不明白的是,如果我们有两个具有相同包名的类,并且应用程序中的一个类首先出现在类路径中(我在运行时通过打印出System.getProperty(“java.class.path”)验证了) )在运行时),为什么库项目仍然使用自己的com.example.lib.R定义而不是应用程序项目中的等效版本? 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. 我想象类加载器中的某些东西说这个类(让我们调用它的MyLibraryActivity)只知道它所依赖的com.example.lib.R类,并加载它。 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. 我理解库项目和应用程序项目之间没有依赖关系,我绝对不想添加这种依赖性,但是我希望运行时的值来自应用程序的R类而不是库R类。

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? 任何人都知道为什么会发生这种情况,如果有办法让我强制应用程序R类成为应用程序项目引用的那个?

-- 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. 经过一番思考后,我认为问题是java编译器正在内联这些值,因为它们是最终的静态变量,因此没有办法解决类加载更改的问题。

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. 我曾经有过的另一个想法是加载所有库项目的R类,并将变量映射到R类中的应用程序项目变量。 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. 每当调用getValue时,我会以某种方式(在运行时)分析堆栈以确定调用者是谁以及哪个R类与该调用者相关联。 From there I could determine what the associated ID was from the application project and the lookup would work as expected. 从那里我可以确定应用程序项目中关联的ID是什么,并且查找将按预期工作。

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. 我知道如何在运行时获取堆栈跟踪并且可以输入一些逻辑来确定调用者是谁,但是确定哪个R类与该调用者关联似乎复杂,缓慢且可能容易出错。

-- 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! - 再次更新 - 如何使用http://www.csg.is.titech.ac.jp/~chiba/javassist/在运行时修改类文件,在适当的地方替换值! 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. Android库项目本身不运行,它总是通过引用依赖应用程序中的库并构建该应用程序来间接编译。

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); 在库项目的gen文件夹中生成的R.java的唯一目的是完成库项目本身,这样IDE在看到像resources.getString(R.string.c);这样的代码时不会弹出任何编译错误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. 库项目的R.java更像是一个临时文件,它永远不会被推入应用程序项目的apk文件中。

Think the library project as two parts, the pure Java source (my-lib/src/) and Android resources (my-lib/res/). 将库项目视为两个部分,纯Java源(my-lib / src /)和Android资源(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 ). 当您在应用程序项目中引用库项目时,纯Java源代码将被编译并最终作为my-lib / bin / my-lib.jar(请注意,在这个所谓的临时jar中没有R.class文件) ,它确实将my-lib.jar添加为应用程序项目中的依赖项(在Eclipse Package Explorer中,查看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. 当ADT认为是时候这样做时,Android资源是全局合并和编译的,通常是在为运行/调试构建应用程序项目时。

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: 编译并打包到最终apk中的R java文件是应用程序项目的gen文件夹下的那些文件,在apk文件中,它们被注入相应的包下,如下所示:

com/
  example/
    lib/
      R       <- com.example.lib.R.java
      ... ... <- package/class from my-lib.jar
    app/
      R       <- com.example.app.R.java
      ... ... <- package/class from application project

The inconsistent R value generated between library and application project is not a problem, this is guaranteed by the SDK design. 在库和应用程序项目之间生成的不一致R值不是问题,这由SDK设计保证。 For you Robolectric pull request, always use R files from application project accordingly. 对于你Robolectric拉请求,总是相应地使用来自应用程序项目的R文件。

Most of info I mentioned can be found in Library Projects & Development considerations section from official dev guide . 我提到的大部分信息都可以在官方开发指南的“图书馆项目和开发考虑”部分找到。

Since Library project's resources are always included in the Application project, you could always assign fixed values to resources in Library project. 由于Library项目的资源始终包含在Application项目中,因此您始终可以将固定值分配给Library项目中的资源。

That could solve your problem. 这可以解决你的问题。 You can declare public.xml to assign fixed values so that the same values are imported to Application project too. 您可以声明public.xml以指定固定值,以便将相同的值导入到Application项目中。

Depending upon the number of resources in your Library this might be tedious. 根据您的资源库中的资源数量,这可能是乏味的。

You need to create public.xml in res/values 您需要在res / values中创建public.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <public type="string" name="c" id="0x7f050003" />
</resources>

For more details check this post 有关详细信息,请查看此帖子

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM