1、前言

Android Studio对模块化开发提供的一个很有用的功能就是可以在主项目下新建库项目(Module),但是在使用库项目时却有一个问题就是资源ID冲突,因为编译时SDK会自动帮我们处理这个问题,所以一般我们不会察觉到,但是在某些情况下,我们需要意识到这个问题的存在。

比如,在新建的库项目中使用如下代码:

public void onButtonClick(View view) {
switch (view.getId()) {
case R.id.button_1:
break;
case R.id.button_2;
break;
}
}

IDE会提示:

Resource IDs cannot be used in a switch statement in Android library modules less.

Validates using resource IDs in a switch statement in Android library module. Resource IDs are non final in the library projects since SDK tools r14, means that the library code cannot treat these IDs as constants.

再比如,我们在库项目中以如下方式使用ButterKnife,编译时就会报错。

@OnClick(R.id.button_1)
public void onButtonClick(View view) { }

2、分析

无论是 switch 语句还是注解,都有一个要求就是使用的值必须是常量。在主项目中, R类中的成员变量都被 static final 修饰,而在库项目中仅被 static 修饰。

// 库项目中生成的R类:
public final class R {
public static final class id {
public static int button_1 = 0x7f0c0001;
}
} // 主项目中生成的R类:
public final class R {
public static final class id {
public static final int text_1 = 2131165184;
}
}

为什么库项目中生成的资源ID没有被 final 修饰呢?官方解释如下:

Non-constant Fields in Case Labels

当多个库项目进行合并时,不同项目中的资源ID可能会重复。在ADT 14之前,无论是主项目还是库项目,资源ID统一被定义为 final 类型的静态变量。这样照成的结果就是主项目进行编译时一旦发现资源ID冲突,库项目中对应的资源文件以及引用资源文件的代码都需要重新编译。

如果代码中使用了被 static final 修饰的变量,那这个变量实际上就是一个常量,编译时会直接使用它的值进行替换。在编译时,如果库项目与主项目的资源ID发生了重复,资源被分配了新的ID后库项目之前编译过的代码也就失效了。

那么当库项目R类中的变量仅被 static 修饰后会起到什么作用呢,我们可以看一下编译后的字节码再反编译后的样子。

// 主项目中的Activity:
public class MainActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 源代码:setContentView(R.layout.activity_main);
this.setContentView(2131296283);
}
}
// 库项目中的Activity:
public LibActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.activity_lib);
}
}

主项目R类中的资源ID被 static final 修饰,编译时直接被替换成了对应的常量。库项目R类中的资源ID仅被 static 修饰,所以保留了变量。这样当资源ID发送冲突时,主项目R类不变,修改库项目R类中的变量,库项目已经编译过的代码仍有效。

3、ButterKnife中的R2类

既然库项目中的资源ID不可以定义为常量,那如何在库文项目使用ButterKnife呢,作者提供了R2类供我使用。

@OnClick({R2.id.button_1, R2.id.button_2})
public void onButtonClick(View view) {
int id = view.getId();
if (id == R.id.button_1) {
// ...
} else if (id == R.id.button_2) {
// ...
}
}

没错在注解中使用R2类,但是在代码里还是需要使用R类,因为R类中的ID不是常量,所以只能使用 if 语句进行判断。

先来看一下ButterKnife为我们生成的R2类与R类有什么不同:

// 库项目中的R类:
public final class R {
public static final class id {
public static int button_1 = 0x7f0c0001;
}
}
// 库项目中ButterKnife为我们生成的R2类:
public final class R2 {
public static final class id {
public static final int button_1 = 0x7f0c0001;
}
}  

ButterKnife做的工作很简单,仅仅是把R类中的变量搬到了R2类里,然后给所有的变量都加上了 final 。根据前面所说,当项目整体编译时,库项目的资源ID一旦与主项目的资源ID发送冲突,库项目的资源会被重新分配ID导致其R类被修改。显然这个过程并不涉及R2类,R2类中保留的仍然是过时的ID。但是ButterKnife提供的注解的作用是什么,它们并不是为了提供运行时信息,而是为了在编译时生成代码。

public class LibActivity_ViewBinding implements Unbinder {

    private LibActivity target;
private View view_button_1;
private View view_button_2; @UiThread
public LibActivity_ViewBinding(final LibActivity target, View source) {
this.target = target;
View view = Utils.findRequiredView(source, R.id.button_1, "method 'onButtonClick'");
this.view_button_1 = view;
//view.setOnClickListener....
view = Utils.findRequiredView(source, R.id.button_2, "method 'onButtonClick'");
this.view_button_2 = view;
//view.setOnClickListener....
}
}

在ButterKnife生成的代码中,使用的仍然是R类。R2起到的作用仅仅是提供一个符号名,只要让程序知道在生成代码时对应哪一个变量即可。这个方法可以说是很“tricky”了。

最新文章

  1. 输入scanner,if-else
  2. 关于angularJS绑定数据时自动转义html标签
  3. alv中编辑的时候quan字段小数位数被截取掉
  4. 在matlab2015b中配置vlfeat-0.9.18
  5. basicjava
  6. 7_Table Views
  7. $.getjson方法配合在url上传递jsoncallback=?参数,实现跨域获取指定网站某商品访问量
  8. 02.PHP7.x编译详解
  9. 读书笔记 effective c++ Item 46 如果想进行类型转换,在模板内部定义非成员函数
  10. 团队作业4---第一次项目冲刺(AIpha版本)第二天
  11. Python基础__字符串拼接、格式化输出与复制
  12. json格式new Date()的一个小坑
  13. VSCode TypeScript开发
  14. Libgdx学习记录28——创建Desktop程序
  15. Android之sandbox技术
  16. 数据库sharding系列好文收藏
  17. Python标准库笔记(9) — functools模块
  18. 3D游戏与计算机图形学中的数学方法-视截体
  19. Redis底层数据类型
  20. socket传送文件

热门文章

  1. 让你的 Xcode8 继续使用插件
  2. php字符串处理函数相关操作
  3. css实现固定高度及未知高度文字垂直居中的完美解决方案
  4. jquery 图片手风琴效果
  5. linux管道学习(二)
  6. jQuery之位置
  7. Python学习入门基础教程(learning Python)--3.2 if-else分支语句
  8. jq屏蔽f5
  9. win8.1 64位+oracle11g R2 64位 +powerdesigner破解版 64位+PL/SQL
  10. 简述Hibernate三种开发方式
  11. [笔记]LibSVM源码剖析(java版)
  12. hadoop+hive+spark搭建(三)
  13. NYOJ--353--bfs+优先队列--3D dungeon
  14. 我的第一个python web开发框架(6)——第一个Hello World
  15. Python学习笔记(十一)
  16. c#动态加载卸载DLL
  17. CentOS 7 下使用虚拟环境Virtualenv安装Tensorflow cpu版记录
  18. 第八次作业:聚类--K均值算法:自主实现与sklearn.cluster.KMeans调用
  19. Linux进程的实际用户ID和有效用户ID
  20. Perl基础速成