React-Native CodePush 之 配置Android
使用RN開發(fā)項(xiàng)目雖然已經(jīng)開發(fā)了很久了,但親自操手配置 CodePush 還是第一次,話不多說(shuō)開始今天的 CodePush 配置之旅;
簡(jiǎn)介
CodePush 是由 Microsoft 提供的一個(gè)用于實(shí)現(xiàn) React Native 應(yīng)用程序熱更新的解決方案。它允許你在不通過(guò)應(yīng)用商店發(fā)布新版本的情況下,將更新推送到現(xiàn)有的安裝應(yīng)用程序。以下是 CodePush 的一些關(guān)鍵概念和用法:
- 部署環(huán)境: 在 CodePush 中,你可以創(chuàng)建不同的部署環(huán)境,例如 "Staging" 和 "Production"。每個(gè)環(huán)境都有自己的部署密鑰和更新。這有助于分離測(cè)試和生產(chǎn)環(huán)境的更新。
- 部署密鑰: 每個(gè)部署環(huán)境都有一個(gè)唯一的部署密鑰,用于標(biāo)識(shí)該環(huán)境的更新。你需要在應(yīng)用程序中配置正確的部署密鑰,以獲取相應(yīng)環(huán)境的更新。
- 發(fā)布更新: 使用 code-push release-react 命令發(fā)布新的更新。該命令可以將 React Native 項(xiàng)目的 JavaScript bundle 和資源文件上傳到 CodePush 服務(wù)器。
開始實(shí)操
開發(fā)文檔傳送門。
1. 需要在項(xiàng)目的根目錄使用npm 下載依賴包
npm install --save react-native-code-push
2. 需要更改android的原生代碼
1)android/settings.gradle在 文件中,進(jìn)行以下添加
include ':app', ':react-native-code-push'
project(':react-native-code-push').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-code-push/android/app')
2)在文件中 android/app/build.gradle ,將 codepush.gradle 文件添加為其他生成任務(wù)定義
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
3)MainApplication.java 更新文件,則通過(guò)以下更改使用 CodePush:
// 1. 導(dǎo)入插件
import com.microsoft.codepush.react.CodePush;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
...
// 2. 重寫 getJSBundleFile 方法以讓 CodePush運(yùn)行時(shí)確定從那里獲取JS
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
};
}
添加部署密鑰
先去后臺(tái)創(chuàng)建app 微軟管理平臺(tái)(備注:創(chuàng)建完成app之后,會(huì)自動(dòng)生成Staging和Production環(huán)境)
第一步 - 使用 appcenter-cli,進(jìn)行創(chuàng)app環(huán)境密鑰
安裝 App Center CLI:
npm install -g appcenter-cli
第二步 - 登入
// 回車之后,按照提示輸入yes,然后會(huì)自動(dòng)打開瀏覽給出密碼,只需要復(fù)制粘貼回終端再回車就OK了
appcenter login
第三步 - 獲取 owner名稱 和 app名稱
appcenter apps list
第四步 - 選擇你要的配置的app項(xiàng)目,進(jìn)行創(chuàng)建SIT和PROD環(huán)境的密鑰(一定要核對(duì)好 owner名稱 和 app名稱的單詞大小寫)
// appcenter codepush release-react -a jackteng666-gmail.com/MyApp -d Staging SIT環(huán)境(測(cè)試環(huán)境)
范例:
appcenter codepush release-react -a 123456-@gamil.com/MyApp -d Staging
// appcenter codepush release-react -a 123456-@gamil.com/MyApp -d Production PROD環(huán)境(生產(chǎn)環(huán)境)
范例:
appcenter codepush release-react -a 123456-@gamil.com/MyApp -d Production
第五步 - 獲取密鑰
appcenter codepush deployment list --app 123456-@gamil.com/MyApp -k
不出意外的話,到這一步已經(jīng)創(chuàng)建好兩個(gè)環(huán)境的密鑰并且返回兩個(gè)環(huán)境的密鑰,接下來(lái)就是在app中配置密鑰

