Tinker热更新(微信)详细教程

Tinker热更新(微信)详细教程

现在热更新有很多的选择,但是有很多都有限制,例如不能更新资源文件,不支持android7.0等,经过多种尝试,发现tinker是支持面最广的热更新插件了。
Tinker官方GitHub

Tinker和其他常见热更新特点对比

Tinker 执行原理分析

个人观点,不知道是否正确
在原项目中集成tinker的plugin,这时会生成old.apk oldresource-R.txt文件及old-mapping.txt(如果开启混淆才会有)
项目bug修复后,配置oldapk,resource,mapping文件,会对比生成一个patch文件,这个文件后缀名默认是apk,但不是一个可以安装的apk,可以根据自己的情况任意修改后缀名,把这个patch文件放到服务端,如果app需要修复bug则下载这个patch,然后执行后就会成修复后的app了

Tinker集成过程

  • 1.项目project gradle引入

  dependencies {
        classpath 'com.android.tools.build:gradle:2.0.0'
        //引入tinker plugin 版本可以参考tinker官方github
        classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.7')       
    }
  • 2.Moudle app gradle引入library
dependencies {
    compile 'com.android.support:appcompat-v7:23.2.1'
    compile 'com.android.support:support-v4:23+'
    debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5'
    releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5'
    //Tinker需要的library开始
    //可选,用于生成application类
    provided('com.tencent.tinker:tinker-android-anno:1.7.7')
    //tinker的核心库
    compile('com.tencent.tinker:tinker-android-lib:1.7.7')
    compile "com.android.support:multidex:1.0.1"
    //Tinker需要的library结束
}
  • 3.设置app的build.gradle

a,如果设置了dex分包则在android节点下的defaultConfig设置

  multiDexEnabled true

b,在app的build.gradle的末尾设置tinker的配置

以下内容很重要,请仔细看,或者直接复制进去后自己根据情况修改

/**Tinker 相关配置----------------开始-----------------------------------*/
//apply tinker插件
apply plugin: 'com.tencent.tinker.patch'
def bakPath = file("${buildDir}/bakApk/")
//这个函数返回的是tinkerid
//tinkerid是用来做代码更改对比的,
//例如 
//线上的是1.0版本
//则这里配置的oldapk 路径就是线上的apk在本地的路径
//如果要在这个版本上更新,则这里返回的string应该与线上版本设置的一模一样
def gitSha() {
   return "1.0"
}
ext {
    //for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    //变量,控制Tinker是否可用
    tinkerEnabled = true
    //for normal build
    //old apk file to build patch apk 旧包名
    //这里对应的路径就是app moudle下的oldApk路径(没有则自己新建),并且把需要修复版本的apk放进去
    tinkerOldApkPath = "${projectDir}/oldApk/old.apk"
    //proguard mapping file to build patch apk  旧包混淆文件目录
    //tinkerApplyMappingPath = "${bakPath}/app-debug-1018-17-32-47-mapping.txt"
    //混淆的mapping 有则配置路径,我这里没有,所以随便写
    tinkerApplyMappingPath = "${bakPath}/"
    //resource R.txt to build patch apk, must input if there is resource changed 旧包R文件
    //跟待修复版本apk一样,把老版本的resoures-r.txt放进来
    //这个是为了避免新旧版本R不一致,导致找不到资源闪退
    tinkerApplyResourcePath = "${projectDir}/oldApk/old-R.txt"
    //only use for build all flavor, if not, just ignore this field  多渠道
    //tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
def getOldApkPath() {
    return  ext.tinkerOldApkPath
}
def getApplyMappingPath() {
    return  ext.tinkerApplyMappingPath
}
def getApplyResourceMappingPath() {
    return  ext.tinkerApplyResourcePath
}
def getTinkerIdValue() {
    return  gitSha()
}
def buildWithTinker() {
    return  ext.tinkerEnabled
}
def getTinkerBuildFlavorDirectory() {
    return ext.tinkerBuildFlavorDirectory
}
if (buildWithTinker()) {
    apply plugin: 'com.tencent.tinker.patch'
    tinkerPatch {
        /**
         * 基准apk包路径,也就是旧包路径
         * */
        oldApk = getOldApkPath()
        /**
         * 如果出现以下的情况,并且ignoreWarning为false,我们将中断编译。因为这些情况可能会导致编译出来的patch包
         * 带来风险:
         * 1. minSdkVersion小于14,但是dexMode的值为"raw";
         * 2. 新编译的安装包出现新增的四大组件(Activity, BroadcastReceiver...);
         * 3. 定义在dex.loader用于加载补丁的类不在main dex中;
         * 4. 定义在dex.loader用于加载补丁的类出现修改;
         * 5. resources.arsc改变,但没有使用applyResourceMapping编译。
         * */
        ignoreWarning = false
        /**
         * 在运行过程中,我们需要验证基准apk包与补丁包的签名是否一致,我们是否需要为你签名
         * */
        useSign = true

        buildConfig {
            /**
             * 可选参数;在编译新的apk时候,我们希望通过保持旧apk的proguard混淆方式,从而减少补丁包的大小。这个只
             * 是推荐的,但设置applyMapping会影响任何的assemble编译。
             * */
            applyMapping = getApplyMappingPath()
            /**
             * 可选参数;在编译新的apk时候,我们希望通过旧apk的R.txt文件保持ResId的分配,这样不仅可以减少补丁包的
             * 大小,同时也避免由于ResId改变导致remote view异常。
             * */
            applyResourceMapping = getApplyResourceMappingPath()
            /**
             * 在运行过程中,我们需要验证基准apk包的tinkerId是否等于补丁包的tinkerId。这个是决定补丁包能运行在哪些
             * 基准包上面,一般来说我们可以使用git版本号、versionName等等。
             * */
            tinkerId = getTinkerIdValue()
        }
        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
                    "com.ubestkid.BlApplication",
                    "com.ubestkid.foundation.base.BaseApplication"
            ]
        }
        lib {
            /**
             * 需要处理lib路径,支持*、?通配符,必须使用'/'分割。与dex.pattern一致, 路径是相对安装包的,例如/assets/...
             */
            pattern = ["lib/armeabi/*.so"]
        }
        res {
            /**
             * 需要处理res路径,支持*、?通配符,必须使用'/'分割。与dex.pattern一致, 路径是相对安装包的,例如/assets/...,
             * 务必注意的是,只有满足pattern的资源才会放到合成后的资源包。
             */
            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
            /**
             * 支持*、?通配符,必须使用'/'分割。若满足ignoreChange的pattern,在编译时会忽略该文件的新增、删除与修改。
             * 最极端的情况,ignoreChange与上面的pattern一致,即会完全忽略所有资源的修改。
             */
            ignoreChange = ["assets/sample_meta.txt"]
            /**
             * 对于修改的资源,如果大于largeModSize,我们将使用bsdiff算法。这可以降低补丁包的大小,但是会增加合成
             * 时的复杂度。默认大小为100kb
             */
            largeModSize = 100
        }
        packageConfig {
            /**
             * configField("key", "value"), 默认我们自动从基准安装包与新安装包的Manifest中读取tinkerId,并自动
             * 写入configField。在这里,你可以定义其他的信息,在运行时可以通过TinkerLoadResult.getPackageConfigByName得到相应的数值。但是建议直接通过修改代码来实现,例如BuildConfig。
             */
            configField("patchMessage", "tinker is sample to use")
        }
        sevenZip {
            /**
             * 例如"com.tencent.mm:SevenZip:1.1.10",将自动根据机器属性获得对应的7za运行文件,推荐使用
             */
            zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
        }
        /**
         *  文件名                              描述
         *  patch_unsigned.apk                  没有签名的补丁包
         *  patch_signed.apk                  签名后的补丁包
         *  patch_signed_7zip.apk              签名后并使用7zip压缩的补丁包,也是我们通常使用的补丁包。但正式发布的时候,最好不要以.apk结尾,防止被运营商挟持。
         *  log.txt                              在编译补丁包过程的控制台日志
         *  dex_log.txt                          在编译补丁包过程关于dex的日志
         *  so_log.txt                          在编译补丁包过程关于lib的日志
         *  tinker_result                      最终在补丁包的内容,包括diff的dex、lib以及assets下面的meta文件
         *  resources_out.zip                  最终在手机上合成的全量资源apk,你可以在这里查看是否有文件遗漏
         *  resources_out_7z.zip              根据7zip最终在手机上合成的全量资源apk
         *  tempPatchedDexes                  在Dalvik与Art平台,最终在手机上合成的完整Dex,我们可以在这里查看dex合成的产物。
         *
         *
         * */

        /**
         * bak apk and mapping
         *  创建Task并执行文件操作
         */
        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 = "${fileNamePrefix}-${date}"

                            def destPath = 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")
                            }
                        }
                    }
                }
            }
        }
    }
}
/**Tinker 相关配置----------------结束-----------------------------------*/

