🔥码云GVP开源项目 12k star Uniapp+ElementUI 功能强大 支持多语言、二开方便! 广告
三、Tinker 的配置及任务 1、开启支持大工程模式 Tinker 文档中推荐将jumboMode 设置为true。 ``` android { dexOptions { // 支持大工程模式 jumboMode = true } ... } ``` 2、配置Tinker 与任务 将下面的配置全部复制粘贴到app 的gradle 文件(app/build.gradle)末尾,内容很多,但现在只需要看懂bakPath 与ext 括号内的东东就好了。 ``` // Tinker 配置与任务 def bakPath = file("${buildDir}/bakApk/") ext { // 是否使用Tinker(当你的项目处于开发调试阶段时,可以改为false) tinkerEnabled = true // 基础包文件路径(名字这里写死为old-app.apk。用于比较新旧app 以生成补丁包,不管是debug 还是release 编译) tinkerOldApkPath = "${bakPath}/old-app.apk" // 基础包的mapping.txt 文件路径(用于辅助混淆补丁包的生成,一般在生成release 版app 时会 使用到混淆,所以这个mapping.txt 文件一般只是用于release 安装包补丁的生成) tinkerApplyMappingPath = "${bakPath}/old-app-mapping.txt" // 基础包的R.txt 文件路径(如果你的安装包中资源文件有改动,则需要使用该R.txt 文件来辅助生 成补丁包) tinkerApplyResourcePath = "${bakPath}/old-app-R.txt" //only use for build all flavor, if not, just ignore this field tinkerBuildFlavorDirectory = "${bakPath}/flavor" } def getOldApkPath() { return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath } def getApplyMappingPath() { return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath } def getApplyResourceMappingPath() { return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath } def getTinkerIdValue() { return hasProperty("TINKER_ID") ? TINKER_ID : android.defaultConfig.versionName } def buildWithTinker() { return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled } def getTinkerBuildFlavorDirectory() { return ext.tinkerBuildFlavorDirectory } if (buildWithTinker()) { //apply tinker 插件 apply plugin: 'com.tencent.tinker.patch' // 全局信息相关的配置项 tinkerPatch { tinkerEnable = buildWithTinker()// 是否打开tinker 的功能。 oldApk = getOldApkPath() // 基准apk 包的路径,必须输入,否则会报错。 ignoreWarning = false // 是否忽略有风险的补丁包。这里选择不忽略,当补丁包风 险时会中断编译。 useSign = true // 在运行过程中,我们需要验证基准apk 包与补丁包的签名 是否一致,我们是否需要为你签名。 // 编译相关的配置项 buildConfig { applyMapping = getApplyMappingPath() // 可选参数;在编译新的apk 时候,我们希望通过保持旧apk 的proguard 混淆方式,从 而减少补丁包的大小。这个只是推荐设置,不设置applyMapping 也不会影响任何的assemble 编译。 applyResourceMapping = getApplyResourceMappingPath() // 可选参数;在编译新的apk 时候,我们希望通过旧apk 的R.txt 文件保持ResId 的分配, 这样不仅可以减少补丁包的大小,同时也避免由于ResId 改变导致remote view 异常。 tinkerId = getTinkerIdValue() // 在运行过程中,我们需要验证基准apk 包的tinkerId 是否等于补丁包的tinkerId。这个 是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git 版本号、versionName 等等。 keepDexApply = false // 如果我们有多个dex,编译补丁时可能会由于类的移动导致变更增多。若打开 keepDexApply 模式,补丁包将根据基准包的类分布来编译。 isProtectedApp = false // 是否使用加固模式,仅仅将变更的类合成补丁。注意,这种模 式仅仅可以用于加固应用中。 supportHotplugComponent = false // 是否支持新增非export 的Activity(1.9.0 版本 开始才有的新功能) } // dex 相关的配置项 dex { dexMode = "jar" // 只能是'raw'或者'jar'。对于'raw'模式,我们将会保持输入dex 的格式。对于'jar'模式,我们将会把 输入dex 重新压缩封装到jar。如果你的minSdkVersion 小于14,你必须选择‘jar’模式,而且它更省存 储空间,但是验证md5 时比'raw'模式耗时。默认我们并不会去校验md5,一般情况下选择jar 模式即可。 pattern = ["classes*.dex", "assets/secondary-dex-?.jar"] // 需要处理dex 路径,支持*、?通配符,必须使用'/'分割。路径是相对安装包的,例如assets/... loader = [ // 定义哪些类在加载补丁包的时候会用到。这些类是通过Tinker 无法修改的类,也 是一定要放在main dex 的类。 // 如果你自定义了TinkerLoader,需要将它以及它引用的所有类也加入loader 中; // 其他一些你不希望被更改的类,例如Sample 中的BaseBuildInfo 类。这里需要 注意的是,这些类的直接引用类也需要加入到loader 中。或者你需要将这个类变成非preverify。 ] } // lib 相关的配置项 lib { pattern = ["lib/*/*.so","src/main/jniLibs/*/*.so"] // 需要处理lib 路径,支持*、?通配符,必须使用'/'分割。与dex.pattern 一致, 路径是相 对安装包的,例如assets/... } // res 相关的配置项 res { pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] // 需要处理res 路径,支持*、?通配符,必须使用'/'分割。与dex.pattern 一致, 路径是相 对安装包的,例如assets/...,务必注意的是,只有满足pattern 的资源才会放到合成后的资源包。 ignoreChange = [ // 支持*、?通配符,必须使用'/'分割。若满足ignoreChange 的pattern,在编译 时会忽略该文件的新增、删除与修改。最极端的情况,ignoreChange 与上面的pattern 一致,即会完 全忽略所有资源的修改。 "assets/sample_meta.txt" ] largeModSize = 100 // 对于修改的资源,如果大于largeModSize,我们将使用bsdiff 算法。这可以降低补丁包 的大小,但是会增加合成时的复杂度。默认大小为100kb } // 用于生成补丁包中的'package_meta.txt'文件 packageConfig { // configField("key", "value"), 默认我们自动从基准安装包与新安装包的Manifest 中读取 tinkerId,并自动写入configField。 // 在这里,你可以定义其他的信息,在运行时可以通过 TinkerLoadResult.getPackageConfigByName 得到相应的数值。 // 但是建议直接通过修改代码来实现,例如BuildConfig。 configField("platform", "all") configField("patchVersion", "1.0") // configField("patchMessage", "tinker is sample to use") } // 7zip 路径配置项,执行前提是useSign 为true sevenZip { zipArtifact = "com.tencent.mm:SevenZip:1.1.10" } } List<String> flavors = new ArrayList<>(); project.android.productFlavors.each { flavor -> flavors.add(flavor.name) } boolean hasFlavors = flavors.size() > 0 def date = new Date().format("MMdd-HH-mm-ss") /** * bak apk and mapping */ android.applicationVariants.all { variant -> /** * task type, you want to bak */ def taskName = variant.name tasks.all { if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) { it.doLast { copy { def fileNamePrefix = "${project.name}-${variant.baseName}" def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}" def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath from variant.outputs.first().outputFile into destPath rename { String fileName -> fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk") } from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt" into destPath rename { String fileName -> fileName.replace("mapping.txt","${newFileNamePrefix}-mapping.txt") } from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt" into destPath rename { String fileName -> fileName.replace("R.txt", "${newFileNamePrefix}-R.txt") } } } } } } project.afterEvaluate { //sample use for build all flavor for one time if (hasFlavors) { task(tinkerPatchAllFlavorRelease) { group = 'tinker' def originOldPath = getTinkerBuildFlavorDirectory() for (String flavor : flavors) { def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release") dependsOn tinkerTask def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest") preAssembleTask.doFirst { String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15) project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk" project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt" project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt" } } } task(tinkerPatchAllFlavorDebug) { group = 'tinker' def originOldPath = getTinkerBuildFlavorDirectory() for (String flavor : flavors) { def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug") dependsOn tinkerTask def preAssembleTask =tasks.getByName("process${flavor.capitalize()}DebugManifest") preAssembleTask.doFirst { String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13) project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk" project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt" project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt" } } } } } } ``` 其中,有几点配置在这里说明一下,方便理解后续的操作(当tinkerEnabled = true 的情况下): 1.app 的生成目录是:主Module(一般是名为app)/build/bakApk 文件夹。 2.补丁包的生成路径:主Module(一般是名为app)/build/outputs/apk/tinkerPatch/debug/patch_signed_7zip.apk。 3.基础包的名字:old-app.apk,放于bakApk 文件夹下。 4.基础包的mapping.txt 和R.txt 文件一般在编译release 签名的apk 时才会用到。 5.在用到mapping.txt 文件时,需要重命名为old-app-mapping.txt,放于bakApk 文件夹下。 6.在用到R.txt 文件时,需要重命名为old-app-R.txt,放于bakApk 文件夹下。 对于mapping.txt 和R.txt 文件,在配置中有说明,请回配置中仔细看。上面只是我项目中的配置,这些其实都是可以自定义的,建议在搞清楚配置内容之后再去自定义修改。 3、什么是基础包?? 基础包就是已经上架的apk 文件(假设是1.0 版本)。这其实很好理解,在新版 本的App 上架之前(假设是2.0 版本),我们会用到Tinker 来修复1.0 版App 中存在的bug,这时就需要用到Tinker 来产生补丁包文件,而补丁包文件的本 质,就是修复好Bug 的App 与1.0 版本App 之间的文件差异。在2.0 版本上架 之前,我们可能会多次产生新的补丁包,用于修复在用户手机上的1.0 版App, 所以补丁包必须以1.0 版App 作为参考标准,也就是说用户手机上的app 就是 基础包,即当前应用市场上的apk 文件(前面说的1.0 版本)。