1. 程式人生 > >React Native 專案整合 CodePush 全然指南

React Native 專案整合 CodePush 全然指南

作者 | 錢凱

640?wx_fmt=jpeg

杏仁移動開發project師,前嵌入式project師。關注大前端技術新潮流。


本文使用的環境:

Why CodePush?

CodePush 是微軟提供的一個熱更新前後臺方案。它對 React Native 專案有非常好的支援。

眼下針對 React Native 的 hot update 方案有很多。可是 CodePush 是最成熟穩定的方案,它最大的特點是提供了完整的後臺工具。它基本的長處是:

  • 微軟出品,大廠保證

  • 良好的多環境支援(Testing。Staging, Production)

  • 灰度公佈、自己主動回滾等等特性

  • 良好的資料統計支援:下載、安裝、出錯一目瞭然

  • 強大的 CLI 工具,一個終端搞定所有流程

因為 React Native 執行的是指令碼 js 檔案,對熱更新有天然的親和,有餘力的團隊能夠嘗試實現自己的框架,一個簡單的實現思路是:

  • 改動載入 jsBundle 的程式碼,轉而從指定的本地儲存位置去載入。假設沒有。下載 bundle, 而且本次開啟使用 app 包中的 bundle。

  • 假設找到 jsBundle 檔案。呼叫 api 比較版本號號,假設不一致,則從指定server下載最新的 bundle 進行替換。

  • 通過反射呼叫私有方法。在下載完畢的回撥中更新執行時資源。從而能馬上看到更新的效果。

  • 使用相似 google-diff-match-patch 的 diff 工具。生成差異化補丁。不必下載完整 bundle,從而大大減小補丁包體積。


網上有非常多資料和原始碼。這裡就不細述了。

後臺配置

為了使用 Code Push 公佈熱更新,我們須要向微軟服務註冊我們的應用。

這部分工作微軟提供了強大的命令列工具:CodePush CLI

640?</p><p>wx_fmt=png

安裝 cli 工具

npm 全域性安裝:

npm install -g code-push-cli

關聯賬號

使用命令

code-push register

註冊一個賬號,能夠直接使用 GitHub 賬號授權。完畢後將 token 複製回命令列中。

640?</p><p>wx_fmt=png

使用 whoami 檢視登入狀態:

code-push whoami

註冊應用

登入成功後,我們註冊一個app:

code-push app add 你的App名稱 android react-native

注意一定要為 Android 和 iOS 分別註冊,兩者的更新包內容會有差異。

640?</p><p>wx_fmt=png

查詢狀態

每一個 App 有不同的執行時環境。比方 Production,Staging等,我們也能夠配置自己的環境。檢視 App 的不同環境和部署狀況:

code-push deployment ls 註冊的app名稱

640?wx_fmt=png

眼下我們還沒有公佈不論什麼更新。所以表中的狀態是空的。

到這裡就完畢了後端的基本配置。

App端配置

版本號相容

安裝 Code Push 環境前首先要 check 版本號的相容性問題,不同的RN版本號須要使用不同的 Code Push。原則上我們建議將 RN 和 CodePush 都升級到最新版本號。

下表是官方文件中的相容性說明:

React Native version(s) Supporting CodePush version(s)
<0.14 Unsupported
v0.14 v1.3 (introduced Android support)
v0.15-v0.18 v1.4-v1.6 (introduced iOS asset support)
v0.19-v0.28 v1.7-v1.17 (introduced Android asset support)
v0.29-v0.30 v1.13-v1.17 (RN refactored native hosting code)
v0.31-v0.33 v1.14.6-v1.17 (RN refactored native hosting code)
v0.34-v0.35 v1.15-v1.17 (RN refactored native hosting code)
v0.36-v0.39 v1.16-v1.17 (RN refactored resume handler)
v0.40-v0.42 v1.17 (RN refactored iOS header files)
v0.43-v0.44 v2.0+ (RN refactored uimanager dependencies)
v0.45 v3.0+ (RN refactored instance manager code)
v0.46 v4.0+ (RN refactored js bundle loader code)
v0.46-v0.53 v5.1+ (RN removed unused registration of JS modules)
v0.54-v0.55 v5.3+ (Android Gradle Plugin 3.x integration)

安裝包

使用命令:

npm info react-native-code-push

