Thinker 使用详解(下)

简介: Thinker 使用详解(下)

8,创建补丁文件


创建补丁文件的时候需要线上的 apk。所以在这里 我们将刚才打包的 apk 名字复制下来,然后放在build.gradle 中的 ext 中,如下所示:

0a2653c851af460fa595bd959398a8f1.png

这里的路径就是 build 中 bakApk 中的文件,第一个是 apk,第二个是混淆路径,因为我没有使用混淆,所以在上面打包后就没有混淆文件。第三个对应资源文件。注意这里一定要填正确,否则会导致不能生成补丁文件。弄完之后同步一下。


接着就可以修复bug了。这里我们进行模拟一下。


<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">
    <androidx.appcompat.widget.AppCompatButton
        android:id="@+id/btn"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_marginTop="20dp"
        android:text="加载 PATCH 包"
        android:textSize="20sp"
        tools:ignore="HardcodedText" />
    <TextView
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:gravity="center"
        android:text="我是被修复的bug"
        android:textSize="25sp" />
</androidx.appcompat.widget.LinearLayoutCompat>


修改了一下布局,添加了一个 TextView。


接着就需要使用 tinker 插件生成补丁了。如下


2d65d23f6d4748949b924e4057485923.png


点击 tinkerPatchRelease 即可生成 补丁包,如果出现 IO 异常,可以删除 build 文件夹中除过 bakApk 文件以外的所有文件,可能是有冲突。


或者是 出现 config 的错误:则在 gradle 的 android 包下添加如下:


signingConfigs {
        config {
            storeFile file('C:\\Users\\Administrator\\Desktop\\345\\Project\\Tinker\\keystore.jks')
            storePassword '123456789'
            keyAlias = 'key0'
            keyPassword '123456789'
        }
    }


因为使用 tinker 插件打包的时候也需要 key 和 密码等。


最后生成的结果如下


4cebaac233b3433da32a72337a77fc60.png


9,加载补丁文件,修复bug


其中 patch_signed.apk 就是我们需要的补丁包。我们需要将补丁包复制到的我们程序中定义的路径中:


0a2653c851af460fa595bd959398a8f1.png


这里我用的电脑直接复制到手机中了


然后点击加载 加载 PATCH 包 接着程序会直接退出。当你再次打开的时候就会发现你添加的 TextView 已经显示出来了,最终的结果如下:


2d65d23f6d4748949b924e4057485923.png


经过上面几步,我们就已经完成了一个从本地加载的热修复


在项目中使用 Tinker


当然了,我们不可能一直从本地加载补丁文件。所以我们需要对加载 补丁文件进行修改一下。


新建一个 服务。在服务中我们会进行请求服务器是否有新的补丁,如果有补丁就下载到指定的目录中,然后进行加载补丁文件。


public class TinkerService extends Service {
    /**
     * 文件后缀名
     */
    private static final String FILE_END = ".apk";
    /**
     * 下载 patch 文件信息
     */
    private static final int DOWNLOAD_PATCH = 0x01;
    /**
     * 检查是否有 patch 更新
     */
    private static final int UPDATE_PATCH = 0x02;
    /**
     * patch 要保存的文件夹
     */
    private String mPatchFileDir;
    /**
     * patch 文件保存路径
     */
    private String mFilePath;
    /**
     * 服务器 patch 的信息
     */
    private BasePatch mBasePatchInfo;
    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UPDATE_PATCH:
                    checkPatchInfo();
                    break;
                case DOWNLOAD_PATCH:
                    downloadPatch();
                    break;
            }
        }
    };
    private void downloadPatch() {
  //下载补丁文件
  mFilePath = mPatchFileDir.concat("tinker")
                        .concat(FILE_END);
  //.....下载完成,进行加载
         TinkerManager.loadPatch(mFilePath);
         //加载完成后终止服务
         stopSelf();
    }
    @Override
    public void onCreate() {
        super.onCreate();
        init();
    }
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        //检查是否有 patch 更新
        mHandler.sendEmptyMessage(UPDATE_PATCH);
        return START_NOT_STICKY;
    }
    private void init() {
        mPatchFileDir = getExternalCacheDir().getAbsolutePath() + "/tPatch/";
        File patchFileDir = new File(mPatchFileDir);
        try {
            if (!patchFileDir.exists()) {
                //文件夹不存在则创建
                patchFileDir.mkdir();
            }
        } catch (Exception e) {
            e.printStackTrace();
            //无法创建文件,终止服务
            stopSelf();
        }
    }
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
    /**
     * 检查是否有更新
     */
    private void checkPatchInfo() {
  //.....网络请求获取是否更新补丁文件,不更新就终止服务
  //下载补丁文件
        mHandler.sendEmptyMessage(DOWNLOAD_PATCH);
    }
}


