react-native-code-push进阶篇

来源:互联网 发布:金融网络理财产品排行 编辑:程序博客网 时间:2024/05/29 06:46

之前写了一篇关于react-native-code-push的入门使用篇:微软的React Native热更新 - 使用篇,真的是很简单的使用,能热更新成功就行了。这一篇通过在项目中实战所遇到的问题,根据源码分析它的原理,来更深入的理解code-push。

这篇文章是在已经搭建好code-push环境(执行过npm install --save react-native-code-push@latest
react-native link react-native-code-push ,并安装了code-push cli且成功登陆)为基础下写的,没有使用CRNA来创建App。

部署与配置

部署(deployment)Test,Staging和Production

在真正的项目中,我们一般会分为开发版(Test),灰度版(Staging)和发布版(Production),在Test中我一般是用来跟踪code-push的执行,在Staging中其实是和Production是同样的代码,但是当要热修复线上版本时,先会发布热更新到Staging版,在Staging测过后再通过promoting推到Production中去。

大致步骤:

  • 通过code-push app add MyAppIOS ios react-native来创建iOS端的App,或者通过code-push app add MyAppAndroid android react-native创建Android端的App。
  • 使用code-push app ls查看是否添加成功,默认会创建两个部署(deployment)环境:Staging和Production,可以通过code-push deployment ls MyAppIOS -k来查看当前App所有的部署,-k是用来查看部署的key,这个key是要方法原生项目中去的。
  • 添加一个Test部署环境:code-push deployment add MyAppIOS Test,添加成功后,就可以通过code-push deployment ls MyAppIOS -k来查看Test部署环境下的key了。

经常使用code-push --h来查看可以执行的操作

最后结果如下图所示:


image.png
image.png
image.png
image.png

在原生项目中动态部署

在上面有提过需要把部署的key添加到原生项目中,这样在不同的运行环境下动态的使用对应的部署key,例如在Staging下使用Stagingkey,在Relase下使用Productionkey,在Debug下不使用热更新(如需在debug环境下测试code-push,可以在codePush.sync里的option参数中动态修改部署key)。

在Android中动态部署key,并且在同一设备同时安装不同部署的Android包
有两种方式:

  • 官方配置入口:https://github.com/Microsoft/react-native-code-push#multi-deployment-testing,通过添加buildConfigFiled使用对应的key,同时在If you want to be able to install both debug and release builds simultaneously on the same device中有提到在同一设备同时安装不同部署的Android包。
  • 第二种方式是通过资源文件R.string来实现同样的效果,在app/src中分别添加staging/res/valuesdebug/res/values两个文件夹,然后复制app/src/main/res/value/strings.xml粘贴到刚新建的两个values目录下,最后在代码中获取key的方式为R.string.reactNativeCodePush_androidDeploymentKey

配置好后可以使用./gradlew assembleStaging来打包Staging下的apk,输出目录在./android/app/build/outputs/apk下,没有在gradle中配置签名安装(adb install app-staging.apk)会出现如下错误:Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES] React native,关于gradle的buildType的使用:http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Types

在iOS中动态部署key
官方配置入口:https://github.com/Microsoft/react-native-code-push#multi-deployment-testing。

在iPhone上同时安装相同App的不同部署包
让你的iOS应用在不同状态(debug, release)有不同的图标和标题

参考项目:https://github.com/lyxia/CodePushExp

应用中经常遇到的技巧

Target binary version 和 Label

image.png
image.png

label代表发布的更新版本,Target binary version代表app的版本号。

1、使用patch打补丁,修改元数据属性。
使用场景:例如当你已经发布了一个更新,但是到有些情况下,比如--des需要修改,--targetBinaryVersion写错了,比如我的8.6.0写成了8.6,然后在我发布8.6.1新版的时候就会拉取8.6的版本更新,这个时候就可以code-push patch MyAppAndroid Production --label v4 --targetBinaryVersion 8.6.1

2、使用promote将Staging推到Production
使用场景:当你在指定的部署环境下测试更新时,例如Staging,测试通过后,想把这个更新发布到正式生产环境Production中,则可以使用code-push promote MyAppAndroid Staging Production,这时可以修改一些元数据,例如--description--targetBinaryVersion--rollout等。

