preface
The whole article is expanded around the knowledge points in tinker's tinker resource idtask.
- Difference between AAPT and aapt2 (operating environment and operating results);
- Fixed resource id;
- Mark PUBLIC;
The running environment of aapt is gradle:2.2.0 and gradle wrapper: 3.4.1
The running environment of aapt2 is gradle:3.3.2 and gradle wrapper: 5.6.2
The Android AAPT sample project is my own experimental sample. There are two branches, AAPT and aapt2, corresponding to their implementation respectively.
AAPT overview
Since Android Studio 3.0, google has enabled aapt2 as the compiler for resource compilation by default. The emergence of aapt2 provides support for incremental compilation of resources. Of course, there will be some problems in the process of use. We can use gradle Configure android.com in properties Enableaapt2 = false to turn off aapt2.
resources
Android naturally does a lot of work to be compatible with a variety of different devices, such as screen size, internationalization, keyboard, pixel density, etc. we can be compatible with using specific resources in a variety of specific scenarios without changing a line of code. Suppose we adapt different resources for a variety of different scenarios, How can we quickly apply these resources? Android provides us with the R class and specifies a resource index (id). Then we just need to tell the system to use the corresponding resources in different business scenarios. The specific file in the specified resources is determined by the system according to the developer's configuration.
In this scenario, assuming that the id given by us is the x value, when the current business needs to use this resource, the status of the mobile phone is the y value. With (x,y), you can quickly locate the specific path of the resource file in a table. This table is resources ArsC, which is compiled from aapt.
In fact, binary resources (such as pictures) do not need to be compiled, but this "compilation" behavior is to generate resources ArsC and binarization of xml files, resources ArsC is the table mentioned above. The binarization of xml is to improve the reading performance of the system. When we call the R-related id, AssetManager will find the corresponding file in this table and read it.
This is what Gradle calls when compiling resources aapt2 command , the parameters passed are also introduced in this document, but the call details are hidden from the developer.
aapt2 is mainly divided into two steps, one is called compile and the other is called link.
Create an empty project: only two XML are written, AndroidManifest.xml XML and activity_main.xml.
Compile
mkdir compiled aapt2 compile src/main/res/layout/activity_main.xml -o compiled/
In the compiled folder, layout is generated_ activity_ main. xml. Flat is a file unique to AAPT2. AAPT does not (AAPT copies the source file). AAPT2 can use it for incremental compilation. If we have many files, we need to call compile in turn. In fact, we can also use the - dir parameter here, but this parameter has no effect of incremental compilation. That is, when passing the entire directory, AAPT2 will recompile all files in the directory even if only one resource has changed.
Link
The workload of link is a little more than that of compile. The input here is multiple flat files and androidmanifest XML, external resources, and the output is apk and R.java containing only resources. The command is as follows:
aapt2 link -o out.apk \ -I $ANDROID_HOME/platforms/android-28/android.jar \ compiled/layout_activity_main.xml.flat \ --java src/main/java \ --manifest src/main/AndroidManifest.xml
- The second line - I is import external resources. Here are mainly some attributes defined under the android namespace. The @ android:xxx we usually use is placed in this jar. In fact, we can also provide our own resources for others to link;
- The third line is the input flat file. If there are multiple flat files, they can be spliced directly behind them;
- The fourth line is the directory generated by R.java;
- The fifth line specifies androidmanifest xml;
After the Link is completed, it will generate out Apk and R.java, out Apk contains a resource ArsC file. Suffixes can be used for files with only resources ap_.
View compiled resources
In addition to using Android Studio to view resources In ArsC, you can also directly use the aapt2 dump apk information to view the resource related ID and status:
aapt2 dump out.apk
The output results are as follows:
Binary APK Package name=com.geminiwen.hello id=7f type layout id=01 entryCount=1 resource 0x7f010000 layout/activity_main () (file) res/layout/activity_main.xml type=XML
You can see layout / activity_ The ID corresponding to main is 0x7f010000.
resource sharing
android.jar is just a pile for compilation. When it is actually executed, Android OS provides a runtime library (framework.jar). android.jar is very much like an apk, except that it exists in the class file, and then there is an androidmanifest XML and resources arsc. This means that we can also use aapt2 dump to execute the following command:
aapt2 dump $ANDROID_HOME/platforms/android-28/android.jar > test.out
Many outputs similar to the following are obtained:
resource 0x010a0000 anim/fade_in PUBLIC () (file) res/anim/fade_in.xml type=XML resource 0x010a0001 anim/fade_out PUBLIC () (file) res/anim/fade_out.xml type=XML resource 0x010a0002 anim/slide_in_left PUBLIC () (file) res/anim/slide_in_left.xml type=XML resource 0x010a0003 anim/slide_out_right PUBLIC () (file) res/anim/slide_out_right.xml type=XML
It has some more PUBLIC fields. If this mark is added to the resources in an apk file, they can be referenced by other apks. The reference method is @ package name: type / name, for example: @ android:color/red.
If we want to provide our resources, first mark our resources with PUBLIC, and then reference your package name in xml, such as: @com.com gemini. App: color/red can reference the color/red defined by you. If you do not specify the package name, it is yourself by default.
As for how AAPT2 generates PUBLIC, those interested can continue to read this article.
ids.xml overview
ids.xml: provide a unique resource ID for the relevant resources of the application. ID is the parameter required to obtain the object in xml, that is, Object = findViewById(R.id.id_name); ID in_ name.
These values can be used in the code as Android R. ID refers to.
If in IDS If Id is defined in XML, @ ID / price can be defined in layout as follows_ Edit, otherwise @ + id/price_edit.
advantage
- Naming is convenient. We can name some specific controls first and directly reference the id when using them, eliminating a naming link.
- Optimize compilation efficiency:
- After adding id, it will be generated in R.java;
- Using IDs XML is managed uniformly and can be compiled once and used many times
However, in the form of "@ + id/btn_next", R.java will re detect every time the file is saved (Ctrl+s). If the id exists, it will not be generated. If it does not exist, it needs to be added. Therefore, the compilation efficiency is reduced.
ids.xml file content:
<?xml version="1.0" encoding="utf-8"?> <resources> <item name="forecast_list" type="id"/> <!-- <item name="app_name" type="string" />--> </resources>
Some people may be curious that there is a line of annotated code on it. If you open the comment, you will find that the compilation will report an error:
Execution failed for task ':app:mergeDebugResources'. > [string/app_name] /Users/tanzx/AndroidStudioProjects/AaptDemo/app/src/main/res/values/strings.xml [string/app_name] /Users/tanzx/AndroidStudioProjects/AaptDemo/app/src/main/res/values/ids.xml: Error: Duplicate resources
Because app_ The resource for name has been declared in value.
public.xml overview
Official instructions Official website: select the resources to be made public.
All resources in the library are open by default. To make all resources implicitly private, you must define at least one specific property as public. Resources include all files in the res / directory of your project, such as images. To prevent users of the library from accessing resources for internal use only, you should use this automatic private identification mechanism by declaring one or more public resources. Alternatively, you can make all resources private by adding an empty < public / > tag. This tag will not make any resources public, but will make everything (all resources) private.
By implicitly making properties private, you can not only prevent users of the library from getting code completion suggestions from internal library resources, but also rename or remove private resources without damaging the library's clients. The system filters out private resources from code completion, and Lint Warns you when you try to reference a private resource.
When building the library, the Android Gradle plug-in will get the public resource definition and extract it to public Txt file, and then the system will package this file into an AAR file.
The measured result is only that the code is not returned automatically, and the compiler reports red. If lint check is performed, there is no warning ~!
Now most of the explanations are: RES / value / public XML is used to assign fixed resource ID s to Android resources.
stackoverfloew:What is the use of the res/values/public.xml file on Android?
public.xml file content:
<?xml version="1.0" encoding="utf-8"?> <resources> <public name="forecast_list" id="0x7f040001" type="id" /> <public name="app_name" id="0x7f070002" type="string" /> <public name="string3" id="0x7f070003" type="string" /> </resources>
Fixed resource id
The fixation of resource id is very important in hot repair and plug-in. In hot repair, when building a patch, you need to keep the resource id of the patch package consistent with the resource id of the benchmark package; In plug-in, if the plug-in needs to reference the host's resources, the host's resource id needs to be fixed. Therefore, fixing the resource id is particularly important in these two scenarios.
In Andro id gradle plugin 3.0.0, aapt2 is enabled by default, and the original resource fixation mode of AAPT is public XML will also fail. We must find a new way to fix resources instead of simply disabling aapt2. Therefore, this paper discusses how AAPT and aapt2 fix resource IDS respectively.
aapt fixes the id
Project environment configuration (PS: make complaints about AAPT has been replaced by aapt2, AAPT related information is almost no, the environment is too hard to build ~!
com.android.tools.build:gradle:2.2.0
distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip
compileSdkVersion 24
buildToolsVersion '24.0.0'
First, under the value file, follow the above IDs XML and public XML content and file name to generate the corresponding file.
Direct compilation results
By directly compiling the contents of the R file, we can see that the resource id we want to set is not generated as expected.
Public Copy the XML file to the corresponding directory of build/intermediates/res/merged
afterEvaluate { for (variant in android.applicationVariants) { def scope = variant.getVariantData().getScope() String mergeTaskName = scope.getMergeResourcesTask().name def mergeTask = tasks.getByName(mergeTaskName) mergeTask.doLast { copy { int i=0 from(android.sourceSets.main.res.srcDirs) { include 'values/public.xml' rename 'public.xml', (i++ == 0? "public.xml": "public_${i}.xml") } into(mergeTask.outputDir) } } } }
This time, we can directly see that the resource id is generated according to our needs.
Why?
-
android gradle plug-in versions below 1.3 can directly convert public XML is placed in the res directory of the source code to participate in the compilation;
-
The android gradle plug-in version 1.3 + ignores public when executing the mergeResource task XML, so there is no public.xml in the res directory under the build directory after the merge is completed XML related content. Therefore, you need to use a script to convert public.xml to XML at compile time Insert the XML into the res directory under the build directory after the merge is completed. This is feasible because aapt itself supports public XML, except that the gradle plug-in performs pre merge on public XML is filtered.
aapt2 fixes the id
After aapt2 compilation (compiling the resource file into binary format), it is found that the resources of merge have been precompiled to generate a flat file. At this time, it will be public Copying an XML file to this directory will generate a compilation error.
However, in the link phase of aapt2, we view the relevant link options:
option | explain |
---|---|
--emit-ids path | Generate a file under the given path that contains a list of resource type names and their ID mappings. It is suitable for use with -- stable IDs. |
--stable-ids outputfilename.ext | Use the file generated through -- emit IDS, which contains the name of the resource type and a list of the IDS assigned to it. This option keeps the assigned ID stable, even if you delete a resource or add a new resource when linking. |
It is found that the combination of -- emit IDs and -- stable IDS commands can fix the id.
android { aaptOptions { File publicTxtFile = project.rootProject.file('public.txt') //If the public file exists, it will be applied; if it does not exist, it will be generated if (publicTxtFile.exists()) { project.logger.error "${publicTxtFile} exists, apply it." //aapt2 add -- stable IDS parameter application aaptOptions.additionalParameters("--stable-ids", "${publicTxtFile}") } else { project.logger.error "${publicTxtFile} not exists, generate it." //aapt2 add -- emit IDS parameter generation aaptOptions.additionalParameters("--emit-ids", "${publicTxtFile}") } } }
- For the first compilation, generate public. Exe in the root directory of the project through -- emit IDs txt;
- Then public The id in txt is changed to the id you want to fix;
- Compile again through -- stable IDs and public. Net under the root directory Txt to fix the resource id;
--Compilation results of emit IDS
Modify public Txt file content recompile
R.txt to public txt
Generally, the intermediate product generated by normal packaging is build/intermediates/symbols/debug/R.txt, which needs to be converted to public txt.
R.txt format (int type name id) or (int[] styleable name {id,id,xxxx})
public.txt format (applicationId:type/name = id)
Therefore, you need to filter out the styleable types in the R.txt file during the conversion process.
android { aaptOptions { File rFile = project.rootProject.file('R.txt') List<String> sortedLines = new ArrayList<>() // Read line by line rFile.eachLine {line -> //rLines.add(line) String[] test = line.split(" ") String type = test[1] String name = test[2] String idValue = test[3] if ("styleable" != type) { sortedLines.add("${applicationId}:${type}/${name} = ${idValue}") } } Collections.sort(sortedLines) File publicTxtFile = project.rootProject.file('public.txt') if (!publicTxtFile.exists()) { publicTxtFile.createNewFile() sortedLines?.each { publicTxtFile.append("${it}\n") } } } }
PUBLIC tag
In the overview of AAPT, we mentioned that if the resources in an apk file are marked with PUBLIC, they can be referenced by other apks. The reference method is @ package name: type / name, for example: @ android:color/red.
After reading the above two parts from fixing id by AAPT to fixing id by aapt2, we know that the methods of fixing id by AAPT and aapt2 are different.
In fact, if we use aapt2 dump build / intermediates / RES / resources debug ap_ Command to view information about the generated resource.
aapt through PUBLIC The resource information with fixed id in XML has PUBLIC tag:
2. The above aapt2 is used to fix the id without the PUBLIC mark in the figure below.
The reason is the difference between AAPT and aapt2. Aapt2 is PUBLIC Txt is not equal to public.txt of AAPT XML, if you want to add the PUBLIC tag in aapt2, you actually have to find another way.
Review and think
review
- aapt fixes the resource id and publishes the price, which is PUBLIC Copy the XML to ${mergeResourceTask.outputDir};
- Compared with AAPT, aapt2 optimizes incremental compilation. Aapt2 parses the file and generates a file with the extension The intermediate binary of flat.
reflection
Can I use aapt2 to make public XML is compiled into public.xml arsc. Flat and copy it to ${mergeResourceTask.outputDir} like the AAPT operation;
Hands on practice
android { //Public Txt to public XML and public Compile aapt2 with XML and copy the results to ${ergeResourceTask.outputDir} //Most of the following code is the source code of copy from tinker applicationVariants.all { def variant -> def mergeResourceTask = project.tasks.findByName("merge${variant.getName().capitalize()}Resources") if (mergeResourceTask) { mergeResourceTask.doLast { //Target conversion file, note public The XML parent directory must contain the values directory, otherwise an illegal file path will be reported during aapt2 execution File publicXmlFile = new File(project.buildDir, "intermediates/res/public/${variant.getDirName()}/values/public.xml") //Convert public Txt file is a publicXml file, and the last parameter true identifies the fixed resource id convertPublicTxtToPublicXml(project.rootProject.file('public.txt'), publicXmlFile, false) def variantData = variant.getMetaClass().getProperty(variant, 'variantData') def variantScope = variantData.getScope() def globalScope = variantScope.getGlobalScope() def androidBuilder = globalScope.getAndroidBuilder() def targetInfo = androidBuilder.getTargetInfo() def mBuildToolInfo = targetInfo.getBuildTools() Map<BuildToolInfo.PathId, String> mPaths = mBuildToolInfo.getMetaClass().getProperty(mBuildToolInfo, "mPaths") as Map<BuildToolInfo.PathId, String> //Generate the public. XML file by using the aapt2 compile command arsc. Flat and output to ${mergeResourceTask.outputDir} project.exec(new Action<ExecSpec>() { @Override void execute(ExecSpec execSpec) { execSpec.executable "${mPaths.get(BuildToolInfo.PathId.AAPT2)}" execSpec.args("compile") execSpec.args("--legacy") execSpec.args("-o") execSpec.args("${mergeResourceTask.outputDir}") execSpec.args("${publicXmlFile}") } }) } } } }
Public Txt file into public XML file
- public.txt, public XML does not exist, so if the styleable type is encountered during the conversion process, it needs to be ignored;
- Vector vector graph resources should also be ignored if there are internal resources. In aapt2, its name starts with $, followed by the main resource name__ Digital incremental index. These resources cannot be referenced externally. They only need a fixed id, do not need to add a PUBLIC tag, and the $symbol is in PUBLIC XML is illegal, so ignore it;
- Because aapt2 has a fixed method of resource id, you can directly lose the id in the conversion process and simply declare it (PS: here, you can control whether a fixed id is required through the withId parameter);
- aapt2 compiled public The parent directory of the XML file must be the values folder, otherwise the compilation process will report an illegal path;
/** * Convert publicTxt to publicXml * copy tinker:com.tencent.tinker.build.gradle.task.TinkerResourceIdTask#convertPublicTxtToPublicXml */ @SuppressWarnings("GrMethodMayBeStatic") void convertPublicTxtToPublicXml(File publicTxtFile, File publicXmlFile, boolean withId) { if (publicTxtFile == null || publicXmlFile == null || !publicTxtFile.exists() || !publicTxtFile.isFile()) { throw new GradleException("publicTxtFile ${publicTxtFile} is not exist or not a file") } GFileUtils.deleteQuietly(publicXmlFile) GFileUtils.mkdirs(publicXmlFile.getParentFile()) GFileUtils.touch(publicXmlFile) project.logger.info "convert publicTxtFile ${publicTxtFile} to publicXmlFile ${publicXmlFile}" publicXmlFile.append("<!-- AUTO-GENERATED FILE. DO NOT MODIFY -->") publicXmlFile.append("\n") publicXmlFile.append("<resources>") publicXmlFile.append("\n") Pattern linePattern = Pattern.compile(".*?:(.*?)/(.*?)\\s+=\\s+(.*?)") publicTxtFile.eachLine {def line -> Matcher matcher = linePattern.matcher(line) if (matcher.matches() && matcher.groupCount() == 3) { String resType = matcher.group(1) String resName = matcher.group(2) if (resName.startsWith('$')) { project.logger.info "ignore to public res ${resName} because it's a nested resource" } else if (resType.equalsIgnoreCase("styleable")) { project.logger.info "ignore to public res ${resName} because it's a styleable resource" } else { if (withId) { publicXmlFile.append("\t<public type=\"${resType}\" name=\"${resName}\" id=\"${matcher.group(3)}\" />\n") } else { publicXmlFile.append("\t<public type=\"${resType}\" name=\"${resName}\" />\n") } } } } publicXmlFile.append("</resources>") }
Through the above thinking and hands-on practice, we not only solved the problem of aapt2 PUBLIC marking, but also found a new method of aapt2 id fixing.
Possible errors:
no signature of method com.android.build.gradle.internal.variant.applicationvariantdata.getscope() is applicable for argument types: () values: []
The solution is to modify the gradle version to gradle:3.3.2 and gradle wrapper: 5.6.2. After all, tinker does not support the latest version of gradle
reference resources:
Fixed resource id for aapt2 adaptation
This is the end of the article. If you need to communicate with others, you can leave a message ~! ~!
If you want to read more articles by the author, you can check me out Personal blog And public number: