Android 热修复之阿里的Sophix

一:简介
移动热修复(Mobile Hotfix)是面向Android、iOS平台应用提供的在线热修复服务方案,产品基于阿里巴巴首创的Hotpatch技术,提供细粒度热修复能力,无需等待发版即可实时修复应用线上问题,用户全程无感知。
image.png
二:Android SDK稳健接入
第一步:在项目下的build.gradle下添加

buildscript {
    ext.kotlin_version = '1.3.50'

    repositories {
        google()
        jcenter()
        mavenCentral()
        maven { url 'https://jitpack.io' }
        maven { url 'http://maven.aliyun.com/nexus/content/repositories/releases/' }

    }
    }
allprojects {
    repositories {
        google()
        jcenter()
        mavenCentral()
        maven { url 'http://maven.aliyun.com/nexus/content/repositories/releases/' }//Maven仓库地址
    }
}

额外话题:
在rootProject下的build.gradle中:buildscript的repositories和allprojects的repositories有什么区别?
1、 buildscript里是gradle脚本执行所需依赖,分别是对应的maven库和插件
2、 allprojects里是项目本身需要的依赖,比如代码中某个类是打包到maven私有库中的,那么在allprojects—>repositories中需要配置maven私有库,而不是buildscript中,不然找不到。
第二步:在app下build.gradle添加热修复的依赖

android {
    ......
    defaultConfig {
        applicationId "com.xxx.xxx" //包名
 ndk {
            //选择要添加的对应cpu类型的.so库。热修复支持5种
            abiFilters 'arm64-v8a','armeabi', 'armeabi-v7a', 'x86_64','x86'
        }
        }
        }
dependencies {
   implementation 'com.aliyun.ams:alicloud-android-hotfix:3.2.18'
   }

第三步添加应用权限Androidmanifest.xml

//允许网络请求,下载补丁时使用。
<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" />
//外部存储读权限,调试工具从SD卡加载本地补丁需要。
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

第四步:除了下面配置文文件引入App ID等,下面这种方式直接引入也可以

 <application
        android:name="com.alibaba.sophix.demo.SophixStubApplication"
        android:allowBackup="true"
        android:icon="@mipmap/app_logo"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
       <meta-data
       android:name="com.taobao.android.hotfix.IDSECRET"
       android:value="App ID" />
       <meta-data
       android:name="com.taobao.android.hotfix.APPSECRET"
       android:value="App Secret" />
       <meta-data
       android:name="com.taobao.android.hotfix.RSASECRET"
       android:value="RSA密钥" />

第四步:下载我们在平台上添加的项目的生成的配置文件
为方便EMAS各产品SDK快速、轻量接入,EMAS提供了统一的SDK依赖方式,供各产品SDK添加依赖使用。用户使用统一接入后,无需手动管理各产品SDK依赖,由插件自行接管完成读取和配置。
并将json文件放到应用根目录下(/app/)。Android端配置文件:aliyun-emas-services.json其内容如下:
配置文件:

