The introduction

In the previous stage, we learned the design concept and concrete implementation of sample-Manager module:

(1) 【 Plug-in & Hotfix series 】Shadow source code parsing sample-Manager (一)

(2) 【 Plug-in & Hotfix series 】Shadow source code parsing sample-Manager (2)

At this point, our hotfix has completed the Host and Admin Plugin modules

The next stage is plug-in modules, such as:

  • How are multiple business plug-in production processes distributed as zip?
  • How is each plug-in written?
  • How are the components in the plug-in supported?
  • .

Let’s start our new journey with how to zip the Plug-in Production Process

The profile

This is the content of the Shadow plugin zip package. There are 4 plug-ins and 1 configuration file

The configuration file above shows some version information of the plug-in

This chapter will learn how to generate this information using the Gradle plugin

The implementation process

1. Achieve your purpose

The main implementation of this project is to package the sample-manager.apk as zip

2. Effect display

Perform the packing taskThe zip package is then produced to the specified directory

3. The plugin defines/uploads/uses the gradle configuration

(1) Plug-in definition

The definition used here is the new version of the definition, not quite the same as before

The new method is called Composing builds, while the old one is called buildSrc

The main differences are:

  • BuildSrc: A change in buildSrc causes the whole project to become out-of-date. BuildSrc: A change in buildSrc causes the whole project to become out-of-date.
  • Composing builds, A composite build is simply a build that includes other builds. In many ways a composite build is similar to a Gradle multi-project build, except that instead of including single projects, Complete builds are included. In many ways, composite builds are similar to Gradle multi-project builds, except that they include full builds instead of individual projects and feature faster builds.

In addition to the official documents, there are also more good online introduction, such as byte students share

(2) Plug-in upload Here, a relatively simple upload method is used to upload to the local project’s REPO

(3) Plug-in introduction

(4) Use of plug-ins

The configuration we use is to package the APK output from sample-Manager into zip

4. Project Introduction

This is the effect of combining the official code, the original official structure is as follows:

5. Source code analysis

(1) Find the local androidJar and customize the classPool to complete the disable_SHADOW_transform switch

Because the switch function is not the focus of this chapter, so the implementation of temporary annotations, to understand its code about the role

(2) Custom expansion

ShadowExtension, is an implementation of a collection

open class ShadowExtension {
        var transformConfig = TransformConfig()
        fun transform(action: Action<in TransformConfig>) {
            action.execute(transformConfig)
        }
}

class TransformConfig {
    var useHostContext: Array<String> = emptyArray()
}
Copy the code

PackagePluginExtension looks like this:

open class PackagePluginExtension {
    var loaderApkProjectPath = ""
    var runtimeApkProjectPath = ""
    var archivePrefix = ""
    var archiveSuffix = ""
    var destinationDir = ""
    var uuid = ""
    var version: Int = 0
    var uuidNickName = ""
    var compactVersion: Array<Int> = emptyArray()
    var buildTypes: NamedDomainObjectContainer<PluginBuildType>
}
Copy the code

The PluginBuildType inside is as follows:

open class PluginBuildType {
    var name = ""
    var loaderApkConfig: Tuple2<String, String> = Tuple2(""."")
    var runtimeApkConfig: Tuple2<String, String> = Tuple2(""."")
    lateinit var pluginApks: NamedDomainObjectContainer<PluginApkConfig>
}
Copy the code

PluginApkConfig looks like this:

open class PluginApkConfig {
    var name = ""
    var partKey = ""
    var businessName = ""
    var apkName = ""
    var apkPath = ""
    var buildTask = ""
    var dependsOn: Array<String> = emptyArray()
    var hostWhiteList: Array<String> = emptyArray()
}
Copy the code

(3) Task creation

 // Create execution tasks based on configured pluginTypes
val tasks = mutableListOf<Task>()
for (i in buildTypes) {
     // Organize plug-ins such as APk and JSON as zip
     val task = createPackagePluginTask(project, i)
     tasks.add(task)
}
Copy the code

Build tasks according to buildTypes

BuildTypes is the PluginBuildType above, and this example has only one debug type

Then let’s look at the implementation of createPackagePluginTask:

