1. 背景
CodePush是微軟VisualStudioAppCenter服務的一個子功能,支持動態下發RN熱更新產物。但是VisualStudioAppCenter 2025/3/31 已經停止服務,如果想繼續使用CodePush需要自己獨立部署。
專門獨立部署一套後台服務,對於個人開發者尤其是客户端開發來説時間成本較高,後期維護也需要投入不少精力,最好還是使用已有的下發平台。
調研了一下發現目前Pushy和Shiply平台都支持RN熱更新。
Pushy 是ReactNative中文網推出的RN下發平台,支持差量更新,CLI和網頁雙端管理。
Shiply 是騰訊端服務團隊推出的面向客户端的全場景發佈平台,支持動態下發配置、資源、軟件包(App)、Android熱更新、flutter熱更新,最近剛上線了RN熱更新功能。
體驗下來感覺Shiply的前端交互更友好,有完整的審批發布流程,支持多維度的下發規則,能看到客户端下發加載數據,下面詳細介紹下Shiply RN熱更新的接入和使用過程。
2. Shiply RN熱更新接入實戰
2.1. 創建Shiply項目及產品
參考第三點中的發佈平台使用指引,創建一個測試項目,並創建Android和iOS產品。
2.2. 創建RN模塊
參考第三點中的發佈平台使用指引,創建一個名叫「testRN」的模塊,綁定2.1中創建的兩個產品。
2.3. 接入Shiply RN動態化SDK
2.3.1. 創建RN demo
先創建一個RN demo工程:
npx @react-native-community/cli init TestShiplyRN --version 0.78
2.3.2. 添加RN層依賴
在demo項目根目錄執行
npm install rn-shiply-upgrade
2.3.3. 添加Android層依賴
在項目Android目錄下的build.gradle文件中添加maven地址
allprojects {
repositories {
maven { url "https://tencent-tds-maven.pkg.coding.net/repository/shiply/repo" }
}
}
2.3.4. 添加熱更新檢查與加載代碼
2.3.4.1. 修改RN層代碼
import React from 'react';
import {Platform, Text, View} from 'react-native';
import { HotUpdateHelper, HotUpdateButton } from 'rn-shiply-upgrade'; // 新增導入輔助類
const App = () => {
// 初始化熱更新配置(只需一次)
HotUpdateHelper.getInstance({
// 需要修改為業務方自己的android/ios appId和appKey
appId: Platform.OS === 'ios' ? 'iOSAppId' : 'androidAppId',
appKey: Platform.OS === 'ios'
? 'iOSAppKey'
: 'androidAppKey',
deviceId: '33333', // 需要修改為實際的設備id
appVersion: "1.0.0", // 應用版本號
shiplyResName: 'testRN', // 資源key名稱,對應shiply平台上中創建的RN模塊名字
});
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text> 熱更新測試: 當前是本地內容</Text>
<View style={{ height: 10 }} />
{/* 默認樣式按鈕 */}
<HotUpdateButton />
</View>
);
};
export default App;
androidAppId/androidAppKey/iOSAppId/iOSAppKey要改成2.1中創建的Android/iOS產品的真實appId/appKey。
2.3.4.2. 修改Android層代碼
class MainApplication : Application(), ReactApplication {
override val reactNativeHost: ReactNativeHost =
object : DefaultReactNativeHost(this) {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
// Packages that cannot be autolinked yet can be added manually here, for example:
// add(MyReactNativePackage())
}
override fun getJSMainModuleName(): String = "index"
override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG
override fun getJSBundleFile(): String? {
var result: String? = null
// 從shiply獲取RN bundle路徑,業務方需要將資源key修改為shiply平台中創建的資源key名稱
result = ShiplyReactNativeUpgradeModule.getJSBundleFilePath(applicationContext as Application, "testRN")
Log.d("MainApplication", "getJSBundleFile: $result")
return result
}
override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED
override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED
}
override val reactHost: ReactHost
get() = getDefaultReactHost(applicationContext, reactNativeHost)
override fun onCreate() {
super.onCreate()
SoLoader.init(this, OpenSourceMergedSoMapping)
if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
// If you opted-in for the New Architecture, we load the native entry point for this app.
load()
}
}
}
複寫getJSBundleFile方法,從shiply獲取bundle路徑。
2.3.4.3. 修改iOS層代碼
class AppDelegate: RCTAppDelegate {
override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
self.moduleName = "TestShiplyRN"
self.dependencyProvider = RCTAppDependencyProvider()
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = [:]
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func sourceURL(for bridge: RCTBridge) -> URL? {
self.bundleURL()
}
override func bundleURL() -> URL? {
// 業務方需要將資源key修改為shiply平台中創建的資源key名稱
ShiplyReactNativeUpgradeUtil.getJSBundleFilePath("testRN");
if let path = ShiplyReactNativeUpgradeUtil.getJSBundleFilePath("testRN") {
NSLog("bundleURL called,path = %@ ", path);
return URL(fileURLWithPath: path)
}
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")
}
}
複寫bundleURL方法,從shiply獲取bundle路徑。
做完以上步驟後就完成了接入。
這時點擊檢查更新沒有什麼效果,因為還沒有在shiply配置新版本RN熱更新產物。
2.4. 打包RN動態化產物
2.4.1. 修改Demo測試代碼
import React from 'react';
import {Platform, Text, View} from 'react-native';
import { HotUpdateHelper, HotUpdateButton } from 'rn-shiply-upgrade'; // 新增導入輔助類
const App = () => {
// 初始化熱更新配置(只需一次)
HotUpdateHelper.getInstance({
// 需要修改為業務方自己的android/ios appId和appKey
appId: Platform.OS === 'ios' ? 'iOSAppId' : 'androidAppId',
appKey: Platform.OS === 'ios'
? 'iOSAppKey'
: 'androidAppKey',
deviceId: '33333', // 需要修改為實際的設備id
appVersion: "1.0.0", // 應用版本號
shiplyResName: 'testRN', // 資源key名稱,對應shiply平台上中創建的RN模塊名字
});
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
{/*<Text> 熱更新測試: 當前是本地內容</Text>*/}
<Text> 熱更新測試: 當前是熱更新內容</Text>
<View style={{ height: 10 }} />
{/* 默認樣式按鈕 */}
<HotUpdateButton />
</View>
);
};
export default App;
主要修改下UI文案,區分本地版本和熱更新版本。
2.4.2. 打包Android產物
在工程的根目錄輸入如下命令進行打包:
react-native bundle --entry-file ./index.js --bundle-output ./bundle/androidBundle/index.android.bundle --platform android --assets-dest ./bundle/androidBundle --dev false
執行後,工程根目錄/bundle/androidBundle下會生成Android產物,全選androidBundle下所有文件後進行壓縮,得到的zip文件將用於上傳到shiply。
2.4.3. 打包iOS產物
在工程的根目錄輸入如下命令進行打包:
react-native bundle --entry-file ./index.js --bundle-output ./bundle/iOSBundle/index.ios.bundle --platform ios --assets-dest ./bundle/iOSBundle --dev false
執行後,工程根目錄/bundle/iOSBundle下會生成iOS產物,全選iOSBundle下所有文件後進行壓縮,得到的zip文件將用於上傳到shiply。
2.5. 發佈RN動態化產物
參考3中的發佈平台使用指引,上傳2.4中的zip文件生成發佈任務:
還原測試代碼,在Demo Android目錄執行./gradlew app:assembleRelease編譯release apk,安裝後運行九能拉到遠端的RN產物了。
這裏按鈕和彈框都是shiply sdk提供的默認UI,也可以參考SDK中的HotUpdateButton 和HotUpdateHelper來自定義UI和請求時機。
安裝加載成功後,shiply前端頁面可以看到相關的數據上報。
2.6. 小結
總體體驗下來接入比較順暢,花個半天時間就能正常接入shiply rn熱更新。因為CodePush下架急需替代方案的開發者可以考慮接入試用下。
3. 參考文檔
Shiply ReactNative 動態化 SDK 接入指引 | Shiply 專業版
ReactNative 動態化發佈平台使用指引 | Shiply 專業版