Android R.java类的手动生成

Android中的资源和R.java类

在Android项目中的res目录中包含了项目使用的各种资源,这些资源全部都分布在res目录下的各个子目录中。每个资源都有两个属性,一个是资源的名字,一个是资源的类型。此外,res目录下的资源在编译后都会有一个对应的id。

R.java类(以下简称R类)是Android中一个非常重要的类,其中定义了res目录中全部资源的id。在代码中通过R类获取到资源的id后,即可调用Android API来获取和使用对应的资源。例如:

ImageView imageView = (ImageView)findViewById(R.id.imageView);
TextView textView = (TextView)findViewById(R.id.textView);
imageView.setImageResource(R.drawable.bg)
textView.setText(R.string.app_name)

R类的生成

R类并不包含在项目代码中,而是由Android SDK在编译阶段通过aapt工具生成的。一般情况下,开发者不需要关注R类的生成,直接在代码中使用即可。然而在某些情况下需要由开发者手动生成R类,并放到项目代码中。

为什么需要手动生成R.java类

Android library

Android 项目根据用途的不同分为app项目和library项目。app项目用来生成可以在Android系统上运行的apk程序,提交到应用市场给用户使用。而library项目并不会生成apk,而是生成一个sdk,提供给其他开发者使用。

对library项目来说,如果library工程中包含了资源,如layout,drawable,string,dimen等,那么需要将这些资源文件和编译后的代码一起放到sdk中。对Eclipse library工程,是将整个res目录原样放到sdk中。对Android Studio的library工程,会将整个res目录打包到aar文件中。

Android library工程中的R类

对包含资源的library工程来说,和app工程一样,需要在代码中通过资源id来获取对应的资源。同样可以在library工程代码中通过library工程包名下的R类来获取对应资源的id。但是library工程中的R类并不会包含在library工程编译后的jar包或arr包中,也就是说,library工程开发完成后,提供给其他开发者的sdk中并没有包含这个R类。这个R类同样是在app工程编译时生成的。那么app工程在编译时是如何知道需要生成library工程包名下的R类呢?

一个app工程可以包含多个library工程(在Android Studio中称为module)。当一个app工程在编译时,会遍历其所引用的所有library工程,生成各个library工程对应的R.java类。这里会用到每个library sdk中提供的另外一个文件AndroidManifest.xml。在library工程的AndroidManifest.xml文件中包含了各个library工程的包名称,通过这个文件就可以知道各个library工程的包名,从而生成各个library工程包名下的R类。

例如,一个被引用的library工程中的AndroidManifest.xml文件中配置的package=”com.cclink.mylib”,则app工程在编译时会为该library工程生成一个com.cclink.mylib.R的类。

最后app工程中全部java代码,app工程的R类,所有library工程的R.java类,都会被编译,并和所有library工程的jar包合并到一起,然后再转换成dex文件。

Android library sdk的特殊使用方式

一个library工程开发完成后会以sdk的形式提供给其他开发者来使用。如前所述,其他开发者在开发app时,如果通过引用的方式来使用该library sdk,则app工程编译时会自动为该library sdk下的资源生成一个对应的R类,并打包到apk中。
然后,app的开发者并不总是希望通过在Android Studio中添加工程引用的方式来使用一个library sdk。他们有时需要将library sdk中的jar和res直接拷贝到app工程来使用,有时需要将所有依赖的sdk中的jar和res拷贝到一起,合并成一个library来使用。
这样做有时是为了方便,减少工作量,有时则是没有办法通过工程引用方式来使用library。这在游戏开发时非常常见。很多游戏引擎只提供了一个目录,用来放置各类第三方的Android插件,根本就找不到一个Android的主工程,来建立引用关系。APK的编译打包也是在游戏引擎中封装好了,并不需要通过Eclipse或Android Studio来打包。
例如,Unity3D中通常都是将Android插件(也就是Android library工程对应的sdk)直接拷贝到/Assets/Plugins/Android目录中来使用,虽然Unity3D提供了导出Android工程的功能,但使用起来非常麻烦。开发者很可能不希望这样去做。

Android library sdk特殊使用方式带来的问题

如果将sdk中的文件拷贝到app工程中来使用,或者合并所有的sdk到一个library中,由于这时所有library都混合在一起,app在编译时根本就找不到原先各个library中的AndroidManifest.xml文件,也就不会为各个library生成对应的R.java类。当app在运行时,library中代码由于找不到对应的R类,会出现ClassNotFound的异常。Unity虽然支持各个sdk分开存放,不需要将sdk合并到一起,但是Unity编译生成的apk文件中不会包含任何R类,即使是app包名下的R类也不会生成。

解决没有R类带来的问题

要解决没有R类的问题,需要从两个角度来考虑。

从library开发者的角度来看,需要让library工程编译后的sdk在这种没有R类情况下能够正常使用。为了达到此目的,需要将代码中所有通过包名下R类来获取资源id的方式替换为其他的实现方式。Android SDK提供了getResources().getIdentifier()方法来获取资源id,library开发者可以手动将代码中所有需要使用资源id的地方用这种方式来获取。但getResources().getIdentifier()的参数是资源的名字,是一个字符串,这样在获取资源id时,就不能使用IDE的代码提示,自动补全等功能了。

从app开发者的角度来看,如果需要使用的sdk没有对这种使用方式做兼容,而且不能通过引用方式来使用该sdk时,需要能够让该sdk打包到apk后能够正常运行。为了达到此目录,需要为该sdk生成一个该sdk包名下的R类,然后将该R类编译打包到apk中。这样sdk中的代码在运行就不会找不到对应的R类了。

AndroidRClassGenerator工具

