Android Tinker集成(含有AndResGuard资源混淆)

Tinker的原理

服务端做dex差量,将差量包下发到客户端,在ART模式的机型上本地跟原apk中的classes.dex做merge,merge成为一个新的merge.dex后将merge.dex插入pathClassLoader的dexElement,为了实现差量包的最小化,Tinker自研了DexDiff/DexMerge算法。Tinker还支持资源和So包的更新,So补丁包使用BsDiff来生成,资源补丁包直接使用文件md5对比来生成,针对资源比较大的(默认大于100KB属于大文件)会使用BsDiff来对文件生成差量补丁。

image

Tinker的集成

添加gradle依赖

在项目的build.gradle中,添加tinker-patch-gradle-plugin的依赖

 classpath("com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}") {
            changing = TINKER_VERSION?.endsWith("-SNAPSHOT")
            exclude group: 'com.android.tools.build', module: 'gradle'
}

我是把TINKER_VERSION放到了gradle.properties里

TINKER_VERSION=1.9.9 

然后在app的gradle文件app/build.gradle,我们需要添加tinker的库依赖以及apply tinker的gradle插件.

apply plugin: 'com.tencent.tinker.patch'
...
  //tinker的核心库
    implementation("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
    annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
    compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
//也需要这个依赖
implementation "com.android.support:multidex:1.0.3"

对于Application我们需要:
1.将我们自己Application类以及它的继承类的所有代码拷贝到自己的ApplicationLike继承类中,例如SampleApplicationLike。
2.Application的attachBaseContext方法实现要单独移动到onBaseContextAttached中;
3.对ApplicationLike中,引用application的地方改成getApplication();
4.对其他引用Application或者它的静态对象与方法的地方,改成引用ApplicationLike的静态对象与方法;
完整项目请查看Tinkerdemo

打包

上线前,先执行installRelease任务,打出Apk,生成在备份目录/bakApk/目录中,同时,需要将该文件夹备份,作为下次热更新的基准包;

打补丁

上线后,若出现bug,需要打补丁:
将之前备份好的文件放置在app模块下的/build/bakApk/目录下(文件包含基准包,mapping.txt,R.txt)修改app模块下build.gradle中基准包目录,如下:

//for normal build
    //old apk file to build patch apk
    tinkerOldApkPath = "${bakPath}/app-release-1024-17-38-43.apk"
    //proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/app-release-1024-17-38-43-mapping.txt"
    //resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/app-release-1024-17-38-43-R.txt"

然后执行tinkerPatchRelease任务,生成补丁:
补丁包与相关日志会保存在/build/outputs/tinkerPatch/。然后我们将patch_signed_7zip.apk推送到手机的sdcard中。

adb push ./app/build/outputs/tinkerPatch/debug/patch_signed_7zip.apk  /sdcard/

打开app,点击LOAD PATCH按钮, 如果看到patch success, please restart process的toast,表示加载成功,即可重新启动app,就可以看到修改后的效果。
我们引入tinker之后,我们想debug直接运行时,就会报错,这是我们需要把app的build.gradle的tinkerEnabled 修改为false

ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = false
    ....
}

兼容AndResGuard

我们公司项目用到了资源混淆AndResGuard,tinker对资源混淆AndResGuard也是支持的,我们需要修改如下:
需要将混淆资源的resource_mapping.txt保留下来,等执行tinkerPatchRelease任务时我们需要用到resource_mapping.txt,

andResGuard {
    //tinker 需要保留此文件
    mappingFile = file("${buildDir}/bakApk/resguard/app-release-1029-17-36-41-resource_mapping.txt")
//    mappingFile = null
  ...
}

同时将r/*也添加到res pattern中

  res {
            /**
             * andresguard 混淆需要添加“r/*“
             */
            pattern = ["res/*",  "r/*","assets/*", "resources.arsc", "AndroidManifest.xml"]
...
}

