热修复Tinker的使用 + TinkerPatch补丁管理平台 gradle接入
来源:互联网 发布:电脑版淘宝微淘哪里看 编辑:程序博客网 时间:2024/05/18 12:44
开场白:我想快速集成并使用热修复Tinker,快速熟悉打补丁,及使用补丁管理平台
(本片文章并没有对知识点进行讲述,简单描述应用到项目中遇到的问题)
第一次写文章,来的各位爷,您就凑合凑合看吧。有描述的不对的地方,请留下您宝贵的建议。
步骤:
①集成Tinker,
②集成TinkerPatch
③打补丁
④使用管理平台进行发布补丁
开发环境
在开始集成前,我先说下我的开发环境:
项目build.gradle
dependencies { classpath 'com.android.tools.build:gradle:2.2.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }
项目\gradle\wrapper\gradle-wrapper.properties文件中
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
app\build.gradle文件中
compileSdkVersion 25buildToolsVersion "25.0.3" defaultConfig { ... minSdkVersion 15 targetSdkVersion 25 ...}
1.集成Tinker,并使用Tinker打补丁
集成Tinker的时候,刚开始看官方文档有点懵逼,看了BigSweetee作者的一篇博客tinker热修复gradle接入才完成了集成,怪我学识太浅,Tinker接入指南确实有点难啃。BigSweetee作者的讲解对我刚使用热修复的人来说很是受用,我就不在这里赘(zhuì)述了。(最优的选择官方文档和源码)
2.集成TinkerPatch
若按照官方文档或上面的作者进行gradle接入,需要修改一些地方。上代码↓
准备工作:
①在gradle.properties文件中创建
TINKER_VERSION=1.8.1TINKERPATCH_VERSION=1.1.8
②在项目的build文件中
dependencies { classpath 'com.android.tools.build:gradle:2.2.2'// classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}" //无需再单独引用tinker的其他库 classpath ("com.tinkerpatch.sdk:tinkerpatch-gradle-plugin:${TINKERPATCH_VERSION}") { changing = true } // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }
③配置app下的build
官方推荐build配置https://github.com/TinkerPatch/tinkerpatch-sample/blob/master/app/build.gradle
我并根据官方文档及BigSweetee作者中的build,进行优化和提出
下面并贴上我的整个build文件
apply plugin: 'com.android.application'dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' //若使用annotation需要单独引用,对于tinker的其他库都无需再引用 provided("com.tinkerpatch.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true } compile("com.tinkerpatch.sdk:tinkerpatch-android-sdk:${TINKERPATCH_VERSION}") { changing = true }}android { compileSdkVersion 25 buildToolsVersion "25.0.3" //配置自己的签名文件 signingConfigs { release { try { keyAlias 'release' keyPassword '123456' storeFile file('D:/PinkCandy/AndroidStudioProjects/MyShack/release.jks') storePassword '123456' } catch (ex) { throw new InvalidUserDataException(ex.toString()) } } debug { storeFile file('D:/PinkCandy/AndroidStudioProjects/MyShack/debug.jks') keyAlias 'debug' keyPassword '123456' storePassword '123456' } } defaultConfig { applicationId "com.yjh.mytinkerpatch" minSdkVersion 19 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true multiDexKeepProguard file("tinkerMultidexKeep.pro") //keep specific classes using proguard syntax } buildTypes { release { minifyEnabled true shrinkResources true signingConfig signingConfigs.release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { debuggable true minifyEnabled false signingConfig signingConfigs.debug proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } sourceSets { main { jniLibs.srcDirs = ['libs'] } }}def bakPath = file("${buildDir}/bakApk/")def variantName = "1101-16-44-30"def buildType = "release"/** * you can use assembleRelease to build you base apk * use tinkerPatchRelease -POLD_APK= -PAPPLY_MAPPING= -PAPPLY_RESOURCE= to build patch * add apk from the build/bakApk *///老版本的文件所在的位置,大家也可以动态配置,不用每次都在这里修改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}/mytinkerpatch-${buildType}-${variantName}.apk" //proguard mapping file to build patch apk tinkerApplyMappingPath = "${bakPath}/mytinkerpatch-${buildType}-${variantName}-mapping.txt" //resource R.txt to build patch apk, must input if there is resource changed tinkerApplyResourcePath = "${bakPath}/${buildType}-${variantName}-R.txt" //only use for build all flavor, if not, just ignore this field tinkerBuildFlavorDirectory = "${bakPath}/mytinkerpatch-1101-13-57-40"}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 buildWithTinker() { return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled}def getTinkerBuildFlavorDirectory() { return ext.tinkerBuildFlavorDirectory}if (buildWithTinker()) { apply plugin: 'com.tencent.tinker.patch' tinkerPatch { /** * necessary,default 'null' * the old apk path, use to diff with the new apk to build * add apk from the build/bakApk */ oldApk = getOldApkPath() /** * optional,default 'false' * there are some cases we may get some warnings * if ignoreWarning is true, we would just assert the patch process * case 1: minSdkVersion is below 14, but you are using dexMode with raw. * it must be crash when load. * case 2: newly added Android Component in AndroidManifest.xml, * it must be crash when load. * case 3: loader classes in dex.loader{} are not keep in the main dex, * it must be let tinker not work. * case 4: loader classes in dex.loader{} changes, * loader classes is ues to load patch dex. it is useless to change them. * it won't crash, but these changes can't effect. you may ignore it * case 5: resources.arsc has changed, but we don't use applyResourceMapping to build */ ignoreWarning = true /** * optional,default 'true' * whether sign the patch file * if not, you must do yourself. otherwise it can't check success during the patch loading * we will use the sign config with your build type */ useSign = true /** * optional,default 'true' * whether use tinker to build */ tinkerEnable = buildWithTinker() /** * Warning, applyMapping will affect the normal android build! */ buildConfig { /** * optional,default 'null' * if we use tinkerPatch to build the patch apk, you'd better to apply the old * apk mapping file if minifyEnabled is enable! * Warning: * you must be careful that it will affect the normal assemble build! */ applyMapping = getApplyMappingPath() /** * optional,default 'null' * It is nice to keep the resource id from R.txt file to reduce java changes */ applyResourceMapping = getApplyResourceMappingPath() /** * necessary,default 'null' * because we don't want to check the base apk with md5 in the runtime(it is slow) * tinkerId is use to identify the unique base apk when the patch is tried to apply. * we can use git rev, svn rev or simply versionCode. * we will gen the tinkerId in your manifest automatic */ tinkerId = "1.0"/*getTinkerIdValue()*/ /** * if keepDexApply is true, class in which dex refer to the old apk. * open this can reduce the dex diff file size. */ keepDexApply = false } dex { /** * optional,default 'jar' * only can be 'raw' or 'jar'. for raw, we would keep its original format * for jar, we would repack dexes with zip format. * if you want to support below 14, you must use jar * or you want to save rom or check quicker, you can use raw mode also */ dexMode = "jar" /** * necessary,default '[]' * what dexes in apk are expected to deal with tinkerPatch * it support * or ? pattern. */ pattern = ["classes*.dex", "assets/secondary-dex-?.jar"] /** * necessary,default '[]' * Warning, it is very very important, loader classes can't change with patch. * thus, they will be removed from patch dexes. * you must put the following class into main dex. * Simply, you should add your own application {@code tinker.sample.android.SampleApplication} * own tinkerLoader, and the classes you use in them * */ loader = [ //use sample, let BaseBuildInfo unchangeable with tinker "tinker.sample.android.app.BaseBuildInfo" ] } lib { /** * optional,default '[]' * what library in apk are expected to deal with tinkerPatch * it support * or ? pattern. * for library in assets, we would just recover them in the patch directory * you can get them in TinkerLoadResult with Tinker */ pattern = ["lib/*/*.so"] } res { /** * optional,default '[]' * what resource in apk are expected to deal with tinkerPatch * it support * or ? pattern. * you must include all your resources in apk here, * otherwise, they won't repack in the new apk resources. */ pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] /** * optional,default '[]' * the resource file exclude patterns, ignore add, delete or modify resource change * it support * or ? pattern. * Warning, we can only use for files no relative with resources.arsc */ ignoreChange = ["assets/sample_meta.txt"] /** * default 100kb * for modify resource, if it is larger than 'largeModSize' * we would like to use bsdiff algorithm to reduce patch file size */ largeModSize = 100 } packageConfig { /** * optional,default 'TINKER_ID, TINKER_ID_VALUE' 'NEW_TINKER_ID, NEW_TINKER_ID_VALUE' * package meta file gen. path is assets/package_meta.txt in patch file * you can use securityCheck.getPackageProperties() in your ownPackageCheck method * or TinkerLoadResult.getPackageConfigByName * we will get the TINKER_ID from the old apk manifest for you automatic, * other config files (such as patchMessage below)is not necessary */ configField("patchMessage", "tinker is sample to use") /** * just a sample case, you can use such as sdkVersion, brand, channel... * you can parse it in the SamplePatchListener. * Then you can use patch conditional! */ configField("platform", "all") /** * patch version via packageConfig */ configField("patchVersion", "1.0") } //or you can add config filed outside, or get meta value from old apk //project.tinkerPatch.packageConfig.configField("test1", project.tinkerPatch.packageConfig.getMetaDataFromOldApk("Test")) //project.tinkerPatch.packageConfig.configField("test2", "sample") /** * if you don't use zipArtifact or path, we just use 7za to try */ sevenZip { /** * optional,default '7za' * the 7zip artifact path, it will use the right 7za with your platform */ zipArtifact = "com.tencent.mm:SevenZip:1.1.10" /** * optional,default '7za' * you can specify the 7za path yourself, it will overwrite the zipArtifact value */// path = "/usr/local/bin/7za" } } List<String> flavors = new ArrayList<>(); project.android.productFlavors.each { flavor -> flavors.add(flavor.name) } boolean hasFlavors = flavors.size() > 0 /** * bak apk and mapping */ android.applicationVariants.all { variant -> /** * task type, you want to bak */ def taskName = variant.name def date = new Date().format("MMdd-HH-mm-ss") 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.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" } } } } }}apply from: 'tinkerpatch.gradle'
==========================不太华丽的分割线build此时已经贴完===========================
在build最后还有一个tinkerpatch.gradle,此gradle是连接管理补丁后台作用,拉取补丁并整合再到应用,这个过程我没去研究。
官方文档的tinkerpatch.gradle:https://github.com/TinkerPatch/tinkerpatch-sample/blob/master/app/tinkerpatch.gradle
下面贴上我的tinkerpatch.gradle文件
apply plugin: 'tinkerpatch-support'/** * TODO: 请按自己的需求修改为适应自己工程的参数 */def bakPath = file("${buildDir}/bakApk/")def variantName = "1101-16-44-30"def buildType = "release"/** * 对于插件各参数的详细解析请参考 * http://tinkerpatch.com/Docs/SDK */tinkerpatchSupport { /** 可以在debug的时候关闭 tinkerPatch **/ /** 当disable tinker的时候需要添加multiDexKeepProguard和proguardFiles, 这些配置文件本身由tinkerPatch的插件自动添加,当你disable后需要手动添加 你可以copy本示例中的proguardRules.pro和tinkerMultidexKeep.pro, 需要你手动修改'tinker.sample.android.app'本示例的包名为你自己的包名, com.xxx前缀的包名不用修改 **/ tinkerEnable = true reflectApplication = false autoBackupApkPath = "${bakPath}" appKey = "c5e9524735f29f95" /** 注意: 若发布新的全量包, appVersion一定要更新 **/ appVersion = "1.0.0" //for normal build //old apk file to build patch apk baseApkFile = "${bakPath}/mytinkerpatch-${buildType}-${variantName}.apk" //proguard mapping file to build patch apk baseProguardMappingFile = "${bakPath}/mytinkerpatch-${buildType}-${variantName}-mapping.txt" //resource R.txt to build patch apk, must input if there is resource changed baseResourceRFile = "${bakPath}/mytinkerpatch-${buildType}-${variantName}-R.txt" /** * 若有编译多flavors需求, 可以参照: https://github.com/TinkerPatch/tinkerpatch-flavors-sample * 注意: 除非你不同的flavor代码是不一样的,不然建议采用zip comment或者文件方式生成渠道信息(相关工具:walle 或者 packer-ng) **/ /** 若有编译多flavors需求,可在flavors中覆盖以下参数 * 你也可以直接通过tinkerPatchAllFlavorDebug/tinkerPatchAllFlavorRelease, 一次编译所有的flavor补丁包 * 注意的是:除非你不同的flavor代码是不一样的,不然建议采用zip comment或者文件方式生成渠道信息 **/ productFlavors { flavor { flavorName = "yingyongbao" appVersion = "${tinkerpatchSupport.appVersion}_${flavorName}" baseApkFile = "${bakPath}/mytinkerpatch-${buildType}-${variantName}.apk" baseProguardMappingFile = "${bakPath}/mytinkerpatch-${buildType}-${variantName}-mapping.txt" baseResourceRFile = "${bakPath}/mytinkerpatch-${buildType}-${variantName}-R.txt" /** 开启加固开关,上传此flavor的apk到加固网站进行加固 **/ protectedApp = true } flavor { flavorName = "360shoujizhushou" appVersion = "${tinkerpatchSupport.appVersion}_${flavorName}" baseApkFile = "${bakPath}/mytinkerpatch-${buildType}-${variantName}.apk" baseProguardMappingFile = "${bakPath}/mytinkerpatch-${buildType}-${variantName}-mapping.txt" baseResourceRFile = "${bakPath}/mytinkerpatch-${buildType}-${variantName}-R.txt" /** 开启加固开关,上传此flavor的apk到加固网站进行加固 **/ protectedApp = true } }}/** * 用于用户在代码中判断tinkerPatch是否被使能 */android { defaultConfig { buildConfigField "boolean", "TINKER_ENABLE", "${tinkerpatchSupport.tinkerEnable}" }}/** * 一般来说,我们无需对下面的参数做任何的修改 * 对于各参数的详细介绍请参考: * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97 */tinkerPatch { 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"// path = "/usr/local/bin/7za" } buildConfig { keepDexApply = false }}
此时还差两个文件,我是用官方文档推荐的
proguardRules.pro和tinkerMultidexKeep.pro
此时已经接入成功并配置成功!!!
3.使用
初始化Application
@SuppressWarnings("unused")@DefaultLifeCycle(application = ".SampleApplication", flags = ShareConstants.TINKER_ENABLE_ALL, loadVerifyFlag = false)public class SampleApplicationLike extends DefaultApplicationLike { private static final String TAG = "tinkerPatch"; public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public void onBaseContextAttached(Context base) { super.onBaseContextAttached(base); //you must install multiDex whatever tinker is installed! MultiDex.install(base); } @Override public void onCreate() { super.onCreate(); initTinker(); }// 初始化TinkerPatch SDK private void initTinker() { if (BuildConfig.TINKER_ENABLE) { //开始检查是否有补丁,这里配置的是每隔访问3小时服务器是否有更新。 TinkerPatch.init(this) .reflectPatchLibrary() .setPatchRollbackOnScreenOff(true) .setPatchRestartOnSrceenOff(true) .setFetchPatchIntervalByHours(3); // 获取当前的补丁版本 Log.d(TAG, "current patch version is " + TinkerPatch.with().getPatchVersion()); //每隔3个小时去访问后台时候有更新,通过handler实现轮训的效果 TinkerPatch.with().fetchPatchUpdateAndPollWithInterval(); } }}
在清单文件下添加name属性,刚才初始化Application的时候并没有以真正的以来Application,而是以来了DefaultApplicationLike类,此时的name填上在这句话的application = “”名字
@DefaultLifeCycle(application = ".SampleApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
<application android:name=".SampleApplication" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme"> <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity> </application>
现在如果你直接运行程序,会报错,application未初始化,此时只需要rebuild一下就好了,清单文件里的name就不会显示红色了
此时全部的配置已经进行完毕,剩下的就是进行编译程序,打包,打补丁,进行使用管理平台了
在build工具里打开app下的build工具中assembleRelease,进行打包
此时会在app\build\bakApk目录下生成文件
在你的项目中更改一些代码逻辑,或者控件的属性:如TextView的textColor属性等,更改后,进行开始打补丁
随后生成如下目录
这个文件就是我们需要的最终的补丁文件
把此补丁发布到管理平台上进行升级就ok了
MainActivity和布局https://github.com/TinkerPatch/tinkerpatch-easy-sample/blob/master/app/src/main/java/com/tinkerpatch/easy_sample/MainActivity.java
附上官方文档的TinkerPatch平台使用说明地址:http://www.tinkerpatch.com/Docs/start
注意:
①不混淆,mapping文件就没有
②旧版本名称要与gradle文件中的名称一致,不然报找不到XXXX-XX 文件
③Application类并没有直接集成,需要rebuild一下,自动创建Application类
④保存好旧版本的三个文件
⑤多看官方文档。
希望您读完后,能够给些宝贵意见。
- 热修复Tinker的使用 + TinkerPatch补丁管理平台 gradle接入
- Tinker热修复server端tinkerPatch的接入
- tinker热修复gradle接入
- tinker热修复gradle接入
- tinker热修复gradle接入
- Tinker热修复(gradle接入--成功)
- 热修复,Tinker的接入
- Tinker热修复简单使用与接入
- Tinker热修复之 —— Gradle接入
- 热修复tinker的使用
- tinker(二)-使用TinkerPatch 平台-集成
- tinker(三)-使用TinkerPatch 平台-实践
- 接入热修复框架TinKer
- Tinker热修复简单接入
- 热修复Tinker简单接入
- Android 热修复 Tinker接入
- Tinker热修复接入心得
- Android 热修复 Tinker 接入
- 渐进增强 VS 优雅降级
- 听说云服务器_也可以竞价了_1折起售_怎么买更划算??
- CentOS 7 python Opencv2.4.13安装记录 Install opencv2.4.13 python in centos 7
- 淘宝抢购倒计时(时、分、秒)
- c# Http Get Post Put Delete类整理
- 热修复Tinker的使用 + TinkerPatch补丁管理平台 gradle接入
- Spark2.2 Worker原理剖析图解及源码剖析
- paperweekly-迁移学习
- CORBA概述
- Spring-ApplicationContext
- 移动端H5页面截图【含 domtoimage、html2canvas 】
- Idea
- springBoot 动态数据源以及Mybatis多数据源
- 在其他数都出现偶数次的数组中找到出现奇数次的数