转自:http://blog.csdn.net/lisdye2/article/details/54411727

热修复框架Tinker的从0到集成之路

转载请标明出处: 
http://blog.csdn.net/lisdye2/article/details/54411727 
本文出自:【Alex_MaHao的博客】 
项目中的源码已经共享到github,有需要者请移步【Alex_MaHao的github】

2015年10月,QQ空间提出了热修复方案,一时间热修复风靡Android界,阿里的AndFix,360的插件形式,开源的NUWA等,都分别提出了自己的热修复实现方案,2016年微信推出了自己的热修复框架Tinker,从原理上来说,Tinker的实现方式和QQ空间的一脉相承,随着时间的推移,Tinker逐渐的成熟并推出了一键集成的SDK,感觉应该比较是靠谱了,所以就动手集成一下吧。集成比较方便,甚至推出了方便集成的SDK版本,不过需要money,所以我们还是从github上入手吧。

推荐大家看一下TinkerWiki,一些细节配置上比较清晰,本篇会忽略这些细节的配置

大致原理

为什么说大致原理呢,因为我也不知道他的原理,不过是在使用中猜的,如果错了,别怪我~~~。

  • 利用MulitDex打出多个dex文件。

    • 主dex中没有逻辑,只是Tinker的所有逻辑,不能更新。
    • 其余dex文件保存我们编写的逻辑,主要用于更新方便。
  • 保存上次打包的记录,便于打补丁包。
  • 根据上一次的记录,一一对比,找出区别之后整合,加签,打包,生成补丁文件。
  • 将补丁文件放到对应位置,验签,加载到内存。(QQ控件热修复原理加载到内存)

具体原理可以看我之前的热修复文章或者百度一下,本篇只说集成。

Gradle 配置

工程的根目录的gradle.properties文件中添加Tinker的版本号


org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 TINKER_VERSION=1.7.6
  • 1
  • 2
  • 3
  • 4
  • 5

工程根目录下的build.gradle中添加Tinker的编译插件

 dependencies {
// Tinker 编译的插件
classpath 'com.tencent.tinker:tinker-patch-gradle-plugin:1.7.6' classpath 'com.android.tools.build:gradle:2.1.0'
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

app 目录下的build.gradle中添加程序运行时的jar包

apply plugin: 'com.android.application'

android {
compileSdkVersion 23
buildToolsVersion "23.0.2" defaultConfig {
applicationId "com.alex.tinkerdemo"
minSdkVersion 15
targetSdkVersion 21
versionCode 1
versionName "1.0" // tinker 基本配置
multiDexEnabled true
buildConfigField "String", "MESSAGE", "\"I am the base apk\""
buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""
buildConfigField "String", "PLATFORM", "\"all\"" } // Tinker 推荐设置
dexOptions {
jumboMode = true
} // 签名信息的配置
signingConfigs {
release {
try {
storeFile file("./keystore/key.jks")
storePassword "123456"
keyAlias "1111"
keyPassword "1234567"
} catch (ex) {
throw new InvalidUserDataException(ex.toString())
}
}
} // 编译类型的配置
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled false
signingConfig signingConfigs.debug
}
}
} dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.2.1' // 多dex 打包的类库
compile "com.android.support:multidex:1.0.1"
// Tinker 基本类库
compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
// 编译时生成Application
provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70

build.gradle中,关键是依赖的添加,分别三个依赖,第一个是MultiDex分包使用的依赖,第二个是基本功能的类库,第三个是重点。

Tinker不推荐我们自己实现Application,而是由Tinker自己生成,那么我们通常开发时都会自己定义一个Application类,进行一些App的初始化操作,怎么解决呢?Tinker推荐了另一种实现的方式,后面再说,而第三个依赖便是方便实现自定义Application所推荐的类库。

如上,就是基本的环境配置,当然还有Tinker的配置,这里我们直接采用Tinker提供的基本配置,具体配置的细节可以看wiki。

在app的build.gradle最后,添加如下配置

//=======================Tinker 配置=======================================

