Tinker的简单接入

Tinker Github:[https://github.com/Tencent/tinker]
我的tinker demo项目完整地址[https://github.com/llayjun/MyTinkerDemo]
**Tinker的一些介绍:
[http://mp.weixin.qq.com/s?__biz=MzAwNDY1ODY2OQ==&mid=2649286306&idx=1&sn=d6b2865e033a99de60b2d4314c6e0a25#rd]

Tinker背景

不知你是否遇到这样的情况?千辛万苦上开发了一个版本,好不容易上线了,突然发现了一个严重bug需要进行紧急修复,怎么办?难道又要重新打包App、测试,发布新个版本?就为了修改一两行的代码?

莫慌,这种问题其实可以分分钟解决。如果你学会了这项黑科技——热修复。在用户使用App的时候,不知不觉,这个Bug就被修复了。

Tinker介绍

热修复:热修复(也称热补丁、热修复补丁,英语:hotfix)是一种包含信息的独立的累积更新包,通常表现为一个或多个文件。这被用来解决软件产品的问题(例如一个程序错误)。——维基百科

Tinker 是微信官方的Android热补丁解决方案,它支持动态下发代码、so库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用 Tinker 来更新你的插件。Tinker 所支持的功能如下:

Tinker 热补丁方案·不仅支持类、So以及资源的替换,它还是 2.X-7.X 的全平台支持。

特别注意

  • 补丁只能针对单一客户端版本,随着版本差异变大补丁体积也会增大;
  • 补丁不能支持所有的修改,例如AndroidManifest;
  • 补丁无论对代码还是资源的更新成功率都无法达到100%。

简单接入

1、在Project的build.gradle中添加

buildscript {repositories {jcenter()}dependencies {classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.11')}
}

2、在app的build.gradle中添加

dependencies {//optional, help to generate the final applicationprovided('com.tencent.tinker:tinker-android-anno:1.7.11')//tinker's main Android libcompile('com.tencent.tinker:tinker-android-lib:1.7.11')//检查Dex文件方法数超过了最大值65536的上限compile "com.android.support:multidex:1.0.1"
}

3、在app的build.gradle中添加

1、在defaultConfig的最后添加

        /*** buildConfig can change during patch!* we can use the newly value when patch*/buildConfigField "String", "MESSAGE", "\"I am the base apk\""/*** client version would update with patch* so we can get the newly git version easily!*/buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""buildConfigField "String", "PLATFORM", "\"all\""

2、在android最后添加

//recommenddexOptions {jumboMode = true}sourceSets {main {jniLibs.srcDirs = ['libs']}}

3、在最后添加,主要注意如下3个地方,中文标注处,其余默认便可

def bakPath = file("${buildDir}/bakApk/")/*** 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//注意1//for normal build//old apk file to build patch apktinkerOldApkPath = "${bakPath}/app-release-0725-13-36-40.apk"//proguard mapping file to build patch apktinkerApplyMappingPath = "${bakPath}/app-release-0725-13-32-35-mapping.txt"//resource R.txt to build patch apk, must input if there is resource changedtinkerApplyResourcePath = "${bakPath}/app-release-0725-13-36-40-R.txt"//only use for build all flavor, if not, just ignore this fieldtinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}def buildWithTinker() {return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
}def getTinkerIdValue() {return hasProperty("TINKER_ID") ? TINKER_ID : android.defaultConfig.versionCode//这里使用versionCode来作为tinkerId,保证版本不一样,//注意2
}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 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//注意3/*** 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 = 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/*** optional, default 'false'* Whether tinker should treat the base apk as the one being protected by app* protection tools.* If this attribute is true, the generated patch package will contain a* dex including all changed classes instead of any dexdiff patch-info files.*/isProtectedApp = 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() > 0def 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.nametasks.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}") : bakPathfrom variant.outputs.outputFileinto destPathrename { String fileName ->fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")}from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"into destPathrename { String fileName ->fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")}from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"into destPathrename { String fileName ->fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")}}}}}}project.afterEvaluate {//sample use for build all flavor for one timeif (hasFlavors) {task(tinkerPatchAllFlavorRelease) {group = 'tinker'def originOldPath = getTinkerBuildFlavorDirectory()for (String flavor : flavors) {def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")dependsOn tinkerTaskdef 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 tinkerTaskdef 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"}}}}}
}

