Tinker是什么
Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。
它主要包括以下几个部分:
- gradle编译插件:
tinker-patch-gradle-plugin
- 核心sdk库:
tinker-android-lib
- 非gradle编译用户的命令行版本:
tinker-patch-cli.jar
为什么使用Tinker
总的来说:
- AndFix作为native解决方案,首先面临的是稳定性与兼容性问题,更重要的是它无法实现类替换,它是需要大量额外的开发成本的;
- Robust兼容性与成功率较高,但是它与AndFix一样,无法新增变量与类只能用做的bugFix方案;
- Qzone方案可以做到发布产品功能,但是它主要问题是插桩带来Dalvik的性能问题,以及为了解决Art下内存地址问题而导致补丁包急速增大的。
特别是在Android N之后,由于混合编译的inline策略修改,对于市面上的各种方案都不太容易解决。而Tinker热补丁方案不仅支持类、So以及资源的替换,它还是2.X-7.X的全平台支持。利用Tinker我们不仅可以用做bugfix,甚至可以替代功能的发布。Tinker已运行在微信的数亿Android设备上,那么为什么你不使用Tinker呢?
Tinker的已知问题
由于原理与系统限制,Tinker有以下已知问题:
- Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件;
- 由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
- 在Android N上,补丁对应用启动时间有轻微的影响;
- 不支持部分三星android-21机型,加载补丁时会主动抛出
"TinkerRuntimeException:checkDexInstall failed"
; - tinker的一般模式并不支持加固,需要使用usePreGeneratedPatchDex模式,即提前生成补丁模式。某些加固工具可能会将非exported的四大组件类名替换,这些类将无法修改。对于Android N之后的设备,本模式可能会因为内联而出现问题,建议过滤N之后的设备;
- 对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。
如何使用Tinker
接下来带大家 在项目中一步步使用 Tinker,我们选一个平台 我选的是bugly ,腾讯的,当然你也可以选择其他的平台,
TinkerPatch ,这个也可以 ,但以后肯定要收费的,
TinkerPatch平台文档
。
首先第一步
添加插件依赖
工程根目录下“build.gradle”文件中添加:
第二步:在app module的“build.gradle”文件中添加(我的配置):
apply
plugin:
'com.android.application'
def releaseTime() {
return new Date().format( "yyyy-MM-dd", TimeZone. getTimeZone( "UTC"))
}
dependencies {
compile fileTree( dir: 'libs', include: [ '*.jar'])
compile 'com.android.support:appcompat-v7:24.1.1'
// 多 dex 配置
compile "com.android.support:multidex:1.0.1"
// 集成 Bugly 热更新 aar (灰度时使用方式)
// compile(name: 'bugly_crashreport_upgrade-1.2.0', ext: 'aar')
compile 'com.tencent.bugly:crashreport_upgrade:latest.release' // 其中 latest.release 指代最新版本号,也可以指定明确的版本号,例如 1.2.0
compile 'com.tencent.bugly:nativecrashreport:latest.release' // 其中 latest.release 指代最新版本号,也可以指定明确的版本号,例如 2.2.0
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
// 编译选项
compileOptions {
sourceCompatibility JavaVersion. VERSION_1_7
targetCompatibility JavaVersion. VERSION_1_7
}
// recommend
dexOptions {
jumboMode = true
}
// 签名配置
signingConfigs {
debug {
storeFile file( "../KeyDianda.jks")
storePassword "111111"
keyAlias "key_dianda"
keyPassword "111111"
}
release {
storeFile file( "../****.jks")//修改成自己的秘钥
storePassword "***"// 改成自己的密码
keyAlias "***"
keyPassword "***"
}
}
// 默认配置
defaultConfig {
applicationId "com.tinker.ddinfo"
minSdkVersion 14
targetSdkVersion 23
versionCode 200
versionName "2.0.0"
// 开启 multidex
multiDexEnabled true
// 以 Proguard 的方式手动加入要放到 Main.dex 中的类
multiDexKeepProguard file( "keep_in_main_dex.txt")
}
// 构建类型
buildTypes {
// release {
// // 不显示 Log
// buildConfigField "boolean", "LOG_DEBUG", "false"
// // 是否进行混淆
// minifyEnabled false
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// signingConfig signingConfigs.release
// applicationVariants.all { variant ->
// variant.outputs.each { output ->
// def outputFile = output.outputFile
// if (outputFile != null && outputFile.name.endsWith('.apk')) {
// // 输出 apk 名称为 ddmall1.0.1_6.apk boohee_v1.0_2015-01-15_wandoujia.apk
// def fileName = "tinker${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
// output.outputFile = new File(outputFile.parent, fileName)
// }
// }
// }
// }
//
// debug {
// debuggable true
// minifyEnabled false
// signingConfig signingConfigs.debug
// }
release {
// 是否进行混淆
minifyEnabled false
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile( 'proguard-android.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled false
signingConfig signingConfigs.debug
}
}
sourceSets {
main {
jniLibs. srcDirs = [ 'libs']
java. srcDirs = [ 'src/main/java']
}
}
repositories {
flatDir {
dirs 'libs'
}
maven { url "https://jitpack.io" }
}
// // 多渠道打包
// productFlavors {
// "Dianda" {}
//// "Tencent" {}
// }
//
// productFlavors.all { flavor ->
// flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
// }
lintOptions {
checkReleaseBuilds false
abortOnError false
}
splits {
abi {
enable true
reset()
include 'armeabi' //,'x86'//, 'x86_64', 'arm64-v8a', 'armeabi-v7a'
universalApk false
}
}
sourceSets. main {
jniLibs.srcDirs = [ 'libs']
}
}
def gitSha() {
try {
String gitRev = 'git rev-parse --short HEAD'.execute( null, project. rootDir). text.trim()
if (gitRev == null) {
throw new GradleException( "can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
}
return gitRev
} catch (Exception e) {
throw new GradleException( "can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
}
}
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
* 打一个补丁包需要改哪些东西?
修复 bug 的类、修改资源
修改 oldApk 配置
修改 tinkerId
*/
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} /app-release-1223-13-43-58.apk"
// proguard mapping file to build patch apk
tinkerApplyMappingPath = "${bakPath} /app-release-1223-13-43-58-mapping.txt"
// resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = "${bakPath} /app-release-1223-13-43-58-R.txt"
// Todo 需要修改
tinkerBuildFlavorDirectory = "${bakPath} /app-release-1223-13-43-58"
}
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 : gitSha()
}
def buildWithTinker() {
return hasProperty( "TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
}
def getTinkerBuildFlavorDirectory() {
return ext.tinkerBuildFlavorDirectory
}
/**
* 更多 Tinker 插件详细的配置,参考: https://github.com/Tencent/tinker/wiki
*/
if (buildWithTinker()) {
apply plugin: 'com.tencent.bugly.tinker-support'
// 依赖 tinker 插件
apply plugin: 'com.tencent.tinker.patch'
tinkerSupport {
}
// 全局信息相关配置项
tinkerPatch {
oldApk = getOldApkPath() // 必选, 基准包路径
ignoreWarning = false // 可选,默认 false
useSign = true // 可选,默认 true , 验证基准 apk 和 patch 签名是否一致
// 编译相关配置项
// Todo 需要修改 tinkerId 每次打补丁时 tinkerId 跟打基准包时得 tinkerId 不能一样
buildConfig {
applyMapping = getApplyMappingPath() // 可选,设置 mapping 文件,建议保持旧 apk 的 proguard 混淆方式
applyResourceMapping = getApplyResourceMappingPath() // 可选,设置 R.txt 文件,通过旧 apk 文件保持 ResId 的分配
tinkerId = "bugbugbug_v2.0.0gggggg"
}
// dex 相关配置项
dex {
dexMode = "jar" // 可选,默认为 jar
usePreGeneratedPatchDex = true // 可选,默认为 false
pattern = [ "classes*.dex",
"assets/secondary-dex-?.jar"]
// Todo 需要修改 自己的包名
loader = [ "com.tencent.tinker.loader.*",
"com.tinker.ddinfo.SampleApplication",
]
}
// lib 相关的配置项
lib {
pattern = [ "lib/armeabi.so"]
}
// res 相关的配置项
res {
pattern = [ "res", "assets", "resources.arsc", "AndroidManifest.xml"]
ignoreChange = [ "assets/sample_meta.txt"]
largeModSize = 100
}
// 用于生成补丁包中的 'package_meta.txt' 文件
packageConfig {
configField( "patchMessage", "tinker is sample to use")
configField( "platform", "all")
configField( "patchVersion", "1.0")
}
// 7zip 路径配置项,执行前提是 useSign 为 true
sevenZip {
zipArtifact = "com.tencent.mm:SevenZip:1.1.10" // optional
// path = "/usr/local/bin/7za" // optional
}
}
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"
}
}
}
}
}
}
def releaseTime() {
return new Date().format( "yyyy-MM-dd", TimeZone. getTimeZone( "UTC"))
}
dependencies {
compile fileTree( dir: 'libs', include: [ '*.jar'])
compile 'com.android.support:appcompat-v7:24.1.1'
// 多 dex 配置
compile "com.android.support:multidex:1.0.1"
// 集成 Bugly 热更新 aar (灰度时使用方式)
// compile(name: 'bugly_crashreport_upgrade-1.2.0', ext: 'aar')
compile 'com.tencent.bugly:crashreport_upgrade:latest.release' // 其中 latest.release 指代最新版本号,也可以指定明确的版本号,例如 1.2.0
compile 'com.tencent.bugly:nativecrashreport:latest.release' // 其中 latest.release 指代最新版本号,也可以指定明确的版本号,例如 2.2.0
}
android {
compileSdkVersion 23
buildToolsVersion "23.0.2"
// 编译选项
compileOptions {
sourceCompatibility JavaVersion. VERSION_1_7
targetCompatibility JavaVersion. VERSION_1_7
}
// recommend
dexOptions {
jumboMode = true
}
// 签名配置
signingConfigs {
debug {
storeFile file( "../KeyDianda.jks")
storePassword "111111"
keyAlias "key_dianda"
keyPassword "111111"
}
release {
storeFile file( "../****.jks")//修改成自己的秘钥
storePassword "***"// 改成自己的密码
keyAlias "***"
keyPassword "***"
}
}
// 默认配置
defaultConfig {
applicationId "com.tinker.ddinfo"
minSdkVersion 14
targetSdkVersion 23
versionCode 200
versionName "2.0.0"
// 开启 multidex
multiDexEnabled true
// 以 Proguard 的方式手动加入要放到 Main.dex 中的类
multiDexKeepProguard file( "keep_in_main_dex.txt")
}
// 构建类型
buildTypes {
// release {
// // 不显示 Log
// buildConfigField "boolean", "LOG_DEBUG", "false"
// // 是否进行混淆
// minifyEnabled false
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// signingConfig signingConfigs.release
// applicationVariants.all { variant ->
// variant.outputs.each { output ->
// def outputFile = output.outputFile
// if (outputFile != null && outputFile.name.endsWith('.apk')) {
// // 输出 apk 名称为 ddmall1.0.1_6.apk boohee_v1.0_2015-01-15_wandoujia.apk
// def fileName = "tinker${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
// output.outputFile = new File(outputFile.parent, fileName)
// }
// }
// }
// }
//
// debug {
// debuggable true
// minifyEnabled false
// signingConfig signingConfigs.debug
// }
release {
// 是否进行混淆
minifyEnabled false
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile( 'proguard-android.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled false
signingConfig signingConfigs.debug
}
}
sourceSets {
main {
jniLibs. srcDirs = [ 'libs']
java. srcDirs = [ 'src/main/java']
}
}
repositories {
flatDir {
dirs 'libs'
}
maven { url "https://jitpack.io" }
}
// // 多渠道打包
// productFlavors {
// "Dianda" {}
//// "Tencent" {}
// }
//
// productFlavors.all { flavor ->
// flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
// }
lintOptions {
checkReleaseBuilds false
abortOnError false
}
splits {
abi {
enable true
reset()
include 'armeabi' //,'x86'//, 'x86_64', 'arm64-v8a', 'armeabi-v7a'
universalApk false
}
}
sourceSets. main {
jniLibs.srcDirs = [ 'libs']
}
}
def gitSha() {
try {
String gitRev = 'git rev-parse --short HEAD'.execute( null, project. rootDir). text.trim()
if (gitRev == null) {
throw new GradleException( "can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
}
return gitRev
} catch (Exception e) {
throw new GradleException( "can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
}
}
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
* 打一个补丁包需要改哪些东西?
修复 bug 的类、修改资源
修改 oldApk 配置
修改 tinkerId
*/
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} /app-release-1223-13-43-58.apk"
// proguard mapping file to build patch apk
tinkerApplyMappingPath = "${bakPath} /app-release-1223-13-43-58-mapping.txt"
// resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = "${bakPath} /app-release-1223-13-43-58-R.txt"
// Todo 需要修改
tinkerBuildFlavorDirectory = "${bakPath} /app-release-1223-13-43-58"
}
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 : gitSha()
}
def buildWithTinker() {
return hasProperty( "TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
}
def getTinkerBuildFlavorDirectory() {
return ext.tinkerBuildFlavorDirectory
}
/**
* 更多 Tinker 插件详细的配置,参考: https://github.com/Tencent/tinker/wiki
*/
if (buildWithTinker()) {
apply plugin: 'com.tencent.bugly.tinker-support'
// 依赖 tinker 插件
apply plugin: 'com.tencent.tinker.patch'
tinkerSupport {
}
// 全局信息相关配置项
tinkerPatch {
oldApk = getOldApkPath() // 必选, 基准包路径
ignoreWarning = false // 可选,默认 false
useSign = true // 可选,默认 true , 验证基准 apk 和 patch 签名是否一致
// 编译相关配置项
// Todo 需要修改 tinkerId 每次打补丁时 tinkerId 跟打基准包时得 tinkerId 不能一样
buildConfig {
applyMapping = getApplyMappingPath() // 可选,设置 mapping 文件,建议保持旧 apk 的 proguard 混淆方式
applyResourceMapping = getApplyResourceMappingPath() // 可选,设置 R.txt 文件,通过旧 apk 文件保持 ResId 的分配
tinkerId = "bugbugbug_v2.0.0gggggg"
}
// dex 相关配置项
dex {
dexMode = "jar" // 可选,默认为 jar
usePreGeneratedPatchDex = true // 可选,默认为 false
pattern = [ "classes*.dex",
"assets/secondary-dex-?.jar"]
// Todo 需要修改 自己的包名
loader = [ "com.tencent.tinker.loader.*",
"com.tinker.ddinfo.SampleApplication",
]
}
// lib 相关的配置项
lib {
pattern = [ "lib/armeabi.so"]
}
// res 相关的配置项
res {
pattern = [ "res", "assets", "resources.arsc", "AndroidManifest.xml"]
ignoreChange = [ "assets/sample_meta.txt"]
largeModSize = 100
}
// 用于生成补丁包中的 'package_meta.txt' 文件
packageConfig {
configField( "patchMessage", "tinker is sample to use")
configField( "platform", "all")
configField( "patchVersion", "1.0")
}
// 7zip 路径配置项,执行前提是 useSign 为 true
sevenZip {
zipArtifact = "com.tencent.mm:SevenZip:1.1.10" // optional
// path = "/usr/local/bin/7za" // optional
}
}
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"
}
}
}
}
}
}
*这里要注意 需要修改tinkerId每次打补丁时tinkerId跟打基准包时得tinkerId不能一样
第三步:
package com.tinker.ddinfo
;
import com.tencent.tinker.loader.app.TinkerApplication ;
import com.tencent.tinker.loader.shareutil.ShareConstants ;
/**
* 自定义 Application.
*
* 注意:这个类集成 TinkerApplication 类,这里面不做任何操作,所有 Application 的代码都会放到 ApplicationLike 继承类当中 <br/>
* <pre>
* 参数解析:
* 参数 1 : int tinkerFlags 表示 Tinker 支持的类型 dex only 、 library only or all suuport , default: TINKER_ENABLE_ALL
* 参数 2 : String delegateClassName Application 代理类 这里填写你自定义的 ApplicationLike
* 参数 3 : String loaderClassName Tinker 的加载器,使用默认即可
* 参数 4 : boolean tinkerLoadVerifyFlag 加载 dex 或者 lib 是否验证 md5 ,默认为 false
* </pre>
* @author wenjiewu
* @since 2016/11/15
*/
public class SampleApplication extends TinkerApplication {
public SampleApplication() {
super(ShareConstants. TINKER_ENABLE_ALL , "com.tinker.ddinfo.SampleApplicationLike" ,//修改成自己的包名
"com.tencent.tinker.loader.TinkerLoader" , false) ;
}
}
import com.tencent.tinker.loader.app.TinkerApplication ;
import com.tencent.tinker.loader.shareutil.ShareConstants ;
/**
* 自定义 Application.
*
* 注意:这个类集成 TinkerApplication 类,这里面不做任何操作,所有 Application 的代码都会放到 ApplicationLike 继承类当中 <br/>
* <pre>
* 参数解析:
* 参数 1 : int tinkerFlags 表示 Tinker 支持的类型 dex only 、 library only or all suuport , default: TINKER_ENABLE_ALL
* 参数 2 : String delegateClassName Application 代理类 这里填写你自定义的 ApplicationLike
* 参数 3 : String loaderClassName Tinker 的加载器,使用默认即可
* 参数 4 : boolean tinkerLoadVerifyFlag 加载 dex 或者 lib 是否验证 md5 ,默认为 false
* </pre>
* @author wenjiewu
* @since 2016/11/15
*/
public class SampleApplication extends TinkerApplication {
public SampleApplication() {
super(ShareConstants. TINKER_ENABLE_ALL , "com.tinker.ddinfo.SampleApplicationLike" ,//修改成自己的包名
"com.tencent.tinker.loader.TinkerLoader" , false) ;
}
}
第四步:
自定义ApplicationLike
package com.tinker.ddinfo;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Build;
import android.support.multidex.MultiDex;
import com.tencent.bugly.Bugly;
import com.tencent.bugly.beta.Beta;
import com.tencent.tinker.loader.app.DefaultApplicationLike;
import com.tinker.ddinfo.utils.ExampleConfig;
/**
* 自定义ApplicationLike类.
*
* 注意:这个类是Application的代理类,以前所有在Application的实现必须要全部拷贝到这里<br/>
*
* @author wenjiewu
* @since 2016/11/7
*/
public class SampleApplicationLike extends DefaultApplicationLike {
public static final String TAG = "Tinker.SampleApplicationLike";
public SampleApplicationLike(Application application, int tinkerFlags,
boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,
long applicationStartMillisTime, Intent tinkerResultIntent, Resources[] resources,
ClassLoader[] classLoader, AssetManager[] assetManager) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime,
applicationStartMillisTime, tinkerResultIntent, resources, classLoader,
assetManager);
}
@Override
public void onCreate() {
super.onCreate();
// TODO: 这里进行Bugly初始化
// 设置开发设备
Bugly.setIsDevelopmentDevice(getApplication(), true);
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
Bugly.init(getApplication(), ExampleConfig.BUGLY_APP_ID, true);
}
@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);
// TODO: 安装tinker
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
getApplication().registerActivityLifecycleCallbacks(callbacks);
}
import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Build;
import android.support.multidex.MultiDex;
import com.tencent.bugly.Bugly;
import com.tencent.bugly.beta.Beta;
import com.tencent.tinker.loader.app.DefaultApplicationLike;
import com.tinker.ddinfo.utils.ExampleConfig;
/**
* 自定义ApplicationLike类.
*
* 注意:这个类是Application的代理类,以前所有在Application的实现必须要全部拷贝到这里<br/>
*
* @author wenjiewu
* @since 2016/11/7
*/
public class SampleApplicationLike extends DefaultApplicationLike {
public static final String TAG = "Tinker.SampleApplicationLike";
public SampleApplicationLike(Application application, int tinkerFlags,
boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,
long applicationStartMillisTime, Intent tinkerResultIntent, Resources[] resources,
ClassLoader[] classLoader, AssetManager[] assetManager) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime,
applicationStartMillisTime, tinkerResultIntent, resources, classLoader,
assetManager);
}
@Override
public void onCreate() {
super.onCreate();
// TODO: 这里进行Bugly初始化
// 设置开发设备
Bugly.setIsDevelopmentDevice(getApplication(), true);
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
Bugly.init(getApplication(), ExampleConfig.BUGLY_APP_ID, true);
}
@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);
// TODO: 安装tinker
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
getApplication().registerActivityLifecycleCallbacks(callbacks);
}
}
第五步:Androidmanifest 文件
第六步:MainActivity 这个类是为了配合显示 修改bug
第七步:根据命令打基准包 (就是apk包) 在bakAPK 目录下
第八步:这时要注意打完基准包后 ,需要修改bug 我在mainActivity 里面修改了一行Toast代码
然后修改 app 下面的build.gradle 文件
记得需要修改 tinkerId
第九步:执行tinkerPatchRelease 命令 打补丁包
第十步 上传 到腾讯Bugly 平台。
需要注意的是 在手机应用上 需要 冷启动 ,杀掉进程 重新 进入应用才会成功。 有时候需要等一两分钟
最后附上 demo 下载链接