def gitSha() {
try {
// String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim()
String gitRev = "1008611"
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'")
}
} // 保存打包old apk 的地址,便于生成补丁包时的对比
def bakPath = file("${buildDir}/bakApk/") ext {
//是否打开tinker的功能。
tinkerEnabled = true // old apk地址
tinkerOldApkPath = "${bakPath}/app-release-16-13.apk"
//old apk 混淆文件地址
tinkerApplyMappingPath = "${bakPath}/app-release-16-13-mapping.txt"
//old apk R 文件地址
tinkerApplyResourcePath = "${bakPath}/app-release-16-13-R.txt" // 多渠道打包相关
tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
} 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
} 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 = false /**
* 保证签名的唯一性
*/
useSign = true /**
* optional,default 'true'
* whether use tinker to build
*/
tinkerEnable = buildWithTinker() /**
* 编译相关配置
*/
buildConfig { /**
* 新的apk使用旧的Map 文件,减少补丁包大小
*/
applyMapping = getApplyMappingPath()
/**
* 同上所述,相同的R文件,减少补丁包大小
*/
applyResourceMapping = getApplyResourceMappingPath() /**
* 补丁的id标识,补丁包的tinkerId和apk的tinkerId相同才能加载补丁
*/
tinkerId = getTinkerIdValue() /**
* 打开keepDexApply模式,补丁包将根据基准包的类分布来编译。
*/
keepDexApply = false
} dex {
/**
* 'raw'模式,将会保持输入dex的格式。
* 'jar'模式,我们将会把输入dex重新压缩封装到jar
*/
dexMode = "jar" /**
* 需要处理dex路径
*/
pattern = ["classes*.dex",
"assets/secondary-dex-?.jar"]
/**
* 放在main.dex中的类,这些类不会被加载
*/
loader = [
//use sample, let BaseBuildInfo unchangeable with tinker
"tinker.sample.android.app.BaseBuildInfo"
]
} lib {
/**
* 需要处理的lib 路径
*/
pattern = ["lib/armeabi/*.so"]
} res {
/**
* 需要处理的资源路径
*/
pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] /**
* 忽视改变的文件,即这些文件的改变不会被打到补丁包中
*/
ignoreChange = ["assets/sample_meta.txt"] /**
*对于修改的资源,如果大于largeModSize,我们将使用bsdiff算法。这可以降低补丁包的大小,但是会增加合成时的复杂度。默认大小为100kb
*/
largeModSize = 100
} packageConfig {
/**
* 配置到清单文件的一些字段,没啥用
*/
configField("patchMessage", "tinker is sample to use")
/**
* 配置到清单文件的一些字段,没啥用
*/
configField("platform", "all")
/**
* 配置到清单文件的一些字段,没啥用
*/
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 {
/**
* zip路径配置项,执行前提是useSign为true,推荐配置
*/
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
/**
* 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")
def date = new Date().format("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"
} }
}
}
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287

在一些基本的配置上都有注释,方便大家理解。

Tinker的编译配置就到此为止,下面便是代码的配置。

代码编写

因为需要的代码不少,这里不在一一讲解,只标明主要的逻辑代码。具体代码可以去github上下载。

首先看一下整个类的结构

  • app:这个我也不知道是干什么的,我感觉没什么用,自己可是删掉试试。

  • crash:崩溃保护。

  • Log:修复过程中的日志打印。

  • reporter:补丁过程中的一些流程的回调。

  • service:修复成功以及耗时等回调

  • util : 工具类,关键是TinkerManager

Application编写

Tinker不推荐编写自定义Application,我们在自定义Application的逻辑,不在继承Application,而继承由Tinker提供的DefaultApplicationLike类,提供好编写的模板如下:

@SuppressWarnings("unused")
@DefaultLifeCycle( application = "com.alex.tinkerdemo.TinkerApp",// 自定义生成
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class App extends DefaultApplicationLike { public static App sApp; public App(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();
sApp = this; /*JPushInterface.setDebugMode(true);
JPushInterface.init(getApplication()); // 友盟统计日志加密
AnalyticsConfig.enableEncrypt(true);*/ } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base); // 其原理是分包架构,所以在加载初要加载其余的分包
MultiDex.install(base); // Tinker管理类,保存当前对象
TinkerManager.setTinkerApplicationLike(this);
// 崩溃保护
TinkerManager.initFastCrashProtect();
// 是否重试
TinkerManager.setUpgradeRetryEnable(true); //Log 实现,打印加载补丁的信息
TinkerInstaller.setLogIml(new MyLogImp()); // 运行Tinker ,通过Tinker添加一些基本配置
TinkerManager.installTinker(this); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
// 生命周期,默认配置
getApplication().registerActivityLifecycleCallbacks(callback);
} public static App getApp(){
return sApp;
} }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