來檢視包相關資訊。

我們建議始終將RN、React以及一些相關庫升級到最新版本號。在根資料夾下使用命令:

npm install --save react-native-code-push

來安裝最新版本號的 CodePush。

也能夠參照上面的相容性表格。安裝指定版本號:

npm install --save [email protected]

project配置(Android)

假設project建立的時候比較早。可能是使用命令create-react-native-app來建立的,則須要在根資料夾執行:

npm run eject

來改變project結構。防止後面的相容性問題。

配置安卓project,官方提供了兩種途徑:

  • 使用命令列工具rnpm(如今已經被整合到React Native CLI工具中了)。

    執行

react-native link react-native-code-push
  • 手動配置

假設你是新手,或者對 gradle、安卓project結構不瞭解,我們強烈建議執行一次手動配置,幫助理解究竟發生了什麼。

手動配置

step 1

android/settings.gradle檔案里加入:

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

這個檔案定義了哪些 module 應該被加入到編譯過程。對於單個 module 的專案能夠不用須要這個檔案,可是對於 multiModule 的專案我們就須要這個檔案。否則 gradle 不知道要載入哪些專案。

這個檔案的程式碼在初始化階段就會被執行。

我們加入的內容告訴 gradle:去 node_modules 資料夾下的 react-native-code-push 載入 CodePush 子專案。

step 2

 android/app/build.gradle 中的 dependencies 方法中加入依賴:

...
dependencies {
    ...
    compile project(':react-native-code-push')
}

這樣就能在主project中引用到 CodePush 模組了。

step 3

繼續在 android/app/build.gradle 中,加入在編譯打包階段 CodePush 須要執行的 task 引用:

...
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
...

這段程式碼事實上就是呼叫了 project 物件的 apply 方法,傳入了一個以 from 為 key 的 map。完整寫出來就是這種:

project.apply([from: '../../node_modules/react-native-code-push/android/codepush.gradle'])

apply from 和 apply plugin的差別在於,前者是從指定 url 去載入指令碼檔案。後者則用是從倉庫拉取 plugin id 相應的二進位制執行包。

step 4

CodePush 公佈有各種環境(deployment)。預設有 Staging 和 Production,我們須要在 buildType 中配置相應的環境。而且設定 PushKey,從而讓 App 端的 CodePush RunTime 依據不同的健值來下載正確的更新包。

查詢各個環境 Key 的方法是使用上文安裝的 CLI 工具:

code-push deployment ls App名稱 -k

640?wx_fmt=png

上表中的 Deployment Key 就是相應環境的 Key 值了。

 android/app/build.gradle 中。配置 buildTypes:

buildTypes {

    // 相應Production環境
    release {
        ...
        buildConfigField "String", "CODEPUSH_KEY", '"從上述結果中複製的production值"'
        ...
    }

    // 相應Staging環境
    releaseStaging {
        // 從 release 拷貝配置,僅僅改動了 pushKey
        initWith release
        buildConfigField "String", "CODEPUSH_KEY", '"從上述結果中複製的stagingkey值"'
    }

    debug {
        buildConfigField "String", "CODEPUSH_KEY", '""'
    }
}

注意這裡不同 buildType 的命名,Staging 環境相應的 buildType 就叫releaseStaging,要符合這種命名規範。

Debug 環境儘管用不到 CodePush, 可是也要配置空的 Key 值,否則會報錯。

step 5

處理完引用關係後,我們改動 MainApplication.java,在 App 執行時啟動 CodePush 服務:

// 宣告包
import com.microsoft.codepush.react.CodePush;

public class MainApplication extends Application implements ReactApplication {

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        ...
        // 重寫 getJSBundleFile() 方法,讓 CodePush 去獲取正確的 jsBundle
        @Override
        protected String getJSBundleFile() {
            return CodePush.getJSBundleFile();
        }

        @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                new MainReactPackage(),
                // 建立一個CodePush執行時例項
                new CodePush(BuildConfig.CODEPUSH_KEY, MainApplication.this, BuildConfig.DEBUG)
                ...
            );
        }
    };
}

js端引入 Code Push

配置完專案project後,我們將 CodePush 引入到 js 端。

首先將 App 的根元件包裹在 CodePush 中:

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

AppRegistry.registerComponent('BDCRM', () => codePush(App));