Tinker 高级用法


Tinker 如何支持多渠道打包


首先在项目中集成多渠道,我使用的是 walle,集成的方式非常简单:


添加插件


classpath 'com.meituan.android.walle:plugin:1.1.6'


在 app 的 build.gradle 中引入插件,设置依赖,进行配置


apply plugin: 'walle'
  android{
    ......
    walle {
          // 指定渠道包的输出路径
        apkOutputFolder = new File("${project.buildDir}/outputs/channels");
          // 定制渠道包的APK的文件名称
        apkFileNameFormat = '${appName}-${channel}-${buildType}-v${versionName}-${versionCode}.apk';
          // 渠道配置文件
          channelFile = new File("${project.getProjectDir()}/channel")
      }
  }
  dependencies {
    implementation 'com.meituan.android.walle:library:1.1.6'
  }


然后在 app 中新建一个channel 文件,不要指定任何后缀,里面写入渠道名称即可


2d65d23f6d4748949b924e4057485923.png


这样多渠道就配置完了。


使用多渠道后打包需要用命令行或者插件来执行,这里使用插件的方式,如下:

4cebaac233b3433da32a72337a77fc60.png


一种是 debug,一种是 release。点击 replace就会生成相应的渠道文件,如下:


6de278e6d6694ce5bb08e7e842b7e74b.png


可以看到渠道已经生成了。在bakApk 中也生成了 基准包,这个 apk 不是用来发布的,而是用来生成补丁用的。生成补丁的方式就和上面讲的一样了。


将渠道文件中的 apk 发布后,出现 bug 。使用基准包生成 补丁文件,然后放在服务器中,进行下发即可。


如果使用 android 自带的渠道,需要每个渠道apk 都要生成补丁文件,而 使用 walle 只需要一个补丁即可,而且 使用 walle 生成渠道apk 的速度非常快。


我们可以app中获取渠道的信息,并且传给 友盟统计,这样非常方便,如下:


//当前渠道
String channel = WalleChannelReader.getChannel(getApplication());
UMConfigure.init(getApplication(), "*********", channel, UMConfigure.DEVICE_TYPE_PHONE, "");


如何自定义 Tinker 行为


1,自定义TinkerResultService 改变 patch 安装成功后行为


一般情况下 patch 安装后 tinker 会杀掉进程,所以我们才会看到 patch 加载完成后程序闪退的问题,我们可以通过 重写 DefaultTinkerResultService 来自定义这个行为,如下:


在 DefaultTinkerResultService 的 onPatchResult 方法中 判断了 patch 安装成功后的行为。如下:


@Override
    public void onPatchResult(PatchResult result) {
      ......
        // only main process can load an upgrade patch!
        if (result.isSuccess) {
          //加载完成后删除 patch 文件
            deleteRawPatchFile(new File(result.rawPatchFilePath));
            //如果成功,则杀掉进程
            if (checkIfNeedKill(result)) {
                android.os.Process.killProcess(android.os.Process.myPid());
            } else {
                TinkerLog.i(TAG, "I have already install the newly patch version!");
            }
        }
    }


从源码可以很清楚地看到杀掉进程。我们可以通过继承的方式来解决如下:


public class CustomResultService extends DefaultTinkerResultService {
    private static final String TAG = "CustomResultService";
    //返回 patch 文件的安装结果
    @Override
    public void onPatchResult(PatchResult result) {
  ......
        // if success and newPatch, it is nice to delete the raw file, and restart at once
        // only main process can load an upgrade patch!
        if (result.isSuccess) {
            deleteRawPatchFile(new File(result.rawPatchFilePath));
            if (checkIfNeedKill(result)) {
                TinkerLog.e(TAG, "patch加载成功,重启生效");
            } else {
                TinkerLog.i(TAG, "I have already install the newly patch version!");
            }
        }
    }
}


其实和源码中差不多,只是改了一句而已。加载成功后打印 log 即可。


弄完以后,我们需要修改一下初始化方法。我们是在 TinkerMananger 的 installTinker 方法中初始化的,下面我们进行修改,如下:


LoadReporter loadReporter = new DefaultLoadReporter(applicationLike.getApplication());
        PatchReporter patchReporter = new DefaultPatchReporter(applicationLike.getApplication());
        PatchListener patchListener = new DefaultPatchListener(applicationLike.getApplication());
        AbstractPatch upgradePatchProcessor = new UpgradePatch();
        //完成 tinker 初始化
        TinkerInstaller.install(applicationLike,
                loadReporter,
                patchReporter,
                patchListener,
                CustomResultService.class,
                upgradePatchProcessor);


CustomResultService 就是我们自定义的。将他传入即可,注意这是一个服务,必须在 AndroidManifest.xml 中注册


结果如下:


2019-11-21 16:59:42.335 6337-6404/? E/CustomResultService: patch加载成功,重启生效


2,自定义 PatchListener 监听 patch receiver 事件


也就是上面代码中的 DefaultPatchListener ,继承他即可,使用它可以完成 patch 的校验


例如,从服务器拉取补丁时服务器返回一个 md5, 我们可以在这个里面进行判断。


/**
 * 1,校验 patch 文件是否合法,2,启动 Service 去安装 patch
 */
public class CustomPatchListener extends DefaultPatchListener {
    private String currentMD5;
    public void setCurrentMD5(String currentMD5) {
        this.currentMD5 = currentMD5;
    }
    public CustomPatchListener(Context context) {
        super(context);
    }
    @Override
    protected int patchCheck(String path, String patchMd5) {
        if (Utils.isFileMD5Matched(path,currentMD5)){
            return ShareConstants.ERROR_PATCH_DISABLE;
        }
        return super.patchCheck(path, patchMd5);
    }
}


同样的要将初始化方法中第四个参数改为这个类对象


3,其他的自定义


//加载补丁文件加载时的异常监听
        LoadReporter loadReporter = new DefaultLoadReporter(applicationLike.getApplication());
        //补丁文件合成阶段的异常监听,
        PatchReporter patchReporter = new DefaultPatchReporter(applicationLike.getApplication());
        PatchListener patchListener = new DefaultPatchListener(applicationLike.getApplication());
        AbstractPatch upgradePatchProcessor = new UpgradePatch();
        //完成 tinker 初始化
        TinkerInstaller.install(applicationLike,
                loadReporter,
                patchReporter,
                patchListener,
                CustomResultService.class,
                upgradePatchProcessor);


通过继承 loadReporter 和 patchReporter 可以实现异常的监听。当然还有一下其他的,可以去官网查看。


总结一下


热修复这块基本的用法基本都已经掌握了。总结如下:


在学习任何一个东西时一定要先看官方的文档,不然吃大亏,就和我一样,搞了好多天。。如果项目中需要用到热修复,不要着急的直接选使用哪个,要从需求和一下客观因素来觉得要使用那种,在满足需求的条件下哪个学习的成本低,就学哪个。学习成本差不多的建议选择大公司的解决方案。


关于tinker的使用,例如上面这种就比较麻烦。但是有了 bugly 以后感觉挺简单的。毕竟也是免费的。tinker 使用的是冷启动,下发一次补丁需要十多分钟,撤回的话没怎么测。但是配合 walle 一起使用效果还是挺好的。


在使用 bugly 的时候发现在有些手机上使用失败的问题。例如我的小米6。。。在 bugly 后台上传 补丁包一直显示 版本对应不上。我还以为是代码问题,结果改代码改了一两天。。最后用了一个模拟器居然成功了。。无奈啊