{
  "config": {
    "emas.appKey":"xxxxx",
    "emas.appSecret":"xxxxxx",
    "emas.packageName":"com.xxx.xxx.demo",
    "hotfix.idSecret":"xxxxx",
    "hotfix.rsaSecret":"xxxxx",
    "httpdns.accountId":"xxxxx",
    "httpdns.secretKey":"xxxxxxxxx"
    "appmonitor.tlog.rsaSecret":"xxxxxxxxxx",
    "appmonitor.rsaSecret":"xxxxxxxxxx"
},
  "services": {
    "hotfix_service":{//移动热修复,status=1,表示使用对应产品,status=0不使用
        "status":1,
        "version":"3.2.14"
    },
    "ha-adapter_service":{//性能分析/远程日志的基础库
        "status":1,
        "version":"1.1.3.4-open"
    },
    "feedback_service":{//移动用户反馈
        "status":1,
        "version":"3.3.1"
    },
    "tlog_service":{//远程日志
        "status":1,
        "version":"1.1.2.3-open"
    },
    "httpdns_service":{
        "status":1,
        "version":"1.3.2.3"
    },
    "apm_service":{//性能分析
        "status":1,
        "version":"1.0.7.9-open"
    },
    "man_service":{//移动数据分析
        "status":1,
        "version":"1.2.4"
    },
    "cps_service":{//移动推送
        "status":1,
        "version":"3.1.12"
    }
},
  "use_maven":true,
  "proguard_keeplist":"\n#httpdns\n-keep class com.taobao.** {*;}\n-keep class com.alibaba.** {*;}\n-keep class com.ta.**{*;}\n-keep class com.ut.**{*;}\n-dontwarn com.taobao.**\n-dontwarn com.alibaba.**\n-dontwarn com.ta.**\n-dontwarn com.ut.**\n\n#cps\n-keep class com.taobao.** {*;}\n-keep class com.alibaba.** {*;}\n-keep class com.ta.**{*;}\n-keep class com.ut.**{*;}\n-dontwarn com.taobao.**\n-dontwarn com.alibaba.**\n-dontwarn com.ta.**\n-dontwarn com.ut.**\n-keepclasseswithmembernames class ** {\nnative <methods>;\n}\n-keepattributes Signature\n-keep class sun.misc.Unsafe { *; }\n-keep class com.alipay.** {*;}\n-dontwarn com.alipay.**\n-keep class anet.**{*;}\n-keep class org.android.spdy.**{*;}\n-keep class org.android.agoo.**{*;}\n-dontwarn anet.**\n-dontwarn org.android.spdy.**\n-dontwarn org.android.agoo.**\n\n#hotfix\n#基线包使用,生成mapping.txt\n-printmapping mapping.txt\n#生成的mapping.txt在app/buidl/outputs/mapping/release路径下,移动到/app路径下\n#修复后的项目使用,保证混淆结果一致\n#-applymapping mapping.txt\n#hotfix\n-keep class com.taobao.sophix.**{*;}\n-keep class com.ta.utdid2.device.**{*;}\n#防止inline\n-dontoptimize\n\n#man\n-keep class com.taobao.** {*;}\n-keep class com.alibaba.** {*;}\n-keep class com.ta.**{*;}\n-keep class com.ut.**{*;}\n-dontwarn com.taobao.**\n-dontwarn com.alibaba.**\n-dontwarn com.ta.**\n-dontwarn com.ut.**\n\n#feedback\n-keep class com.taobao.** {*;}\n-keep class com.alibaba.** {*;}\n-keep class com.ta.**{*;}\n-keep class com.ut.**{*;}\n-dontwarn com.taobao.**\n-dontwarn com.alibaba.**\n-dontwarn com.ta.**\n-dontwarn com.ut.**\n"
}

配置文件引用
在项目级目录下build.gradle(/build.gradle)文件中添加阿里云Maven仓库地址和emas-services插件。

buildscript {
  repositories {
    maven {
        url 'http://maven.aliyun.com/nexus/content/repositories/releases/'
    }
  }
  dependencies {
    // 添加emas-services插件
    classpath 'com.aliyun.ams:emas-services:1.0.1'//引入
  }
}
allprojects {
    repositories {
      ...
        maven {
            url 'http://maven.aliyun.com/nexus/content/repositories/releases/'
        }
    }
}

在应用级build.gradle(<project>/<app-module>/build.gradle)文件中添加代码段,应用插件。

apply plugin: 'com.aliyun.ams.emas-services'

第五步:初始化
初始化的调用应该尽可能的早,必须在Application.attachBaseContext()的最开始(在super.attachBaseContext之后,如果有Multidex,也需要在Multidex.install之后)进行SDK初始化操作,初始化之前不能用到其他自定义类,否则极有可能导致崩溃。而查询服务器是否有可用补丁的操作可以在后面的任意地方。不建议在Application.onCreate()中初始化,因为如果带有ContentProvider,就会使得Sophix初始化时机太迟从而引发问题。

/**
 * 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(MyApplication .class)
    static class RealApplicationStub {}
    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
//         如果需要使用MultiDex,需要在此处调用。
//         MultiDex.install(this);
        initSophix();
    }
    private void initSophix() {
        String appVersion = "1.1.1";//app对应的版本名
        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();
    }
}

其中最重要的是

  @Keep
    @SophixEntry(MyApplication .class)
    static class RealApplicationStub {}
    
    //SophixEntry应指定项目中原先真正的Application(原项目里application的android::name指定的)
public class MyApplication extends Application {
 @Override
    public void onCreate() {
        super.onCreate();
 SophixManager.getInstance().queryAndLoadNewPatch();
 
 /// queryAndLoadNewPatch为拉取控制台补丁
//不可放在attachBaseContext 中,否则无网络权限,建议放在主进程任意时刻,如Application的onCreate中
//SophixManager.getInstance().queryAndLoadNewPatch();这个方法用来查询有没有补丁包生成
 }
}
//现在的application
 <application
        android:name=".SophixStubApplication"
        android:allowBackup="false"/>

第六步:混淆配置

#基线包使用,生成mapping.txt
-printmapping mapping.txt
#生成的mapping.txt在app/build/outputs/mapping/release路径下,移动到/app路径下
#修复后的项目使用,保证混淆结果一致
#-applymapping mapping.txt
#hotfix
-keep class com.taobao.sophix.**{*;}
-keep class com.ta.utdid2.device.**{*;}
#防止inline
-dontoptimize

阿里热修复接入文档
https://help.aliyun.com/docum...

END:当你真正喜欢做一件事时,自律就会成为你的本能。


Rocky_ruan
57 声望5 粉丝

不积跬步,无以至千里