4

我正在尝试在 Robolectric(android 单元测试框架)中实现对库项目的支持。我有框架为库项目加载所有资源,并且已经测试过它可以正常工作。这个过程非常简单,从我在 project.properties 中读取的 RobolectricConfig 并在循环中查找 android.library.reference.x 值,并在每个项目中递归。

棘手的部分与在运行时从库项目代码库中解析 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 值不同(应用程序中的 c = 0x7f050003 和库中的 c = 0x7f040003)。我知道库不了解应用程序,因此它自己的 R 类不可能生成相同的值,因此应用程序类必须重新映射其值。我想知道的是,我怎么可能在运行时使用应用程序项目中定义的 com.example.lib.R 值,而不是库的 com.example.lib.R 类中定义的值?

失败的是,当 Robolectric 测试运行器运行我的测试时,当它进入库项目代码库时,我会查找字符串c,如下所示:

 resources.getString(R.string.c);

所以当我进行查找时得到的是 0x7f040003 而不是 0x7f050003。似乎大多数值都恰好比 0x10000 高,但这并不总是有效,所以我不能依赖它。

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

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

我知道库项目和应用程序项目之间没有依赖关系,我绝对不想添加这种依赖关系,但是我希望运行时的值来自应用程序的 R 类而不是库 R 类.

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

- 更新 -

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

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

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

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

4

2 回答 2

2

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

那可以解决你的问题。您可以声明 public.xml 以分配固定值,以便将相同的值也导入到应用程序项目中。

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

您需要在 res/values 中创建 public.xml

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

有关更多详细信息,请查看此帖子

于 2012-12-17T16:25:07.607 回答
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 包资源管理器中,签出my-app -> Android Dependencies -> my-lib.jar)。Android 资源会在 ADT 认为是时候进行全局合并和编译时,通常是在为运行/调试构建应用程序项目时。

编译打包到最终 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 拉取请求,请始终相应地使用应用程序项目中的 R 文件。

我提到的大部分信息都可以在官方开发指南的图书馆项目和开发注意事项部分找到。

于 2012-12-10T22:52:30.860 回答