android之sophix热修复

热修复概述

近 2 年来,热修复技术飞速发展。毕竟每次一个小版本的更新都要通过发布 apk 来实现,这样用户使用感可以说也是非常差了。怎么让客户不去重新下载一个 apk 而实现一个小版本或者一次小 bug 的修复,因此,热修复应运而生。

特别是 android studio 2.0 版本后 推出了 Instant Run 这个功能,关于 Instant Run 可以看 郭大的博文。从此,各种热修复热更新技术相继出现,国内比较成熟的主流 APP 都拥有自己的热修复技术,像支付宝、QQ、微信、美团等。

之前的热修复一般只支持代码热修复,而不支持资源热修复。代码热修复只是针对一些逻辑性错误的代码修改,到了后面又相继出现资源热修复,以及 so 热修复。本文主要是说阿里的 sophix ,所以就拿阿里的各版本热修复来说明比较。具体看下图:

基本可以看清楚,阿里从 Andfix 到最新的 Sophix 正在逐步完善。

而本文就将介绍,如何在 Android 项目中集成 Sophix 以及 如何使用Sophix。

Sophix 集成和准备

  1. 集成准备

    工具:android studio

    环境:jdk 1.8

    找到项目的 build.gradle 直接添加 maven 仓库地址:

    1
    2
    3
    4
    5
    repositories {
    maven {
    url "http://maven.aliyun.com/nexus/content/repositories/releases"
    }
    }

    然后找到 app 的 gradle 添加版本依赖:

    1
    compile 'com.aliyun.ams:alicloud-android-hotfix:3.2.2'

    点击一波 sync now,完成配置。

  1. 权限说明

    Siphix 需要用到 4 个基本权限:

    1
    2
    3
    4
    5
    6
    <! -- 网络权限 -->
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <! -- 外部存储读权限,调试工具加载本地补丁需要 -->
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

    第四个权限是属于 Dangerous Permissions,所以需要在 android 6.0 的版本以上进行动态权限获取。

  2. 混淆配置

    需要在 proguard-rules.pro 混淆文件中加入混淆代码

    1
    2
    3
    4
    5
    6
    7
    8
    #hotfix
    -keep class com.taobao.sophix.**{*;}
    -keep class com.ta.utdid2.device.**{*;}
    #防止inline
    -dontoptimize
    -keepclassmembers class com.zyue.zyueapp.base.BaseApplication {
    public <init>();
    }
  3. Sophix 的稳健初始化方式

    本文采用 Sophix 的最新版本初始化方式,该方法显得更加简单明了,而且跟原始的自己所加载的 Application 不会造成冲突。这样使得原先真正的 Application 也可以热修复,并且减少了补丁预加载的时间,同时还兼容了 Aandroid 8.0 以后的版本。

    具体就是要我们自己加入一个类:

    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
    package com.my.pkg;
    import android.app.Application;
    import android.content.Context;
    import android.support.annotation.Keep;
    import android.util.Log;
    import com.taobao.sophix.PatchStatus;
    import com.taobao.sophix.SophixApplication;
    import com.taobao.sophix.SophixEntry;
    import com.taobao.sophix.SophixManager;
    import com.taobao.sophix.listener.PatchLoadStatusListener;
    import com.my.pkg.MyRealApplication;
    /**
    * Sophix入口类,专门用于初始化Sophix,不应包含任何业务逻辑。
    * 此类必须继承自SophixApplication,onCreate方法不需要实现。
    * 此类不应与项目中的其他类有任何互相调用的逻辑,必须完全做到隔离。
    * AndroidManifest中设置application为此类,而SophixEntry中设为原先Application类。
    * 注意原先Application里不需要再重复初始化Sophix,并且需要避免混淆原先Application类。
    * 如有其它自定义改造,请咨询官方后妥善处理。
    */
    public class SophixStubApplication extends SophixApplication {
    private final String TAG = "SophixStubApplication";
    // 此处SophixEntry应指定真正的Application,并且保证RealApplicationStub类名不被混淆。
    @Keep
    @SophixEntry(MyRealApplication.class)
    static class RealApplicationStub {}
    @Override
    protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    // 如果需要使用MultiDex,需要在此处调用。
    // MultiDex.install(this);
    initSophix();
    }
    private void initSophix() {
    String appVersion = "0.0.0";
    try {
    appVersion = this.getPackageManager()
    .getPackageInfo(this.getPackageName(), 0)
    .versionName;
    } catch (Exception e) {
    }
    final SophixManager instance = SophixManager.getInstance();
    instance.setContext(this)
    .setAppVersion(appVersion)
    .setSecretMetaData(null, null, null)
    .setEnableDebug(true)
    .setEnableFullLog()
    .setPatchLoadStatusStub(new PatchLoadStatusListener() {
    @Override
    public void onLoad(final int mode, final int code, final String info, final int handlePatchVersion) {
    if (code == PatchStatus.CODE_LOAD_SUCCESS) {
    Log.i(TAG, "sophix load patch success!");
    } else if (code == PatchStatus.CODE_LOAD_RELAUNCH) {
    // 如果需要在后台重启,建议此处用SharePreference保存状态。
    Log.i(TAG, "sophix preload patch success. restart app to make effect.");
    }
    }
    }).initialize();
    }
    }

    这边的初始化动作一定要放在 attachBaseContext 中进行,onCreate 不需要自行实现。并且如果项目中因为方法类过多,超过 64K ,需要使用到MultiDex,就必须把该方法放到初始化动作之前,也就是上类中注释掉的部分。

    在这个类中,关键一点要加入自己写的 Application.

    1
    2
    3
    @Keep
    @SophixEntry(MyRealApplication.class)
    static class RealApplicationStub {}

    这里的 MyRealApplication 就换成自己写的 Application。

    为了防止自己写的 Application 和 这个类混淆,在 proguard 文件中加入以下混淆代码:

    1
    2
    3
    4
    5
    -keepclassmembers class com.my.pkg.MyRealApplication {
    public <init>();
    }
    # 如果不使用android.support.annotation.Keep则需加上此行
    # -keep class com.my.pkg.SophixStubApplication$RealApplicationStub

    最后,将 AndoridManifest.xml 中的 application 的 name 换成这个类名。

    1
    2
    3
    4
    <application
    android:name="com.my.pkg.SophixStubApplication"
    ... ...>
    ... ...

    最为关键的一步:

    到此处,稳健接入初始化,还差一个步骤,就是进行补丁下拉,也就是 queryAndLoadNewPatch 这个方法的调用。我个人是直接放到了自己写的 Application onCreate中,不建议放到其他方法类下。

    或者自己写个线程进行间隔拉取补丁进行热修复。

    1
    2
    3
    4
    5
    @Override
    public void onCreate() {
    super.onCreate();
    SophixManager.getInstance().queryAndLoadNewPatch();
    }

    到这里,这样除了一个 setSecretMetaData 没有配置,其他都配置 OK 了。setSecretMetaData 里面的参数,需要在阿里控制台进行其他操作才能进行。

    若有不理解处,可以具体查看官方官方技术文档

