There have been a lot of online crashes on Android recently, and hot fixes are on the agenda. The implementation scheme is Tinker and Jenkins package, and finally upload the patch package to Bugly for distribution. Mainly in the Jenkins packing area to climb a lot of pits, now recorded for your reference.

1. Tinker + Bugly hot repair implementation

First is the local implementation, according to the official documentation, as long as step by step according to the document, this step is relatively easy, here is no longer described, do not understand can first refer to the official documentation: Bugly Android hot update guide, Bugly Android hot update details. Here is the access process:

  • Install the reference package and report it to the network (note: fill in the unique tinkerId)
  • Bug fixes to the base pack (Java code changes, resource changes)
  • Modify the base package path, tinkerId patch package, mapping file path (required if obfuscating is enabled), and resId file path
  • Run buildTinkerPatchRelease to build the Release patch pack
  • Then select app/build/outputs/patch and upload it (note: do not select tinkerPatch, otherwise uploading will have problems)
  • To edit the patch rule, click Immediately
  • Kill the process and restart the base pack to request a patch policy (the SDK automatically downloads the patch and synthesizes it)
  • Restart the base pack again to verify the patch application results
  • View the activation data change page

Here’s the third step in the instructions: To initialize the SDK, I’m going to use enableProxyApplication = false, originally it’s going to be a bit more flexible like enableProxyApplication = true, but the program gets an error and I don’t have time to go into the cause, In addition, the direct inheritance of access is also no cost, no matter why, know the reason can be easily informed. ┑( ̄  ̄  ̄)┍

It is relatively easy to pull it off. After the code is connected, make a package (reference package), install it on the phone and run it again, so that the program can be reported to Bugly through the network. After that, modify the baseApkDir parameter in tinker-support.gradle file according to the baseline version of base package, and then you can install the patch package.

2. Combine Jenkins’ pit

First, I would like to explain the background of Jenkins packaging for APK. Jenkins uses Ant plug-in to package APK. Due to the company’s project, it is not convenient to show the package script. If you have any questions, you can explain them in the comments, and I will help you solve them privately.

“Climb the pits below

Pit 1 ☞ patch pack, where to find the base pack?

Since Jenkins’ packaging policy is to execute clean before build, this means that it is not possible to find the base package in app/build/bakApk/ app-XXXX-xx-xx-xx as local packaging. So what? How do you make incremental packages without base packages? After a long period of thinking, I foolishly came up with the idea of creating a folder under the project path, copying the base package to that folder and uploading it to SVN for incremental packages. Intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect intersect .

Then he went to the operation and maintenance students and found after communication, Jenkins every package in Jenkins directory/jobs/pipeline name/builds/build number/archive/app/build/outputs/apk kungeek/release/save a copy of the apk file. The component numbers in the path are shown in the figure:

Next, When installing the patch package, change the baseApkDir parameter in the tinker-support.gradle file to / jobs/pipeline name/builds/build number/archive/app/build/outputs/apk/kungeek/release/can. The code is as follows:

/** * Fill in the base package directory generated for each build. Note that the variables must be customized */
def baseApkDir = "${rootProject.projectDir}/.. /.. / jobs / ${name} pipeline/builds / ${baseApkBuildNumber} / archive/app/build/outputs/apk/kungeek/release"
Copy the code

☞ Linux file copy wildcard problem

Since the mapping file (which needs to be configured if obfuscation is enabled) and resId file generated during the construction of the base package are also needed in the construction of the patch package, therefore, during the construction of the base package, Need to copy these two files to/jobs/pipeline name/builds/build number/archive/app/build/outputs/apk/kungeek/release/directory and copy the HTML code is as follows:

<! -- Copy tinker generated files (apk file, mapping.txt, r.txt) -->
<copy todir=".. /.. /.. /.. / jobs/pipeline name/builds / ${env. BUILD_NUMBER} / archive/app/build/outputs/apk kungeek/release/" flatten="true">
	<fileset dir="${android.root}/app/build/bakApk/">
        <include name="* / *" />
    </fileset>
