• 热修复框架Tinker的从0到集成之路(转)


    转自: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();
  • 相关阅读:
    现在, Delphi 的多线程已经非常易用了!
    发现 TSplitter 在嵌套时不好用, 索性写了个替代品
    关于显示透空歌词的思路 回复 "zhaoboaidelphi" 的问题
    简单获取钢琴 88 个键的音高频率值
    准备理一下菜单和工具栏相关的组件
    在 StringGrid 上画线时, 使用 GDI+ 以消除锯齿 回复 "gsjn_8888_6666" 的问题
    解压 svgz 到 svg
    jQuery能做到,PHP能做到,C#也能做到
    监测ASP.NET应用程序性能最简单的方法
    支持高并发的IIS Web服务器常用设置
  • 原文地址:https://www.cnblogs.com/weizhxa/p/7735218.html
Copyright © 2020-2023  润新知