There are Flavors and Meituan Walle solutions that can be packaged through multiple channels.

I tried Flavors in Gradle, but I soon found that it was too slow to pack. Especially when I executed the assemble task, it was compiled in six channels for about ten minutes, which was totally unacceptable. Then we began to use The Walle of Meituan. Here is the address first:

Blog: tech.meituan.com/2017/01/13/…

Github:github.com/Meituan-Dia…

The principle of Meituan’s multi-channel packaging is also easy to understand:

Wall · E improves channel package generation efficiency by adding customized channel information to the Apk Signature Block in Apk.

The configuration and use of Walle is also very simple, which is not explained here. Please refer to the official documentation for details.

After multi-channel packaging, how to reinforce is still a problem, my reinforcement scheme is 360 reinforcement bao’s reinforcement scheme, one by one reinforcement, let me feel too waste of time, too troublesome, also true a little low.

My initial idea was to use a task to rely on Walle’s Assemblereleashannels task, and then traverse the multi-channel output path to traverse the multi-channel APK for traversal reinforcement. Then through practice, it is found that this scheme is not feasible, hardening will report the following error

Upload failed 10418 The submission times are too frequent, please try again later

Since the channel information is written in the APK Signature Block, other APK information is the same, hardening will also report the error of submitting the same file repeatedly.

Upload failed 10419 The same file is submitted frequently, please try again later

In addition, the hardening process cannot avoid the process of uploading, hardening, downloading and signing each APK.

In addition to the problem of reinforcement, there is also a serious problem. After reinforcement and re-signature, channel information will become invalid. This issue is also addressed on Walle’s Github:

Wiki:github.com/Meituan-Dia…

There is a Wiki that provides a solution, but it requires a Python environment. Forgive me for being a bit lazy, but the process seems a bit tedious.

This is not the optimal solution I want, so I find another way.

In the process of using Walle, I found:

The assembleRelease task is executed before the Assemblereleashannels task.

AssembleReleaseChannels relies on the assembleRelease task. The assembleRelease task builds a release package in the outputs directory, and Then Walle uses this APK to write multichannels. So, can we do something in this original release APK to change the process of multi-channel packaging -> reinforcement -> multi-channel packaging into reinforcement -> multi-channel packaging? In this way, you only need to harden the original APK once before writing the channel information, which greatly reduces the hardening time. Most importantly, you need to harden the apK first and write the channel information after the hardening, so that the channel information will not be overwritten.

The multi-channel packaging function is mainly done by the Walle plugin, so take a look at this part of the process.

In the source code of the Walle project, library Moudle is mainly open for users to read channel information, the specific implementation is in payload_reader, payload_writer moudle function is to write channel information, Walle – CLI is a command line program provided by Walle, plugin is the main plug-in packaged by multiple channels.

In plugin, the groovy directory contains the main implementation source code for the plugin.

GradlePlugin is the entry point for plug-in execution, Extension is Gradle configuration information, and ChannelMaker is the main task implementation.

The solution is realized by modifying Extension and ChannelMaker.

Start with the Extension

class Extension {
    static final String DEFAULT_APK_FILE_NAME_TEMPLATE = '${appName}-${buildType}-${channel}.apk'File apkOutputFolder String apkFileNameFormat File channelFile; File configFile; String variantConfigFileName; . }Copy the code

If these fields look familiar, yes, they are the information we need to configure Walle in Gradle

walle {
    apkOutputFolder = new File("${project.buildDir}/outputs/channels")
    apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}- ${buildTime}-${flavorName}.apk'
    ConfigFile and channelFile must exist; otherwise, channel packages cannot be generated. ConfigFile is preferentially executed if both exist
    channelFile = new File("${project.getProjectDir()}/channel")
    //configFile = new File("${project.getProjectDir()}/config.json")
}
Copy the code

From here, we can configure the information of the reinforcing treasure

class Extension {
    static final String DEFAULT_APK_FILE_NAME_TEMPLATE = '${appName}-${buildType}-${channel}.apk'

    File apkOutputFolder

    String apkFileNameFormat

    File channelFile;

    File configFile;

    String variantConfigFileName;
    // Harden the bajar path
    String jiaguPath
    // The user name
    String jiaguUser
    // Add a password to the password
    String jiaguPwd
}
Copy the code

And then we can configure it in Gradle

walle {
    // Specify the output path of the channel package
    apkOutputFolder = new File("${project.buildDir}/outputs/channels");
    // The APK file name of the custom channel package
    apkFileNameFormat = '${appName}-${channel}-${buildType}-v${versionName}-${buildTime}.apk';
    // Channel configuration file
    channelFile = new File("${project.getProjectDir()}/channel")
  
    jiaguPath ="/Users/wyl/Downloads/360jiagubao_mac/jiagu/jiagu.jar"

    jiaguUser ="182xxxxx10"

    jiaguPwd  ="xixxxa1"
}
Copy the code

Then we move on to ChannelMaker

The packaging approach.

Extension extension = Extension.getConfig(targetProject);
Copy the code

This is to get our gradle configuration information.