3、使用rollback回滚
使用场景:当你发布的更新测试没通过时,可以回滚到之前的某个版本。code-push rollback MyAppAndroid Production,当执行这个命令时它会在MyAppAndroid上的Production部署上再次发布一个release,这个release的代码和元属性与Production上倒数第二个版本一致。也可以通过可选参数--targetRelease来指定rollback到的版本,例如code-push rollback MyAppAndroid Production --targetRelase v2,则会新建一个release,这个release的代码和元属性与v2相同。

注意:这个回滚是主动回滚,与自动回滚不一样

4、使用debug查看是否使用了热更新版本
使用场景:当你想知道code-push的状态时,比如正在检查是否有更新包,正在下载,正在安装,当前加载的
bundle路径等,对于android可以使用code-push debug android,对于iOS可以使用code-push debug ios

注意:debug ios必须在模拟器下才可以使用

5、使用deployment h查看更新状态
使用场景:在发布更新后,需要查看安装情况,可以通过code-push deployment h MyAppAndroid Production来查看每一次更新的安装指标。

6、较难理解的发布参数

  • Mandatory 代表是否强制性更新,这个属性只是简单的传递给客户端,具体要对这个属性如何处理是由客户端决定的,也就是说,如果在客户端使用codePush.sync时,updateDialogtrue的情况下,如果-mandatoryfalse,则更新提示框会弹出两个按钮,一个是【确认更新】,一个是【取消更新】,但是在-mandatorytrue的情况下就只有一个按钮【确认更新】用户没法拒绝安装这个更新。在updateDialogfalse的情况下,-mandatory 就不起作用了,因为都会静默更新。

    注意:mandatory是服务器传给客户端的,它是一个“动态”属性,意思就是当你正在使用版本v1的更新,然后现在服务器上有v2v3的更新可用,v2mandatorytrue,v3mandatoryfalse,此时去check update,服务器会返回v3的更新属性给客户端,这时服务返回的v3mandatorytrue,因为v3v2之后发布的更新,它会被认为是包含v2的所有更新信息的,竟然v2有强制更新的需求,那跳过v2直接更新到v3的情况下,v3也被要求强制更新。但是如果你当前是在使用v2的更新包,check update时服务器返回v3的更新包属性,此时v3mandatoryfalse,因为对于v2而言v3不是强制要更新的。

  • Disabled 默认是为false,顾名思义,这个参数的意思就是这个更新包是否让用户使用,如果为true,则不会让用户下载这个更新包,使用场景:
    • 当你想发布一个更新,但是却不想让这个更新立马生效,比如想对外公布一些信息后才让这个更新生效,这时候就可以使用code-push promote MyAppAndroid Staging Production --disabled false来发布更新到正式环境,在对外公布信息后,使用code-push patch MyAppAndroid Production --disabled true来让用户可以使用这个更新。
  • Rollout 用来指定可以接收到这个更新的用户的百分比,取值范围为0-100,不指定时默认为100。如果你希望部分用户体验这个新的更新,然后在观察它的崩溃率和反馈后,在将这个更新发布给所有用户时,这个属性就非常有用。当部署中的最后一个更新包的rollout值小于100,有三点要注意:
    • 不能发布新的更新包,除非最后一个更新包的rollout值被patch100
    • rollback时,rollout值会被置空(为100)。
    • promote去其他部署时,rollout会被置空(为100),可以重新指定--rollout

7、理解安装指标(Install Metrics)数据
先来看下试用过程,现在有两个机子,分别为A和B
第一步:发了一个更新包,Install Metrics中提示No install recorded表示没有安装记录

image.png
image.png

第二步:A安装了这个更新包,并且现在正在使用这个更新包
image.png
image.png

第三步:给v1打了个patch,把App Version改为1.0.0,并且把元属性Disabled改为true
image.png
image.png

第四步:A卸掉App,发现Install Metrics中的Activite0%了(0 of 1),证明在of左边的数是会增降的,of右边的数是只会增不会降的,of左边的数代表当前install或者receive的总人数,当有用户卸载App,或者使用了更新的更新包时,这个数就会降低。因此它很好的解释了当前更新包有多少活跃用户,多少用户接收过这个安装包。Install Metrics中的total并没有改变,还是为1,代表有多少个用户install过这个更新包,这个数字只增不降,注意totalactive的区别。
image.png
image.png

