Solution of Resource ID Conflict in Android Library Project

  • 2021-08-28 21:17:18
  • OfStack

1. Preface

Android Studio provides a very useful function for modular development is to create a new library project (Module) under the main project, but there is a problem when using the library project, which is the resource ID conflict. Because SDK will automatically help us deal with this problem at compile time, we will 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 the newly created library project:


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

IDE prompts:

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 another example, we use ButterKnife in the library project in the following way, and an error will be reported at compile time.


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

} 

2. Analysis

Whether it is an switch statement or an 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 all modified by static final, while in the library project they are only modified by static.


//  The generated in the library project R Class: 
public final class R {
  public static final class id {
    public static int button_1 = 0x7f0c0001;
  }
}

//  The generated in the main project R Class: 
public final class R {
  public static final class id {
    public static final int text_1 = 2131165184;
  }
} 

Why is the resource ID generated in the library project not decorated with final? The official explanation is as follows:

Non-constant Fields in Case Labels

When multiple library projects are merged, the resources ID in different projects may be duplicated. Prior to ADT 14, resource ID 1 was defined as a static variable of type final, whether it was a master project or a library project. As a result, when the main project is compiled, once the resource ID conflict is found, the corresponding resource files in the library project and the code referencing the resource files need to be recompiled.

If you use a variable decorated with static final in your code, the variable is actually a constant, which will be replaced directly with its value at compile time. At compile time, if the library project duplicates the resource ID of the main project, the code compiled before the library project is invalidated after the resource is assigned a new ID.

So what role will it play when the variables in the library project R class are only modified by static? We can see what the compiled bytecode looks like after decompilation.


//  In the main project Activity : 
public class MainActivity extends AppCompatActivity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    //  Source code: setContentView(R.layout.activity_main);
    this.setContentView(2131296283);
  }
}

//  In the library project Activity : 
public LibActivity extends AppCompatActivity {
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(R.layout.activity_lib);
  }
} 

The resource ID in the main project R class is modified by static final and is directly replaced with the corresponding constant at compile time. The resource ID in the library project R class is only modified by static, so the variable is retained. In this way, when the resource ID sends a conflict, the main project R class remains unchanged, and the variables in the library project R class are modified, and the compiled code of the library project is still valid.

3. Class R2 in ButterKnife

Since the resource ID in the library project cannot be defined as a constant, how can I use ButterKnife in the library project? The author provides R2 class for my use.


@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) {
    // ...
  }
} 

Yes, the R2 class is used in the annotation, but the R class is still needed in the code, because ID in the R class is not a constant, so you can only use the if statement for judgment.

First, let's look at the difference between R2 class generated for us by ButterKnife under 1 and R class:


//  In the library project R Class: 
public final class R {
  public static final class id {
    public static int button_1 = 0x7f0c0001;
  }
}

//  Library project ButterKnife Generated for us R2 Class: 
public final class R2 {
  public static final class id {
    public static final int button_1 = 0x7f0c0001;
  }
}     

What ButterKnife does is simply move the variables from the R class to the R2 class, and then add final to all the variables. According to the above, when the whole project is compiled, the resource ID1 of the library project conflicts with the resource ID of the main project, and the resource of the library project will be reallocated ID, resulting in its R class being modified. Obviously, this process does not involve R2, and the outdated ID is still retained in R2. But what is the purpose of the annotations provided by ButterKnife? They are not intended 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) {
    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....
  }
} 

In the code generated by ButterKnife, the R class is still used. R2 only provides a symbolic name, as long as the program knows which variable to correspond to when generating code. This method can be said to be very "tricky".


Related articles: