什么是CodePush
CodePush是一個(gè)微軟開(kāi)發(fā)的云服務(wù)器。通過(guò)它,開(kāi)發(fā)者可以直接在用戶(hù)的設(shè)備上部署手機(jī)應(yīng)用更新。CodePush相當(dāng)于一個(gè)中心倉(cāng)庫(kù),開(kāi)發(fā)者可以推送當(dāng)前的更新(包括JS/HTML/CSS/IMAGE等)到CoduPush,然后應(yīng)用將會(huì)查詢(xún)是否有更新。
接入流程
- 安裝 CodePush CLI
- 注冊(cè) CodePush賬號(hào)
- 在CodePush服務(wù)器注冊(cè)App
- RN代碼中集成CodePush
- 原生應(yīng)用中配置CodePush
- 發(fā)布更新的版本
1. 安裝 CodePush CLI
安裝CodePush指令,直接在終端上輸入如下命令即可,注意:這個(gè)CodePush指令只需要全局安裝一次即可,如果第一次安裝成功了,那后面就不在需要安裝
npm install -g code-push-cli

2、注冊(cè) CodePush賬號(hào)
注冊(cè)CodePush賬號(hào)也很簡(jiǎn)單,同樣是只需簡(jiǎn)單的執(zhí)行下面的命令,同樣這個(gè)注冊(cè)操作也是全局只需要注冊(cè)一次即可
code-push register
注意:當(dāng)執(zhí)行完上面的命令后,會(huì)自動(dòng)打開(kāi)一個(gè)授權(quán)網(wǎng)頁(yè),讓你選擇使用哪種方式進(jìn)行授權(quán)登錄,這里我們統(tǒng)一就選擇使用GitHub即可

當(dāng)注冊(cè)成功后,CodePush會(huì)給我們一個(gè)key

我們直接復(fù)制這個(gè)key,然后在終端中將這個(gè)key填寫(xiě)進(jìn)去即可,填寫(xiě)key登錄成功顯示效果如下

我們使用下面的命令來(lái)驗(yàn)證我的登錄是否成功
code-push login

CodePush注冊(cè)登錄相關(guān)命令:
- code-push login 登陸
- code-push loout 注銷(xiāo)
- code-push access-key ls 列出登陸的token
- code-push access-key rm <accessKye> 刪除某個(gè) access-key
3、在CodePush服務(wù)器注冊(cè)App
為了讓CodePush服務(wù)器有我們的App,我們需要CodePush注冊(cè)App,輸入下面命令即可完成注冊(cè),這里需要注意如果我們的應(yīng)用分為iOS和Android兩個(gè)平臺(tái),這時(shí)我們需要分別注冊(cè)兩套key
應(yīng)用添加成功后就會(huì)返回對(duì)應(yīng)的production 和 Staging 兩個(gè)key,production代表生產(chǎn)版的熱更新部署,Staging代表開(kāi)發(fā)版的熱更新部署,在ios中將staging的部署key復(fù)制在info.plist的CodePushDeploymentKey值中,在android中復(fù)制在Application的getPackages的CodePush中
添加iOS平臺(tái)應(yīng)用
code-push app add iOSrn_app ios react-native

添加Android平臺(tái)應(yīng)用
code-push app add Androidrn_app Android react-native

我們可以輸入如下命令來(lái)查看我們剛剛添加的App
code-push app list

CodePush管理App的相關(guān)命令:
- code-push app add 在賬號(hào)里面添加一個(gè)新的app
- code-push app remove 或者 rm 在賬號(hào)里移除一個(gè)app
- code-push app rename 重命名一個(gè)存在app
- code-push app list 或則 ls 列出賬號(hào)下面的所有app
- code-push app transfer 把a(bǔ)pp的所有權(quán)轉(zhuǎn)移到另外一個(gè)賬號(hào)
- code-push release-react <appName> <platform> -t 版本 -d 環(huán)境 --des 描述 -m true (強(qiáng)制更新)// 發(fā)布
- code-push deployment clear <appName> Production or Staging // 清除歷史部署記錄
- code-push rollback <appName> Production --targetRelease v4(codepush服務(wù)部署的版本號(hào)) // 回滾
// 應(yīng)用信息相關(guān)
- code-push deployment add <appName> 部署
- code-push deployment rm <appName> 刪除部署
- code-push deployment rename <appName> 重命名
- code-push deployment ls <appName> 列出應(yīng)用的部署情況
- code-push deployment ls <appName> -k 查看部署的key
- code-push deployment history <appName> <deploymentName> 查看歷史版本
4、RN代碼中集成CodePush
首先我們需要安裝CodeoPush組件,然后通過(guò)link命令添加原生依賴(lài),最后在RN根組件中添加熱更新邏輯代碼
安裝組件
npm install react-native-code-push --save