第五步:分别在A、B上安装这个App。发现图中数据和上图没有任何区别,那是因为disabledtrue,因此不会接收这个更新包。
image.png
image.png

第六步:给v1打了个patch,把元属性Disabled改为true,让Bcheck update,发现下图中activeof右边的数增加了1,代表多了一个用户receivedv1,但是of左边的数字为0,代表v1没有活跃用户,total的改变是多了(1 pending),代表有一个用户receivedv1,但是还没有install(也就是notifyApplicationReady没被调用)
image.png
image.png

第七步:让Acheck update,发现Active没有任何改变,因为B以前就接收过v1。totalpending数为2了,代表有两个用户receivedv1。
image.png
image.png

第八步:让Binstallv1,active变为50%,可以看出installed/received为50%。total增加了1,代表v1多了一次installed,一共经历了2installed(1 pending)代表还有一个received
image.png
image.png

第九步:让Ainstallv1,active变为100%total增加了1,代表v1多了一次installed,一共经历了3installed,没有pending代表没有received
image.png
image.png

第十步:发一个可以触发rollback的更新。
App.js的构造函数中添加如下代码:
constructor() {        super(...arguments)        throw new Error('roll back')    }

然后发个更新出去:code-push release-react MyAppIOS ios -d Staging --dev false --des rollBackTest
此时code-push deployment h MyAppIOS Staging为:

image.png
image.png

这时我们让A去check update,并且把code-push debug ios打开(注意debug必须使用模拟器)。发现v2的total直接从v1total中读下来,也就是说所有的v1用户都会receivedv2,pending1代表Areceviedv2,但没有installed
image.png
image.png

这时,我们让Ainstalledv2,发现A会闪退,然后再次进入App,发现pending没有了,但是total并没有增加,active也没有改变,pending的加到rollbacks去了。
image.png
image.png

此时code-push debug ios会打印Update did not finish loading the last time, rolling back to a previous version.
第十一步:发布个修订版,修复v2产生的bug。然后让B安装。
image.png
image.png

哈哈,这个图看懂了吗,看懂了就代表了解它的意思了O(∩_∩)O哈哈~
第十二步:发布一个强制更新的更新包。

image.png
image.png

经过上面的测试,大致了解了Install metrics中各个参数的意思,这里大概总结一下:

  • Active 成功安装并运行当前release的用户的数量(当用户打开你的App就会运行这个release),这个数字会根据用户成功installed这个release或者离开这个release(installed了别的更新包,或者卸载了App),总之有它就知道当前release的活跃用户量
  • Total 成功installed这个release的用户的数量,这个数量只会增不会减。
  • Pending 当前这个release被下载的数量,但是还没有被installed,因此这一个数值会在release被下载时增长,在installed时降低。这个指标主要是适配于没有为更新配置立马安装(mandatory)。如果你为更新配置了立马安装但是还是有pending,很有可能是你的App启动时没有调用notifyApplicationReady
  • Rollbacks 这个数字代表在客户端自动回滚的数量,理想状态下,它应该为0,如果你发布了一个更新包,在installing中发生crash,code-push将会把它回滚到之前的一个更新包中。

可以在https://github.com/lyxia/CodePushExp运行示例。

源码解读

js模块

code-push中Javascript API并不多,可以在JavaScript API查阅。
而快速接入的方法也就两种,一种是sync,一种是root-level HOC。现在来看HOC的源码:

//CodePush.js 456行componentDidMount() {  if (options.checkFrequency === CodePush.CheckFrequency.MANUAL) {    //如果是手动检查更新,直接installed    CodePush.notifyAppReady();  } else {    ...     //如果不是手动更新,则每次start app都会去sync    CodePush.sync(options, syncStatusCallback, downloadProgressCallback, handleBinaryVersionMismatchCallback);    if (options.checkFrequency === CodePush.CheckFrequency.ON_APP_RESUME) {      //每次从后台恢复时sync      ReactNative.AppState.addEventListener("change", (newState) => {        newState === "active" && CodePush.sync(options, syncStatusCallback, downloadProgressCallback);      });    }  }}

可以看出更新的代码是sync

