转载请标明出处:
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上入手吧。
推荐大家看一下Tinker
的Wiki,一些细节配置上比较清晰,本篇会忽略这些细节的配置
大致原理
为什么说大致原理呢,因为我也不知道他的原理,不过是在使用中猜的,如果错了,别怪我~~~。
- 利用
MulitDex
打出多个dex文件。
- 主dex中没有逻辑,只是
Tinker
的所有逻辑,不能更新。 - 其余dex文件保存我们编写的逻辑,主要用于更新方便。
- 保存上次打包的记录,便于打补丁包。
- 根据上一次的记录,一一对比,找出区别之后整合,加签,打包,生成补丁文件。
- 将补丁文件放到对应位置,验签,加载到内存。(QQ控件热修复原理加载到内存)
具体原理可以看我之前的热修复文章或者百度一下,本篇只说集成。
Gradle 配置
工程的根目录的gradle.properties
文件中添加Tinker
的版本号
org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8TINKER_VERSION=1.7.6
工程根目录下的build.gradle
中添加Tinker
的编译插件
dependencies { classpath 'com.tencent.tinker:tinker-patch-gradle-plugin:1.7.6' classpath 'com.android.tools.build:gradle:2.1.0' }
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" multiDexEnabled true buildConfigField "String", "MESSAGE", "\"I am the base apk\"" buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\"" buildConfigField "String", "PLATFORM", "\"all\"" } 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' compile "com.android.support:multidex:1.0.1" compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true } 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
- 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
最后,添加如下配置
def gitSha() { try { 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'") }}def bakPath = file("${buildDir}/bakApk/")ext { tinkerEnabled = true tinkerOldApkPath = "${bakPath}/app-release-16-13.apk" tinkerApplyMappingPath = "${bakPath}/app-release-16-13-mapping.txt" 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 = [ "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") } /** * 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("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 { 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
- 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上下载。
首先看一下整个类的结构
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; } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public void onBaseContextAttached(Context base) { super.onBaseContextAttached(base); MultiDex.install(base); TinkerManager.setTinkerApplicationLike(this); TinkerManager.initFastCrashProtect(); TinkerManager.setUpgradeRetryEnable(true); TinkerInstaller.setLogIml(new MyLogImp()); 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
- 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()
方法和Applciation
的onCreate()
类似,在这里做一些初始化操作,例如友盟的初始化等等,不在过多的讲解。
其中一个区别便是上下文对象不能够在使用该对象,而是使用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; } LoadReporter loadReporter = new SampleLoadReporter(appLike.getApplication()); PatchReporter patchReporter = new SamplePatchReporter(appLike.getApplication()); 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
- 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
- 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 { tinkerEnabled = true tinkerOldApkPath = "${bakPath}/app-release-27-43.apk" tinkerApplyMappingPath = "${bakPath}/app-release-27-43-mapping.txt" tinkerApplyResourcePath = "${bakPath}/app-release-27-43-R.txt" tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"}
注意,其中红色框处圈出的就是最终的补丁包,将他改成mdtinker.apk
导入到手机对应存储。
- 之后打开app会提示补丁加载成功,后台杀死进程再次打开,补丁就成功加载了。
补丁第一次加载之后,会删除补丁包,之后每次加载,都是已经修改过后,打过补丁的apk。
附加