Good friends, a new week begins, let’s continue to learn the knowledge of plug-ins. Let’s review the structure of the series

According to my writing ideas, this article explains some knowledge of resources and App packaging. This is the second basic article in the plug-in series. After reading this article, you should know:

  • The composition of resource ids,
  • The secret of R.j ava
  • App packaging process

The resources part will start from your intuitive impression and gradually increase the depth. Then I will explain the packaging process of App based on the paving of the resources in the first half. If you read this and think, gee, I didn’t know that, then this article has some meaning. Since this is still a basic article on plug-ins, I won’t cover them, but I will use some of the knowledge from this article when I talk about plug-ins. From another point of view, this article is also a comparative knowledge of its own article. OK, let’s get started.

Resources and R.j ava

To do a little preparation, we will build a project with three modules, App and our self-built ModuleA and ModuleB. The dependency of the three modules is APP -> ModuleA -> ModuleB. And then we put a little bit of a resource in each module, like a string or something, so here I put a string called testA in moduleA and a string called testB in moduleB. We will find the r.java file under build/generated/source/r in each module. This is the resource ID directory generated by the aAPT tool during the Android packaging process.

Then we open r.Java for the main module and ModuleA respectively. Here are examples of ids in r.java files for the main and normal modules.

// R.java in the main module app
public static final int testA = 0x7f0b002a;
public static final int testB=0x7f0b002b;

// R.java in moduleA
public static int testA = 0x7f15002b;
public static int testB = 0x7f15002c;
Copy the code

You can see:

  • Why does the resource composition begin with 0x7f?
  • Why are application Module resources final and non-library modules not final
  • Why are resource ID values in R. Java generated by different modules different for the same resource

Why is that? We will explain these later and conclude at the end

1. Composition of resource ids

Let’s first look at the composition of resource ids. As you all know, a resource ID is a unique identifier for a resource. There are so many modules, so many resource types, and even Android resources. Why do resource ids not repeat? The secret lies in the composition of the resource ID.

PackageId: The first two characters are packageids, which are used as a namespace to distinguish different package Spaces (not different modules). Currently, when compiling an app, there are at least two package Spaces: the Android system resource pack and our own app resource pack. If you look at the R.java file, you can see that some of the files begin with 0x01 and some begin with 0x7f. The resource ID starting with 0x01 is the one already built in the system, and the one starting with 0x7f is the APP resource ID added by ourselves.

Android resources include animator, Anim, Color, Drawable, Layout, string, etc. TypeId is used to distinguish different resource types.

EntryId: An entryId is the order in which each resource appears in the resource type to which it belongs. Note that Entry ids for different types of resources may be the same, but because they are of different types, we can still distinguish them by their resource ids.

By dividing the three blocks of resource ID, during compilation, the same resource in ordinary APK will only belong to one package, one type, and only have one order, so the ID of one resource will not be repeated with other resources. Of course, this is normal, but what if we have some resources that are not part of the package? For example, we want to say plug-in, plug-in is to deliver a plug-in, plug-in of course, there are resources, this part of the resources are not unified compilation, then there may be a conflict with the host (plug-in to the App) resources. For instance you had arranged 108 to liangshan, everyone has a title, but came again from the foot of the mountain “timely rain” Song River, is that existence at the same time two timely rain, listen to who? Liangshan will be in chaos, and so will app.

To avoid this, the plugin’s resource ID is usually a number between 0x02 and 0x7e to avoid conflicts with the host resource. As for how to do this, we will talk about it in the next article

2. Use the resource ID

We usually code with references like r.layout.xxxx, which are fields in r.java files. And since there seems to be no difference between the main module and the Library module when we use these ids, is there really no difference between the two?

Let’s first look at the difference between using ids in the main module and in the library module. We create an Activity in the APP module and moduleA module respectively. Each Activity has a snippet of code that you should be familiar with

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
}
Copy the code

Then go to Android Studio’s Tools-> Kotlin -> Show Kotlin bytecode to see the bytecode directly. Of course, it’s still difficult to look at bytecode directly, so let’s look at decompile on the panel and parse it into Java code. And then we’ll see that there’s a slight difference.

// The main module code
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(-1300009);
}

// Library module code
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    this.setContentView(layout.activity_module_a);
}
Copy the code

As you can see, r.layout. XXX in the main module is inline directly into the code as a constant. In the library module, r.layout. XXX is still referred to as a variable in the code. This rule also applies at compile time. This rule is consistent with the previous processing of fields in R.Java, i.e.,

  • Fields in R.Java in the main module are final and exist as constants.
  • Fields in R.Java in the library module are not final and are referred to as variables by the code in the project.