添加原生依賴(lài),這里添加依賴(lài)我們使用自動(dòng)添加依賴(lài)的方式(RN0.60版本以上的 cli里集成了AutoLink,不用手動(dòng)link資源了)
react-native link react-native-code-push

我們?cè)赗N項(xiàng)目的根組件(例如App.js)中添加熱更新邏輯代碼如下
~
import codePush from "react-native-code-push";
const codePushOptions = { checkFrequency: codePush.CheckFrequency.MANUAL };
export default class App extends Component<{}> {
componentDidMount(){
codePush.sync({
updateDialog: true,
installMode: codePush.InstallMode.IMMEDIATE,
mandatoryInstallMode:codePush.InstallMode.IMMEDIATE,
//deploymentKey為剛才生成的,用Platform判斷下平臺(tái)(填寫(xiě)你自己的)
deploymentKey: Platform.OS === 'ios'?'sYvpLUxuBU9FxICqJ5sccL2GDUPZcc988a73-c917-4dba-bd40-1837998442a6':'fqdFCqLyL4XclNZjWvNN3KNhImR5cc988a73-c917-4dba-bd40-1837998442a6',
});
}
~
5,模擬器上運(yùn)行項(xiàng)目
react-native run-ios
react-native run-android
5.1 如圖下所顯打包過(guò)程中可能遇到的

錯(cuò)誤發(fā)生在java編譯器執(zhí)行過(guò)程中


看到出來(lái)是剛剛引入的Code-Push包,實(shí)例化的時(shí)候getString方法獲取我們之前的DeploymentKey失敗.
getString是android 開(kāi)發(fā)中Context類(lèi)上的一個(gè)獲取字符串的方法,在RN中會(huì)從根目錄下/android/app/src/main/res/values/strings.xml文件中獲取值。
我們打開(kāi)strings.xml文件后添加這樣一行代碼:
<string moduleConfig="true" name="CodePushDeploymentKey"><string moduleConfig="true" name="CodePushDeploymentKey">xxxxxxx你剛剛注冊(cè)DeploymentKey</string></string>
例如

再次react-native run-android即可成功運(yùn)行
6. deployment-key的設(shè)置與獲取bundle路徑 (關(guān)于Grande)
- 在上述代碼中我們?cè)趧?chuàng)建CodePush實(shí)例的時(shí)候需要設(shè)置一個(gè)deployment-key,因?yàn)閐eployment-key分生產(chǎn)環(huán)境與測(cè)試環(huán)境兩種,所以建議大家在build.gradle中進(jìn)行設(shè)置。在build.gradle中的設(shè)置方法如下:
打開(kāi)android/app/build.gradle文件,找到android { buildTypes {} }然后添加如下代碼即可:
buildTypes {
release {
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
signingConfig signingConfigs.config
+ buildConfigField "String", "CODE_PUSH_KEY", CODE_PUSH_KEY_PRODUCTION
+ buildConfigField "boolean", "IS_CODE_PUSH", "true"
}
debug {
+ buildConfigField "String", "CODE_PUSH_KEY", CODE_PUSH_KEY_STAGING
+ buildConfigField "boolean", "IS_CODE_PUSH", "true"
}
}
心得:另外,我們也可以將deployment-key存放在gradle.properties中:
CODE_PUSH_KEY_PRODUCTION="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
CODE_PUSH_KEY_STAGING="XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"

在android/app/build.gradle設(shè)置好deployment-key之后呢,我們就可以這樣使用了:
MainApplication.java下的ReactNativeHost方法中添加
@Override
protected String getJSBundleFile() {
// gradlew assembleRelease會(huì)把所有用到的JavaScript代碼都打包內(nèi)置到APK中
if (BuildConfig.IS_CODE_PUSH) {
return CodePush.getJSBundleFile(); // code-push熱更新
}
//return UpdateContext.getBundleUrl(MainApplication.this); // pushy熱更新
}
7.修改versionName和引入code-push設(shè)置。
在 android/app/build.gradle中有個(gè) android.defaultConfig.versionName屬性,我們需要把 應(yīng)用版本改成 1.0.0(默認(rèn)是1.0,但是codepush需要三位數(shù))。
android{
defaultConfig{
versionName "1.0.0"
}
}
//文件最下方
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
\android\app\src\main\java\com\ysty_app3\MainApplication.java添加包
import com.microsoft.codepush.react.CodePush;
至此Code Push for Android的SDK已經(jīng)集成完成。
8、發(fā)布更新的版本
在使用之前需要考慮的是檢查更新時(shí)機(jī),更新是否強(qiáng)制,更新是否要求即時(shí)等
更新時(shí)機(jī)
一般常見(jiàn)的應(yīng)用內(nèi)更新時(shí)機(jī)分為兩種,一種是打開(kāi)App就檢查更新,一種是放在設(shè)置界面讓用戶(hù)主動(dòng)檢查更新并安裝
- 打開(kāi)APP就檢查更新
最為簡(jiǎn)單的使用方式在React Natvie的根組件的componentDidMount方法中通過(guò)
codePush.sync()(需要先導(dǎo)入codePush包:import codePush from 'react-native-code-push')方法檢查并安裝更新,如果有更新包可供下載則會(huì)在重啟后生效。不過(guò)這種下載和安裝都是靜默的,即用戶(hù)不可見(jiàn)。如果需要用戶(hù)可見(jiàn)則需要額外的配置。具體可以參考codePush官方API文檔,部分代碼,完整代碼請(qǐng)參照文檔上面
codePush.sync({
updateDialog: true,//code-push 接管更新
installMode: codePush.InstallMode.IMMEDIATE,
mandatoryInstallMode:codePush.InstallMode.IMMEDIATE,
//deploymentKey為剛才生成的,用Platform判斷下平臺(tái)
deploymentKey: Platform.OS === 'ios'?'IOSKey':'andoridKey',
});
上面的配置在檢查更新時(shí)會(huì)彈出提示對(duì)話框, mandatoryInstallMode表示強(qiáng)制更新,
- 用戶(hù)點(diǎn)擊檢查更新按鈕
在用戶(hù)點(diǎn)擊檢查更新按鈕后進(jìn)行檢查,如果有更新則彈出提示框讓用戶(hù)選擇是否更新,如果用戶(hù)點(diǎn)擊立即更新按鈕,則會(huì)進(jìn)行安裝包的下載(實(shí)際上這時(shí)候應(yīng)該顯示下載進(jìn)度,這里省略了)下載完成后會(huì)立即重啟并生效(也可配置稍后重啟),部分代碼如下
codePush.checkForUpdate(deploymentKey).then((update) => {
if (!update) {
Alert.alert("提示", "已是最新版本--", [
{
text: "Ok", onPress: () => {
console.log("點(diǎn)了OK");
}
}
]);
} else {
codePush.sync({
deploymentKey: deploymentKey,
updateDialog: {
optionalIgnoreButtonLabel: '稍后',
optionalInstallButtonLabel: '立即更新',
optionalUpdateMessage: '有新版本了,是否更新?',
title: '更新提示'
},
installMode: codePush.InstallMode.IMMEDIATE,
},
(status) => {
switch (status) {
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
console.log("DOWNLOADING_PACKAGE");
break;
case codePush.SyncStatus.INSTALLING_UPDATE:
console.log(" INSTALLING_UPDATE");
break;
}
},
(progress) => {
console.log(progress.receivedBytes + " of " + progress.totalBytes + " received.");
}
);
}
}
更新是否要求即時(shí)
在更新配置中通過(guò)指定installMode來(lái)決定安裝完成的重啟時(shí)機(jī),亦即更新生效時(shí)機(jī)
codePush.InstallMode.IMMEDIATE :安裝完成立即重啟更新
codePush.InstallMode.ON_NEXT_RESTART :安裝完成后會(huì)在下次重啟后進(jìn)行更新
-
codePush.InstallMode.ON_NEXT_RESUME :安裝完成后會(huì)在應(yīng)用進(jìn)入后臺(tái)后重啟更新
如何發(fā)布CodePush更新包
工程根目錄新增 bundles文件夾:
mkdir bundles
1.自動(dòng)生成bundles文件發(fā)布:
code-push release-react 《app名稱(chēng)》《平臺(tái)》 -t 《版本號(hào)》 -d Production --des "描述" -m true
例如
code-push release-react weather android --t 1.0.0 -d Production --dev false --des "1.優(yōu)化操作流程,2.我裂開(kāi)" --m false
2.手動(dòng)生成bundles文件發(fā)布, 首先在根目錄創(chuàng)建bundles文件夾(每次重新生成文件時(shí)需刪除上次的文件)
- 單js文件
1. 創(chuàng)建bundles里的文件
打包命令
react-native bundle --platform 平臺(tái) --entry-file 啟動(dòng)文件 --bundle-output 打包js輸出文件 --assets-dest 資源輸出目錄 --dev 是否調(diào)試
打包整個(gè)項(xiàng)目的js文件,例如:
react-native bundle --platform android --entry-file index.js --bundle-output ./bundles/index.android.bundle --dev false
2. 發(fā)布更新
發(fā)布命令:
code-push release <應(yīng)用名稱(chēng)> <Bundles所在目錄> <對(duì)應(yīng)的應(yīng)用版本> --deploymentName 更新環(huán)境 --description 更新描述 --mandatory 是否強(qiáng)制更新
例如:
code-push release CodePushDemo-android ./bundles/index.android.bundle 1.0.0 --deploymentName Production --description "熱更新" --mandatory true
- js文件+圖片資源,–assets-dest 后就是放圖片的文件夾路徑
打包命令:–assets-dest 后就是放圖片的文件夾路徑
react-native bundle --platform android --entry-file index.js --bundle-output ./bundles/index.android.bundle --assets-dest ./bundles --dev false
發(fā)布bundles文件:
code-push <release/debug> <projectName(與注冊(cè)的app同名)><bundle文件名> <版本號(hào)> --deploymentName 更新環(huán)境 --description 更新描述 --mandatory 是否強(qiáng)制更新
例如:
code-push release CodePushDemo-android ./bundles 1.0.0 //省略默認(rèn)是發(fā)布Staging
code-push release CodePushDemo-android ./bundles 1.0.0 -d Production --des "熱更新" --mandatory true
需要注意的是:
- 輸出的bundle文件名不叫其他,而是 index.android.bundle,是因?yàn)?在debug模式下,
工程讀取的bundle就是叫做 index.android.bundle。 - 平臺(tái)可以選擇 android 或者 ios。
查看發(fā)布信息
查看已a(bǔ)dd appName
code-push app list
查看部署的歷史版本信息
code-push deployment ls CodePushDemo-android -k
9,自定義更新窗口
我們?cè)俅未蜷_(kāi) app是就可以看到,code-push彈出的 更新提示彈框

這是code-push提供的彈窗,如果需要自定義文字
修改更新彈出框內(nèi)容
進(jìn)入node_modules->react-native-code-push->Codepush.js修改以下內(nèi)容

需要更多自定義設(shè)置參考https://blog.csdn.net/weixin_42613755/article/details/104964557
可能出現(xiàn)的問(wèn)題(react-native run-android)
- What went wrong:
Execution failed for task ':app:mergeDebugResources'.
Could not read path 'E:\cli3\weather2\android\app\build\intermediates\incremental\mergeDebugResources\merged.dir\values'.
cd android 執(zhí)行 gradlew clean解決 - [Error:Execution failed for task ':app:transformClassesWithDexForDebug]
在項(xiàng)目android/app/build.gradle下添加
defaultConfig {
...
multiDexEnabled true
}
本片內(nèi)容借鑒至https://segmentfault.com/a/1190000016273902?utm_source=tag-newest
可訪問(wèn) 查閱關(guān)于ios端的熱更新