1. Preface
A very useful feature provided by Android Studio for modular development is that it can create a new library project (Module) under the main project. However, when using the library project, there is a problem that is resource ID conflicts. Because the SDK will automatically help us deal with this problem during compilation, we generally do not notice it, but in some cases, we need to be aware of the existence of this problem.
For example, use the following code in a newly created library project:
public void onButtonClick(View view) { switch (()) { case .button_1: break; case .button_2; break; } }
The IDE will prompt:
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.
For example, if we use ButterKnife in the library project in the following way, an error will be reported during compilation.
@OnClick(.button_1) public void onButtonClick(View view) { }
2. Analysis
Whether it is a switch statement or annotation, there is a requirement that the value used must be a constant. In the main project, the member variables in the R class are modified by static final, while in the library project are modified by static.
// The R class generated in the library project:public final class R { public static final class id { public static int button_1 = 0x7f0c0001; } } // The R class generated in the main project:public final class R { public static final class id { public static final int text_1 = 2131165184; } }
Why are the resource IDs generated in the library project not modified by final? The official explanation is as follows:
Non-constant Fields in Case Labels
When multiple library projects are merged, resource IDs in different projects may be duplicated. Before ADT 14, resource IDs were uniformly defined as static variables of final type, whether they were the main project or the library project. The result of this is that once a resource ID conflict is found when the main project is compiled, the corresponding resource file in the library project and the code that references the resource file need to be recompiled.
If a variable modified by static final is used in the code, then this variable is actually a constant, and its value will be replaced directly when compiled. During compilation, if the library project is duplicated with the resource ID of the main project, the code compiled before the library project will be invalid after the resource is assigned a new ID.
So what will the variables in the R class in the library project only have static modified? We can take a look at the compiled bytecode and then decompiled.
// Activity in the main project:public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { (savedInstanceState); // Source code: setContentView(.activity_main); (2131296283); } } // Activity in the library project:public LibActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { (savedInstanceState); (.activity_lib); } }
The resource ID in the R class of the main project is modified by static final and is directly replaced with the corresponding constant during compilation. The resource ID in the library project R class is only modified by static, so variables are retained. In this way, when the resource ID sends a conflict, the main project class R remains unchanged, and the variables in the library project class R are modified, and the code that the library project has compiled is still valid.
3. R2 class in ButterKnife
Since the resource ID in the library project cannot be defined as a constant, how to use ButterKnife in the library project? The author provides the R2 class for me to use.
@OnClick({.button_1, .button_2}) public void onButtonClick(View view) { int id = (); if (id == .button_1) { // ... } else if (id == .button_2) { // ... } }
Yes, we use R2 class in the annotation, but we still need to use R class in the code, because the ID in the R class is not a constant, so we can only use the if statement for judgment.
Let’s first take a look at the difference between R2 and R classes generated by ButterKnife for us:
// Class R in library project:public final class R { public static final class id { public static int button_1 = 0x7f0c0001; } } // The R2 class generated by ButterKnife for us in the library project:public final class R2 { public static final class id { public static final int button_1 = 0x7f0c0001; } }
ButterKnife does a very simple job, just move the variables in the R class into the R2 class, and then add final to all variables. According to the above, when the project is compiled as a whole, once the resource ID of the library project conflicts with the resource ID of the main project, the resource of the library project will be reassigned to cause its R class to be modified. Obviously, this process does not involve R2 class, and what is retained in R2 class is still outdated ID. But what does the annotations provided by ButterKnife do? They are not to provide runtime information, but to generate code at compile time.
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) { = target; View view = (source, .button_1, "method 'onButtonClick'"); this.view_button_1 = view; //.... view = (source, .button_2, "method 'onButtonClick'"); this.view_button_2 = view; //.... } }
In the code generated by ButterKnife, the R class is still used. R2 only plays a role in providing a symbol name, just let the program know which variable corresponds to when generating the code. This method can be said to be very "tricky".
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.