3. Resource consolidation

Generally, there are three sources of resources in APK. For details, please refer to the official website:

  • Main source set: such as SRC /main/res
  • Build Variant source set: e.g. SRC /demoDebug/res
  • Libraries: We introduced aArs.

A resource is usually identified by its filename, that is, resources with the same filename under the same Resource type(anim/drawable/string, etc.) and the same resource qualifier(hdPI, language in value, etc.). The system thinks he’s unique. The same resource may exist under a single module, such as multiple main resource sets. So when this conflict occurs, what does the system do? The system merges and low-priority resources are overwritten.

The override priority is as follows: Build variant > Build Type > Product flavor > main source set > Library dependences

For example, if we have two resources in the main resource set: RES/Layout /a.xml, res/layout/ B.xml, and res/ Layout/A.xml under the Build Type folder. The res/layout/ A.xml in the final package generated APK comes from build Type and res/ Layout/B.xml comes from main Source set.

In addition to resource overrides under different folders in a single module, there are also resource overrides between modules. For example, if the APP module relies on moduleA and both modules have a res/ Layout/A.xml file, the res/ Layout/A.xml file in the compiled APK must be the one defined by the APP module.

What are the practical implications of resource consolidation? I personally believe that a higher level of adaptive packaging can be achieved through resource consolidation. For example, we can set different resources for different product Flavors, such as page XML, so that just changing the Product Flavor can render different packages and achieve a higher level of adaptation.

4. Generation of R files

Given the mechanics of resource ids, let’s look at how R files are generated. The rules here are based on Gradle 3.1.2.

First let’s look at the quantitative rules, again using our example above. The dependency of the three Modules is APP -> ModuleA -> ModuleB. Modulea has a string called testA and moduleB has a string called testB. Finally, we found three R files under the APP module.

We found that the R file under plugindemo contains the string we defined in ModuleA and ModuleB.

The conclusion can be drawn from the above example, illustrated by a graph.

1. Quantity rule: When a module is compiled, it generates an R file for the current module, and the module’s dependent module or AAR also generates an R file for the current Module. This dependency is different from gradle’s implementation dependency. Implementation cannot be passed across levels, but R file generation can be passed across levels. So, module R files = number of dependent Modules/AArs + 1(its own R files)

For example, module A relies on module B and also relies on Fresco. How many R files does it generate? The answer is three, B module, Fresco, and its own R file.

2. Generate the order rule. The dependency relationship of the three modules is APP -> ModuleA -> ModuleB. R files are generated layer by layer from bottom to top. That is to say, Mr. Module is moduleB, regenerated into moduleA, regenerated into APP module.

3. Resource rules: upper-layer modules merge R files of dependent modules. For example, the app module does not have testA and testB strings, but the APP R file contains the ids of these two resources. This is because upper-level modules merge resources from lower-level modules.

5. To summarize

With these rules in mind, we are ready to answer the three questions posed at the beginning of this section.

1. Why do all resource ids start with 0x7f? These resources belong to the application package and start with 0x7f

2. Why are application Module resources final and non-library Modules not final? In earlier versions of AAPT, the resource ids generated by non-main modules are indeed final, which brings a problem. These resource ids are all inlined in the code. Once the resources are added or deleted, the resource IDS will change, and all the codes need to be recompiled, resulting in serious compilation time. In this way, when the App module is compiled from bottom to top, all resource ids have been determined. Resources of the underlying module only need to be referenced to get their corresponding IDS. After modifying (adding, deleting and modifying) resources, You just need to regenerate the R file. Compilation time is greatly reduced.

3. Why are the resource IDS in R. Java generated by different modules different for the same resource? Because the resource ID only represents the order of the resources, not any other property bound to the resource itself. As more resources are compiled for different modules, the order will definitely change. The resource ID is changed. In addition, the resource ID of the sub-module only exists in the code in the form of reference, and the specific value of ID is not very care.

I don’t know if you have anything to gain after reading these.

6. Supplement your knowledge

I don’t know if you’ve used ButterKnife, the dependency injection framework, but the core usage scenario of ButterKnife is dependency injection using annotations. Such as

@BindView(R.id.user) EditText username;
Copy the code

So, what’s the catch? As we mentioned above, resource ids in non-main modules are variables with no final modifier. But annotations as you all know, the argument passed in must be a final constant. Wouldn’t that be the opposite?

In fact, neither conclusion is wrong. Butterknife does a dirty treatment for this situation. He directly copied r.Java from module, made r2. Java, and changed all resource ids in R.Java to final so that they could be used in annotations. Wait until it is actually used, then replace it with the generated resource ID of the real main module.

