Background:

Because aihuishuopaojiotang APP needs to enter the application market, the channel operation has asked me to provide the multi-channel package for reinforcement. Now, the Android application market in the domestic market needs reinforcement, so we need to carry out reinforcement and multi-channel package construction automation.

Say first conclusion

We originally planned to use 360 reinforcement and multi-channel packaging, but later, because 360 would inject a lot of kits into the code, we gave up 360’s scheme and adopted Vasdolly’s multi-channel packaging scheme.

However, this article has respectively described 360 reinforcement packaging scheme and Vasdolly packaging method.

Preliminary technical research

Investigated the Android native solution, Meituan Walle solution and 360 reinforcement package, each with its own advantages and disadvantages:

Technical solution advantages disadvantages
Android Native Solution Variant packaging via PrpductFlovers, flexible build from variant to variant Each variant is assebleXXXRelease repackaged, which is slow
Meituan walle Each channel is quickly packaged by solving APK, inserting channel information, and then re-signing Hardening requires your own implementation or other third-party solutions
360 reinforcement is protected Similar to Walle scheme, fast packaging speed; Can consolidate multi – channel packaging integration Differences between channels require hard coding of meta-data

Our actual demand

  • At present, each channel is only a pure APK for listing, and there is no special demand for a channel for the time being.

  • We need to pass the source of the channel to BI for channel analysis

  • Many channels need apK reinforcement, so APK reinforcement must be required

Based on the above research and our actual needs, we decided to choose 360 reinforcement package for reinforcement.

Then start working on…

Hardening package flow chart

Need to comb

  1. Create a new task after assembleRelease to process the APK package

  2. Find the apprelease.apk generated by assembleRelease

  3. Harden this APK

  4. Multichannel packaging of this APK

  5. Re-sign the multi-channel APK

  6. Continuous delivery of APK packages to test and channel operations

Code implementation

Find the zip and documentation for 360 Reinforcement:

Download address: jiagu.360.cn/index.html

Since we need to integrate automatic reinforcement and multi-channel packaging into automated CI(Jenkins), we need to use the command line tool of 360 Reinforcement

Command-line tool documentation: jiagu.360.cn/qcms/help.h…

Through the documentation, find a few key command lines

#The loginJava -jar jiagu.jar -login <username> <password>
#Import the signature
java -jar jiagu.jar  -importsign <keystore_path> <keystore_password> <alias><alias_password>

#Import the channel list file
java -jar jiagu.jar -importmulpkg <mulpkg_path>

#Reinforcement multi-channel packaging
java -jar jiagu.jar -jiagu <inputAPKpath> <outputpath> -autosign  -automulpkg

Copy the code

Code implementation

  1. Find the apprelease.apk generated by assembleRelease
    /** * Only apk files that end in APk and contain a release string */ can be matched in the app Build folder
    findReleaseApkPath = { ->
        def appBuildOutPut = new File("${rootProject.rootDir}/app/build/outputs/apk/release")
        def apkFile = null
        appBuildOutPut.eachFile {
            if (it.name.endsWith(".apk") && it.name.contains("release")) {
                println(it)
                apkFile = it
            }
        }
        return apkFile
    }
Copy the code
  1. Call 360 harden. Jar to harden the package
 /** * consolidate by calling 360 command line and multichannel package * apk -> original release package * outputPath -> multichannel package outputPath */
    reinForceApk = { File apk, File outPutPath ->
        println(outPutPath)
        if (apk == null| |! apk.exists()) {println("No APK file found")
            throw new FileNotFoundException("No APK file found")}if(! outPutPath.exists()) { outPutPath.mkdirs() }"java -jar ${rein360ForceJarPath} -login ${account360} ${psw360}".execute().waitForProcessOutput(System.out, System.err)

        "java -jar ${rein360ForceJarPath} -importmulpkg ${mulpkgPath}".execute().waitForProcessOutput(System.out, System.err)

        "Java-jar ${rein360ForceJarPath} -importSign ${keyStorePath} ${KEYSTORE_PASSWORD} ${KEY_ALIAS} ${KEY_PASSWORD}".execute().waitForProcessOutput(System.out, System.err)

        "java -jar ${rein360ForceJarPath} -config -analyse".execute().waitForProcessOutput(System.out, System.err)

        "java -jar ${rein360ForceJarPath} -jiagu ${apk.path} ${outPutPath.path} -autosign -automulpkg".execute().waitForProcessOutput(System.out, System.err)

    }
