零客戶端開發經驗 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、參考文章

相關文章
相關標籤/搜索