For details, see R.java, R2. Java is time to understand

The App package

In this section on the packaging process, I will cover the basic process first and then add some extended knowledge about the application of the packaging process.

1. Packaging process

Let’s start with a packaging flowchart.

1. Package resource files and generate R.Java files. This process is mainly for AAPT to process resource files of res and Asset folders, Androidmanifest.xml, Android library (AAR, JAR) and so on. First check the validity of androidmanifest. XML, then compile the resources in RES and implies directory and generate the resource. Except for assets and RES/RAW resources, which are packaged intact into APK, all other resources are compiled or processed. Most XML resource files in text format are compiled into binary format XML resource files. All assets other than assets are given a resource ID in the R file. Arsc is a resource index table, where the resource ID is the key and the value is the resource path. When we use drawable-xdpi or drawable-xxdpi, we rely on resource. Arsc to select different images based on the device’s resolution.

2. Process aiDL files and generate corresponding.java files. This step is that aiDL files in our code are generated into Java files.

3. Compile the project source code, generate the corresponding class file R file, AIDL generated Java files and source code in our project are compiled into class files by Javac tools.

4. Convert all the class files to generate the classes.dex file. The executable file of dalvik vm for Android is in dex format. The main work of dx tool is to convert Java bytecode to Dalvik bytecode, compress constant pool, eliminate redundant information, etc. Here, when dex is generated, 65536 problems will be encountered. The number of methods in a DEX file is indexed by using the primitive type short, that is, a maximum of 65536 methods can be expressed in 2 bytes. So when there are too many methods, you must use multidex.

5. Generate APK Package all dex files into one APK file.

6. Sign apK files. Apk requires a signature to be installed on a mobile phone. Our tests usually use a debug.keystore to sign the APK. A signature file that meets the requirements in the Android development documentation is required for official release. Examples include Jarsigner and APK Signature Scheme V2.

Zipalign is located in the Android-sdk /tools directory. The source code is located in the Build /tools/zipalign directory of the Android system resources. Its main job is to align the APK package. Offsets all resource file sample files in the APK package to an integer multiple of 4 bytes for faster access to APK through memory mapping. Why is it fast? If each resource starts with 4n bytes after one resource, then access to the next resource will skip to 4 bytes after the next resource without traversing.

8. Obfuscate ProGuard: The main purpose of ProGuard is to obfuscate code and protect application source code. Secondary features include removing useless classes, optimizing bytecode, and reducing packet size.

  • Shrink: Detects and removes unnecessary classes, fields, methods, and attributes from code.
  • Optimize: Bytecode optimization to remove useless instructions.
  • Obfuscate: Rename bases, fields and methods using short and meaningless names such as A, B, C and D.
  • Preveirfy: Prechecks processed code on the Java platform to ensure that loading the class file is executable.

2. Some technical points

As we mentioned earlier, ProGuard’s function is to obfuscate code and reduce volume. But ProGuard can’t handle resource files. To solve the problem of confusion in resource files, wechat launched AndResGuard. Using AndResGuard, you can even reduce package size.

In addition to AndResGuard, we also have the problem of resources being reused. Identifying duplicate resources is as simple as calculating MD5. Arsc, we can process all resources in resources.arsc, delete the resources according to MD5, point the id of the resource that uses the same resource to the same resource, delete the redundant resources. Just say resources.arsc again. Of course, there’s a lot of science to it.

Transform Transform is a set of apis provided by the Android Gradle Plugin for developers. It allows developers to modify the class after compiling and before dex. Developers can register for Transform via AppExtension or LibraryExtension. Multiple transforms form a chain. The output of the previous Transform is the input to the next Transform, so the order of the transforms is also important.

Now that we have this Transform, it means we have an opportunity to manipulate Java bytecode. Common bytecode processing frameworks on the web include AspectJ, Javasist, ASM. These tools can be used for bytecode staking. This allows you to merge code at the bytecode stage that cannot be coupled to the business code.

The difference between Android and iOS is that there are many markets and channels. In order to distinguish and count the effects of different channel packages, there needs to be a way to mark them. You might think of productFlavor, but it takes a long time to build as many packages as you need.

It is better to write some information in the signature block when APK is signing v2, which is faster and safer. Details can refer to Android Meituan multi-channel packaging Walle integration

3. Summary

This section explains the packaging process and some of the technical points you can make with the packaging mechanism. The packaging process, if well learned, opens up a lot of possibilities for Android development.

Android application resources compilation and packaging process analysis

Android Meituan multi-channel packaging Walle integration

It’s time to understand

I am Android stupid bird journey, a accompany you slowly stronger public number, welcome to pay attention to me to learn together, progress together ha ~