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”了。

Android库项目中的资源ID冲突的更多相关文章

  1. Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突

    Android插件化(六): OpenAtlasの改写aapt以防止资源ID冲突 转 https://www.300168.com/yidong/show-2791.html   核心提示:引言And ...

  2. 如何在Android Studio项目中导入开源库?

    前两天,谷歌发布了Android Studio 1.0的正式版,也有更多的人开始迁移到Android Studio进行开发.然而,网上很多的开源库,控件等还是以前的基于Eclipse进行开发,很多人不 ...

  3. 解决资源id冲突

    --摘自<android插件化开发指南> 1.一套完整的Android App打包流程(Gradle方案) 第一步:aapt.为res目录下的资源生成R.java文件,同时为Android ...

  4. Android Studio 项目中集成百度地图SDK报Native method not found: com.baidu.platform.comjni.map.commonmemcache.JNICommonMemCache.Create:()I错误

    Android Studio 项目中集成百度地图SDK报以下错误: java.lang.UnsatisfiedLinkError: Native method not found: com.baidu ...

  5. 【Android】项目中每个文件夹的作用

    1. src:存放所有的*.java源程序. 2. gen:为ADT插件自动生成的代码文件保存路径,里面的R.java将保存所有的资源ID. 3. assets:可以存放项目一些较大的资源文件,例如: ...

  6. 解决项目中.a文件的冲突

    .a文件是静态文件,有多个.o文件组合而成的,在ios项目开发中,当引用第三方库的时候,时不时的会碰到诸如库冲突.库包含了某些禁用的API等问题,而这些库往往都被打包成了静态库文件(即 .a文件)来使 ...

  7. 简述MVC框架模式以及在你(Android)项目中的应用

    标题是阿里电话面试的问题,一直以为自己很清楚MVC模式,结果被问到时,居然没法将MVC和Android中各个组件对应起来,所以,面试肯定挂了,不过面试也是学习的一种方式,可以知道大公司看中什么,以及自 ...

  8. Android获取所有应用的资源id和对应的uri

    背景 在某些应用中,为了实现应用apk资源放入重复利用,或者使用反射得到本应用的资源,需要使用反射方式获得,但Resources类中也自带了这种获取方式,并且功能更加强大 你可以获取string,co ...

  9. java web项目中打开资源文件中文乱码

    1 java web项目中经常使用多模块管理.在某一个模块中添加了一些资源文件.但不是启动项目.有时候需要在程序中读取资源文件内容,打包后放到容器中就不能正常运行了.需要将所有资源文件放到启动项目的 ...

随机推荐

  1. Storm业务功能

    监控平台当前使用storm对日志进行流式分析计算,用于支撑监控数据清洗,后来逐渐在storm上搭建起数据在线关联,数据离线关联,明细数据清洗,日志搜索等功能,本章节对各功能进行简要概述. 对storm ...

  2. nodejs之socket.io模块——实现了websocket协议

    Nodejs实现websocket的4种方式:socket.io.WebSocket-Node.faye-websocket-node.node-websocket-server,主要使用的是sock ...

  3. c#判断外部可执行程序是否已打开(若未打开则打开)

    #region 通过当前代码执行路径向上找到相关exe,并根据processes.Length判断是否已启动 private bool CheckAndOpenExe(string exeName) ...

  4. mysql 主从同步 mysql代理服务器

    搭建mysql主从同步(实现数据自动备份)实例:把主机192.168.4.100的数据库配置为主机192.168.4.99的从数据库 主数据库服务器配置修改配置文件: [root@mysql ~]# ...

  5. SyntaxError: Missing parentheses in call to 'print'

    C:\Users\konglb>python Python 3.6.3 (v3.6.3:2c5fed8, Oct  3 2017, 17:26:49) [MSC v.1900 32 bit (I ...

  6. canvas API总结

    从简单的基本图形,到复杂炫酷的动画,通过canvas元素获取的2D图形渲染上下文CanvasRenderingContext2D,能够使用丰富的API来进行图形绘制.这篇文章将会总结在之前的canva ...

  7. Minicom串口工具安装及配置

    Minicom串口工具安装及配置 1. 简述 嵌入式开发多采用串口线连接电脑进行开发及调试. 2 安装及配置串口工具(以Minicom为例) Tiny4412开发板提供的有RS232电平的DB9公头接 ...

  8. AHB/APB简介

    AHB AHB总线互联结构图 随着深亚微米工艺技术日益成熟,集成电路芯片的规模越来越大.数字IC从基于时序驱动的设计方法,发展到基于IP复用的设计方法,并在SOC设计中得到了广泛应用.在基于IP复用的 ...

  9. NLP︱LDA主题模型的应用难题、使用心得及从多元统计角度剖析

    将LDA跟多元统计分析结合起来看,那么LDA中的主题就像词主成分,其把主成分-样本之间的关系说清楚了.多元学的时候聚类分为Q型聚类.R型聚类以及主成分分析.R型聚类.主成分分析针对变量,Q型聚类针对样 ...

  10. ip2long的用法

    ip2long:将IPv4的ip地址(以小数点分隔形式)转换为int Description int ip2long ( string ip_address ) 如果ip地址非法,返回FALSE(PH ...