preface
The whole article is expanded around the knowledge points in Tinker ResourceidTask.
aapt
和aapt2
Differences (operating environment and operating results);- resources
id
The fixed; - for
PUBLIC
The tag;
Aapt runs gradle:2.2.0 and gradle-wrapper:3.4.1
Aapt2 run 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 a compiler for resource compilation by default. Aapt2 is available to support incremental compilation of resources. To disable AAPT2, set Android. enableAapt2=false in gradle.properties.
resources
Android is compatible with various natural quite a lot of work has been done on different devices, such as screen size, internationalization, keyboard, pixel density and so on, we can use for a particular variety of specific scenarios compatible without resources changes one line of code, suppose we adapted for a variety of different scenarios of different resources, How can I use these resources quickly? Android provides us with R class to specify the index (ID) of a resource, and then we only need to tell the system to use the corresponding resource in different business scenarios. As for the specific file in the specified resource, it is decided by the system according to the configuration of the developer.
In this scenario, assuming that the given ID is the x value, when the current business needs to use the resource, the state of the phone is the Y value. With (x,y), the specific path of the resource file can be quickly located in a table. This table is resources.arsc, which is compiled from AAPT.
Binary resources (such as images) do not need to be compiled, but the act of “compiling” is used to generate resources. Arsc is the table above, and binary XML file is used to make the system read better. AssetManager will find the corresponding file in this table and read it when we call the R related ID.
Gradle calls these aapT2 commands when compiling resources, and the parameters passed are described in this document, but the call details are hidden from the developer.
Aapt2 mainly have two steps, one is compile and the other is link.
Create an empty project: write only two XML, androidmanifest.xml and activity_main.xml.
Compile
mkdir compiled
aapt2 compile src/main/res/layout/activity_main.xml -o compiled/
Copy the code
In the compiled folder, there is a file called layout_activity_main.xml. Flat, which is aapT2 specific and not available in AAPT (aAPT copies source files), and which aapT2 use for incremental compilation. If we have a lot of files, we need to compile them one by one. We can also use the -dir parameter, but this parameter does not have the effect of incremental compilation. That is, AAPT2 recompile all the files in the directory even if only one resource changes when passing the entire directory.
Link
Link is a bit more work than compile, where the input 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
Copy the code
- The second line
-I
是import
External resources, mainly hereandroid
Some properties defined under the namespace that we normally use@android:xxx
It’s all herejar
In fact, we can also provide their own resources for others to link; - The third line is input
flat
File, if there are multiple, directly in the back can be spliced; - In the fourth row a
R.java
Generated directories; - The fifth line is specified
AndroidManifest.xml
;
After Link is completed, out.apk and r.java are generated, and out.apk contains a resources.arsc file. Those with only resource files can use the suffix. Ap_.
View compiled resources
In addition to using Android Studio to check resources. Arsc, you can also directly use aapT2 dump APK information to check the ID and status of resources:
aapt2 dump out.apk
Copy the code
The output is 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
Copy the code
You can see that the ID of layout/ Activity_main is 0x7F010000.
Resource sharing
Android.jar is just a compilation stump, and when it comes time to actually execute, the Android OS provides a runtime library (framework.jar). Android.jar is a lot like an APK, except it has a class file, and then an Androidmanifest.xml and resources.arsc. This means that we can also use aapT2 dump on it and run the following command:
aapt2 dump $ANDROID_HOME/platforms/android-28/android.jar > test.out
Copy the code
We get a lot of output like this:
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=XMLCopy the code
It has some PUBLIC fields. Resources in an APK file, if marked with this tag, can be referenced by other APKs using @ package name: type/name, for example: @android:color/red.
If we want to provide our resources, we will first mark our resources as PUBLIC and then reference your package name in XML, such as @com.gemini. App :color/red to reference your defined color/red.
For those interested in how AAPT2 generate PUBLIC, read on.
Ids. Summary of XML
Ids. XML: provides unique resource ids for application-related resources. Object = findViewById(R.id_name); Object = findViewById(R.id_name); The id_name.
These values can be referenced in the code with Android.r.ID. If ID is defined in ids. XML, @id/price_edit can be defined in layout as follows; otherwise, @+ ID /price_edit can be defined in layout.
advantages
- Naming is convenient, we can put some specific control first life good name, when using direct reference
id
Can, save a naming link. - Optimize compilation efficiency:
- add
id
Will be in afterR.java
Generated; - use
ids.xml
Unified management, one-time compilation can be used many times. But the use of"@+id/btn_next"
Form each time the file is saved(Ctrl+s
)After R.j ava
Will be retested if theid
If it does not exist, it needs to be addedid
. Therefore, the compilation efficiency decreases.
- add
Ids.xml file contents:
<resources>
<item name="forecast_list" type="id"/>
<! -- <item name="app_name" type="string" />-->
</resources>
Copy the code
If you are curious, there is a line of annotated code above. 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
Copy the code
App_name is already declared in value.
Public. Summary of XML
Official website: Select resources to be made public.
All resources in the library are public by default. To make all resources implicitly private, you must define at least one specific property as public. Resources include all files, such as images, in your project’s RES/directory. To prevent users of a library from accessing resources intended 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
tag, which does not make any resources public, but makes everything (all resources) private. By making properties implicitly private, you not only prevent users of your library from getting code completion suggestions from internal library resources, but you can also rename or remove private resources without breaking the library’s clients. Private resources are filtered from code completion, and Lint will warn you when you try to reference private resources.
When you build the library, the Android Gradle plug-in takes the public resource definition and extracts it into a public.txt file, which is then packaged into an AAR file.
The measured results are just not back to the code automatically incomplete, the compiler reported red. If lint is checked, compile without warning ~!
The file RES/value/public.xml is used to assign fixed resource ids to Android resources.
stackoverfloew:What is the use of the res/values/public.xml file on Android?
The contents of the public.xml file:
<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>
Copy the code
Fixed resource ID
The fixation of resource ids is extremely important in hot fixes and plugins. During patch construction, ensure that the resource ID of the patch package is consistent with that of the base package. In plug-in, if the plug-in needs to reference the host resource, the host resource ID needs to be fixed. Therefore, resource ID fixation is particularly important in these two scenarios.
In Android Gradle Plugin 3.0.0, aapT2 is enabled by default, and the original aAPT resource fixing method, public. XML, will also be disabled. 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
forid
The fixed
Project environment configuration (PS: Just to make fun of aAPT has been replaced by AAPT2, there is almost no relevant information about AAPT, it is too difficult to build environment ~!)
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'
Generate the corresponding files in the value file according to the contents and file names of ids. XML and public. XML above.
Direct compilation results
By looking directly at the contents of the compiled R file, we can see that the resource IDS we want to set are not generated as expected.
To the public. The XML file copy to build/intermediates/res/merged the corresponding directory
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)
}
}
}
}
Copy the code
This time we can directly see the resource ID generated as we need it.
Why is that?
-
Public. XML can be compiled directly in the source res directory.
-
The 1.3+ version of Android Gradle plugin ignores public. XML when the mergeResource task is executed, so there is no public. So you need to insert public. XML into the RES directory in the build directory after the merge at compile time through a script. This is possible because Aapt supports public. XML itself, but gradle filters public. XML when merging resources.
aapt2
forid
The fixed
Copying the public. XML file to this directory will cause a compilation error when aapT2 compile the resources into binary format and discover that the resources of the merge have been precompiled, resulting in flat files.
But in the link stage of AAPT2, we look at the relevant link options:
options | instructions |
---|---|
--emit-ids path |
Generates a file at the given path that contains the name of the resource type and a list of its ID mappings. It is suitable for the--stable-ids Use in combination. |
--stable-ids outputfilename.ext |
Used by--emit-ids Generated file that contains the name of the resource type and a list of ids assigned to it. This option keeps assigned ids stable even if you remove or add new resources when linking. |
— ememit -ids and — stables -ids can be used to fix ids.
android {
aaptOptions {
File publicTxtFile = project.rootProject.file('public.txt')
// If the public file exists, apply it; if it does not exist, generate it
if (publicTxtFile.exists()) {
project.logger.error "${publicTxtFile} exists, apply it."
// Add aapT2 -- stables are applied
aaptOptions.additionalParameters("--stable-ids"."${publicTxtFile}")}else {
project.logger.error "${publicTxtFile} not exists, generate it."
// aapT2 add --emit- IDS parameter generated
aaptOptions.additionalParameters("--emit-ids"."${publicTxtFile}")}}}Copy the code
- First compile, pass first
--emit-ids
Generated at the root of the projectpublic.txt
; - then
public.txt
On the insideid
Change to what you want to fixid
; - Compile again, pass
--stable-ids
And the root directorypublic.txt
For resourceid
The fixed;
–emit-ids compilation result
Modify the contents of the public. TXT file to compile again
R.t xt turn public. TXT
Our normal packaging generated intermediate is the build/intermediates/symbols/debug/R.t xt, need to convert it to the public. TXT.
R.xt format (int type name id) or (int[] styleable name {id,id, XXXX})
TXT format (applicationId:type/name = id)
Therefore, styleable types in r.tuck files need to be filtered out during conversion.
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")}}}}Copy the code
PUBLIC tag
In the AAPT overview section, we explained that resources in an APK file can be referenced by other APKs if they are tagged with a PUBLIC flag, using @ package name: type/name, e.g., @ Android :color/red.
Reading the sections “AAPT for ID fixation” and “AAPT2 for ID fixation” above, we know that the methods of AAPT and AAPT2 for ID fixation are not the same.
In fact, if we use aapt2 dump the build/intermediates/res/resources – debug. Ap_ command to view information generated resources.
Aapt uses public. XML for fixed resource information with public tags:
Second, the above method of AAPT2 id fixing does not have the PUBLIC mark as shown in the figure below.
This is because of the difference between AAPT and AAPT2. Aapt2’s public. TXT is not equal to AAPT’s public.
Review of thinking
review
aapt
For resourceid
Fixed andPUBLIC
Price tag, is willpublic.xml
Copied to the${mergeResourceTask.outputDir}
;aapt2
Compared to theaapt
, optimized for incremental compilation.AAPT2
The file is parsed and an extension is generated.flat
The intermediate binary file of.
thinking
Can use aapt2 themselves to the public. The XML for the public. Arsc. Flat, and copy it to the like aapt operation ${mergeResourceTask. OutputDir};
Hands-on practice
android {
/ / will be public. TXT into public. XML, and the public. The XML aapt2 compile copies the result to ${ergeResourceTask. OutputDir}
// Most of the following code is copied from tinker's source code
applicationVariants.all { def variant ->
def mergeResourceTask = project.tasks.findByName("merge${variant.getName().capitalize()}Resources")
if (mergeResourceTask) {
mergeResourceTask.doLast {
// Target conversion file, note that the parent directory of public. XML must have values directory, otherwise AAPT2 will report an illegal file path
File publicXmlFile = new File(project.buildDir, "intermediates/res/public/${variant.getDirName()}/values/public.xml")
// Convert the public.txt file to publicXml, with the last parameter true identifying 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>
Public. / / by aapt2 compile command generated arsc. The 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}")}})}}}}Copy the code
Convert the public. TXT file to a public. XML file.
public.txt
instyleable
Type resource,public.xml
Does not exist, so if encountered during conversionstyleable
Type, to be ignored;vector
If there is an internal vector resource, it also needs to be ignoredaapt2
In, its name is$
The primary resource name is followed by the __ number increment index. These resources are not referenced externally, but need to be fixedid
You do not need to addPUBLIC
Mark, and$
Sign inpublic.xml
Is illegal, so ignore it.- Due to the
aapt2
Has the resourcesid
The fixed way, so the conversion process can be directly discardedid
, simple declaration can be (PS: here throughwithId
Whether the parameter control needs to be fixedid
);aapt2
Compilation ofpublic.xml
The parent directory of the file must bevalues
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>")}Copy the code
Through the above process of thinking and hands-on practice, we not only solved the problem of AAPT2’s PUBLIC labeling, but also found a new method for AAPT2’s ID fixation.
Possible errors:
no signature of method com.android.build.gradle.internal.variant.applicationvariantdata.getscope() is applicable for argument types: () values: []
Copy the code
The solution is to change gradle versions to gradle:3.3.2 and gradle-wrapper:5.6.2. Tinker does not support the latest gradle versions either.
Reference:
Github:tinker
Android public. XML usage
Android – Gradle notes
The resource ID of AAPT2 adaptation is fixed
The article here is all about the end, if there are other need to exchange can leave a message oh ~! ~!