4、完整build.gradle文件请看[https://github.com/llayjun/MyTinkerDemo/blob/master/app/build.gradle]

4、初始化Tinker

假如你有使用自定义的Application类,那么就要注意了

1、创建SampleApplicationLike.java类,继承DefaultApplicationLike,内容如下:

/*** Created by Administrator on 2017/7/25 0025.*/
@SuppressWarnings("unused")
@DefaultLifeCycle(application = "com.millet.myapplication.SampleApplication", flags = ShareConstants.TINKER_ENABLE_ALL, loadVerifyFlag = false)
public class SampleApplicationLike extends DefaultApplicationLike {public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);}@Overridepublic void onCreate() {super.onCreate();}/*** install multiDex before install tinker* so we don't need to put the tinker lib classes in the main dex** @param base*/@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)@Overridepublic void onBaseContextAttached(Context base) {super.onBaseContextAttached(base);//you must install multiDex whatever tinker is installed!MultiDex.install(base);//初始化TinkerTinkerInstaller.install(this);}@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {getApplication().registerActivityLifecycleCallbacks(callback);}}

然后将之前自定义的Application中的内容,添加到此类中去,注意:有些朋友会纳闷,该类并没有继承Application,那我应用的Application哪去了。这里该类会通过注解生成一个SampleApplication类,在manifests文件中删除之前的,添加android:name=".SampleApplication"就可以了,报错请编译一下。

5、潜在问题

假如到这里,你编译没有任何错误,那么恭喜你,你很厉害哦
问题1:tinkerId is not set ,这个问题是因为用户没有设置tinkerId,我这边使用的是return hasProperty("TINKER_ID") ? TINKER_ID : android.defaultConfig.versionCode//这里使用versionCode来作为tinkerId
问题2:无法tinkerpath.release,这个问题需要添加此类代码
在app的build.gradle中的android中添加:

signingConfigs {release {try {storeFile file("./keystore/mi.jks")storePassword "qazwsx"keyAlias "qazwsx"keyPassword "qazwsx"} catch (ex) {throw new InvalidUserDataException(ex.toString())}}debug {storeFile file("./keystore/mi.jks")storePassword "qazwsx"keyAlias "qazwsx"keyPassword "qazwsx"}}buildTypes {release {minifyEnabled truesigningConfig signingConfigs.releaseproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}debug {debuggable trueminifyEnabled falsesigningConfig signingConfigs.debugproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}

6、获取差异包

1、项目调整到Project状态
!](http://upload-images.jianshu.io/upload_images/3545858-2e0bddf5c1ca30b7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

2、运行应用,运行获得第一次的错误版本,在bakApk中,会生成两个文件(打正式包会多一个mapping文件),如图:

复制名字分别填写到build.gradle中的相应处(正式包多一个mapping需要填写),修改代码或者bug,如图:

3、在右边的Gradle中选择tinkerPathDebug(正式包选择tinkerPathRelease如图:)

4、在tinkerPath文件中会多出一个patch_signed_7zip.apk文件,此包便是差异包

5、将它复制到sd卡中,进行如下加载

//寻找文件地址
String patchPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
//进行加载
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), patchPath);

demo代码如下:

 String patchPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";File file = new File(patchPath);boolean is = file.exists();Toast.makeText(this, "是否存在:" + is + "地址是:" + patchPath, Toast.LENGTH_SHORT).show();if (file.exists()) {Log.v(TAG, "补丁文件存在");TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), patchPath);} else {Log.v(TAG, "补丁文件不存在");}