Copy the code
  1. Write the hardening task on task, and build it after assembleRelease
task assembleReinForceRelease() {
    group 'multipleChannels'
    dependsOn('assembleRelease')

    doLast {
        def apk = findReleaseApkPath()
        def outputFile = new File(reinForcedOutPutPath)
        reinForceApk(apk, outputFile)
    }
}

Copy the code
  1. File all APK documents through Jenkins (specific operations in Jenkins CI)

Complete code implementation

Download the 360 hardening ZIP package from the official website and decompress it to a subdirectory of the project. The directory structure is as follows:

Create a new multiple-channels. Gradle file under rootProject

ext {
    reinForceJarPath = "${project.rootDir}/360jiagu/jiagu.jar"

    keyStorePath = "${rootProject.rootDir}/app/keystore/observer_app.keystore"

    rein360ForceDirPath = "${rootProject.rootDir}/360jiagu"

    reinForcedOutPutPath = "${rootProject.rootDir}/app/build/outputs/apk/release/channels"

    rein360ForceJarPath = "${rein360ForceDirPath}/jiagu.jar"

    account360 = "xxxxxx"

    psw360 = "xxxxxx"

    mulpkgPath = ${rein360ForceDirPath}/ multichannel template.txt"


    /** * Only apk files that end in APk and contain a release string */ can be matched in the app Build folder
    findReleaseApkPath = { ->
        def appBuildOutPut = new File("${rootProject.rootDir}/app/build/outputs/apk/release")
        def apkFile = null
        appBuildOutPut.eachFile {
            if (it.name.endsWith(".apk") && it.name.contains("release")) {
                println(it)
                apkFile = it
            }
        }
        return apkFile
    }


    /** * consolidate by calling 360 command line and multichannel package * apk -> original release package * outputPath -> multichannel package outputPath */
    reinForceApk = { File apk, File outPutPath ->
        println(outPutPath)
        if (apk == null| |! apk.exists()) {println("No APK file found")
            throw new FileNotFoundException("No APK file found")}if(! outPutPath.exists()) { outPutPath.mkdirs() }"java -jar ${rein360ForceJarPath} -login ${account360} ${psw360}".execute().waitForProcessOutput(System.out, System.err)

        "java -jar ${rein360ForceJarPath} -importmulpkg ${mulpkgPath}".execute().waitForProcessOutput(System.out, System.err)

        "Java-jar ${rein360ForceJarPath} -importSign ${keyStorePath} ${KEYSTORE_PASSWORD} ${KEY_ALIAS} ${KEY_PASSWORD}".execute().waitForProcessOutput(System.out, System.err)

        "java -jar ${rein360ForceJarPath} -config -analyse".execute().waitForProcessOutput(System.out, System.err)

        "java -jar ${rein360ForceJarPath} -jiagu ${apk.path} ${outPutPath.path} -autosign -automulpkg".execute().waitForProcessOutput(System.out, System.err)

    }
}

Copy the code

Add build. Gradle in app Module

apply from: ".. /multiple-channels.gradle"

task assembleReinForceRelease() {
    group 'multipleChannels'
    dependsOn('assembleRelease')

    doLast {
        def apk = findReleaseApkPath()
        def outputFile = new File(reinForcedOutPutPath)
        reinForceApk(apk, outputFile)
    }
}

Copy the code

The last

Call./gradlew assembleReinForceRelease to consolidate and complete all channel packages

Some of the pit

  • The first one

PC is MAC, Jenkins CI is Linux, downloaded zip is MAC version, Jenkins will report aAPT check failed. Null error.

  • The second

360 reinforcement treasure command is the need for network request, is the need to wait for the asynchronous return of login login results

The original

  exec {
            commandLine("sh"."-c"."java -jar ${rein360ForceJarPath} -login ${jia_gu_user_name} ${jia_gu_psw}")
            commandLine("sh"."-c"."java -jar ${rein360ForceJarPath} -importmulpkg ${mulpkgPath}")
            commandLine("sh"."-c"."java -jar ${rein360ForceJarPath} -importsign ${keyStorePath} ${KEYSTORE_PASSWORD} ${KEY_ALIAS} ${KEY_PASSWORD}")
            commandLine("sh"."-c"."java -jar ${rein360ForceJarPath} -showmulpkg")
            commandLine("sh"."-c"."java -jar ${rein360ForceJarPath} -showconfig")
            commandLine("sh"."-c"."java -jar ${rein360ForceJarPath} -jiagu ${apk.path} ${outPutPath.path} -autosign -automulpkg")}Copy the code

