• android 热修复框架Tinker的详细简单使用


    这里写图片描述

    Tinker 腾讯开源热修复框架,https://github.com/Tencent/tinker 
    已知的的问题 
    Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件(1.9.0支持新增非export的Activity); 
    由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码; 
    在Android N上,补丁对应用启动时间有轻微的影响; 
    不支持部分三星android-21机型,加载补丁时会主动抛出”TinkerRuntimeException:checkDexInstall failed”; 
    对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。

    Tinker的详细介绍见文档:https://github.com/Tencent/tinker/wiki

    本文章只介绍简单的使用,集成及生成补丁文件(方式是gradle方式,非命令行) 
    步骤如下: 
    1: android studio新建一个项目 
    2:因为tinker是热修复的,也就是基于原有app,所以进行签名打包,用于配置build.gradle 
    3:在项目的build.gradle中,TINKER_VERSION是为了方便统一管理,需要在gradle.properties中声明(此次版本为1.9.1)

    classpath "com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}"
    • 1

    4:app的build.gradle基本复制官方demo的gradle文件,因为本次用的是android studio3.0,gradle的插件用到了3.0.0 
    接下来贴出build.gradle的内容

    apply plugin: 'com.android.application'
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        implementation 'com.android.support:appcompat-v7:26.1.0'
        implementation 'com.android.support.constraint:constraint-layout:1.0.2'
        testImplementation 'junit:junit:4.12'
        androidTestImplementation 'com.android.support.test:runner:1.0.1'
        androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
        //gradle 3.0.0的一定要使用如下的依赖
        implementation("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
        annotationProcessor("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
        compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
        //支持multidex
        implementation "com.android.support:multidex:1.0.1"
    }
    //获取提交git的版本号,作为tinkerid
    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'")
        }
    }
    //java 1.7
    def javaVersion = JavaVersion.VERSION_1_7
    
    android {
        compileSdkVersion 26
        buildToolsVersion '26.0.2'
    
        compileOptions {
            sourceCompatibility javaVersion
            targetCompatibility javaVersion
        }
        //recommend
        dexOptions {
            jumboMode = true
        }
    
        signingConfigs {
            //签名配置,
            release {
                try {
                    storeFile file("E:/xxx/release.jks")
                    storePassword "123456"
                    keyAlias "mvp"
                    keyPassword "123456"
                } catch (ex) {
                    throw new InvalidUserDataException(ex.toString())
                }
            }
            //debug版本使用同一个jks
            debug {
                storeFile file("E:/xxx/release.jks")
                storePassword "123456"
                keyAlias "mvp"
                keyPassword "123456"
            }
        }
    
        defaultConfig {
            applicationId "tinker.sample.android"
            minSdkVersion 14
            targetSdkVersion 22
            versionCode 1
            versionName "1.0.0"
            /**
             * you can use multiDex and install it in your ApplicationLifeCycle implement
             */
            multiDexEnabled true
            /**
             * buildConfig can change during patch!
             * we can use the newly value when patch
             */
            buildConfigField "String", "MESSAGE", ""I am the base apk""
    //        buildConfigField "String", "MESSAGE", ""I am the patch 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""
        }
    
    //    aaptOptions{
    //        cruncherEnabled false
    //    }
    
    //    //use to test flavors support
    //    productFlavors {
    //        flavor1 {
    //            applicationId 'tinker.sample.android.flavor1'
    //        }
    //
    //        flavor2 {
    //            applicationId 'tinker.sample.android.flavor2'
    //        }
    //    }
    
        buildTypes {
            release {
                minifyEnabled true
                signingConfig signingConfigs.release
                proguardFiles getDefaultProguardFile('proguard-android.txt'), project.file('proguard-rules.pro')
            }
            debug {
                debuggable true
                minifyEnabled false
                signingConfig signingConfigs.debug
            }
        }
        sourceSets {
            main {
                jniLibs.srcDirs = ['libs']
            }
        }
    }
    //
    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
    
        //for normal build
        //old apk file to build patch apk
        tinkerOldApkPath = "${bakPath}/app-debug-0201-18-36-11.apk"
        //proguard mapping file to build patch apk
        tinkerApplyMappingPath = "${bakPath}/app-debug-1018-17-32-47-mapping.txt"
        //resource R.txt to build patch apk, must input if there is resource changed
        tinkerApplyResourcePath = "${bakPath}/app-debug-0201-18-36-11-R.txt"
    
        //only use for build all flavor, if not, just ignore this field
        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
    
            /**
             * 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
    
                /**
                 * optional, default 'false'
                 * Whether tinker should support component hotplug (add new component dynamically).
                 * If this attribute is true, the component added in new apk will be available after
                 * patch is successfully loaded. Otherwise an error would be announced when generating patch
                 * on compile-time.
                 *
                 * <b>Notice that currently this feature is incubating and only support NON-EXPORTED Activity</b>
                 */
                supportHotplugComponent = 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
        def 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.name
    
            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.first().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
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422
    • 423
    • 424
    • 425
    • 426
    • 427
    • 428
    • 429
    • 430
    • 431
    • 432
    • 433
    • 434
    • 435
    • 436
    • 437
    • 438
    • 439
    • 440
    • 441
    • 442
    • 443
    • 444
    • 445
    • 446
    • 447
    • 448
    • 449
    • 450
    • 451
    • 452
    • 453
    • 454
    • 455
    • 456
    • 457
    • 458
    • 459
    • 460
    • 461
    • 462
    • 463
    • 464
    • 465

    再贴出gradle.properties文件

    org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=1024m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
    
    # When configured, Gradle will run in incubating parallel mode.
    # This option should only be used with decoupled projects. More details, visit
    # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
    # org.gradle.parallel=true
    TINKER_VERSION = 1.9.1
    TINKER_ID = 100//id随意写
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    5:接下来就是application了,

    @SuppressWarnings("unused")
    @DefaultLifeCycle(application = "com.greatxiaohaihai.tinkerdemo.app.SampleApplication",
                      flags = ShareConstants.TINKER_ENABLE_ALL,
                      loadVerifyFlag = false)
    public class SampleApplicationLike extends DefaultApplicationLike {
        private static final String TAG = "Tinker.SampleApplicationLike";
    
        public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
                                     long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
            super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
        }
    
        /**
         * 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)
        @Override
        public void onBaseContextAttached(Context base) {
            super.onBaseContextAttached(base);
            //you must install multiDex whatever tinker is installed!
            MultiDex.install(base);
    
    
            TinkerInstaller.install(this);
    
        }
    
        @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
        public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
            getApplication().registerActivityLifecycleCallbacks(callback);
        }
    
    }
    
    • 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

    Manifest文件:如果写android:name=”.app.SampleApplication”报红,build一下,

    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
        package="com.greatxiaohaihai.tinkerdemo">
        <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"></uses-permission>
        <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
        <application
            android:name=".app.SampleApplication"
            android:allowBackup="true"
            android:icon="@mipmap/ic_launcher"
            android:label="@string/app_name"
            android:roundIcon="@mipmap/ic_launcher_round"
            android:supportsRtl="true"
            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>
    
    </manifest>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    6 : 以上完成后,运行程序就会在build-bakapk下生成apk包,R文件,如下:

    这里写图片描述

    运行一次就会生成两个文件(apk,R),由于我们是debug,所以只有两个文件,没有mapping文件,回头去看build.gradle中的代码

    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-debug-0201-18-36-11.apk"
        //proguard mapping file to build patch apk
        tinkerApplyMappingPath = "${bakPath}/app-debug-1018-17-32-47-mapping.txt"
        //resource R.txt to build patch apk, must input if there is resource changed
        tinkerApplyResourcePath = "${bakPath}/app-debug-0201-18-36-11-R.txt"
    
        //only use for build all flavor, if not, just ignore this field
        tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    我们把基类包apk替换成我们刚才在bakapk文件夹下的apk文件名,R文件也要改,如果以后发行版本mapping文件也要修改,这就是定一个基准包,以后代码有改动,重新生成的apk就会计算出一个差别补丁包,我们最终要把这个差别补丁给加载到原apk中,重新生成新的包,达成bug的修改,(当然如果是线上,我们只要把这个差别补丁上传到平台,平台会自动下发,用户在重新加载程序就会检测到,当然是进程的重新启动以后才可以,在后台不可以加载到) 
    7:修改完ext{里面的文件名后,修改代码,代码改完后,运行tinker的tinkerPatchDebug,在哪里呢,如下图: 
    这里写图片描述

    运行完毕后,如果成功的话,如下图:

    这里写图片描述

    这个7zip.apk就是补丁包

    如果你想本地测试的话

    一句代码,

     TinkerInstaller.onReceiveUpgradePatch(MainActivity.this, Environment.getExternalStorageDirectory().getAbsolutePath()+"/patch_signed_7zip.apk");
    • 1

    前提你要把这个文件导入到SD卡的根目录

    完,

  • 相关阅读:
    1.7 All components require plug-in?
    1.6 Why only in China?
    1.5 A better alternative thing: React Native
    1.4 The usage of plug-in
    1.3 History of Android Plug-in Programing
    SQL Server 查询请求
    matplotlib 绘图的核心原理
    数据加密 第六篇:透明文件加密
    数据加密 第五篇:非对称密钥
    SSIS 数据类型 第二篇:变量的数据类型
  • 原文地址:https://www.cnblogs.com/kekexuanxaun/p/9480315.html
Copyright © 2020-2023  润新知