注意热修复的作用不是替代版本的迭代。他只是为了在线上的 app 出现问题后的一种解决方式。按照一般流程重新进行版本的迭代就会浪费大量的时间,而且还指不定用户会不会更新。因为如此,所以才有了热修复。但是不能将他和迭代混为一谈。


关于上面 tinker 的用法我写了一个demo,demo中也集成了walle和友盟统计。bugly就不传了,官方文档非常详细。但是注意文档上的依赖版本不是最新的。这个亏我已经吃了。。。


其实学完以后才发现这种用法已经比较老了。所以我还是觉定用 bugly。他内部也是用的 tinker ,使用起来比较简单,当前一定要认真看文档,比如说我。。。吃了不少亏

相关文章
|
10月前
|
存储 监控 网络虚拟化
Hitachi 日立能源 560NMD11 DIN导轨集成管理型开关
Hitachi 日立能源 560NMD11 DIN导轨集成管理型开关
|
编解码 区块链 图形学
CVPR 2023 | LED阵列+LCD面板=3072个投影仪:浙大-相芯联合团队实现复杂物体高质量数字化建模
CVPR 2023 | LED阵列+LCD面板=3072个投影仪:浙大-相芯联合团队实现复杂物体高质量数字化建模
116 0
|
算法 API 开发工具
Thinker 使用详解(上)
Thinker 使用详解(上)
Thinker 使用详解(上)
|
人工智能 机器人 云计算
Xilinx Zynq-7015 SoC工业级核心板 SOM-XQ7Z15 Cortex-A9 + Artix-7
SOM-XQ7Z15是广州星嵌电子科技有限公司新推出的一款基于Xilinx Zynq-7000系列XC7Z015高性能低功耗处理器设计的异构多核工业级核心板,处理器集成PS端单/双核ARM Cortex-A9 + PL端Artix-7架构28nm可编程逻辑资源、最大频率766MHz,支持6.25G的高速SerDes,可支持PCIe、SATA、SFP等。
Xilinx Zynq-7015 SoC工业级核心板 SOM-XQ7Z15 Cortex-A9 + Artix-7
|
vr&ar 开发者
为了不再让你撞墙,HTC Vive 勾搭上了英特尔
在体验 HTC Vive 时,由于用户处于一个封闭的视觉环境,对外界环境一无所知,因此有时候总难免会碰撞到一些物体,虽然其自身有“防护墙”系统,但如果突然家里的猫跑过来这种事故就难以避免了。
158 0
为了不再让你撞墙,HTC Vive 勾搭上了英特尔
|
传感器 机器人 vr&ar
NASA用上了索尼 PS VR;MOTO X 四代或用全金属机身 | 快报
据外媒报道,近日,美国航空航天局NASA与索尼在虚拟现实领域展开了合作,共同研发一款叫“Mighty Morphenaut”的PlayStation VR虚拟现实应用。
204 0
NASA用上了索尼 PS VR;MOTO X 四代或用全金属机身 | 快报
|
机器学习/深度学习 传感器 人工智能
登上Nature子刊封面:英特尔神经芯片实现在线学习
神经形态芯片真的可以模拟人脑吗?最近一期《自然机器智能》的封面研究向我们展示了这一可能性。
179 0
登上Nature子刊封面:英特尔神经芯片实现在线学习
|
存储 编解码 Ubuntu
启明云端分享|IDO-SOM3828模块,你值得拥有。
IDO-SOM3828 是基于瑞芯微 RK3288 SoC(ARM Cortex A17 四核 主频 1.8G)的超小 型 SOM(System On Module)模块。模块在 6 x 4.6 CM 的 PCB 面积上整合 4 片 DDR3L、 1 片 EMMC、1 个千兆以太网 PHY(RTL8211F)以及电源管理 PMIC(RK808-B)电路,拥 有强大的多线程运算能力、图形处理能力以及硬件解码能力,而且支持 Android(7.1 及以 上),Ubuntu,Debian 系统
279 0
启明云端分享|IDO-SOM3828模块,你值得拥有。