Welcome to follow my public numberEfficient Android Development“Focuses on Android project efficiency and development experience, covering topics such as infrastructure, Kotlin Multiplatform, Gradle construction and optimization, etc. At the same time, we also talk about overseas work and life, and push the latest Podcast of” Two-part radio “.
“Build North” is a series of articles exploring Android building. It covers Gradle, Android Gradle Plugin, Kotlin Script, and other tools, as well as related architecture applications. To find the problem to solve the problem as the starting point, transfer new knowledge to improve production efficiency as the foothold.
Issue review
The debuggable attribute in build.gradle is invalid. You can only write the debuggable attribute in the application node of androidmanifest.xml manually. Remove this property before publishing, and add it after publishing. I recently spent two or three weekends researching why.
By default, readers of this article already know this:
The criterion for determining whether an App can be debugged is whether the application node in androidmanifest.xml has the debuggable attribute and its value is true. (Please refer to official documentation)
Problem analysis & source code reproduction
0x01
Start with the debuggable property in buildType. Now that we know that debuggable actually works on the Manifest property, look for manifest-related processes. Process {variant}Manifest, Process {variant}Resources, package{variant} and other tasks in Android Gradle Plugin Nothing unusual was found. But they did learn something about their intermediate products:
process{variant}Manifest
的AndroidManifest.xml
Intermediate is located in the/app/build/intermediates/manifests, is divided into instant – run and other variant (debug, release, etc.) and other folders;process{variant}Resources
的AndroidManifest.xml
Located in the/app/build/intermediates/res/resources – debug. Ap_ compressed package (in fact at first you don’t look at this task, is a product in the middle of the other task, it found that this task is actuallyAndroidManifest.xml
The product);package{variant}
的AndroidManifest.xml
This is actually the file in the last APK package;
Other than that, I don’t see any direct writing of debuggable to androidmanifest.xml (except merge).
0x02
Compare a new project with a problem project and find:
- New Projects:
process{variant}Manifest
Is not found in the product ofdebuggable
Attributes, but inprocess{variant}Resources
This property (true in debug state) is present in the product of, indicating that the internal action occurred at this step; - Problem engineering: in all tasks
AndroidManifest.xml
None of the productsdebuggable
The value of the;
Process {variant}Resources (aAPT) {variant}Resources (aAPT)
// AaptV1.java
@Override
@NonNull
protected ProcessInfoBuilder makePackageProcessBuilder(@NonNull AaptPackageConfig config)
throws AaptException {
ProcessInfoBuilder builder = new ProcessInfoBuilder();
/* * AaptPackageProcessBuilder had this code below, but nothing was ever added to * mEnvironment. */
//builder.addEnvironments(mEnvironment);
builder.setExecutable(getAaptExecutablePath());
builder.addArgs("package"); . (Omit some code that doesn't matter)// The debuggable property of buildType is injected here
if (config.isDebuggable()) {
builder.addArgs("--debug-mode"); }...return builder;
}
Copy the code
As you can see, the debug parameter is added to the input parameter of AAPT, which is the external process of calling AAPT. The Android Gradle Plugin is not in control.
0x03
Find the Android SDK source code, compile your own AAPT (see here for details), and don’t forget to add some logs in key places:
- First import the file, find the corresponding read
--debug-mode
[main.cpp], is temporarily stored in a bundle with the correct argument true:
if (strcmp(cp, "-debug-mode") = =0) {
fprintf(stderr, "AAPTDEBUG: set debug == true");
bundle.setDebugMode(true);
}
Copy the code
- Again, find
bundle->getDebugMode()
And found that this was the core of it, reallydebuggable
Is written toAndroidManifest.xml
; But there’s an error hereIf the application node cannot be obtained, it is null[Resource. CPP] :
status_t massageManifest(Bundle* bundle, sp<XMLNode> root)
{
root = root->searchElement(String16(), String16("manifest"));
if (root == NULL) {
fprintf(stderr, "No <manifest> tag.\n");
return UNKNOWN_ERROR;
}
// ...
fprintf(stderr, "AAPTDEBUG: bundle->getDebugMode()");
if (bundle->getDebugMode()) {
fprintf(stderr, "AAPTDEBUG: bundle->getDebugMode() - true");
sp<XMLNode> application = root->getChildElement(String16(), String16("application"));
if (application == NULL) {
fprintf(stderr, "AAPTDEBUG: application == NULL"); // Print out this line of log
}
if(application ! =NULL) {
fprintf(stderr, "AAPTDEBUG: application ! = NULL");
if (!addTagAttribute(application, RESOURCES_ANDROID_NAMESPACE, "debuggable"."true",
errorOnFailedInsert)) {
fprintf(stderr, "AAPTDEBUG: error on insert");
returnUNKNOWN_ERROR; }}}// ...
}
Copy the code
- Take a look at
getChildElement()
[xmlNode.cpp] : [xmlNode.cpp] : [xmlNode.cpp] : [xmlNode.cpp] : [xmlNode.cpp] : [xmlNode.cpp] : [xmlNode.cpp] :
sp<XMLNode> XMLNode::getChildElement(const String16& tagNamespace, const String16& tagName)
{
printf("AAPTDEBUG: root -> %s\n".String8(mElementName).string());
for (size_t i=0; i<mChildren.size(a); i++) { sp<XMLNode> child = mChildren.itemAt(i);
if (child->getType() == XMLNode::TYPE_ELEMENT) {
printf("AAPTDEBUG: getChildElement-> %s\n".String8(child->mElementName).string());
}
if (child->getType() == XMLNode::TYPE_ELEMENT
&& child->mNamespaceUri == tagNamespace
&& child->mElementName == tagName) {
returnchild; }}return NULL;
}
Copy the code
- Then the final question is, why not manifest level application node child nodes, check the compiled binary manifest (. / app/build/intermediates/res/resources – the debug. Ap_, To avoid sensitive information, use the DebuggableTest project as an example) :
- Application’s direct parent is namespace!
0x04
See all kinds of dependent libraries Manifest (build – cache/exploded – aar), check the Manifest combine log (app/build/outputs/logs/Manifest – merger – {variant} – report). The Manifest Precheck plugin Seal can cause this problem in two ways:
- Dependencies on the library itself to declare namespace not only in the manifest node, for example, in the application node declared android namespace, can refer to my pub this DebuggableTest project;
- Before the Manifest merge (i.e
process{variant}Manifest
Before) if there is an exampleSealThis cleaning of the Manifest that depends on the library,And it just so happens that you dogroovy.util
XML libraries do XML parsing and output, so congratulations you, the library has a chance to various nodes may cause you to duplicate the namespace, such as USES – SDK, applicationHowever, I have not seriously investigated under what circumstances such a problem might occur. Currently, only a small number of samples in the experiment have such a problem, so I have not found their common points for the time being.
The solution
-
Using a custom AAPT, a single line of code solves this problem: Call sp
XMLNode::getChildElement(const string16&tagnamespace, Const string16&tagName) instead of another built-in method sp
XMLNode::searchElement(const string16&tagnamespace, Const string16&tagName), because the implementation of searchElement is to loop through all the deep children:
// Resource.cpp status_t massageManifest(Bundle* bundle, sp<XMLNode> root) { root = root->searchElement(String16(), String16("manifest")); if (root == NULL) { fprintf(stderr, "No <manifest> tag.\n"); returnUNKNOWN_ERROR; }...fprintf(stderr, "AAPTDEBUG: bundle->getDebugMode()"); if (bundle->getDebugMode()) { fprintf(stderr, "AAPTDEBUG: bundle->getDebugMode() - true"); // change the lookup method! sp<XMLNode> application = root->searchElement(String16(), String16("application")); . }... }// XMLNode.cpp sp<XMLNode> XMLNode::searchElement(const String16& tagNamespace, const String16& tagName) { if (getType() == XMLNode::TYPE_ELEMENT && mNamespaceUri == tagNamespace && mElementName == tagName) { return this; } // Recursive implementations avoid the above problems for (size_t i=0; i<mChildren.size(a); i++) { sp<XMLNode> found = mChildren.itemAt(i)->searchElement(tagNamespace, tagName); if(found ! =NULL) { returnfound; }}return NULL; } Copy the code
-
Or use the new Seal plug-in V1.1.0, which adds a configuration item called xmlnsSweep, which cleans its artifacts XMLNS after executing process{variant}Manifest. To avoid unnecessary namespace declarations outside the MANIFEST node, refer to the README of the Seal repository for detailed instructions. There is no guarantee that all dependent libraries will not fail in Precheck.
conclusion
This is probably the most troublesome problem I have studied this year. The link is long and the pits are constantly large and small. Some of them are dug by Google and some by Groovy.
Dang! Update issue address: issuetracker.google.com/issues/6607… !
(Note, the project was based on AAPT1 when this article was written. Currently, the Seal Plugin has been upgraded to version 3.0 and migrated to AAPT2, supporting Android Gradle Plugin 4.2.x alpha. Please refer to the latest instructions in the warehouse.)
Welcome to comment like, and follow my public accountEfficient Android Development.