Change to a shell that waits for results


  "java -jar ${rein360ForceJarPath} -login ${account360} ${psw360}".execute().waitForProcessOutput(System.out, System.err)

        "java -jar ${rein360ForceJarPath} -importmulpkg ${mulpkgPath}".execute().waitForProcessOutput(System.out, System.err)

        "Java-jar ${rein360ForceJarPath} -importSign ${keyStorePath} ${KEYSTORE_PASSWORD} ${KEY_ALIAS} ${KEY_PASSWORD}".execute().waitForProcessOutput(System.out, System.err)

        "java -jar ${rein360ForceJarPath} -config -analyse".execute().waitForProcessOutput(System.out, System.err)

        "java -jar ${rein360ForceJarPath} -jiagu ${apk.path} ${outPutPath.path} -autosign -automulpkg".execute().waitForProcessOutput(System.out, System.err)

Copy the code

The decompilation revealed that 360 would inject a lot of 360 services, broadcasts, activities and application loaders into the code

It is found that 360 will modify the application pointing file in the manifest file.

In addition, if you choose the update service and other services, it may also add the update service update broadcast to the file. In short, all four components have been added.

In this way, we may be added by 360 advertising and other services, and lose part of the control of the app.

In addition, 360 market must be reinforced by 360, and App Treasure recommends LEGu reinforcement, so those reinforced by 360 cannot be online by app Treasure.

Based on the above points, our technical solution is as follows:

  1. Using VasDolly multi-channel packaging (Walle Plugin always fails to load and then gives up)

  2. Other markets will be directly put on the shelves, and the markets that need reinforcement will be manually reinforced by the operation colleagues.

Integrated VasDolly

Making address github.com/Tencent/Vas…

The preferred command line tool because we’ve written so much that we can’t waste it

Command line Address: github.com/Tencent/Vas…

Create a Vasdolly folder, download the JAR package and put it in it

java -jar VasDolly.jar put -c channel.txt /home/user/base.apk /home/user/

How VasDolly works:

Github.com/Tencent/Vas…

Channel parameters are written in the signature based on v1 and V2 signature modes. 👍👍👍👍👍, fast speed also for APK itself without any intrusion, just in the signature to increase the channel information.

Last Vasdolly complete code

New Directory structure Delete all 360 hardened folders add a Vasdolly folder

multiple-channeles.gradle

ext {
    jarPath = "${project.rootDir}/vasdolly/VasDolly.jar"
    channelsPath = "${project.rootDir}/vasdolly/channels.txt"
    outputChannelsFilePath = "${project.rootDir}/app/build/outputs/apk/release/channels/"



    /** * Only apk files that end in APk and contain a release string */ can be matched in the app Build folder
    findReleaseApkPath = { ->
        def appBuildOutPut = new File("${rootProject.rootDir}/app/build/outputs/apk/release")
        def apkFile = null
        appBuildOutPut.eachFile {
            if (it.name.endsWith(".apk") && it.name.contains("release")) {
                apkFile = it
            }
        }
        return apkFile
    }


    /** * consolidate by calling 360 command line and multichannel package * apk -> original release package * outputPath -> multichannel package outputPath */
    buildMultipleChannels = { File apk, File outPutPath ->
        println(outPutPath)
        if (apk == null| |! apk.exists()) {throw new FileNotFoundException("No APK file found")}if(! outPutPath.exists()) { outPutPath.mkdirs() }def cmd = "java -jar ${jarPath} put -c ${channelsPath} ${apk} ${outPutPath}"
        println cmd
        cmd.execute().waitForProcessOutput(System.out, System.err)
    }
}
Copy the code

app build.gradle


apply from: ".. /vasdolly/multiple-channels.gradle"


/** * Unlock the multi-channel packaging task * this task will rely on the APK package produced by assembleRelease */
task assembleMultipleChannelsRelease() {
    group 'multipleChannels'
    dependsOn('assembleRelease')

    doLast {
        buildMultipleChannels(findReleaseApkPath(), new File(outputChannelsFilePath))
    }
}

Copy the code

Get the channel name from the code

fun getChannelName(ctx: Activity): String {
    return try {
        ChannelReaderUtil.getChannel(ctx.application)
    } catch (e: Exception) {
        e.printStackTrace()
        "official"}}Copy the code

The code is much cleaner. 👍 👍 👍 👍 👍 👍

In the end we perform. / gradlew assembleMultipleChannelsRelease

View the results:

finished