CodePush 會在 App 啟動後自己主動去 check 和更新最新的版本號。我們能夠加入一些配置,讓它在進入後臺的時候也執行檢查:

let codePushOptions = { checkFrequency: codePush.CheckFrequency.MANUAL };
AppRegistry.registerComponent('BDCRM', () => codePush(codePushOptions)(App));

CodePush js端的 api 不多,我們能夠用這些 api 控制更新的一系列流程,經常使用的有:

// 檢測是否有更新包可用
codePush.checkForUpdate(deploymentKey: String = null, handleBinaryVersionMismatchCallback: (update: RemotePackage) => void): Promise<RemotePackage>;

// 獲取本地最新更新包的屬性
codePush.getCurrentPackage(): Promise<LocalPackage>;

// 重新啟動app(即使不用在 Hot Updating。也挺實用的)
codePush.restartApp(onlyIfUpdateIsPending: Boolean = false): void;

// 手動進一次更新
codePush.sync(options: Object, syncStatusChangeCallback: function(syncStatus: Number), downloadProgressCallback: function(progress: DownloadProgress), handleBinaryVersionMismatchCallback: function(update: RemotePackage)): Promise<Number>;

很多其它具體資訊見文件。

使用 CodePush CLI 公佈更新

完畢前後端的配置,打包公佈應用後,興許的改動我們就能通過 CLI 工具來公佈啦!

升級前首先要 check:

  • 應用的版本號號要有更新(app/build.gradle: defaultConfig/versionName)

  • js bundle 要有改動,Code Push 會 diff 前後版本號。假設程式碼一致會覺得是無效的更新包

開啟終端,進入到project資料夾,完整公佈命令是:

code-push release-react <appName> <platform>
[--bundleName <bundleName>]
[--deploymentName <deploymentName>]
[--description <description>]
[--development <development>]
[--disabled <disabled>]
[--entryFile <entryFile>]
[--gradleFile <gradleFile>]
[--mandatory]
[--noDuplicateReleaseError]
[--outputDir <outputDir>]
[--plistFile <plistFile>]
[--plistFilePrefix <plistFilePrefix>]
[--sourcemapOutput <sourcemapOutput>]
[--targetBinaryVersion <targetBinaryVersion>]
[--rollout <rolloutPercentage>]
[--privateKeyPath <pathToPrivateKey>]
[--config <config>]

命令引數非常多,但用途都一目瞭然。嫌每次打麻煩的話,做成指令碼也能夠。

一般來說,我們公佈應用首先會在測試環境進行穩定性測試。通過後再公佈到生產環境中:

  • 打包公佈 Staging 環境

code-push release-react 應用名 --platform android --deploymentName Staging --description "修復一些bug"

這樣。我們 Staging 環境就能夠收到更新推送啦。具體載入新 bundle 的實際。和我們在應用中配置的策略有關。上文已經介紹過了。


  • 測試 ok 後,提升(Promoting)到 Production 環境,而且進行灰度20%公佈

code-push promote 應用名 Staging Production --rollout 20%
  • 在生產環境驗證 ok,使用 patch 將灰度改動為100%,進行全網公佈:

code-push patch 應用名 Production -rollout 100%

以上就是依照 測試 - 灰度 - 所有公佈 步驟的一個典型 CodePush 公佈工作流。

整體來說,CodePush 能滿足我們灰度公佈 React Native 應用的大部分需求了,由微軟提供的server端支援能夠節省非常多工作。是一個成熟可靠的方案。假設要說缺點,可能有幾個須要考慮一下:

  • server速度。國內網路狀況可能會影響下發的成功率和效率。

  • 汙染程式碼,在 js 端必須將根節點包裹到 CodePush 模組中去,汙染了程式碼。

  • 冗餘,假設僅僅是想要簡單的下發小體積的 js bundle。CodePush 顯得太“重”,過於冗餘了。這時候用輕量化的方案更好。

總之,我們依據自己專案的須要去進行選型就好了!

很多其它細節,能夠參考文件


全文完



下面文章您可能也會感興趣:


我們正在招聘 Java project師,歡迎有興趣的同學投遞簡歷到 [email protected]


640?wx_fmt=png


杏仁技術站

長按左側二維碼關注我們。這裡有一群熱血青年期待著與您相會。