我們知道 Android 項(xiàng)目中會(huì)通過(guò)自動(dòng)生成一個(gè) R.java 類(lèi)的方式來(lái)保存項(xiàng)目中所有資源文件的標(biāo)識(shí)绰播。在主項(xiàng)目中生成的 R.java 中的資源聲明是一個(gè)靜態(tài)常量花枫,而在 module 中它卻是一個(gè)靜態(tài)變量刻盐。這是為什么呢掏膏?我們知道在 java 中如果某個(gè)值被聲明成常量(用 final 修飾),則在編譯后敦锌,該常量會(huì)被直接替換成值馒疹。而在 java 語(yǔ)法中,注解的屬性和 switch-case 中的 case 表達(dá)式乙墙,必須使用常量或者直接使用值颖变,否則會(huì)報(bào)語(yǔ)法錯(cuò)誤。下面我們會(huì)展開(kāi)討論下為什么 module 中的 R 類(lèi)中聲明的資源標(biāo)識(shí)不是 final 的听想,這些又導(dǎo)致了哪些現(xiàn)象腥刹?
主項(xiàng)目中
比如你在主項(xiàng)目中創(chuàng)建了一個(gè) activity_main.xml 的布局文件,則 R.java 中會(huì)自動(dòng)加入一行如下靜態(tài)常量汉买。
public static final class layout {
...
public static final int activity_main=0x7f09001b;
此后你就可以通過(guò) R.layout.activity_main
的方式使用該資源
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
我們編譯上述代碼后得到 MainActivity.class
衔峰,會(huì)發(fā)現(xiàn)里面的靜態(tài)常量被直接替換成了值。代碼運(yùn)行過(guò)程中蛙粘,就可以直接通過(guò)值來(lái)找到對(duì)應(yīng)資源了垫卤。
public class MainActivity extends AppCompatActivity {
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131296283);
}
}
Module中
然后我們?cè)僭谝粋€(gè) module 中同樣創(chuàng)建一個(gè) MainActivity 和對(duì)應(yīng)的資源,我們查看該 module 下的 R.java
组题。
public static final class layout {
...
public static int activity_main = 0x7f0f001c;
大家有發(fā)現(xiàn)區(qū)別了嗎葫男?在 module 中添加的該資源少了 final。我們?cè)賮?lái)看下 MainActivity.class 文件崔列。我們會(huì)發(fā)現(xiàn)此處的資源引用是使用的靜態(tài)變量方式,而未直接使用資源的值旺遮。
public class MainActivity extends AppCompatActivity {
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(layout.activity_main);
}
}
為什么這樣做
Android 中赵讯,如果你在 module 中添加了一個(gè)資源,就拿這里的 activity_main.xml 舉例耿眉。我們此處假設(shè)如果在 module 中也是 final 的边翼,那會(huì)出現(xiàn)什么情況?第一鸣剪,該 module 編譯后的代碼中該資源會(huì)被替換成值组底;第二,當(dāng)該 module 被添加到主項(xiàng)目中后筐骇,如果主項(xiàng)目中有一個(gè)同樣名稱(chēng)的資源债鸡,那么 module 中的該資源就會(huì)被替換;第三铛纬,主項(xiàng)目中會(huì)重新針對(duì)該資源生成一個(gè) ID厌均;最終就會(huì)出現(xiàn) module 中那個(gè)資源 ID 找不到了。所以呢告唆,這也是為什么 module 中的資源 ID 聲明不使用 final 的原因棺弊。
有關(guān)資源合并的規(guī)則晶密,可以參考下 google 的官方文檔
https://developer.android.com/studio/write/add-resources.html。
導(dǎo)致的幾個(gè)現(xiàn)象
1模她,這就是為什么當(dāng)主項(xiàng)目與 module 中有同樣資源時(shí)稻艰,module 卻會(huì)使用主項(xiàng)目的資源。
2侈净,這也是為什么我們?cè)?module 中無(wú)法針對(duì)資源使用 switch-case 方式的原因尊勿。
3,這也是為什么我們無(wú)法在 module 中直接使用 butterknife用狱,因?yàn)樽⒔獾膶傩孕枰?final 的运怖。當(dāng)然現(xiàn)在 butterknife 已經(jīng)提供了一個(gè)解決方案。就是利用 gradle 拷貝一份 R.java 命名成 R2.java夏伊,R2.java 里面的資源聲明都是 final 的摇展。這樣就躲過(guò)了語(yǔ)法檢查。當(dāng)然使用butterknife編譯后的字節(jié)碼中使用的還是R.java中的資源聲明溺忧。
作者簡(jiǎn)介
彭濤(@彭濤me) 致力于讓技術(shù)變得易懂且有趣
個(gè)人博客:http://pengtao.me
簡(jiǎn)書(shū):http://www.reibang.com/u/f9246f41945e
GitHub:https://github.com/CPPAlien