這是我在2023第十五屆 iThome 鐵人賽發表的系列文章。https://ithelp.ithome.com.tw/users/20136637/ironman/6408
前言
有些時候會遇到應用新版本剛上線就發現有bug必須馬上修復的情況,如果是大改動的話重build、重送審也就算了,但如果只是改一行程式碼、改文字、換圖片…等簡單的內容,那麼重新送審就非常浪費時間。
遇到這種情況可以用 CodePush 熱更新應用來解決。
CodePush 是一個雲服務,開發者可以將應用更新上傳到雲端存儲庫,然後 user 在打開應用時會檢查是否有最新的更新可以使用(更新策略可以自行設置),就不需要等待應用重新送審和重新下載的時間。
注意:改原生程式碼(Java, Objective-c)無法透過 CodePush 更新
APP Center 初始化
這部分 iOS 和 Android 方式都一樣所以只以 Android 為例。
先到 APP Center 註冊一個帳號並且新增一個 APP 為 Android – React Native
接著安裝 appcenter-cli 並登入 appcenter
npm install -g appcenter-cli
appcenter login
然後我們需要透過 appcenter apps set-current <ownerName>/<appName>
指令將主要 APP 設為剛剛在 APP Center 新增的 APP:
appcenter apps list
確認應用列表appcenter apps get-current
獲取主要 appappcenter apps set-current
設置主要 app
~/R/demo (main)> appcenter apps list
test/Pokedex
~/R/demo (main)> appcenter apps get-current
~/R/demo (main)> appcenter apps set-current test/Pokedex
~/R/demo (main)> appcenter apps get-current
test/Pokedex
APP Center 更新的流程大概會是從 Staging -> Production,所以預設是會有兩個 deployments,我們需要為這兩個 deployment 建立 KEY:
appcenter codepush deployment add Staging
appcenter codepush deployment add Production
建立完之後,如果要查看所有 deployment 的 key 可以使用下面這個指令:
appcenter codepush deployment list --displayKeys
react-native-code-push
APP Center 的部分設置好之後我們可以用 react-native-code-push 這個官方提供的庫來與 React Native 做連結:
npm install --save react-native-code-push
cd ios && pod install && cd ..
Android 設置
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')
android/app/src/main/java/com/<PROJECT_NAME>/MainApplication.java
添加以下:
// 1. import CodePush
import com.microsoft.codepush.react.CodePush;
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new DefaultReactNativeHost(this) {
// ...
// 2. 新增 getJSBundleFile() 在 mReactNativeHos 裡面
@Override
protected String getJSBundleFile() {
return CodePush.getJSBundleFile();
}
};
}
在 android/gradle.properties
添加 staging 和 release 的 key:
appcenter codepush deployment list --displayKeys
可以獲得 key
CODE_PUSH_STAGING_KEY=xxxxxxx
CODE_PUSH_RELEASE_KEY=xxxxxxx
接著在 android/app/build.gradle
將 staging 和 production key 分別寫在 debug, release:
def jscFlavor = 'org.webkit:android-jsc:+'
// 1. 加上這行
apply from: "../../node_modules/react-native-code-push/android/codepush.gradle"
// 2. 設置 CodePushDeploymentKey
android {
buildTypes {
// ...
debug {
resValue "string", "CodePushDeploymentKey", CODE_PUSH_STAGING_KEY
// ...
}
release {
resValue "string", "CodePushDeploymentKey", CODE_PUSH_RELEASE_KEY
// ...
}
}
}
注意:
- 網上很多教程會讓你在
strings.xml
添加 CodePushDeploymentKey,其實是不需要的,在android/app/build.gradle
這裡添加了的話記得將 strings.xml 裡面的 CodePushDeploymentKey 刪除,不然 build 的時候會失敗。- 還有些會讓你在
android/app/build.gradle
加上apply from "../../node_modules/react-native/react.gradle"
,這個也不需要,一樣會 build 會失敗。
iOS 設置
記得 Android 跟 iOS 是不同應用,所以 CodePush key 會不一樣,別用錯了。
在 ios/<project_name>/AppDelegate.mm
- 新增
#import <CodePush/CodePush.h>
- 替換
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
為return [CodePush bundleURL];
Xcode – <PROJECT_NAME> – Info – Configuration – Duplicate “Release” Configuration 輸入 Satging
切到 Build Settings – + Add User-Defined Setting
自定義 KEY 名(如:CODEPUSH_KEY
),分別輸入 CodePush Staging
和 Production
deployment 的 key:
Info.plist
– Information Property List – + CodePushDeploymentKey
:$(CODEPUSH_KEY)
使用方法
最簡單的方式是 CodePush-ify 應用的根組件,通常都是 App:
import codePush from "react-native-code-push";
const App = (): JSX.Element => {
// ...
}
export default codePush(App)
上面的設置都完成之後我們接下來要考慮的部分是:
- 設置什麼時機(多久)檢查一次更新?(例如,應用啟動、從背景切回前景、點擊按鈕檢查…等)
- 當有可用更新時該如何下載?(例如:直接自動更新、請用戶自行下載)
在什麼都不設置的情況下 CodePush 會在每次啟動應用的時候檢查更新,如果有可用更新的話它會在下一次重啟應用時自動安裝,用戶可以直接使用到熱更新後的版本而不需要重新下載應用。
若要控制檢查應用更新的時間可以設置 checkFrequecy:
let codePushOptions = {
checkFrequency: codePush.CheckFrequency.ON_APP_RESUME
}
export default codePush(codePushOptions)(App)
更多 options 請參考官方文檔:https://github.com/microsoft/react-native-code-push/blob/master/docs/api-js.md#codepushoptions
完成以上步驟之後我們先 build 一個最新的 APP 出來安裝並啟動,然後稍微修改一些 UI 部分再進行下面的步驟(為了確認是否有正常熱更新)。
發布應用更新
記得先
appcenter apps get-current
確認一下目前操作的應用是 Android 的還是 iOS 的,別搞混了。或者可以在指令後面加上-a <APP_NAME>
來指定要操作的是哪個應用。
發布應用更新大致的 workflow 如下:
- 使用
appcenter codepush release-react
指令將 CodePush 更新至 Staging - 使用
appcenter codepush promote
指令將測試的版本從 Staging 推到 Production
這邊我們直接使用最基本的指令發布更新:
appcenter codepush release-react
如果需要直接在某個 deployment 發布更新,比如我想直接上 Production,可以加上 -d Production
:
appcenter codepush release-react -d Production
或者也可以在 APP Center 這邊直接按 Promote 的 button 來將更新推上 Production
一些常見的 flag:
[-a <ownerName>/<appName>]
:指定應用[-d <deploymentName>]
:指定更新要發布到哪個deployment。預設為 Staging[-t|--target-binary-version <targetBinaryVersion>]
: 指定要更新的應用的原生版本[-m]
:是否強制更新(mandatory),默認為 false[--development]
:指定是否生成未壓縮的 js bundle[--description <description>]
:更新描述
使用以下指令可以列出最近發出過的更新:
appcenter codepush deployment list
重新開啟 APP 看看剛剛修改的 UI 有沒有更新上去了
注意事項:
1.build android 時 target binary version 會自動讀取 build.gradle 中的 versionName,所以一般情況下不需要特別去設置更新目標版本。
- 使用 release-react 指令的時候最開始會去
android/app/build.gradle
抓target binary version
Detecting android app version:
Using the target binary version value "1.2.0" from "android/app/build.gradle".
2.Android 使用 ./gradlew assemblerelease
指令打包對應的是 Production deployment,iOS archive 預設也是對應 Production,所以更新時記得指定 deployment 或者將更新 promote 到 Production。
3.如果原本安裝的應用版本為 1.0.0,那發布更新時只有指定目標更新(target-binary-version)為 1.0.0 才能更新,或者使用 -t
指定更新的版本範圍,如:appcenter codepush release-react -t ">=1.0.0" -d Production
,範圍如何下請參考: https://learn.microsoft.com/zh-tw/appcenter/distribution/codepush/cli#target-binary-version-parameter
4.前面我們有設置過主要的 APP(appcenter apps set-current
) 所以不需要另外使用 -a
來指定 APP,如果沒指定過的話記得加上 -a <ownerName>/<appName>
5.應用更新上去之後至少要等待幾分鐘才會檢查到更新。
6.如果版本為 1.0 缺少 patch version 的話,則 patch version 會被視為 0 即 1.0.0
和 Sentry 一起用
Sentry 的配置方式:【DAY25】React Native 使用 Sentry 監控錯誤、異常和性能
CodePush 和 Sentry 可以一起使用,Sentry 官方文檔也有提到集成 CodePush 的方法。
codePush(codePushOptions)(Sentry.wrap(App))
// App.tsx
import * as Sentry from '@sentry/react-native'
import CodePush from 'react-native-code-push'
let codePushOptions = {
checkFrequency : codePush.CheckFrequency.ON_APP_START
};
Sentry.init({
dsn: Config.SENTRY_DSN,
release: Config.release,
dist: Config.dist,
integrations: [
new Sentry.ReactNativeTracing({
routingInstrumentation
})
]
})
function App(): JSX.Element {
// ...
}
export default codePush(codePushOptions)(Sentry.wrap(App))
更新時透過指定 –sourcemap-output 和 –output-dir ./build 來輸出套件和來源映射。這將輸出到 ./build/CodePush 資料夾。
appcenter codepush release-react -d {DEPLOYMENT} --sourcemap-output --output-dir ./build
將輸出的套件和來源映射上傳到 Sentry
export SENTRY_PROPERTIES=./ios/sentry.properties<br>sentry-cli react-native appcenter {OWNER_NAME}/{APP_NAME} ios ./build/CodePush --deployment {DEPLOYMENT} --dist {DIST}