</copy>
Copy the code

** Note 1: ** code relative path problem readers have questions, please comment again to ask.

The pits encountered here are: The APK file constructed by Tinker is stored in app-XXXX-XX-XX-xx directory, so it needs to use wildcards to assist in copying files. The o&M student originally intended to add wildcards to fileset to form a complete path. After a painful attempt and baidu search, he found that, Wildcards can only be used in include tags. (Blue translation  ̄,)

▌ Can not find the generated patch pack after completing the build?

And after Jenkins finally hit the base pack, / jobs/pipeline name/builds/build number/archive/app/build/outputs/apk/kungeek/release/directory with the benchmark the apk, mapping files, resId.

Next, I thought I just needed to configure the component number of the base pack and build the patch pack. After Jenkins built the patch package APK file, he showed the results and failed to find the apK file, which hit me in the head and still failed. Frustration sets in

Later, the operation and maintenance students confirmed that the patch_signed_7zzip. apk file was generated in the app/build/outputs/patch directory during Jenkins construction, but it disappeared after the completion of construction. Then I took a look at the commands executed during the build, which looked something like this:

sh gradlew clean buildTinkerPatchRelease  --stacktrace
sh gradlew checklist
Copy the code

After the implementation of buildTinkerPatchRelease, the checklist task was also executed. Is it because the patch was cleared during the implementation of Checklist? Later, I tried to comment out this command, and the patch package was successfully installed again. Sure enough, it is this checklist that causes trouble. Later, it is found that after patching, when executing Gradle Task again, the patch directory will almost always be cleared. This is a pit that you should remember to avoid.

▌ Failed to patch multiple applications in a project?

As we know, in Android Studio, a project can have multiple modules, including modules of application type. Gradlew assembleRelease is an example of a gradlew assembleRelease task. The gradlew assembleRelease task is an example of a gradlew assembleRelease task. The Gradlew assembleRelease task is an example of a Gradlew assembleRelease task.

Gradle provides productFlavors that can be configured separately for each application. Each application has to be named differently, so that different build tasks can be created for different apps. If you specify a_app in build.gradle of A, A task named buildTinkerPatchA_appRelease will be generated, which will be used to build the patch package.

So the question is, what is the final form of packaging? Is this?

sh gradlew buildTinkerPatchA_appRelease buildTinkerPatchA_appRelease
Copy the code

Or is it?

sh gradlew buildTinkerPatchA_appRelease 
sh gradlew buildTinkerPatchA_appRelease
Copy the code

No, the two options are the same as the package without productFlavors. How to package them?

The answer is that in Ant’s packaging script, the key code is as follows:

<! Build APP A -->
<exec dir="." executable="bash" failonerror="false">
    <arg value="generated_apk_hotfix.sh"/>
    <arg value="buildTinkerPatchApp_aRelease"/>
</exec>

<! Build APP B -->
<exec dir="." executable="bash" failonerror="false">
    <arg value="generated_apk_hotfix.sh"/>
    <arg value="buildTinkerPatchApp_bRelease"/>
</exec>
Copy the code

The key codes of the generated_apk_hotfix.sh file are as follows:

#! /bin/sh
command=The $1;

Incremental packages must be packaged separately, otherwise they will fail
sh gradlew ${command} --stacktrace
Copy the code

3. Summary

The pit mentioned above is only 4 points, but in fact, there have been a lot of small problems, but those need not say more, it is easy to solve.

Finally, summarize the idea of constructing patch package with Jenkins.

First, specify the path for storing the base package APK, mapping, and R.tuck files of the baseline version, and save the three files to this directory when creating the base package. If stored in Jenkins pipeline build directory like this, remember to adjust the pipeline cleanup strategy, otherwise it will be embarrassing to find the baseline version of the APK package cleaned up when the patch package needs to be installed. I put it in this directory for the purpose of reuse space.

