1. Before you start
Gradle DSL document
Gradle is based on Groovy, and Groovy is based on Java, so it always runs on the JVM. Gradle, build. Gradle, settings. Gradle and the like are all eventually made into an object before execution.
- Gradle objects: each execution
gradle taskName
Gradle builds a Gradle object by default. During execution, there is only one Gradle object and it is rarely customized. - Project object: A build.gradle corresponds to a Project object.
- Settings object: A settings.gradle corresponds to a Settings object.
Their lifecycle nodes are as follows:
2. Create the Plugin
Create the buildSrc module, which is used to develop the Gradle plugin. Then create a new plug-in class: ManifestDemoPlugin.
import org.gradle.api.Plugin
import org.gradle.api.Project
class ManifestDemoPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
}
}
Copy the code
The above code is standard code that you must inherit from your Plugin to write plug-ins.
3. The analysis
Requirement: Let’s say android.permission.READ_PHONE_STATE is removed. (Sometimes there may be some permissions defined in the aar, but it is not possible for the app to have these permissions. There is a better way to remove permissions. Tools :remove.
We need to get the merged Androidmanifest.xml file and modify it to remove the android.permission.READ_PHONE_STATE content before packaging.
But how do we hook the time to merge androidManifest.xml and get the contents of the manifest file? First, run the./gradlew tasks –all command to see what tasks are available. Since the merge manifest file must be a task, we only need to execute our code logic after the task.
> task :tasks...... app:makeApkFromBundleForDebug app:makeApkFromBundleForRelease app:mergeDebugAndroidTestAssets app:mergeDebugAndroidTestGeneratedProguardFiles app:mergeDebugAndroidTestJavaResource app:mergeDebugAndroidTestJniLibFolders app:mergeDebugAndroidTestNativeLibs app:mergeDebugAndroidTestResources app:mergeDebugAndroidTestShaders app:mergeDebugAssets app:mergeDebugGeneratedProguardFiles app:mergeDebugJavaResource app:mergeDebugJniLibFolders app:mergeDebugNativeLibs app:mergeDebugResources app:mergeDebugShaders app:mergeDexRelease app:mergeExtDexDebug app:mergeExtDexDebugAndroidTest app:mergeExtDexRelease app:mergeLibDexDebug app:mergeLibDexDebugAndroidTest app:mergeProjectDexDebug app:mergeProjectDexDebugAndroidTest app:packageDebug app:packageDebugAndroidTest app:packageDebugBundle app:packageDebugUniversalApk app:packageRelease app:packageReleaseBundle app:packageReleaseUniversalApk app:parseDebugIntegrityConfig app:parseReleaseIntegrityConfig app:preBuild app:preDebugAndroidTestBuild app:preDebugBuild app:preDebugUnitTestBuild prepareKotlinBuildScriptModel app:prepareKotlinBuildScriptModel app:prepareLintJar app:prepareLintJarForPublish ......Copy the code
There is a task called app:mergeDebugResources, which translates to merge resources. Here’s what Android Plugin Task generally means.
4. Start coding
class ManifestDemoPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
// After afterEvaluate, you can get the complete directed graph of those tasks
project.afterEvaluate {
//1. Find the task mergeFreeDebugResources
def mergeDebugResourcesTask = project.tasks.findByName("mergeFreeDebugResources")
if(mergeDebugResourcesTask ! =null) {
//2. Create a task
def parseDebugTask = project.tasks.create("ParseDebugTask", ParseDebugTask.class)
//3. Add a task parseDebugTask to be executed immediately after mergeDebugResourcesTask ends
mergeDebugResourcesTask.finalizedBy(parseDebugTask)
}
}
}
}
Copy the code
- We need to get all the tasks after the Project configuration is complete, because this is when the complete directed graph task dependency is generated.
- The second is through the API:
project.tasks
Get all the tasks (The API documentation is located here), and thenFindByName methodFind the task. - At this point we need to create our own task and in
mergeFreeDebugResources
Execution starts immediately after completion.
Let’s see how our Task reads:
class ParseDebugTask extends DefaultTask {
@TaskAction
void doAction() {
//1. Find the manifest file
def file = new File(project.buildDir, "/intermediates/merged_manifests/freeDebug/AndroidManifest.xml")
if(! file.exists()) { println("File does not exist")
return
}
//2. Obtain the contents of the file
def fileContent = file.getText()
removePermission(file, fileContent)
}
@param rootNode Node * @param file File */
void removePermission(File file,String fileContent) {
// Option 1 will remove all permissions
//def rootNode = new XmlParser().parseText(fileContent)
//def node = new Node(rootNode, "uses-permission"/*,["android:name" : "android.permission.READ_PHONE_STATE"]*/)
//rootNode.remove(node)
//def updateXmlContent = XmlUtil.serialize(rootNode)
//println(updateXmlContent)
// Option 2 reads the XML content and replaces the string that specifies the permission
fileContent = fileContent.replace("android.permission.READ_PHONE_STATE"."")
println(fileContent)
// Write the string to the file
file.write(fileContent)
}
}
Copy the code
- First, the Task must inherit from DefaultTask
- The merged manifest file is in
build/intermediates/merged_manifests/freeDebug/
Directory, get this file first - through
file.getText()
Get the contents of the file and remove the string “android.permission-read_phone_state” from the contents. - The string is then written to the manifest file (which will be used for packaging).
By the way, I can come again a, dynamically add a permission: android. Permission. The INTERNET
@param rootNode Node * @param file File */
void addPermission(File file,,String fileContent) {
def rootNode = new XmlParser().parseText(fileContent)
//3. Add network permissions: XMLNS: Android
//<uses-permission android:name="android.permission.INTERNET"/>
//xmlns:android="http://schemas.android.com/apk/res/android"
rootNode.appendNode("uses-permission"["xmlns:android": "http://schemas.android.com/apk/res/android"."android:name" : "android.permission.INTERNET"])
// You can dynamically add meta-data to an Application
//rootNode.application[0].appendNode("meta-data", ['android:name': 'appId', 'android:value': 546525])
//4. Get the modified XML content
def updateXmlContent = XmlUtil.serialize(rootNode)
println(updateXmlContent)
//5. Write the modified XML to the file
file.write(updateXmlContent)
}
Copy the code
5. To summarize
When I first started, I was very unfamiliar with the API, so I had to look it up frantically. I tried to think of some requirements, do some exercises, write more code, look up the API more, and get familiar with the process. The Gradle plugin is very important.