基于微信Tinker的热更新详细说明

来源:互联网 发布:生日视频制作软件 编辑:程序博客网 时间:2024/06/06 09:33

  先来吐槽一下,这个更新方法简直6的没话说,经我详细的测试,可以更新类及新增类,删除类什么的,以及对XML资源文件的更新,好像还能更新library,但是我还没测试过,总之可以可以很强势,但是在集成的过程中也很多坑,集了我一天多,报错太多了,网上资料还不怎么详细,找了好久,然后各种方法集入,终于OK了,相对 Andfix(只能对方法进行更新)和Nuwa(只对类更新不能新增类)及HotFix(这个我已经无力吐槽,一旦混淆就报未知的错,混淆文件我都有配置的还出错,我也很绝望就果断放弃了)来说就复杂了一点,在这期间遇到了非常多的bug,然后各种资料API的查阅,终于给搞定了!

  注:演示的话我就不弄了,视频肯定会很大,弄成图片也传不上来,(到时我直接放DEMO)这里先说一个小提醒,在执行更新成功后,会自己杀死进程,然后我们要自己手动打开才能看到效果,到时发布给客户用了,建议大家弄个框提示一下,不然以为发生了啥,怎么闪退了。。  

先来说一个官方集入指南点:

1、点击打开Tinker官方SDK文档
2、点击打开SDK接入指南
3、点击下载官方提供的DEMO
 现在热修复已经很热门了,比较著名的有阿里巴巴的AndFix、Dexposed,腾讯QQ空间的超级补丁和微信最近开源的Tinker。
  Tinker是一个android的热修复库,在不重新安装apk的情况就可以更新dex,library和resource。Tinker区别于AndFix和QQ空间超级补丁采用了更好的dexdiff算法。想要了解详细介绍参考下面微信负责人张绍文的博客链接。
参考博客:
微信 Tinker 负责人张绍文关于 Android 热修复直播分享记录
 想要快速学习Tinker的使用,可以只查看Tinker GitHub和配置参考博客。这里我也会具体写一下配置步骤还有自己遇到的问题。Tinker在github上的接入指南(wiki)看起来确实有点难的啊,搞了半天都没搞明白为什么有两个Application,有明白了的给留个言啊。先不管这个问题了,说下具体配置。
 

 配置build.gradle

  参考官方的build.gradle配置自己的build.gradle,顺序可以不按照官方的https://github.com/Tencent/tinker/blob/master/tinker-sample-android/app/build.gradle,注意compileSdkVersion跟v7最好都不要使用24的
这里添加javaVersion最好不要改成VERSION_1_8,改成8可能需要添加其他的支持。sigingConfig里面的debug的配置可以注释掉,否则会报关于debug找不到的错。
这里写图片描述

  下面这个是签名和混淆的配置,这里比较重要,到时候配置Tinker中会有一个变量 useSign = true如果为ture就必须要有签名否则打不了release包,我这用了OkHttp,所以混淆文件也要注意一下,要放上去,不然会报非常多稀奇古怪的错
这里写图片描述

  然后添加依赖,图例中显示的版本号我放置在了gradle.properties文件内
app Build
这里写图片描述

project Build
这里写图片描述

gradle.properties
这里写图片描述

  根据官方提供的DEMO来进行下面图片中的配置,复制进去即可,然后在将里面的Application包名换成自己的即可,具体可以参考DEMO,比较多代码就不放截图了,我将比较重要的放上来,下面代码放在build.gradle(APP)的dependencies 下面就直接把剩下的配置原样拷贝过来就可以了。

  先说一件事,如果加入下面这段代码,拷贝过来后,一但 Sync Now会立马报错,需要添加git仓库来获取获取git的提交版本号,不然会报错提示 tinkerId is not set
这里写图片描述

解决方法:

1、其实可以简单粗暴的方式解决,那就是在app/build.gradle中更改以下代码:

