零客户端开发经验 React Native 热更新 CodePush 打包集成指北

本文永久地址:github.com/rccoder/blo…,其余平台可能不是最新文章。文章评论等也但愿去原文进行。javascript

1、背景

个人毕业设计是用 React Native 写一款校园 APP,服务端采用 egg + MongoDB。java

选用 React Native 一来是想借助他更加的学习巩固 React、Redux 生态系统;二来是作成 APP 而不是网站会在老师面前显得不是那么的 Low,同时借助双平台为忽悠填一份色彩;三来是 React Native 确实在性能上是优于 H5,不须要 XX 内核(如 UC、QQ等)来抹平杂乱机型的性能与兼容问题,同时还能和 H5 同样保持热更新。node

为何不用 Weex?react

曾经作过简单的 Weex 开发,不使用 Weex 有如下几点:一是定题目的时候(没错,题目中就有 React Native) Weex 的上层 DSL 还只有 Vue,当时还没有出现 Rax;二来是相比于 React Native,Weex 的生态与社区还比较年轻,惧怕本身跳进去爬不出来致使毕业延期。android

若是感兴趣的话,代码在这里:ios

2、热更新原理

2.一、引言

React Native 的原理大体是上层写 React 式的代码,而后利用相关的 loader 打包成 bundle 和相关的静态文件,而后利用 Android、iOS 里的 SDK 解析 bundle,而后以 Native 的方式执行。git

在大型 APP 中,针对 H5 中的静态文件会设置离线包,以达到秒出加速的目的,固然离线包的增大会致使 APP 体积的增大。针对一些场景(好比营销页面等),并不但愿离线加载,这样的场景就不使用离线包进行加载。github

离线包也须要进行更新,这里就须要一套比较完善的更新机制来保证(大公司本身造,小公司找有没有开源的)。npm

2.二、正文

热更新指的就是离线包(React Native 中的 bundle)更新的这个机制。咱们能够在 APP 运行的过程当中 “偷偷” 下载 bundle,而后在下次 APP 开启的时候(或者某种自定义的时机,好比:弹窗提示、直接重启等)使用新的 bundle。react-native

3、CodePush 介绍

如何去维护这样一个比较完善的更新机制呢?Microsoft 给出了一个很好的答案 —— CodePush。幸运地,他还没被“墙“,咱们能够直接的使用他的服务。

CodePush 集成了微软的一个云服务器,他至关因而一个中心发布器,APP 能够询问他是否有新的 bundle 更新,而后进行下载等操做。

CodePush 官方提供了 React Native 的集成方案,能够比较容易的集成到原有代码中(固然,也存在一些坑或者其余的地方,这也是原本出现的目的之一)。

4、实施流程

一、CodePush 服务配置

CodePush 的官网在

1.一、安装 CodePush CLI

npm install -g code-push-cli复制代码

1.二、注册 CodePush

code-push register复制代码

会自动打开浏览器弹出注册界面,注册完成以后会获得一个 token,复制后填写在 Terminal 里面便可。

1.三、注册应用

code-push app add WeHIT-Android
code-push app add WeHIT-iOS复制代码

每一个 APP 都会获得 ProductionProduction 状态的两个 Key:

为何要注册两个 APP

CodePush 在发布新 bundle 的时候目前只能一个一个平台发,为了保证你的 history 看起来不是那么的乱,建议直接分红两个 APP 处理

二、SDK 集成

SDK 的集成包含三部分,分别是 JS 源码、iOS 客户端、Android 客户端。

SDK 直接用 CodePush 官方发布的 react-native-code-push 便可。

值得注意是,react-native-code-push 最近发布的 2.0-beta 版本,而改版本只支持 React Native 0.43 及其以上的版本中。若是是 0.43 如下版本的须要使用 1.17.x(最新 1.17.4-beta)。

我这里使用的 RN 版本是 0.40,因此安装 1.17.4-beta 版本的 react-native-code-push。

yarn add react-native-code-push@1.17.4-beta复制代码

在进行下面的三个以前,咱们先用 React Native 使用的 rnpm(React Native Package Manage) link 如下这个包 (自动改变 ios 和 android 目录下的一些文件)。

npm install rnpm -g
rnpm link react-native-code-push复制代码