密鑰的配置分兩種形式,a. 固定環(huán)境 b.多種環(huán)境,這些根據(jù)自己的業(yè)務(wù)需求選擇
a. 固定環(huán)境:
在 android/app/src/main/res/values/strings.xml 進(jìn)行添加
<resources>
<string name="app_name">AppName</string>
<string moduleConfig="true" name="CodePushDeploymentKey">替換自己需要的環(huán)境密鑰</string>
</resources>
b.多種環(huán)境
在 android/app/build.gradle 進(jìn)行添加
android {
...
buildTypes {
debug {
...
resValue "string", "CodePushDeploymentKey", '""'
...
}
releaseStaging {
...
resValue "string", "CodePushDeploymentKey", '"<INSERT_STAGING_KEY>"'
matchingFallbacks = ['release']
...
}
release {
...
resValue "string", "CodePushDeploymentKey", '"<INSERT_PRODUCTION_KEY>"'
...
}
}
...
}
到這一步app密鑰已經(jīng)配置好了,接下來(lái)就是寫js代碼進(jìn)行發(fā)布和測(cè)試熱更新
詳細(xì)介紹可以看這里API Reference
import CodePush from 'react-native-code-push';
...
export default CodePush(App);
如果不想在啟動(dòng)app的時(shí)候,檢查的話,也可以單獨(dú)寫成組建,在需要的地方進(jìn)行加載;
import React, { useState, useEffect } from "react";
import codePush from "react-native-code-push";
const CheckUpdate = () => {
/** 執(zhí)行CodePush更新 */
const processCodepushUpdate = () => {
codePush.notifyAppReady(); // 加這行避免自動(dòng)rollback, https://docs.microsoft.com/zh-tw/appcenter/distribution/codepush/rn-api-ref#codepushnotifyappready
codePush
.sync(
{
installMode: codePush.InstallMode.ON_NEXT_RESUME, // 非強(qiáng)制:下次喚起時(shí)安裝新版本
mandatoryInstallMode: codePush.InstallMode.ON_NEXT_RESUME, // 強(qiáng)制:下次喚起時(shí)安裝新版本
minimumBackgroundDuration: 0, // 背景停留多久后觸發(fā)按照新版本(預(yù)設(shè)0)
rollbackRetryOptions: { delayInHours: 1, maxRetryAttempts: 50 }, // rollback重試設(shè)定
},
(status) => {
// codePush.SyncStatus.CHECKING_FOR_UPDATE 0 : 正在查詢 CodePush 服務(wù)器是否有更新。
// codePush.SyncStatus.AWAITING_USER_ACTION 1 : 有更新可用,并向最終用戶顯示確認(rèn)對(duì)話框。(僅在updateDialog使用時(shí)適用)
// odePush.SyncStatus.DOWNLOADING_PACKAGE 2 : 在從 CodePush 服務(wù)器下載可用更新。
// codePush.SyncStatus.INSTALLING_UPDATE 3 : 已下載可用更新并將安裝
// codePush.SyncStatus.UP_TO_DATE 4 :應(yīng)用程序已完全更新到配置的部署
// codePush.SyncStatus.UPDATE_IGNORED 5 : 應(yīng)用程序有一個(gè)可選更新,最終用戶選擇忽略該更新。(僅在updateDialog使用時(shí)適用)
// codePush.SyncStatus.UPDATE_INSTALLED 6 : 已安裝可用更新,并將在syncStatusChangedCallback函數(shù)返回后立即運(yùn)行或在下次應(yīng)用程序恢復(fù)/重新啟動(dòng)時(shí)運(yùn)
// codePush.SyncStatus.SYNC_IN_PROGRESS 7: 正在進(jìn)行的sync操作阻止當(dāng)前調(diào)用的執(zhí)行。
// codePush.SyncStatus.UNKNOWN_ERROR -1 : 同步操作發(fā)現(xiàn)未知錯(cuò)誤。
switch (status) {
case codePush.SyncStatus.DOWNLOADING_PACKAGE:
console.log("在從 CodePush 服務(wù)器下載可用更新");
break;
case codePush.SyncStatus.INSTALLING_UPDATE:
console.log("有更新可用,已下載可用更新并將安裝");
break;
case codePush.SyncStatus.UPDATE_INSTALLED:
console.log("已安裝可用更新,并將在syncStatusChangedCallback函數(shù)返回后立即運(yùn)行或在下次應(yīng)用程序恢復(fù)/重新啟動(dòng)時(shí)運(yùn)");
codePush.restartApp();
break;
}
},
({ receivedBytes, totalBytes }) => {
// 計(jì)算出下載百分比
const percent = Math.floor((receivedBytes / totalBytes) * 100);
}
)
.catch((error) => {
console.log(JSON.stringify(error))
});
};
useEffect(() => {
processCodepushUpdate();
}, []);
return ...
};
export default CheckUpdate;
到這一步熱更新框架基本上完成了,接下來(lái)就是測(cè)試熱更新
發(fā)布版本指令:
當(dāng)前發(fā)布的是SIT環(huán)境,PROD環(huán)境同理
appcenter codepush release-react -a 123456-@gamil.com/MyApp -t "*" -d Staging --description "v1.0.0 測(cè)試更新" --sourcemap-output --output-dir ./build/android
備注:
-t 是指目標(biāo)版本,意思是只有是指定的目標(biāo)版本才會(huì)觸發(fā)更新,比如說(shuō),現(xiàn)在你的用戶用的版本是1.1.1 和 1.0.0這兩種版本,那么 -t "1.1.1" 就是發(fā)布之后,只有版本為1.1.1的用戶才會(huì)觸發(fā)熱更新
如果不需要指定目標(biāo)版本就輸入 -t "*" 通配符為全部版本 或者不輸入 -t
當(dāng)終端返回以下內(nèi)容,則表示已經(jīng)發(fā)布成功了(SIT環(huán)境)
Successfully released an update containing the "build/android/CodePush" directory to the "Staging" deployment of the "MyApp" app.
查看發(fā)布?xì)v史
appcenter codepush deployment history -a 123456-@gamil.com/MyApp Staging