//CodePush.js 344行syncStatusChangeCallback(CodePush.SyncStatus.CHECKING_FOR_UPDATE);const remotePackage = await checkForUpdate(syncOptions.deploymentKey, handleBinaryVersionMismatchCallback);

在checkForUpdate中会去拿App的版本号,部署key和当前更新包的hash值,确保客户端接受到服务器正确的更新包,有几种情况拿不到更新包,第一种是服务端没有更新包,第二种是服务端的更新包要求的版本号与当前App版本不符,第三种是服务端的更新包和App当前正在使用的更新包Hash值相同。

//CodePush.js 85行//PackageMixins.remote(...)执行后返回一个对象包含两属性,分别是download和isPending。//download是一个异步方法用来下载更新包,isPending初始值为false,表示没有installed。const remotePackage = { ...update, ...PackageMixins.remote(sdk.reportStatusDownload) };remotePackage.failedInstall = await NativeCodePush.isFailedUpdate(remotePackage.packageHash);remotePackage.deploymentKey = deploymentKey || nativeConfig.deploymentKey;

当拿到更新包后会标志这个更新包是否回滚过。

    //CodePush.js 362行    //如果有拿个更新包,但是这个更新包是安装失败的包,并且设置中配置忽略安装失败的包,则这个更新包会被忽略    const updateShouldBeIgnored = remotePackage && (remotePackage.failedInstall && syncOptions.ignoreFailedUpdates);    if (!remotePackage || updateShouldBeIgnored) {      if (updateShouldBeIgnored) {          log("An update is available, but it is being ignored due to having been previously rolled back.");      }      //会去原生端拿当前下载的更新包,如果这个更新包没有installed,就会提示安装这个更新包,如果已经installed就会提示已经是最新版本。      const currentPackage = await CodePush.getCurrentPackage();      if (currentPackage && currentPackage.isPending) {        syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);        return CodePush.SyncStatus.UPDATE_INSTALLED;      } else {        syncStatusChangeCallback(CodePush.SyncStatus.UP_TO_DATE);        return CodePush.SyncStatus.UP_TO_DATE;      }    } else{      //如果设置中配置弹提示框,则根据mandatory弹出不同的提示框,根据用户的选择决定是否下载更新包。      //如果没有配置弹提示框,则直接下载更新包      ...    }

下载的代码:

    const doDownloadAndInstall = async () => {      syncStatusChangeCallback(CodePush.SyncStatus.DOWNLOADING_PACKAGE);      //使用之前提到的download方法来下载更新包。      const localPackage = await remotePackage.download(downloadProgressCallback);      //检查安装方式      resolvedInstallMode = localPackage.isMandatory ? syncOptions.mandatoryInstallMode : syncOptions.installMode;      syncStatusChangeCallback(CodePush.SyncStatus.INSTALLING_UPDATE);      //安装更新      await localPackage.install(resolvedInstallMode, syncOptions.minimumBackgroundDuration, () => {        syncStatusChangeCallback(CodePush.SyncStatus.UPDATE_INSTALLED);      });      return CodePush.SyncStatus.UPDATE_INSTALLED;    };

原生模块:

寻找jsbundle过程:getJSBundleFile中改成了CodePush.getJSBundleFile(),在这里面会判断是否有新下载的更新包,如果比本地新则加载这个更新包,否则加载本地包,
安装更新包过程:会把最新下好的包(pending package)标记的pending updated标记为false。
notifyAppReady:会将pending package移除。
initializeUpdateAfterRestart:加载更新包的bundle时会执行这个方法。

  • 在这里面会判断pending package是否存在如果不存在则代表没有要安装的更新包,方法结束。
  • 如果pending updatedtrue,如果为true则代表该更新包已经安装过,但是pending package依然存在,代表是一个roll back的包,将package标记为failed,方法结束。
  • 如果pending updatedfalse,则代表这个更新包没有安装过,pending updated 会被标志为true

这样做的的用处是在如果下载并install这个更新包后却没有notifyAppReady,则pending updated会从false变为true,最后被roll back。如果调用了notifyAppReady则不会被roll back。

Demo

地址:https://github.com/lyxia/CodePushExp

image.png
image.png
image.png
image.png
image.png
image.png
image.png


作者:lyxia_ios
链接:http://www.jianshu.com/p/6e96c6038d80
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
原创粉丝点击