AndroidRClassGenerator是一个用来生成R.java类的Python脚本,基于Python2.7版本。它可以以两种方式生成R.java类。工具下载地址附在文章最后。
对library开发者来说,AndroidRClassGenerator能够生成一个新的R类,新的R类中使用了不同的方式来获取资源id,同时提供了和原先R类一样的资源访问的方式。因此,library开发者可以像开发app一样使用R类来获取资源,只需要将代码中原先的import R类的信息替换替换为新的R类即可访问到对应的资源id。AndroidRClassGenerator可同时生成R类,并完成代码中import信息的替换。
对app开发者来说,AndroidRClassGenerator可以根据library工程中的res目录,和指定的包名来生成对应的R类。

参数配置

在使用前需要先配置config.ini,config.ini中各个参数的含义如下。

  1. ProjectOrResDir:表示library工程的路径(可以是Eclipse的工程,也可以是Android Studio的工程)。也可以直接指定到某个资源目录。
  2. sdkdir:Android SDK的路径
  3. RClassPackage:要生成的目标R类的包名
  4. ReplaceCode:是否替换代码中的import信息,true表示替换,false表示不替换。
    例如library工程的包名为x.y.z,RClassPackage为a.b.c,则当ReplaceCode为true时除了会生成一个a.b.c.R.java类外,还会自动将代码中所有的import x.y.z.R,改为import a.b.c。然代码通过新生成的R类来获取资源id。
    只有ProjectOrResDir配置的library工程的路径时此参数才有效,当ProjectOrResDir配置的是资源目录时,此项配置会被忽略。

library开发者的使用方法

对library开发者来说,需要生成一个包含指定包名的R类放到代码中,然后将其他代码中所有用到默认R类(即该library工程包名下R类)的地方都替换为生成的R类。
具体配置流程如下。

  1. 配置ProjectOrResDir为工程的路径,可以是Eclipse的工程,也可以是Android Studio的工程。
  2. 配置sdkdir为本地的Android SDK的路径
  3. 配置RClassPackage为需要生成的R类的包名,此包名不可和library的包名完全相同。以免在工程编译时出现相同类名的编译错误。
  4. ReplaceCode一般应配置为true。如果已经替换过一次,且没有新的代码需要替换,可以设置为false。
  5. 运行RClassGenerator.py
  6. 在library工程入口代码处(例如初始化,或者第一个界面显示时)调用新生成R类的R.init(context)方法,context是某个Context对象。

app开发者的使用方法

对app开发者来说,需要生成一个包含library包名的R类放到app工程的代码中。
具体配置流程如下。

  1. 如果是Eclipse或完整的Android Studio的library工程,配置ProjectOrResDir为library工程中res目录的路径,如果是单独的aar文件,需要先将res目录从aar文件中解压出来,然后配置ProjectOrResDir为解压后的res目录路径。
  2. 配置sdkdir为本地的Android SDK的路径。
  3. 配置RClassPackage为library的包名,library的包名可以在library工程,或aar压缩包中的AndroidManifest.xml文件中找到。
  4. ReplaceCode参数在ProjectOrResDir配置为资源目录时会被自动忽略。这里不需要配置。
  5. 运行RClassGenerator.py。
  6. 生成的R.java类在res目录的同级目录中,将生成的R类拷贝到app项目中,这可能是一个app工程,也可能是工程中某个可以编辑java代码的目标,有时甚至需要手动将R类编译成class并打包成jar放到app工程中。
  7. 在app工程某个入口处调用新生成R类的R.init(context)方法,context是某个Context对象。这可能是在主Activity的onCreate()中调用,也可能是通过jni方式调用。

实现原理

通过用AndroidRClassGenerator生成R类代替了原本应该在app编译时生成的R类,用这种方式来让library中代码能够正确的得到资源id。由于资源id只有在apk编译时才能最终确定,所以AndroidRClassGenerator生成的R类不能用某次编译时的常量来代替。
有两种方式可以获取到资源id:一种是通过反射app包名下的R类来获取,一种是通过context.getResources().getIdentifier()方法来获取。
通过反射方式来获取资源id的原理是,虽然app编译时不会为每个library生成单独的R类,但是仍然会生成一个app包名下的R类,这个R类中包含了所有library中的资源id(当然也还包含app自身的全部资源的id),通过反射读取这个R类,可以得到对应的资源id。
context.getResources().getIdentifier()是android sdk提供的一个api,通过此接口可以获取到指定类型,指定名称的资源id。
AndroidRClassGenerator生成的R类同时包含了这两种实现方式,它首先会尝试通过反射方式来获取系统资源id,如果反射方式获取失败,再尝试用context.getResources().getIdentifier()方式来获取。
之所以先用反射方式来获取资源id,是因为反射方式比context.getResources().getIdentifier()方式要快得多。但反射方式的缺点是必须要存在一个app包名下的R类。如果这个类不存在(例如,通过Unity3D直接编译出的apk中就不包含R类),则反射方式失效。
无论有没有app包名下的R类,context.getResources().getIdentifier()都能够获取到资源id,但context.getResources().getIdentifier()方式比反射方式要慢一些。

工具路径:http://download.csdn.net/download/ccpat/9443653

Android Studio的打包问题

Android Studio从某个版本(具体哪个版本没有去考究)开始,在编译时会自动删除项目中的R.class类,这样在运行时会出现找不到R类的问题。这时只需要在脚本中将R.java类改为一个其他的名字就可以了。也就是将”public final class R”改为”public final class XXXR”,同时将destRClassFile = os.path.join(filePath, ‘R.java’)改为destRClassFile = os.path.join(filePath, ‘XXXR.java’)。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 像素格子 设计师:CSDN官方博客 返回首页