期间会提示你输入 iOS 和 Android 端的 key,这里输入你用 CodePush 注册 APP 时产生的 key,这里咱们先能够都输入每一个 Staging 的 key。

若是不幸你忘记了以前产生的 key,能够输入如下的命令查看:

code-push deployment ls WeHIT-Android复制代码

2.一、React Native 源码集成

在 React Native 源码中,咱们须要引入 react-native-code-push,而后用 CodePush 包裹一下最外层的组件。

没有 CodePush 以前咱们的代码这样写:

// index.ios[android].js
...
import { AppRegistry } from 'react-native';
import AppContainer from './src'
AppRegistry.registerComponent('WeHIT', () => AppContainer);

// src/index.js

export default function AppContainer () {
  return (
    <Navigator initialRoute={routeMap.home} configureScene={configureScene} renderScene={renderScene} /> )复制代码

如今咱们只须要改动一下 src/index.js,用 CodePush 包裹如下这个组件便可:

// src/index.js

import codePush from "react-native-code-push";

class App extends Component{

  componentDidMount() {}

  render() {
    return (
      <Navigator initialRoute={routeMap.intro} configureScene={configureScene} renderScene={renderScene} /> ) } } /** * Configured with a MANUAL check frequency for easy testing. For production apps, it is recommended to configure a * different check frequency, such as ON_APP_START, for a 'hands-off' approach where CodePush.sync() does not * need to be explicitly called. All options of CodePush.sync() are also available in this decorator. */ let codePushOptions = { checkFrequency: codePush.CheckFrequency.ON_APP_RESUME }; const AppContainer = codePush(codePushOptions)(App); export default AppContainer;复制代码

这里 checkFrequency 定义了什么时候进行 bundle 更新,这里 ON_APP_RESUME 是指在 APP 从新打开的时候进行更新替换。更加详细的状况能够参加 react-native-code-push 的 example 进行编写。目前这样仅仅是够用。

为何这样说?

据说在 Android 端和 iOS 端对更新要求是不同的。Android 是要求你在更新的时候提示用户更新,用户须要点击确认以后才进行更新;而 iOS 端是但愿这个更新是用户无感知的(上面这种就行),默默的在后台更新好而且使用最新的便可。

2.二、 iOS SDK 集成与打包

2.2.一、rnpm 自动配置

在上面使用 rnpm link 以后,其实大部分的问题都已经基本解决了。

这里你须要打开 git diff 看一下 rnpm link 有没有作一些 ”坏事“ 可能会对你的代码形成影响。

这里咱们发现他改变了下面几个文件:

重点看一下是在可辨识的范围内修改了 Info.plist,在里面加入了 key 方便调用。

除此以外还重点修改了一下 AppDelegate.m

这是指在 debug 包中继续以前的老套路远程加载 bundle,在 release 模式中托管给 CodePush 管理 bundle 的加载。

对,这和咱们以前预想的彻底同样。

接着,用 Xcode 打开 iOS 工程,检查一下版本号是否是三位,若是不是的话,修改成三位(好比:1.0.0):

为何要修改成三位

CodePush 只支持三位的,否则在推包的时候没法肯定推包给哪一个版本。

2.2.二、忽略 Test

这部分你能够先跳过,直接看 build 部分,若是有问题再回开看。

我在 build 的时候发生了错误,log 以下:

这看起来时测试出了问题,忽略掉他便可。

commmand + shift + ,,在弹出的框里面选择 build,而后取消掉 Test 部分的勾。

而后继续 build。

2.2.三、打build 包

在以前的 AppDelegate.m 中,咱们代码的意图是在 debug 模式下经过远程加载 bundle,在 release 模式下用 CodePush 来处理。这样保证了咱们在开发的时候不受 CodePush 的影响,因此要测试 CodePush,就打在 release 包。

command + shift + ,,在 run 里面 Build Configuration 里面选择 release。

而后 command + R 或者点击按钮进行打包。

理论上你会注意到打了一个不须要加载远程 bundle 的包。

如何打包到物理机上

这里不作讨论,只作简单的说明:首先须要一个开发者帐号,在项目工程的 Singning 里面进行配置(不想买的话能够去淘宝看看),而后插上手机以后进行 build。

和其余 iOS 工程打包没有什么区别。

2.三、Android SDK 集成与打包

2.2.一、rnpm 自动配置

和 iOS 端同样,利用 rnpm link 就能解决大部分的问题。rnpm link 以后,咱们 git diff 一下看看有哪些变更:

核心是修改了上面的几个文件:

自动在 setting.gradle 里面加入了 CodePush:

include ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')复制代码

自动在 build.gradle 加入了 CodePush 的编译依赖:

自动在 string.xml 里面加入了 key,方便 java 代码中的调用。

自动在 MainActivity.java 里面引入的 CodePush。

自动在 MainApplication.java 中重写的 getJSBundleFile 函数。

到这里,进行 run 的时候会提示找不到 bundle,出现下面的错误:

Caused by: com.microsoft.codepush.react.CodePushNotInitializedException: A CodePush instance has not been created yet. Have you added it to your app's list of ReactPackages?复制代码

模拟器也是红红的提示找不到 bundle。

这和咱们猜想的一直,整个 rnpm link 以后的代码中没有找到如何使用那个 key。

参考官方 example 中的代码,最终找到要在 MainApplication.java 中使用那个 Key:

那个 id 是 string.xml 里面的 id。

这样从新进行 run,可能会发现又提示:

Could not get BatchedBridge, make sure your bundle is packaged properly” on start of app复制代码

模拟是也是大大的红色错误提示:

参考 React Native 的 issues#9336stackoverflow

不要使用 Android Studio 启动,使用:

react-native run-android
react-native start --reset-cache复制代码

很好,能跑起来了!

2.3.二、打 build 包

如今已经打了 debug 的包,那如何打一个 release 包来测试咱们的 CodePush 呢?

2.3.2.一、Android Studio 生成证书 jks 文件

首先咱们须要一个签名,关于这个签名,咱们在开发的时候其实是使用了 Android Studio 自带的一个 debug 签名,在打 release 包的时候,咱们就须要本身签名了:

以下图,在 build 里面选择 Generator Singed Apk:

而后点击 Create New...

而后填写即将生成的 patch 路径,密码,Alias的名字,密码,还有一些公司我的相关的东西:

OK,看看你填写的证书路径里是否有一个 xx.jks 的证书了。经过这个证书,咱们就能够获得 App 的 Sha1 等用于一些第三方 SDK,更多细节能够参考 获取Android SHA1 、生成jks密钥、签名Apk

2.3.2.二、Android Studio 自动打包

理论上在刚才的基础下点击下一步,选择 Release 包就能获得 Release 包了,但用:

adb install ./Android/app-release.apk复制代码

安装以后,打开会直接 crash(须要先卸载以前的 APP),解压 APK,发现 asset 里面没有和 CodePush 有关的任何东西,猜想多是这里引发 Crash。

因此这种打包方式不能用。

部分解释参见: www.jianshu.com/p/1cff76e20…

2.3.2.三、手动配置 Gradle 进行自动打包

找到以前生成的证书,复制到 app 目录下,好比个人证书叫 WeHIT.jks

接着修改 android/gradle.properties,加入证书的相关信息,方便其余文件调用:

MYAPP_RELEASE_STORE_FILE=WeHIT.jks
MYAPP_RELEASE_KEY_ALIAS=WeHITKey
MYAPP_RELEASE_STORE_PASSWORD=123456
MYAPP_RELEASE_KEY_PASSWORD=123456复制代码

而后在 app/build.gradle 里配置打包签名:

这里会用到以前保存在 android/gradle.properties 里的证书信息。

而后,切换到 andriod 目录下执行:

./gradlew assembleRelease复制代码

在 app/build/outputs/apk 里面会获得 release 包:

adb install ./WeHIT/WeHIT/android/app/build/outputs/apk/app-release.apk复制代码

注1:

ADB 可能不在环境变量中,若是你是用 Android Studio 安装的 Android SDK,先设置一下。

好比我用的是 zsh,修改 ~/.zsh.rc,加入:

export ANDROID_HOME=${HOME}/Library/Android/sdk
export PATH=${PATH}:${ANDROID_HOME}/tools
export PATH=${PATH}:${ANDROID_HOME}/platform-tools复制代码

注2:

理论上 Android Studio 集成了 gradlew 的功能,在 IDE 右侧 Gradle 中就有,但打包以后依然缺失 CodePush,因此仍是用命令行打包吧.

2.3.2.四、额外小记

在运行模拟器的时候,提示:

Starting emulator for AVD 'x86_QVGA_Level10'
emulator: device fd:1044
HAX is working and emulator runs in fast virt mode
emulator: Failed to sync vcpu reg
emulator: Failed to sync HAX vcpu context复制代码

发现是 Docker 这种虚拟机在跑,关掉便可正常打开模拟器。

三、CodePush 推送新 bundle

改完代码后,咱们须要更新 bundle,这样就须要咱们打包而后把 bundle 推送到 CodePush 服务器上。

3.一、打包推送一步流

在项目根目录执行:

Android 推包

code-push release-react WeHIT-Android android复制代码

iOS 推包

code-push release-react WeHIT-ios ios复制代码

看到 log:

Detecting android app version:

Using the target binary version value "1.0.0" from "android/app/build.gradle".

Running "react-native bundle" command:

node node_modules/react-native/local-cli/cli.js bundle --assets-dest /var/folders/qv/3gzgsb153qj0gk8ljwl0kg0w0000gn/T/CodePush --bundle-output /var/folders/qv/3gzgsb153qj0gk8ljwl0kg0w0000gn/T/CodePush/index.android.bundle --dev false --entry-file index.android.js --platform android
[05/08/2017, 23:06:39] <START> Initializing Packager
[05/08/2017, 23:06:40] <START> Building Haste Map
[05/08/2017, 23:06:41] <END>   Building Haste Map (1632ms)
[05/08/2017, 23:06:41] <END>   Initializing Packager (2679ms)
[05/08/2017, 23:06:41] <START> Transforming files
[05/08/2017, 23:07:14] <END>   Transforming files (32488ms)
bundle: start
bundle: finish
bundle: Writing bundle output to: /var/folders/qv/3gzgsb153qj0gk8ljwl0kg0w0000gn/T/CodePush/index.android.bundle
bundle: Done writing bundle output
bundle: Copying 15 asset files
bundle: Done copying assets

Releasing update contents to CodePush:

Upload progress:[==================================================] 100% 0.0s
Successfully released an update containing the "/var/folders/qv/3gzgsb153qj0gk8ljwl0kg0w0000gn/T/CodePush" directory to the "Staging" deployment of the "WeHIT" app.复制代码

表示已经成功。

3.二、打包推送两步流

上面的一步流是把 打包和推送 结合到了一块儿,从 log 中也能简单看到先是执行打包,而后在 Push 的。

在 APP 的测试包中,咱们可能但愿能保留开发 Log 等,或者说咱们想比较好的自定义 history 等。这样就须要咱们手动的两次进行打包和推送了。

3.2.一、打包

首先咱们建立一个 bundles 文件夹来存储打包后的 bundles

mkdir bundles复制代码

而后进行打包

// react-native bundle --platform 平台 --entry-file 启动文件 --bundle-output 打包js输出文件 --assets-dest 资源输出目录 --dev 是否调试

react-native bundle --platform android --entry-file index.android.js --bundle-output ./bundles/index.android.bundle --dev false

react-native bundle --platform ios --entry-file index.ios.js --bundle-output ./bundles/index.ios.bundle --dev false复制代码

这样就在 bundles 文件夹下面生成 bundle

3.2.二、推送

bundle 已经打好,如今须要推送到 CodePush 平台上。

// code-push release <应用名称> <Bundles所在目录> <对应的应用版本>
--deploymentName 更新环境 staging时不须要这个
--description 更新描述
--mandatory 是否强制更新 默认否

code-push release WeHIT-Android ./bundles 1.0.0 --description “Android Update”
code-push release WeHIT-iOS ./bundles 1.0.0 --description “iOS Update”复制代码

3.三、暗中观察

code-push deployment history WeHIT-Android Staging复制代码

若是不想看这么多的版本,能够用:

code-push deployment ls WeHIT-Android -k复制代码

同时,你会发现装了 release 包的客户端会自动更新。

4、后记

若是以前没有客户端的开发经验,配置 CodePush SDK 仍是有点复杂,若是你在配置过程当中遇到了什么问题,或者发现文章中有错误,欢迎指出。

与文章的互动请去原文 github.com/rccoder/blo…

5、参考文章

相关文章
相关标签/搜索