tinkerId = getTinkerIdValue()//更改为tinkerId = "TinkerSample"//或者其他你想要的id

2、配置git跟github并上传一次代码,解决tinkerId is not set问题。

去官网下载git,并安装,给AndroidStudio设置git,点击test
这里写图片描述

配置github账号,点击test
这里写图片描述

为project设置git
这里写图片描述

上传一次项目到github就不会出tinkerId is not set问题了。
这里写图片描述
此时进行sync Now或者clean project

实现自己的Application类呢,主要还是参考Tinker推荐的方式,通过注解生成Application类。

BaseApplication(manifest中添加这个application)

package czb.com.tinker.application;import com.tencent.tinker.loader.app.TinkerApplication;import com.tencent.tinker.loader.shareutil.ShareConstants;/** * Created by AnmiLin on 2017/9/7. */public class BaseApplication extends TinkerApplication {    private  static final String TAG=BaseApplication.class.getSimpleName();    public  BaseApplication(){        super(                //tinkerFlags, which types is supported                //dex only, library only, all support                ShareConstants.TINKER_ENABLE_ALL,                // This is passed as a string so the shell application does not                // have a binary dependency on your ApplicationLifeCycle class.                "czb.com.tinker.application.BaseApplicationLike");    }}

BaseApplicationLike

package czb.com.tinker.application;import android.annotation.TargetApi;import android.app.Application;import android.content.Context;import android.content.Intent;import android.content.res.AssetManager;import android.content.res.Resources;import android.os.Build;import android.support.multidex.MultiDex;import com.tencent.tinker.lib.tinker.TinkerInstaller;import com.tencent.tinker.loader.app.DefaultApplicationLike;/** * Created by AnmiLin on 2017/9/7. */public class BaseApplicationLike extends DefaultApplicationLike {    public BaseApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);    }    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)    @Override    public void onBaseContextAttached(Context base) {        super.onBaseContextAttached(base);        MultiDex.install(base);        TinkerInstaller.install(this);    }    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)    public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {        getApplication().registerActivityLifecycleCallbacks(callback);    }}

然后在manifest中添加这个BaseApplication及加入对于sd卡的读写及网络权限
这里写图片描述

MainActivity