具体请看build.gradle
执行resguardRelease任务,打出Apk,把生成的备份放到了/bakApk/resguard/目录中
我们还需要修改build.gradle具体代码为

  project.afterEvaluate {
        def date = new Date().format("MMdd-HH-mm-ss")
        def apkSuffix = "_7zip_aligned_signed.apk"
        /**
         * bak apk and mapping
         */
        android.applicationVariants.all { variant ->
            /**
             * task type, you want to bak
             */
            def taskName = variant.name
            String name = variant.name.toLowerCase()
            String destFilePrefix = "${project.name}-${name}"

            // find resguard task first
            def resguardTask = project.tasks.findByName("resguard${taskName.capitalize()}")
            if (resguardTask == null) {
                println("resguardTask not found, just return")
                return
            }

            def tinkerPatchTask = project.tasks.findByName("tinkerPatch${taskName.capitalize()}")
            if (tinkerPatchTask == null) {
                println("resguardTask not found, just return")
                return
            }
            tinkerPatchTask.doFirst {
                def buildApkPath = "${buildDir}/outputs/apk/${taskName}/AndResGuard_${project.getName()}-${taskName}/${project.getName()}-${taskName}${apkSuffix}"
                println("change tinkerPatchTask buildApkPath to resugurad output ${buildApkPath}")
                tinkerPatchTask.buildApkPath = buildApkPath
            }
            tinkerPatchTask.dependsOn resguardTask
            resguardTask.doLast {
                copy {
                    from "${buildDir}/outputs/apk/${taskName}/AndResGuard_${project.getName()}-${taskName}/${project.getName()}-${taskName}_7zip_aligned_signed.apk"
                    into file(bakPath.absolutePath + "/resguard")
                    rename { String fileName ->
                        fileName.replace("${project.getName()}-${taskName}_7zip_aligned_signed.apk", "${project.getName()}-${taskName}-${date}.apk")
                    }

                    from "${buildDir}/outputs/mapping/${taskName}/mapping.txt"
                    into file(bakPath.absolutePath + "/resguard")
                    rename { String fileName ->
                        fileName.replace("mapping.txt", "${project.getName()}-${taskName}-${date}-mapping.txt")
                    }

                    from "${buildDir}/intermediates/symbols/${taskName}/R.txt"
                    into file(bakPath.absolutePath + "/resguard")
                    rename { String fileName ->
                        fileName.replace("R.txt", "${project.getName()}-${taskName}-${date}-R.txt")
                    }
                    from "${buildDir}/outputs/apk/${taskName}/AndResGuard_${project.getName()}-${taskName}/resource_mapping_${project.getName()}-release.txt"
                    into file(bakPath.absolutePath + "/resguard")
                    rename { String fileName ->
                        fileName.replace("resource_mapping_${project.getName()}-release.txt", "${project.getName()}-${taskName}-${date}-resource_mapping.txt")
                    }
                }
            }
        }
    }

具体步骤就是
1. 上线前,先执行reguardRelease任务,把备份的了/bakApk/resguard/里的文件保存下来。当做下次的基准包。

  1. 打补丁时,把将之前备份好的文件放置在模块下的/build/bakApk/resguard/(文件包含基准包,mapping.txt,R.txt,resource_mapping.txt),并修改build.guald
andResGuard {
    //tinker 需要保留此文件
    mappingFile = file("${buildDir}/bakApk/resguard/app-release-1029-17-36-41-resource_mapping.txt")
//    mappingFile = null
  ...
}
...
ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true
    //for normal build
    //old apk file to build patch apk
    tinkerOldApkPath = "${bakPath}/resguard/app-release-1029-17-36-41.apk"
    //proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/resguard/app-release-1029-17-36-41-mapping.txt"
    //resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/resguard/app-release-1029-17-36-41-R.txt"

}

执行tinkerPatchRelease任务, 补丁包会保存在/build/outputs/tinkerPatch/里,至此我们就拿到了补丁包,就可以用来下发给APP,来加载补丁。
完整项目请查看Tinkerdemo

你可能感兴趣的:(Android Tinker集成(含有AndResGuard资源混淆))