Android 热修复 关于接入Tinker所遇到的错误

1 篇文章 0 订阅
1 篇文章 0 订阅

Android 热修复 关于接入Tinker所遇到的错误

当项目发展到一定程度,有了不少当用户后。当线上app出现bug都会影响好多人的使用。bug不影响主流程还好。当bug出现在主流程怎么办?? 加急发版本么??远水不救近火啊!当当当当~~这时候就需要热修复了。在用户不知情的情况下把bug给修复了
紧急需要热修复功能的时候,原理啥的都不好使。只有解决遇到的问题才硬道理。所以在这里不讲原理啥的,只说遇到的问题,最直接的形成战斗力。
下面进入正题,tinker接入。tinker github链接
一 在项目的根目录build.gradle中,添加tinker-patch-gradle-plugin的依赖
buildscript {
dependencies {
classpath (‘com.tencent.tinker:tinker-patch-gradle-plugin:1.7.11’)
}
}
二 然后在app的gradle文件app/build.gradle中添加依赖
dependencies {
//可选,用于生成application类
provided(‘com.tencent.tinker:tinker-android-anno:1.7.11’)
//tinker的核心库
compile(‘com.tencent.tinker:tinker-android-lib:1.7.11’)
}


//apply tinker插件
apply plugin: ‘com.tencent.tinker.patch’

tinker的初步接入完成了。 就是这么简单!
tinker和android studio的Instant Run 不兼容。 所以啦!当你项目接入tinker热修复时,一定要把要把 instant run 给关掉。
否则就回报这个错误
错误一:
Error:A problem occurred configuring project ‘:app’.

Tinker does not support instant run mode, please trigger build by assembleDebug or disable instant run in ‘File->Settings…’.
根据错误提示

在 设置里面找到Instant Run 取消箭头所指向的选中项。点击apply 就行了

关于项目在gradle里面的配置就不说了 看官方demo。里面注释,解说很详细了。在此只说配置过程中会遇到的困难,出的错误

新手初次配置基本都会遇到这个问题
错误二:
Error:Execution failed for task ‘:app:tinkerProcessDebugManifest’.

tinkerId is not set!!!

TinkerId 在这配置
这里写图片描述

我是用app的版本号作为tinkerid的 可以自己配置。
tinkerId的作用的官方解释:
在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId。这个是决定补丁包能运行在哪些基准包上面,一般来说我们可以使用git版本号、versionName
接下来在gradle里面配置就好说了 这是我配置的基本照着demo copy过来就行
def bakPath = file(“${buildDir}/bakApk/”)
//if(!bakPath.isFile()){
// bakPath.mkdir()
// println(bakPath.absolutePath+”“)
//}

ext {
tinkerEnabled = true

//for normal build
//old apk file to build patch apk
tinkerOldApkPath = "${bakPath}/app-release-0612-10-50-24.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-1018-17-32-47-R.txt"

//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"

}

def gitSha() {
return ‘git rev-parse –short HEAD’.execute().text.trim()
}

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 getTinkerBuildFlavorDirectory() {
return ext.tinkerBuildFlavorDirectory
}

def buildWithTinker() {
return hasProperty(“TINKER_ENABLE”) ? TINKER_ENABLE : ext.tinkerEnabled
}

if (buildWithTinker()) {

apply plugin: 'com.tencent.tinker.patch'

tinkerPatch {

    oldApk = getOldApkPath()

    ignoreWarning = true

    useSign = true

    buildConfig {
        /*保持使用old apk的proguard混淆方式,从而减小patch的大小 会影响assemble编译*/
        applyMapping = getApplyMappingPath()
        /*使用old apk的R.txt文件保持ResId,减小patch大小 避免remote view 异常*/
        applyResourceMapping = getApplyResourceMappingPath()

        /*验证基准apktinkerid 是否等于patch的tinkerid*/
        tinkerId = android.defaultConfig.versionName

    }


    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 = ["com.tencent.tinker.loader.*",
                  //warning, you must change it with your application
                "xxxxxxxxxxxxxx.SampleApplication"]

// loader=[]
}

    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/armeabi/*.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", "*.png"]

        /**
         * 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")


        configField("patchVersion", buildConfig.tinkerId)
    }

    sevenZip {
        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")

    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)//dependsOntinkerTask//defpreAssembleTask=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)//dependsOntinkerTask//defpreAssembleTask=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”
// }
//
// }
// }
// }
// }
}
要注意。xxxxxxxxxxxxxx.SampleApplication 路径是代理application,并且manifests里面的application指向的也是xxxxxxxxxxxxxx.SampleApplication 这个代理application。application改造接下来会说到。
配置上的东西基本就这样。接下来就是重头戏改造application了。这里写图片描述

注解中的application 也是代理的。Baseapplication才是平时使用的application
在改造过程中报错
错误三:
java.lang.RuntimeException: Unable to instantiate application com.shuai.BaseApplication: java.lang.InstantiationException: class com.shuai.BaseApplication has no zero argument constructor

虽然报错为没有无参数的构造函数,这个问题也坑了我好久。
其实在manifests application节点下的 name属性
应该指向代理application。SampleApplcation,绝不对BaseApplication。大家一定要留意这点。
错误四:
catch exception when loading tinker:com.tencent.tinker.loader.TinkerRuntimeException: Tinker Exception:you must install tinker before get tinker sInstance
at com.tencent.tinker.lib.tinker.Tinker.with(Tinker.java:103)
at com.tencent.tinker.lib.tinker.TinkerInstaller.onReceiveUpgradePatch(TinkerInstaller.java:91)
报这个错误是因为在Baseapplication中的onBaseContextAttached 没有初始化tinker的一些东西
如图
这里写图片描述

加上这个就好啦!
跳过这几个坑后,基本没啥问题了。大家就可以愉快的玩耍热修复了。再也不怕boss拿着app指着鼻子责问了
目前只遇到过这几个问题,以后再有问题继续追加。
各位遇到什么问题可以在评论留言!我会尽快帮大家处理。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值