-4.设置application

新建一个类继承DefaultApplicationLike
注意,这个并不是继承原生的application,也不是application的子类
里面有提供getApplication则是真正的application
初始化的一些代码可以写在onBaseContextAttached这个方法里面

@SuppressWarnings("unused")
@DefaultLifeCycle(application = "tinker.sample.android.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);

        SampleApplicationContext.application = getApplication();
        SampleApplicationContext.context = getApplication();
        TinkerManager.setTinkerApplicationLike(this);

        TinkerManager.initFastCrashProtect();
        //should set before tinker is installed
        TinkerManager.setUpgradeRetryEnable(true);

        //optional set logIml, or you can use default debug log
        TinkerInstaller.setLogIml(new MyLogImp());

        //installTinker after load multiDex
        //or you can put com.tencent.tinker.** to main dex
        TinkerManager.installTinker(this);
        Tinker tinker = Tinker.with(getApplication());
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
        getApplication().registerActivityLifecycleCallbacks(callback);
    }

}

-5,修改AndroidManifest.xml
这里的name设置的名称应该与上一步一致

@DefaultLifeCycle(application = "tinker.sample.android.app.SampleApplication",
                  flags = ShareConstants.TINKER_ENABLE_ALL,
                  loadVerifyFlag = false)
 <application
 android:name="tinker.sample.android.app.SampleApplication"
        //other 
        />

这里可能会报红,不用管,编译后会自动生成这个application

-6.设置加载patch的代码
在程序的入口或者你想加载补丁的地方设置代码

//path根据自己下载后保存的路径填写,这里不用判断文件是否存在,因为文件不存在tinker会自动判断的

String path = ""; 

//注意这个方法名,是onReceiveUpgradePatch,TinkerInstaller有个方法名与这个非常想,别写错了
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(),path);

OK 调试,应该就好了,如果有其他问题可以评论留言,或者加我QQ 857879622 欢迎骚扰

另外,希望可以进去看看我的github,虽然很烂。。。后期会越来越好的
博主GitHub
我的个人主页

以下的文章也非常不错,可以去看看

Tinker微信热修复框架新手接入

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值