我們知道 Android 專案中會通過自動生成一個 R.java 類的方式來儲存專案中所有資原始檔的標識。在主專案中生成的 R.java 中的資源宣告是一個靜態常量,而在 module 中它卻是一個靜態變數。這是為什麼呢?我們知道在 java 中如果某個值被宣告成常量(用 final 修飾),則在編譯後,該常量會被直接替換成值。而在 java 語法中,註解的屬性和 switch-case 中的 case 表示式,必須使用常量或者直接使用值,否則會報語法錯誤。下面我們會展開討論下為什麼 module 中的 R 類中宣告的資源標識不是 final 的,這些又導致了哪些現象?
主專案中
比如你在主專案中建立了一個 activity_main.xml 的佈局檔案,則 R.java 中會自動加入一行如下靜態常量。
public static final class layout {
...
public static final int activity_main=0x7f09001b;
複製程式碼
此後你就可以通過 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
,會發現裡面的靜態常量被直接替換成了值。程式碼執行過程中,就可以直接通過值來找到對應資源了。
public class MainActivity extends AppCompatActivity {
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131296283);
}
}
複製程式碼
Module中
然後我們再在一個 module 中同樣建立一個 MainActivity 和對應的資源,我們檢視該 module 下的 R.java
。
public static final class layout {
...
public static int activity_main = 0x7f0f001c;
複製程式碼
大家有發現區別了嗎?在 module 中新增的該資源少了 final。我們再來看下 MainActivity.class 檔案。我們會發現此處的資源引用是使用的靜態變數方式,而未直接使用資源的值。
public class MainActivity extends AppCompatActivity {
public MainActivity() {
}
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(layout.activity_main);
}
}
複製程式碼
為什麼這樣做
Android 中,如果你在 module 中新增了一個資源,就拿這裡的 activity_main.xml 舉例。我們此處假設如果在 module 中也是 final 的,那會出現什麼情況?第一,該 module 編譯後的程式碼中該資源會被替換成值;第二,當該 module 被新增到主專案中後,如果主專案中有一個同樣名稱的資源,那麼 module 中的該資源就會被替換;第三,主專案中會重新針對該資源生成一個 ID;最終就會出現 module 中那個資源 ID 找不到了。所以呢,這也是為什麼 module 中的資源 ID 宣告不使用 final 的原因。
有關資源合併的規則,可以參考下 google 的官方文件
https://developer.android.com/studio/write/add-resources.html。
導致的幾個現象
1,這就是為什麼當主專案與 module 中有同樣資源時,module 卻會使用主專案的資源。
2,這也是為什麼我們在 module 中無法針對資源使用 switch-case 方式的原因。
3,這也是為什麼我們無法在 module 中直接使用 butterknife,因為註解的屬性需要是 final 的。當然現在 butterknife 已經提供了一個解決方案。就是利用 gradle 拷貝一份 R.java 命名成 R2.java,R2.java 裡面的資源宣告都是 final 的。這樣就躲過了語法檢查。當然使用butterknife編譯後的位元組碼中使用的還是R.java中的資源宣告。
作者簡介 彭濤(@彭濤me) 致力於讓技術變得易懂且有趣 個人部落格:http://pengtao.me, GitHub地址:https://github.com/CPPAlien