本文永久地址:github.com/rccoder/blo…,其余平台可能不是最新文章。文章评论等也但愿去原文进行。javascript
个人毕业设计是用 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
React Native 的原理大体是上层写 React 式的代码,而后利用相关的 loader 打包成 bundle 和相关的静态文件,而后利用 Android、iOS 里的 SDK 解析 bundle,而后以 Native 的方式执行。git
在大型 APP 中,针对 H5 中的静态文件会设置离线包,以达到秒出加速的目的,固然离线包的增大会致使 APP 体积的增大。针对一些场景(好比营销页面等),并不但愿离线加载,这样的场景就不使用离线包进行加载。github
离线包也须要进行更新,这里就须要一套比较完善的更新机制来保证(大公司本身造,小公司找有没有开源的)。npm
热更新指的就是离线包(React Native 中的 bundle)更新的这个机制。咱们能够在 APP 运行的过程当中 “偷偷” 下载 bundle,而后在下次 APP 开启的时候(或者某种自定义的时机,好比:弹窗提示、直接重启等)使用新的 bundle。react-native
如何去维护这样一个比较完善的更新机制呢?Microsoft 给出了一个很好的答案 —— CodePush。幸运地,他还没被“墙“,咱们能够直接的使用他的服务。
CodePush 集成了微软的一个云服务器,他至关因而一个中心发布器,APP 能够询问他是否有新的 bundle 更新,而后进行下载等操做。
CodePush 官方提供了 React Native 的集成方案,能够比较容易的集成到原有代码中(固然,也存在一些坑或者其余的地方,这也是原本出现的目的之一)。
CodePush 的官网在 这。
npm install -g code-push-cli复制代码
code-push register复制代码
会自动打开浏览器弹出注册界面,注册完成以后会获得一个 token,复制后填写在 Terminal 里面便可。
code-push app add WeHIT-Android
code-push app add WeHIT-iOS复制代码
每一个 APP 都会获得 Production
与 Production
状态的两个 Key:
为何要注册两个 APP
CodePush 在发布新 bundle 的时候目前只能一个一个平台发,为了保证你的 history 看起来不是那么的乱,建议直接分红两个 APP 处理
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复制代码
在 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 端是但愿这个更新是用户无感知的(上面这种就行),默默的在后台更新好而且使用最新的便可。
在上面使用 rnpm link 以后,其实大部分的问题都已经基本解决了。
这里你须要打开 git diff 看一下 rnpm link 有没有作一些 ”坏事“ 可能会对你的代码形成影响。
这里咱们发现他改变了下面几个文件:
重点看一下是在可辨识的范围内修改了 Info.plist
,在里面加入了 key 方便调用。
除此以外还重点修改了一下 AppDelegate.m
:
这是指在 debug 包中继续以前的老套路远程加载 bundle,在 release 模式中托管给 CodePush 管理 bundle 的加载。
对,这和咱们以前预想的彻底同样。
接着,用 Xcode 打开 iOS 工程,检查一下版本号是否是三位,若是不是的话,修改成三位(好比:1.0.0):
为何要修改成三位
CodePush 只支持三位的,否则在推包的时候没法肯定推包给哪一个版本。
这部分你能够先跳过,直接看 build 部分,若是有问题再回开看。
我在 build 的时候发生了错误,log 以下:
这看起来时测试出了问题,忽略掉他便可。
commmand + shift + ,
,在弹出的框里面选择 build,而后取消掉 Test 部分的勾。
而后继续 build。
在以前的 AppDelegate.m
中,咱们代码的意图是在 debug
模式下经过远程加载 bundle,在 release
模式下用 CodePush 来处理。这样保证了咱们在开发的时候不受 CodePush 的影响,因此要测试 CodePush,就打在 release
包。
command + shift + ,
,在 run 里面 Build Configuration
里面选择 release。
而后 command + R
或者点击按钮进行打包。
理论上你会注意到打了一个不须要加载远程 bundle 的包。
如何打包到物理机上
这里不作讨论,只作简单的说明:首先须要一个开发者帐号,在项目工程的
Singning
里面进行配置(不想买的话能够去淘宝看看),而后插上手机以后进行 build。和其余 iOS 工程打包没有什么区别。
和 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#9336 和 stackoverflow。
不要使用 Android Studio 启动,使用:
react-native run-android
react-native start --reset-cache复制代码
很好,能跑起来了!
如今已经打了 debug 的包,那如何打一个 release 包来测试咱们的 CodePush 呢?
首先咱们须要一个签名,关于这个签名,咱们在开发的时候其实是使用了 Android Studio 自带的一个 debug 签名,在打 release 包的时候,咱们就须要本身签名了:
以下图,在 build 里面选择 Generator Singed Apk:
而后点击 Create New...
:
而后填写即将生成的 patch 路径,密码,Alias的名字,密码,还有一些公司我的相关的东西:
OK,看看你填写的证书路径里是否有一个 xx.jks 的证书了。经过这个证书,咱们就能够获得 App 的 Sha1 等用于一些第三方 SDK,更多细节能够参考 获取Android SHA1 、生成jks密钥、签名Apk
理论上在刚才的基础下点击下一步,选择 Release
包就能获得 Release 包了,但用:
adb install ./Android/app-release.apk复制代码
安装以后,打开会直接 crash(须要先卸载以前的 APP),解压 APK,发现 asset 里面没有和 CodePush 有关的任何东西,猜想多是这里引发 Crash。
因此这种打包方式不能用。
部分解释参见: www.jianshu.com/p/1cff76e20…
找到以前生成的证书,复制到 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,因此仍是用命令行打包吧.
![]()
在运行模拟器的时候,提示:
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 这种虚拟机在跑,关掉便可正常打开模拟器。
改完代码后,咱们须要更新 bundle,这样就须要咱们打包而后把 bundle 推送到 CodePush 服务器上。
在项目根目录执行:
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.复制代码
表示已经成功。
上面的一步流是把 打包和推送 结合到了一块儿,从 log 中也能简单看到先是执行打包,而后在 Push 的。
在 APP 的测试包中,咱们可能但愿能保留开发 Log 等,或者说咱们想比较好的自定义 history 等。这样就须要咱们手动的两次进行打包和推送了。
首先咱们建立一个 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
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”复制代码
code-push deployment history WeHIT-Android Staging复制代码
若是不想看这么多的版本,能够用:
code-push deployment ls WeHIT-Android -k复制代码
同时,你会发现装了 release 包的客户端会自动更新。
若是以前没有客户端的开发经验,配置 CodePush SDK 仍是有点复杂,若是你在配置过程当中遇到了什么问题,或者发现文章中有错误,欢迎指出。
与文章的互动请去原文 github.com/rccoder/blo…