Sophix 具体使用

  1. 首先进入阿里云的移动研发平台。找到移动热修复功能点,或者搜索。

  2. 点击添加产品

  3. 填写产品信息,创建完成后,点击该产品进入。

  4. 点击添加应用

  5. app 名称和包名跟自己项目的保持一致,然后创建。

    这里可以看到项目中所需要的 AppSecret,还有2 个参数,就点击下载 aliyun-emas-services.json,打开找 到 IDSECRET ,APPSECRET ,RSASECRET 这 3 个参数,写入到一开始稳健接入 SophixStubApplication 类中。主要设置为 setSecretMetaData 方法。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    final SophixManager instance = SophixManager.getInstance();
    instance.setContext(this)
    .setAppVersion(appVersion)
    .setSecretMetaData("IDSECRET", "APPSECRET ","RSASECRET")//填写密钥和ID
    .setEnableDebug(true)
    .setEnableFullLog()
    .setPatchLoadStatusStub(new PatchLoadStatusListener() {
    @Override
    public void onLoad(final int mode, final int code, final String info, final
    int handlePatchVersion) {
    if (code == PatchStatus.CODE_LOAD_SUCCESS) {
    Log.i(TAG, "sophix load patch success!");
    } else if (code == PatchStatus.CODE_LOAD_RELAUNCH) {
    // 如果需要在后台重启,建议此处用SharePreference保存状态。
    Log.i(TAG, "sophix preload patch success. restart app to make effect.");
    }
    }
    }).initialize();

    到这一步完成,Sophix 的初始化才算真正完成。

  6. 然后点击移动热修复,点击功能。

    点击添加版本

    注意:这里的版本号必须跟项目中的 versionName 保持一致,否则项目拉取补丁会失败。

    到这个就可以上传自己项目的补丁了。

  7. 补丁制作

    是需要下载官方的一个工具,然后进行补丁打包。再将该补丁上传到该版本下。

    生成补丁看这里,生成补丁.

    又要注意了:这里的补丁名称不要乱修改,就得用这个工具生成的。

    到了此处,终于到了补丁发布了。折腾这么久,听着流泪,闻着伤心。

  8. 上传完补丁后,点击详情,进入该补丁界面。

    这个右侧有一个扫码验证补丁:点击下载 Sophix自带的一个自测 apk,安装在自己手机上,可以扫码来验证补丁是否有效,有效后,可以点击,新建发布。然后打开手机上的 app,就可以坐等热修复了。​​​​

总结

到这里,Sophix 热修复的功能就基本结束了。其中,小编自己踩的坑也都表明了注意,要是还有其他问题可以访问官方技术文档,或者进行问题搜索都可以得到一个满意的答复。