Next, find the base package, mapping file, and R.tuck file through the agreed path and install the patch package. There is a need to define a strategy for finding the base package. For example, HERE I match the base package path with the build number and then use a fixed naming format (e.g. App_release_version.apk) to match the base pack as well as the mapping file and r.tuck files, so I only need to determine the version number and build number of the baseline version.

Tinker-support. gradle: tinker-support.gradle: tinker-support.gradle: tinker-support.gradle: tinker-support.gradle: tinker-support.gradle

apply plugin: 'com.tencent.bugly.tinker-support'

def bakPath = file("${buildDir}/bakApk/")

/** Jenkins build number of base package */
def baseApkBuildNumber = project.property("baseApkBuildNumber")
/** The version number of the base package */
def baseApkVersion = project.property("baseApkVersion")

/** * Fill in the base package directory generated for each build */
def baseApkDir = "${rootProject.projectDir}/.. /.. /jobs/Android_Trunk/builds/${baseApkBuildNumber}/archive/app/build/outputs/apk/release"

/** The apK file name of the base package */
def baseApkFileName = "app-v${baseApkVersion}"

/** * For details about plug-in parameters, see */
tinkerSupport {

    // Enable tinker-support. The default value is true
    enable = true

    // tinkerEnable function switch
    tinkerEnable = true

    // Specify the archive directory. The default is tinker, the subdirectory of the current module
    autoBackupApkDir = "${bakPath}"

    autoGenerateTinkerId = true

    // The prefix of the r.tb and mapping.txt file names is generated when the base package is typed
    // rootproject.ext.android_version indicates the version number when packaging
    targetFileNamePrefix = "app-v${rootProject.ext.android_version}"

    // Whether to enable overwriting tinkerPatch configuration. The default value is false
    // The tinkerPatch configuration does not take effect, that is, tinkerPatch is not required
    overrideTinkerPatchConfiguration = true
    When compiling the patch pack, you must specify the baseline version of APK. The default value is null
    // If it is empty, the patch pack is not being compiled
    // @{link tinkerPatch.oldApk }
    baseApk = "${baseApkDir}/${baseApkFileName}.apk"

    // Corresponds to the tinker plugin applyMapping
    baseApkProguardMapping = "${baseApkDir}/${baseApkFileName}-mapping.txt"

    // The corresponding tinker plug-in applyResourceMapping
    baseApkResourceMapping = "${baseApkDir}/${baseApkFileName}-R.txt"

    tinkerId = "Base - 1.0.1." "

// buildAllFlavorsDir = "${bakPath}/${baseApkDir}"
    // Whether to enable the hardening mode. The default value is false
    // isProtectedApp = true

    // Whether to enable reflection Application mode
    enableProxyApplication = false

    supportHotplugComponent = true

}

/** * Generally speaking, we do not need to make any changes to the following parameters * For detailed description of each parameter please refer to: * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97 */
tinkerPatch {
    //oldApk ="${bakPath}/${appName}/app-release.apk"

    // tinkerEnable function switch
    tinkerEnable = true
    ignoreWarning = false
    useSign = true
    dex {
        dexMode = "jar"
        pattern = ["classes*.dex"]
        loader = []
    }
    lib {
        pattern = ["lib/*/*.so"]
    }

    res {
        pattern = ["res/*"."r/*"."assets/*"."resources.arsc"."AndroidManifest.xml"]
        ignoreChange = []
        largeModSize = 100
    }

    packageConfig {
    }
    sevenZip {
        zipArtifact = "Com. Tencent. Mm: SevenZip: 1.1.10"
    }
    buildConfig {
        keepDexApply = false}}Copy the code

Then there are two variables maintained in the gradle.properties file:

BaseApkBuildNumber = 1 baseApkBuildNumber = 1.0.0.197094Copy the code