到這里最新版本的代碼已經(jīng)發(fā)布到codPush服務(wù)器上了,接下來(lái)就可以用已經(jīng)有熱更新代碼版本的app來(lái)測(cè)試了;
最簡(jiǎn)單的方式就是
1、把當(dāng)前版本打包給Android手機(jī)安裝,然后再改代碼(能夠明顯看到不一樣的界面,比較好分辨是否更新成功),然后再重新發(fā)布一次;
2、重新打開APP檢查是否有更新提示(上面的組建,可以根據(jù)自己的想法寫出彈窗和顯示進(jìn)度條)
我當(dāng)前遇到一個(gè)錯(cuò)誤狀態(tài)碼是 codePush.SyncStatus.UNKNOWN_ERROR -1 : 同步操作發(fā)現(xiàn)未知錯(cuò)誤。,這個(gè)錯(cuò)誤會(huì)有很多種情況,比較難查。我的app上導(dǎo)致這個(gè)狀態(tài)的原因是:
{
"nativeStackAndroid":[{"lineNumber":49,"file":"CodePush.java","methodName":"h","class":"com.microsoft.codepush.react.a"},{"lineNumber":25,"file":"CodePushNativeModule.java","methodName":"a","class":"com.microsoft.codepush.react.CodePushNativeModule$c"},{"lineNumber":3,"file":"CodePushNativeModule.java","methodName":"doInBackground","class":"com.microsoft.codepush.react.CodePushNativeModule$c"},{"lineNumber":389,"file":"AsyncTask.java","methodName":"call","class":"android.os.AsyncTask$3"},{"lineNumber":266,"file":"FutureTask.java","methodName":"run","class":"java.util.concurrent.FutureTask"},{"lineNumber":1167,"file":"ThreadPoolExecutor.java","methodName":"runWorker","class":"java.util.concurrent.ThreadPoolExecutor"},{"lineNumber":641,"file":"ThreadPoolExecutor.java","methodName":"run","class":"java.util.concurrent.ThreadPoolExecutor$Worker"},{"lineNumber":929,"file":"Thread.java","methodName":"run","class":"java.lang.Thread"}],
"userInfo":null,
"message":"Error in getting binary resources modified time",
"code":"EUNSPECIFIED"
}
解決方式是在 android/app/build.gradle 的 defaultConfig 中增加:
原文
resValue 'string', "CODE_PUSH_APK_BUILD_TIME", String.format("\"%d\"", System.currentTimeMillis())
然后重新編譯打包給Android機(jī)安裝,然后再重新改代碼,進(jìn)行發(fā)布測(cè)試熱更新
總結(jié):
1、使用 npm 安裝 react-native-code-push 依賴
2、更改android的原生代碼進(jìn)行codePush連線
3、使用 微軟管理平臺(tái) 創(chuàng)建app
4、使用 appcenter-cli 進(jìn)行指令管控
5、使用 react-native-code-push 進(jìn)行編碼更新
6、發(fā)布更新測(cè)試
7、解決更新失敗的問(wèn)題
以上是codePush一個(gè)簡(jiǎn)單實(shí)用,如想更詳細(xì)更多功能,建議查看官網(wǎng)文檔;