def iterator = variant.outputs.iterator();
while (iterator.hasNext()) {
    def it = iterator.next();
    def apkFile = it.outputFile
    def apiIdentifier = null;
    if(! it.outputs[0].filters.isEmpty()) {
        def tempIterator = it.outputs[0].filters.iterator();
        while (tempIterator.hasNext()) {
            FilterData filterData = tempIterator.next();
            if (filterData.filterType == "ABI") {
                apiIdentifier = filterData.identifier
                break; }}}if (apkFile == null| |! apkFile.exists()) {throw new GradleException("${apkFile} is not existed!"); }}Copy the code

Here is the constructed APK traversed, and then nulls the APK for verification.

In fact, we have learned enough here, because the subsequent multi-channel writing channel number is based on this APK operation, we only need to add reinforcement process here can meet our needs.

First create a folder to store our hardened APK files

def appFilePath = project.getProjectDir().absolutePath + "/build/outputs/jiagu"
File appDoc = new File(appFilePath)
if(! appDoc.exists()) appDoc.mkdir()Copy the code

The method for obtaining signatures is already provided in the GrdlePlugin and can be used directly

SigningConfig getSigningConfig(BaseVariant variant) {
    return variant.buildType.signingConfig == null ? variant.mergedFlavor.signingConfig :     variant.buildType.signingConfig;
}
Copy the code

Then you can harden it

if(extension.jiaguPath ! =null&& extension.jiaguUser ! =null&& extension.jiaguPwd ! =null) {
    project.exec {
        it.commandLine("java"."-jar", extension.jiaguPath, "-login", extension.jiaguUser, extension.jiaguPwd)
    }

    project.exec {
        it.commandLine("java"."-jar", extension.jiaguPath, "-importsign", signingConfig.storeFile, signingConfig.storePassword, signingConfig.keyAlias, signingConfig.keyPassword)
    }

    def iterator = variant.outputs.iterator();
    while (iterator.hasNext()) {
        def it = iterator.next();
        def apkFile = it.outputFile
        project.exec {
            it.commandLine("java"."-jar", extension.jiaguPath, "-jiagu", apkFile, appFilePath, "-autosign")}}}Copy the code

Then through traversing the apK files of the hardened folder, we can carry out multi-channel apK after the reinforcement.

File[] files = appDoc.listFiles()
for (i in 0..<files.size()) {
    File apkFile = files[i]

    if (apkFile == null| |! apkFile.exists()) {throw new GradleException("${apkFile} is not existed!");
    }

    checkV2Signature(apkFile)
    .......
}
Copy the code

The subsequent flow should be the same as the original flow.

I published the plugin locally through the Maven plugin for testing

Rely on the Maven plugin in the build.gradle plugin directory

apply plugin: 'maven'
Copy the code

Then configure the publishing information:

group = 'com.xl.channel-plugin'
version = "1.0.0"

// Upload the package to the local directory
uploadArchives {
    repositories {
        flatDir {
            dirs '.. /repo/'}}}Copy the code

Click on the task to publish the plug-in.

Import the repository address and plug-ins in the root gradle directory

repositories {
    google()
    mavenCentral()
    flatDir {
        dirs './repo/'
    }
}

classpath 'com. Xl. Channel - the plugin: channel_plugin: 1.0.0'

/ / app build. Gradle
apply plugin: 'xl-channel'
Copy the code

Then configure Walle

walle {
    // Specify the output path of the channel package
    apkOutputFolder = new File("${project.buildDir}/outputs/channels");
    // The APK file name of the custom channel package
    apkFileNameFormat = '${appName}-${channel}-${buildType}-v${versionName}-${buildTime}.apk';
    // Channel configuration file
    channelFile = new File("${project.getProjectDir()}/channel")

    jiaguPath ="/Users/wyl/Downloads/360jiagubao_mac/jiagu/jiagu.jar"

    jiaguUser ="182xxlxlxl10"

    jiaguPwd  ="xixixixi"
}
Copy the code

Then execute the task to test our results

After executing the task, you will see the logs on the console

. Omit... loading success begin jiagu task begin sign ################################################ # # # ## # # ## ### ### ## ### # # # # # # # # # # # # # # # # ### # # ### # # # ## # # # # # ### ### # # # ### # # ### # # # # Obfuscation by Allatori Obfuscator v56. DEMO #
#                                              #
#           http://www.allatori.com ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # sign upload success began to upload progress22%... Omit... Download progress90% Download progress100% download successful start signature # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # ### # # ### # # # ## # # # # # ### ### # # # ### # # ### # # # # Obfuscation by Allatori Obfuscator v56. DEMO #
#                                              #
#           http://www.allatori.com ## # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # _ completion signed APK Signature Scheme v2 Channel Maker takes the about57843 milliseconds
Copy the code

Finally, in the Build/Outputs directory, a two-folder, jiagu and channles, will be generated. Jiagu stores reinforced APK, while Channels stores multi-channel APK.

Channel information can be obtained through Walle’s tools

final ChannelInfo channelInfo = WalleChannelReader.getChannelInfo(this.getApplicationContext()); if (channelInfo ! = null) { tv.setText(channelInfo.getChannel()); }Copy the code

At this point, the program is completed standing on the shoulders of giants, thanks to Meituan Walle.

Code address: github.com/WngYilei/Ch…

If you have any questions, please leave a comment.