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

时间:2012-12-08 16:28:16

标签: android unit-testing reflection classloader robolectric

我试图在Robolectric(android单元测试框架)中实现对Library项目的支持。我有框架加载库项目的所有资源,并已测试此工作正常。这个过程非常简单,我在project.properties中读取的RobolectricConfig中查找了循环中的android.library.reference.x值,并逐步遍历每个项目。

棘手的部分与在Library Project代码库中执行此操作时在运行时解析R引用有关。让我们举例来说,我们有一个带有1个库项目的应用程序,如下所示:

com.example.app ,它取决于库 com.example.lib

两个项目都有资源。在com.example.app项目下,我们有:

GEN / com.example.app.R

GEN / com.example.lib.R

com.example.app.R是com.example.lib.R的超集,因为它包含所有com.example.lib.R的定义,然后是因为自己的资源而添加的定义。我原本以为它们是相同的,但我错了,它们实际上是不同的。但是对于R的内部类((R.string,R.color,R.attr等),名称/值的映射与应用程序项目中com.example.app.R中的对应值相同。库项目的R类值不会映射到与应用程序项目中相同的值。

作为简化,让我们说它们看起来像这样:

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;

     }
}

然后,在图书馆项目下有 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;
     }
}

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

当Robolectric Test运行我的测试时,当它进入库项目代码库时,我会查找字符串 c ,结果如下:

 resources.getString(R.string.c);

所以当我进行查找时得到的是0x7f040003而不是0x7f050003。似乎大多数值都精确地映射了0x10000,但这并不总是有效,所以我不能依赖它。

我不明白的是,如果我们有两个具有相同软件包名称的类,并且应用程序中的一个类首先出现在类路径中(我在运行时通过打印出System.getProperty验证了这一点)&#not> 34; java.class.path")在运行时),为什么库项目仍然使用自己的 com.example.lib.R 定义而不是应用程序项目中的等效版本?它们都具有完全相同的规范名称。

我想象类加载器中的某些东西说这个类(让我们调用它的MyLibraryActivity) 只知道它依赖的com.example.lib.R类,并加载它。也许安全经理或某事做出决定?我真的不知道。但我希望以某种方式我可以改变这种行为,以便从库项目中的库项目资源查找可以解析为应用程序项目中的版本(因为它毕竟是在类路径中)。也许有一种方法我可以提前强制加载这个类,所以系统没有尝试重新加载它?

我知道库项目和应用程序项目之间没有依赖关系,我绝对不想添加该依赖项,但是我希望运行时的值能够从应用程序中进入&# 39; s R类而不是库R类。

任何人都知道为什么会发生这种情况,以及是否有办法迫使应用程序R类成为应用程序项目引用的类?

- 更新 -

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

我曾经有过的另一个想法是加载所有库项目的R类,并将变量映射到R类中的应用程序项目变量。每当调用getValue时,我会以某种方式(在运行时)分析堆栈以确定调用者是谁以及哪个R类与该调用者相关联。从那里我可以确定应用程序项目中关联的ID是什么,并且查找将按预期工作。

任何人都知道这是否可行?我知道如何在运行时获取堆栈跟踪,并可以输入一些逻辑来确定调用者是谁,但是确定哪个R类与该调用者相关联似乎很复杂,很慢,并且可能容易出错。

- 再次更新 - 如何使用http://www.csg.is.titech.ac.jp/~chiba/javassist/在运行时修改类文件,在适当的地方替换值!这将是惊人的,但可能相当困难。

2 个答案:

答案 0 :(得分:2)

Android库项目本身不运行,它总是间接编译,通过引用依赖应用程序中的库并构建该应用程序。

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

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

编译并打包到最终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

库和应用程序项目之间生成的R值不一致不是问题,这是由SDK设计保证的。对于Robolectric pull请求,请始终相应地使用应用程序项目中的R文件。

我提到的大多数信息都可以在Library Projects & Development considerations section from official dev guide找到。

答案 1 :(得分:2)

由于Library项目的资源总是包含在Application项目中,因此您始终可以将固定值分配给Library项目中的资源。

这可以解决您的问题。 您可以声明public.xml以指定固定值,以便将相同的值导入到Application项目中。

根据您资源库中的资源数量,这可能会很乏味。

您需要在res / values

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

有关详细信息,请查看此post