在类上标有注解@DefaultLifeCycle,该注解是为了定义在编译是自动生成的Application类的信息,清单文件中的Application注册需要填此处定义的名字。

onCreate()方法和ApplciationonCreate()类似,在这里做一些初始化操作,例如友盟的初始化等等,不在过多的讲解。

其中一个区别便是上下文对象不能够在使用该对象,而是使用app.getApplication()方法获取上下文对象。

onBaseContextAttached()Applcation的方法功能类似,在这里我们做一些基本操作。

  • 加载分包:MultiDex.install(base);
  • 初始化Tinker对象,设置一些信息。

关于Tinker初始化信息,主要用到TinkerManager对象,注释很详细,看注释即可。关键方法便是TinkerManager.installTinker(this);,他进行初始化的最终操作。看一下这个方法


/**
* you can specify all class you want.
* sometimes, you can only install tinker in some process you want!
*
* @param appLike
*/
public static void installTinker(ApplicationLike appLike) {
if (isInstalled) {
TinkerLog.w(TAG, "install tinker, but has installed, ignore");
return;
}
//Tinker在加载补丁时的一些回调,我们实现对应方法获取回调
LoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication());
//Tinker在修复或者升级补丁时的一些回调
PatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication());
//用来过滤Tinker收到的补丁包的修复、升级请求
PatchListener patchListener = new SamplePatchListener(appLike.getApplication());
// 补丁包的核心处理类
AbstractPatch upgradePatchProcessor = new UpgradePatch(); TinkerInstaller.install(appLike,
loadReporter, patchReporter, patchListener,
SampleResultService.class, upgradePatchProcessor); isInstalled = true;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

注释很清晰,自己理解吧~~。

加载补丁包

MainActivity中的代码

    public class MainActivity extends AppCompatActivity {

    @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main); // 加载补丁包
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), "/sdcard/mdtinker.apk");
} /**
* =====================Tinker 默认配置========================
*/ protected void onResume() {
super.onResume();
Utils.setBackground(false); } @Override
protected void onPause() {
super.onPause();
Utils.setBackground(true);
}
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

关键性便是加载补丁包,这里为了省事直接加载,如果内存理没有补丁,直接会返回了,程序不会崩溃。

开始操作

Debug的不在演示,直接按照Release的方式演示。

  • 正常打出签名包

先按照正常流程打包签名APK,此时将签名文件安装到手机中。

会在上图目录下多出一些文件,这些文件保存好,因为生成补丁的时候需要这个东西。

  • 代码随便改出一些不同

  • 修改生成补丁的配置文件

随便修改一些文件之后,打开app下的build.gradle文件,修改如下文件配置为我们的文件地址

