AndroidR.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(View);
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中提供的另外⼀个⽂件l。在library⼯程的l⽂件中包含了各个library⼯程的包名称,通过这个⽂件就可以知道各个library⼯程的包名,从⽽⽣成各个library⼯程包名下的R类。
例如,⼀个被引⽤的library⼯程中的l⽂件中配置的package=”lib”,则app⼯程在编译时会为该library⼯程⽣成⼀个lib.R的类。
最后app⼯程中全部java代码,app⼯程的R类,所有library⼯程的R.java类,都会被编译,并和所有library⼯程的jar包合并到⼀起,然后再转换成dex⽂件。
Android library sdk的特殊使⽤⽅式
⼀个library⼯程开发完成后会以sdk的形式提供给其他开发者来使⽤。如前所述,其他开发者在开发ap
p时,如果通过引⽤的⽅式来使⽤该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中,由于这时所有libra
ry都混合在⼀起,app在编译时根本就不到原先各个library中的l⽂件,也就不会为各个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时除了会⽣成⼀个R.java类外,还会⾃动将代码中所有的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压缩包中的l⽂件中到。
4. ReplaceCode参数在ProjectOrResDir配置为资源⽬录时会被⾃动忽略。这⾥不需要配置。
5. 运⾏RClassGenerator.py。
6. ⽣成的R.java类在res⽬录的同级⽬录中,将⽣成的R类拷贝到app项⽬中,这可能是⼀个app⼯程,也可能是⼯程中某个可以编辑
java代码的⽬标,有时甚⾄需要⼿动将R类编译成class并打包成jar放到app⼯程中。
python转java代码7. 在app⼯程某个⼊⼝处调⽤新⽣成R类的R.init(context)⽅法,context是某个Context对象。这可能是在主Activity的onCreate()中
调⽤,也可能是通过jni⽅式调⽤。
实现原理
通过⽤AndroidRClassGenerator⽣成R类代替了原本应该在app编译时⽣成的R类,⽤这种⽅式来让library中代码能够正确的得到资源id。由于资源id只有在apk编译时才能最终确定,所以AndroidRClassGenerator⽣成的R类不能⽤某次编译时的常量来代替。
有两种⽅式可以获取到资源id:⼀种是通过反射app包名下的R类来获取,⼀种是通过Resources().getIdentifier()⽅法来获取。
通过反射⽅式来获取资源id的原理是,虽然app编译时不会为每个library⽣成单独的R类,但是仍然会⽣成⼀个app包名下的R类,这个R类中包含了所有library中的资源id(当然也还包含app⾃⾝的全部资源的id),通过反射读取这个R类,可以得到对应的资源id。
之所以先⽤反射⽅式来获取资源id,是因为反射⽅式⽐Resources().getIdentifier()⽅式要快得多。但反射⽅式的缺点是必须要存在⼀个app包名下的R类。如果这个类不存在(例如,通过Unity3D直接编译出的apk中就不包含R类),则反射⽅式失效。
⽆论有没有app包名下的R类,Resources().getIdentifier()都能够获取到资源id,但
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’)。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。