Site icon May's Notes

React Native 使用 CodePush 熱更新應用

React Native logo

這是我在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:

~/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:

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
			// ...
		}
	}
}

注意

  1. 網上很多教程會讓你在 strings.xml 添加 CodePushDeploymentKey,其實是不需要的,在 android/app/build.gradle 這裡添加了的話記得將 strings.xml 裡面的 CodePushDeploymentKey 刪除,不然 build 的時候會失敗。
  2. 還有些會讓你在 android/app/build.gradle 加上 apply from "../../node_modules/react-native/react.gradle",這個也不需要,一樣會 build 會失敗。

iOS 設置

記得 Android 跟 iOS 是不同應用,所以 CodePush key 會不一樣,別用錯了。

在 ios/<project_name>/AppDelegate.mm

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 如下:

  1. 使用 appcenter codepush release-react 指令將 CodePush 更新至 Staging
  2. 使用 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:

使用以下指令可以列出最近發出過的更新:

appcenter codepush deployment list

重新開啟 APP 看看剛剛修改的 UI 有沒有更新上去了

注意事項

1.build android 時 target binary version 會自動讀取 build.gradle 中的 versionName,所以一般情況下不需要特別去設置更新目標版本。

    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}

      參考資料

      Exit mobile version