Tinker的简单接入相关推荐

  1. 微信热修复tinker及tinker server快速接入

    博客: 安卓之家 掘金: jp1017 微博: 追风917 CSDN: 蒋朋的家 简书: 追风917 当前热修复方案很多,今天研究了下微信的tinker,使用效果还是不错的,配合tinker serv ...

  2. 手把手教你简单接入微信SDK

    就看微信现在这么火的样子,如果你的APP不接入微信的SDK好像就有点脱离了时代大车轮一样.一个成功的APP,不单单凭借着一个好的想法,一个好的功能,最主要还是用户量.用户量就好像是水,我们的APP就一 ...

  3. 微信公众号自动回复html,[.NET] 简单接入微信公众号开发:实现自动回复

    简单接入微信公众号开发:实现自动回复 一.前提 先申请微信公众号的授权,找到或配置几个关键的信息(开发者ID.开发者密码.IP白名单.令牌和消息加解密密钥等). 二.基本配置信息解读 开发者ID:固定 ...

  4. 微信公众号简单接入springboot集成weixin4j

    微信公众号简单接入springboot集成weixin4j 内网穿透 登录地址:https://natapp.cn/ 注册用户,购买免费渠道 进行配置端口号(我配置的是8802) 根据网址进行下一步操 ...

  5. Android 微信Tinker三分钟接入七分钟原理 让你成为热修复牛人

    继插件化后,热补丁技术在2015年开始爆发,热修复作为当下热门的技术,在业界内比较著名的有阿里巴巴的AndFix.腾讯QQ空间的超级补丁和微信的Tinker. 为什么用热修复? 1.无需重新发版,实时 ...

  6. 框架笔记:记录XLua的简单接入

    阅前提示 本系列为作者在学习框架与编写框架时的心得与笔记 适合人群:All 阅读方式:工具文章 本系列会不断更新,如果对你有所帮助,点赞收藏吧:) 文章目录 阅前提示 XLua 配置 [LuaCall ...

  7. unity steamworksdk简单接入

    VR越来越热,这个时间节点psvr还没正式发行,HTCvive属于VR设备里体验比较优秀的设备.开发vive应用上线主要有两个渠道,viveport官方商店及steam.两者官方都有详细的文档,但是上 ...

  8. Android 热修复 Tinker接入及源码浅析

    本文已在我的公众号hongyangAndroid首发. 转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/54882693 本文出自 ...

  9. Android热更新Tinker + 多渠道打包 + 加固的流程详解

    一.Tinker热修复 关于热修复的作用,不用多说了,一句话概括就是通过让用户无感的方式来修复线上应用的bug.这里介绍的是微信Tinker. 下面的接入方式都是参考自Tinker官方文档来.我这里主 ...

  10. 腾讯Tinker 热修复 Andriod studio 3.0 配置和集成(一)

    本文说明 面试的时候经常问我有没有用过热修复?用谁的?能说下原理吗?当时我回答得不好,毕竟以前的项目都没有用,又不敢装逼,mmp,但是基本流程还是知道的,所以我们来初探下Tinker 这个热修复,如果 ...

最新文章

  1. “面试不败计划”:面试题基础二
  2. .Net Micro Framework 快速入门
  3. java调用php session_php读取memcahed java session
  4. 字符串连接符(Java)
  5. python装饰器详解-如何更通俗地讲解Python的装饰器?
  6. HDU1754 I Hate It (线段树单点修改+区间查询)
  7. heidisql与 MySQL区别,heidisql怎么使用 MySQL可视化工具heidisql安装使用教程
  8. 这几本游戏开发书籍推荐给为未来的游戏工程师
  9. 2022-2028年中国新疆旅游行业发展动态及投资前景分析报告
  10. 全国计算机一级ms考试内容,2020年全国计算机等级考试一级MSOFFICE考试内容
  11. html 设置横向打印,电脑打印怎么横向打印出来_打印机设置横向打印的图文教程...
  12. gateway+vue实现防接口重放、防篡改
  13. 2019杭电多校第七场 HDU - 6656 Kejin Player——概率期望
  14. compact mysql_mysql中compact行的存储结构
  15. springboot 中动态切换数据源(多数据源应用设计)
  16. 【Nacos 学习笔记】01 - 快速入门
  17. 汽车美容会员管理软件用什么好-云上铺
  18. Cassandra入门调研
  19. js实现手机拨号功能
  20. 小米妙享客户端PC 出现打不开的问题

热门文章

  1. chattr 设置隐藏属性
  2. windows 编译FFMPEG
  3. windows下编译可在visual studio中调试的FFmpeg
  4. Makefile.am编写规则
  5. linux内存源码分析 - 内存压缩(实现流程)
  6. 生态系统服务——食物生产功能分布数据
  7. 卡特兰数(JAVA大数)Buy the Ticket
  8. 项目介绍star原理_这个Python项目厉害了!多个实战案例教你分析时空数据处理...
  9. 天翼宽带怎么开虚拟服务器,天翼宽带怎么设置无线路由器?
  10. mysql 数据写入文件格式_数据写入