ext {
//是否打开tinker的功能。
tinkerEnabled = true // old apk地址
tinkerOldApkPath = "${bakPath}/app-release-27-43.apk"
//old apk 混淆文件地址
tinkerApplyMappingPath = "${bakPath}/app-release-27-43-mapping.txt"
//old apk R 文件地址
tinkerApplyResourcePath = "${bakPath}/app-release-27-43-R.txt" // 多渠道打包相关
tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 运行补丁命令

  • 获取补丁文件,导入到手机文件对应目录

注意,其中红色框处圈出的就是最终的补丁包,将他改成mdtinker.apk导入到手机对应存储。

  • 之后打开app会提示补丁加载成功,后台杀死进程再次打开,补丁就成功加载了。

补丁第一次加载之后,会删除补丁包,之后每次加载,都是已经修改过后,打过补丁的apk。

附加

        // 清除补丁包方法
//Tinker.with(getApplicationContext()).cleanPatch();

热修复框架Tinker的从0到集成之路(转)的更多相关文章

  1. 热修复框架Tinker快速集成

    由于腾讯官方的demo对于刚接触的我来说,太过复杂,找不到核心配置,因此将tinker集成中最核心的东西抽取出来,整合到一个demo中. demo工程已经提交到github上,点击跳转 更多使用方法, ...

  2. Tinker 热修复框架 简单上手教程

    当你们看到Tinker的时候是不是有点愣逼这个是什么东西? 简单来说就是不需要重新下载app和重新安装app 来进行更新app的技术框架. 看看这个吧,我也是才学习 ,先做个学习记录 参考:Tinke ...

  3. 阿里SopHix热修复框架

    2015年以来,Android开发领域里对热修复技术的讨论和分享越来越多,同时也出现了一些不同的解决方案,如QQ空间补丁方案.阿里AndFix以及微信Tinker(Bugly sdk也集成Tikner ...

  4. 手把手带你打造一个 Android 热修复框架(上篇)

    本文来自网易云社区 作者:王晨彦 前言 热修复和插件化是目前 Android 领域很火热的两门技术,也是 Android 开发工程师必备的技能. 目前比较流行的热修复方案有微信的 Tinker,手淘的 ...

  5. Android热修复原理(一)热修复框架对比和代码修复

    在Android应用开发中,热修复技术被越来越多的开发者所使用,也出现了很多热修复框架,比如:AndFix.Tinker.Dexposed和Nuwa等等.如果只是会这些热修复框架的使用那意义并不大,我 ...

  6. 十分钟教会你使用安卓热修复框架AndFix

    腾讯最近开发出一个Tinker,阿里也有一个Dexposed框架,当然还有一个就是今天的主角热修复框架AndFix.接下来,我会从它的概念.原理.使用方法等为你详细介绍. 1.什么是AndFix? A ...

  7. Unity3D游戏xlua轻量级热修复框架

    这是什么东西 前阵子刚刚集成xlua到项目,目的只有一个:对线上游戏C#逻辑有Bug的地方执行修复,通过考察了xlua和tolua,最终选择了xlua,原因如下: 1)项目已经到了后期,线上版本迭代了 ...

  8. 微信开源的Android热补丁框架 Tinker

    前不久,微信开源了其Android热补丁框架Tinker,它的特别之处在于放在github.com/Tencent下面,是该账号下第一个正式的开源项目,可以看到腾讯对它的重视和认可. 早在6月份微信客 ...

  9. Android 热修复方案Tinker(一) Application改造

    基于Tinker V1.7.5 Android 热修复方案Tinker(一) Application改造    Android 热修复方案Tinker(二) 补丁加载流程    Android 热修复 ...

随机推荐

  1. php yield关键字以及协程的实现

    php的yield是在php5.5版本就出来了,而在初级php界却很少有人提起,我就说说个人对php yield的理解 Iterator接口 在php中,除了数组,对象可以被foreach遍历之外,还 ...

  2. 2019HDU多校训练第二场 Longest Subarray

    题意:给你一个串,问满足以下条件的子串中最长的是多长:对于每个数字,要么在这个子串没出现过,要么出现次数超过k次. 思路:对于这类问题,常常转化为数据结构的询问问题.我们考虑枚举右端点,对于当前右端点 ...

  3. oracle client 卸载

    1.停用oracle服务:进入计算机管理,在服务中,找到oracle开头的所有服务,右击选择停止 2.在开始菜单中,找到Universal Installer,运行Oracle Universal I ...

  4. 静态部署TOMCAT

    常见部署方式:静态部署和容器化部署 一.下载tomcat安装包 下载地址:https://tomcat.apache.org/download-90.cgi 图上是显示最新版本,而我下载的是9.0.8 ...

  5. 【leetcode】935. Knight Dialer

    题目如下: A chess knight can move as indicated in the chess diagram below:  .            This time, we p ...

  6. springbootboot 语句

    -- ------------------------------ Table structure for `user`-- ----------------------------DROP TABL ...

  7. POJ 3468 A Simple Problem with Integers(线段树区间修改及查询)

    Description You have N integers, A1, A2, ... , AN. You need to deal with two kinds of operations. On ...

  8. Mysql学习-安装与启动

    安装mysqlrpm -ivh Mysql-server....rpm安装完成后,会提示/usr/bin/mysqladmin -u root password 'new-password'也可以用m ...

  9. Database基础(四):密码恢复及设置、 用户授权及撤销、数据备份与恢复、MySQL管理工具

    一.密码恢复及设置 目标: 本案例要求熟悉MySQL管理密码的控制,完成以下任务操作: 练习重置MySQL管理密码的操作 通过正常途径设置MySQL数据库的管理密码 步骤: 步骤一:重置MySQL管理 ...

  10. python中API接口是什么

    首先还是举个例子:你要去银行取钱的例子.如果没有银行柜员给你服务,你自己去存钱,你需要做的事情有: 一,打开金库的大门 二,把钱放进去 三,记账,存放了多少钱 四,离开. 问题解决了,但是其中有不少问 ...