package czb.com.tinker;import android.content.Intent;import android.os.Environment;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.util.Log;import android.view.View;import android.widget.Toast;import com.tencent.tinker.lib.tinker.TinkerInstaller;import java.io.File;import java.io.IOException;import czb.com.tinker.util.BugClass2;import czb.com.tinker.util.HttpDownload;import czb.com.tinker.util.LoadBugClass;public class MainActivity extends AppCompatActivity {    private static final String TAG = MainActivity.class.getSimpleName();    private String fileState = null;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        Log.v(TAG, "onCreate");        Log.e(TAG, "i am on patch log");        Toast.makeText(MainActivity.this, new BugClass2().getStr()+"-"+new LoadBugClass().getStr(), Toast.LENGTH_SHORT).show();        findViewById(R.id.btn_load_patch).setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View view) {                //准备补丁,从assert里拷贝到dex里                new Thread(new Runnable() {                    @Override                    public void run() {                        String url = "http://xxx.com/patch_signed_7zip.apk";                        final HttpDownload httpDownload = new HttpDownload();                        try {                            String path = Environment.getExternalStorageDirectory() + File.separator + "TinkerHotFix";                            int flag = httpDownload.downToFile(url, path);                            if (flag == 1) {                                fileState = "下载完成";                                final String pathUrl = path + "/" + HttpDownload.getFileName(url);                                runOnUiThread(new Runnable() {                                    @Override                                    public void run() {                                        try{                                            TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), pathUrl);                                        }catch (Exception e){                                            Log.e(TAG,e.getMessage());                                        }                                        Log.e(TAG, "安装完成");                                        Toast.makeText(getApplicationContext(), "安装完成", Toast.LENGTH_LONG).show();                                    }                                });                            } else if (flag == -1) {                                fileState = "下载错误";                            }                            runOnUiThread(new Runnable() {                                @Override                                public void run() {                                    Toast.makeText(MainActivity.this, fileState, Toast.LENGTH_SHORT).show();                                }                            });                        } catch (IOException e) {                            e.printStackTrace();                        }                    }                }).start();            }        });    }    public void onRestart(View view) {        //重启应用        Intent i = getPackageManager()                .getLaunchIntentForPackage(getPackageName());        i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);        startActivity(i);        finish();        android.os.Process.killProcess(android.os.Process.myPid());    }}

运行代码并在终端运行命令行,编译apk

  代码编写完成后,运行自己的代码,如果是release的,则会自动在app/build/bakApk目录下会有生成好的apk文件一份以及对应的资源文件txt文件一份比如app-release-0907-15-19-19.apk和app-release-0907-15-19-19-R.txt及app-release-0907-15-19-19-mapping.txt,如果是debug的,则会生成app-debug-0907-15-19-19.apk和app-debug-0907-15-19-19-R.txtdebug 此时app应该已经部署到手机或者模拟器上。
 
在As的terminal终端使用命令行生成基础APP信息文件
1、如果是debug就执行下面命令
gradlew assemblehDebug
2、如果是release则执行
gradlew assembleRelease

  如果以前没有使用过命令行,可能会下载一些东西,不知道是不是我家里网络的原因,我开的翻墙才下载成功的。如果以前使用过命令就可以直接编译了,编译完成之后可以在你的目录下面看到新生成的apk。
截图示例:
这里写图片描述

 注:这是旧的APP发布后生成的文件信息,不是新的APP生成的,在生成成功后,将目录中的文件名对应下面配置提取出来,放置所需代码块中,在不安装新APP的情况下,这个要备份出来,以后叠加修复需要

//基准APP信息文件路径(用于存基准的应用(这个应用一定要保留下来,后期修复都需要这个里的文件))def bakPath = file("${buildDir}/bakApk/")ext {    //debug build?是否打开tinker的功能    tinkerEnabled = true    //for normal build    //打开上面bakApk文件夹,在提取出旧的APK文件名    tinkerOldApkPath = "${bakPath}/app-release-0907-15-19-19.apk"    //proguard mapping file to build patch apk   //打开上面bakApk文件夹,在提取出旧的mapping文件名,如果是debug就没有mapping文件,则路径可以这么写:  // tinkerApplyMappingPath = "${bakPath}/"  //relase下就要同上方法提取出来    tinkerApplyMappingPath = "${bakPath}/app-release-0907-15-19-19-mapping.txt"    //resource R.txt to build patch apk, must input if there is resource changed    //如果更新了资源文件就需要把生成好的放进来    tinkerApplyResourcePath = "${bakPath}/app-release-0907-15-19-19-R.txt"    //only use for build all flavor, if not, just ignore this field    //tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"}

配置好后然后在命令行中执行
1、如果是debug就执行下面命令
gradlew tinkerPatchDebug
2、如果是release则执行
gradlew tinkerPatchRelease

这里写图片描述

编译完成之后可以在你的目录下面看到新生成的(打补丁的)apk。
这里写图片描述

将生成的patch_signed_7zip.apk下载放到代码中编写的路径下面,运行app点击更新按钮就可以了。
注:此时如果加载补丁成功,应用会闪退,Android Studio 日志控制台会打印出如下日志输出:
这里写图片描述
再次运行app,可以发现之前打开的注释代码已经被差异化修复到app中,并且打开的日志已经输出到控制台,

注意:

如果apk运行之后点击按钮没有反应不能进行热修复的话注意检查自己的apk的版本号跟BaseApk是否一致(也就是你build.gradle中修改的版本号一致) configField(“patchVersion”, “1.1”)这个第一次更新都需要改变

混淆文件我就不放上来了,大家看代码复制就行

以上就是我的分享,希望能对大家有帮助

代码地址:StudyTinker