internal fun createPackagePluginTask(project: Project, buildType: PluginBuildType): Task {

    /** ** directory ** /
    val targetConfigFile = File(project.projectDir.absolutePath + "/generatePluginConfig/${buildType.name}/config.json")

    /** * 1) rely on createGenerateConfigTask * 2) zip package ** /
    return project.tasks.create("package${buildType.name.capitalize()}Plugin", Zip::class.java) {
        System.err.println("PackagePluginTask task start run...")

        //runtime apk file
        val runtimeApkName: String = buildType.runtimeApkConfig.first
        var runtimeFile: File? = null
        if (runtimeApkName.isNotEmpty()) {
            System.err.println("runtime apk...")
            runtimeFile = ShadowPluginHelper.getRuntimeApkFile(project, buildType, false)}//loader apk file
        val loaderApkName: String = buildType.loaderApkConfig.first
        var loaderFile: File? = null
        if (loaderApkName.isNotEmpty()) {
            System.err.println("loader apk...")
            loaderFile = ShadowPluginHelper.getLoaderApkFile(project, buildType, false)}//config file
        / / does not exist: / Users/yabber AndroidStudioProjects/DemoHot/sample - the plugin - app/build + / intermediates
        //val targetConfigFile = File(project.buildDir.absolutePath + "/intermediates/generatePluginConfig/${buildType.name}/config.json")
        //val targetConfigFile = File(project.projectDir.absolutePath + "/generatePluginConfig/${buildType.name}/config.json")
        val result = targetConfigFile.parentFile.mkdirs()
        System.err.println(Json parentFile dir, result =" + result)
        System.err.println("targetConfigFile = " + targetConfigFile.absoluteFile)

        //all plugin apks
        val pluginFiles: MutableList<File> = mutableListOf()
        for (i in buildType.pluginApks) {
            val file = ShadowPluginHelper.getPluginFile(project, i, false)
            System.err.println("pluginFile = " + file.absoluteFile)
            pluginFiles.add(file)
        }

        it.group = "plugin"
        it.description = "Package plug-ins"
        it.outputs.upToDateWhen { false }
        if(runtimeFile ! =null) {
            pluginFiles.add(runtimeFile)
        }
        if(loaderFile ! =null) {
            pluginFiles.add(loaderFile)
        }
        it.from(pluginFiles, targetConfigFile)// from

        val packagePlugin = project.extensions.findByName("packagePlugin")
        val extension = packagePlugin as PackagePluginExtension

        val suffix = if (extension.archiveSuffix.isEmpty()) "" else extension.archiveSuffix
        val prefix = if (extension.archivePrefix.isEmpty()) "plugin" else extension.archivePrefix
        if (suffix.isEmpty()) {
            it.archiveName = "$prefix-${buildType.name}.zip"
        } else {
            it.archiveName = "$prefix-${buildType.name}-$suffix.zip"
        }
        //destinationDir
        it.destinationDir = File(if (extension.destinationDir.isEmpty()) "${project.rootDir}/build" else extension.destinationDir)
        System.err.println("destinationDir = " + it.destinationDir.absoluteFile)

        System.err.println("PackagePluginTask task end...")
    }.dependsOn(createGenerateConfigTask(project, buildType))
}

Copy the code

Here are a few things:

(1) create a task package ${buildType. Name. Capitalize ()} the Plugin

(2) Prepare the plug-in APK File, such as runtime APK/Loader APK/sample-Manager APK/service plug-in APK

(3) Prepare a set of parameters, and then copy the plug-in to the specified directory

(4) Create another task and make it dependent on it

Now let’s see what the other task (createGenerateConfigTask) does.

private fun createGenerateConfigTask(project: Project, buildType: PluginBuildType): Task {
    System.err.println("GenerateConfigTask task run ... ")

    /** ** directory ** /
    val targetConfigFile = File(project.projectDir.absolutePath + "/generatePluginConfig/${buildType.name}/config.json")

    val packagePlugin = project.extensions.findByName("packagePlugin")
    val extension = packagePlugin as PackagePluginExtension

    //runtime apk build task
    val runtimeApkName = buildType.runtimeApkConfig.first
    var runtimeTask = ""
    if (runtimeApkName.isNotEmpty()) {
        runtimeTask = buildType.runtimeApkConfig.second
        System.err.println("runtime task = $runtimeTask")
        //println("runtime task = $runtimeTask")
    }


    //loader apk build task
    val loaderApkName = buildType.loaderApkConfig.first
    var loaderTask = ""
    if (loaderApkName.isNotEmpty()) {
        loaderTask = buildType.loaderApkConfig.second
        System.err.println("loader task = $loaderTask")
        //println("loader task = $loaderTask")
    }

    // Plug-in project tasks, such as :sample-manager:assembleDebug
    val pluginApkTasks: MutableList<String> = mutableListOf()
    for (i in buildType.pluginApks) {
        val task = i.buildTask
        System.err.println("pluginApkProjects task = $task")
        //println("pluginApkProjects task = $task")
        pluginApkTasks.add(task)
    }

    /** * 1) Rely on pluginApkTasks ** /
    val task = project.tasks.create("generate${buildType.name.capitalize()}Config") {
        it.group = "plugin"
        it.description = "Generate plug-in configuration file"
        it.outputs.file(targetConfigFile)
        it.outputs.upToDateWhen { false }
    }
            .dependsOn(pluginApkTasks)// Depends on plug-in project tasks such as: : sample-Manager :assembleDebug
            .doLast {

                System.err.println("generate json Config task begin")
                //println("generateConfig task begin")
                val json = extension.toJson(project, loaderApkName, runtimeApkName, buildType)
                System.err.println("json = " + json)

                val bizWriter = BufferedWriter(FileWriter(targetConfigFile))
                bizWriter.write(json.toJSONString())
                bizWriter.newLine()
                bizWriter.flush()
                bizWriter.close()

                System.err.println("generateConfig task done")}if (loaderTask.isNotEmpty()) {
        task.dependsOn(loaderTask)
    }
    if (runtimeTask.isNotEmpty()) {
        task.dependsOn(runtimeTask)
    }
    return task
}
Copy the code

I mainly did several things:

(1) Parameter preparation, such as reading parameters in the expansion

(2) Create a task, perform json file generation, and execute the task after the specified task (e.g. after sample-Manager :assembleDebug)

At this point, you can see that the task sequence looks something like this:

  • First, the plug-in generates apK, such as sample-Manager :assembleDebug
  • The json file is then generated based on the generated APK
  • Finally, package apK and JSON into ZIP

The source address

Github.com/DaviAndorid…

At the end

Haha, that’s all for this article (systematic learning and growing together)

Tips

Dig into android mobile field, precipitation android application and other technologies, wechat “DaviZgx”, welcome to add to the group communication