Browse Source

feat(update): 实现基础包热更新功能

- 新增 AppUpdateEvent 和 UpdateManager 类
- 实现基础包下载和更新逻辑- 在 MainActivity 中添加更新事件处理
- 更新 MainApplication 以支持新功能
- 修改前端页面,增加更新进度显示和相关功能- 引入 react-native-fs 和 react-native-zip-archive 依赖
xuqm 4 days ago
parent
commit
02e9dfea07

+ 19 - 0
android/app/src/main/java/com/trust/ywx/MainActivity.kt

@@ -6,6 +6,10 @@ import com.facebook.react.ReactActivityDelegate
 import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled
 import com.facebook.react.defaults.DefaultReactActivityDelegate
 import com.trust.ywx.utils.AppManager
+import org.greenrobot.eventbus.EventBus
+import org.greenrobot.eventbus.Subscribe
+import org.greenrobot.eventbus.ThreadMode
+import com.trust.ywx.event.AppUpdateEvent
 
 class MainActivity : ReactActivity() {
     override fun getMainComponentName(): String = "app"
@@ -16,4 +20,19 @@ class MainActivity : ReactActivity() {
         super.onCreate(savedInstanceState)
         AppManager.addActivity(this)
     }
+
+    @Subscribe(threadMode = ThreadMode.MAIN)
+    fun onMessageEvent(event: AppUpdateEvent) {
+        reactHost.reload("update Data")
+    }
+
+    override fun onStart() {
+        super.onStart()
+        EventBus.getDefault().register(this);
+    }
+
+    override fun onStop() {
+        super.onStop()
+        EventBus.getDefault().unregister(this);
+    }
 }

+ 2 - 0
android/app/src/main/java/com/trust/ywx/MainApplication.kt

@@ -12,6 +12,7 @@ import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost
 import com.facebook.react.defaults.DefaultReactNativeHost
 import com.trust.ywx.event.BundleUpdateEvent
 import com.trust.ywx.specs.navigation.NavigationPackage
+import com.trust.ywx.specs.update.UpdatePackage
 import com.trust.ywx.utils.BUNDLE_VERSION_CODE
 import com.trust.ywx.utils.FileHelper
 import com.trust.ywx.utils.SHARED_PREFERENCES_NAME
@@ -25,6 +26,7 @@ class MainApplication : Application(), ReactApplication {
         override fun getPackages(): List<ReactPackage> = PackageList(this).packages.apply {
             // Packages that cannot be autolinked yet can be added manually here, for example:
             add(NavigationPackage())
+            add(UpdatePackage())
         }
 
         override fun getJSMainModuleName(): String =

+ 4 - 0
android/app/src/main/java/com/trust/ywx/event/AppUpdateEvent.java

@@ -0,0 +1,4 @@
+package com.trust.ywx.event;
+
+public class AppUpdateEvent {
+}

+ 25 - 0
android/app/src/main/java/com/trust/ywx/specs/update/UpdateManager.kt

@@ -0,0 +1,25 @@
+package com.trust.ywx.specs.update
+
+import android.content.Intent
+import com.facebook.react.bridge.ReactApplicationContext
+import com.trust.ywx.BuzActivity
+import com.trust.ywx.specs.NativeUpdateManagerSpec
+import com.trust.ywx.utils.AppManager
+import com.trust.ywx.event.AppUpdateEvent
+import org.greenrobot.eventbus.EventBus
+
+class UpdateManager(reactContext: ReactApplicationContext) :
+    NativeUpdateManagerSpec(reactContext) {
+    override fun update(name: String) {
+    }
+
+    override fun reload() {
+        EventBus.getDefault().post(AppUpdateEvent())
+    }
+
+    override fun getName() = NAME
+
+    companion object {
+        const val NAME = "UpdateManager"
+    }
+}

+ 31 - 0
android/app/src/main/java/com/trust/ywx/specs/update/UpdatePackage.kt

@@ -0,0 +1,31 @@
+package com.trust.ywx.specs.update
+
+import com.facebook.react.BaseReactPackage
+import com.facebook.react.bridge.NativeModule
+import com.facebook.react.bridge.ReactApplicationContext
+import com.facebook.react.module.model.ReactModuleInfo
+import com.facebook.react.module.model.ReactModuleInfoProvider
+
+class UpdatePackage : BaseReactPackage() {
+    override fun getModule(
+        name: String,
+        reactContext: ReactApplicationContext
+    ): NativeModule? = if (name == UpdateManager.NAME) {
+        UpdateManager(reactContext)
+    } else {
+        null
+    }
+
+    override fun getReactModuleInfoProvider() = ReactModuleInfoProvider {
+        mapOf(
+            UpdateManager.NAME to ReactModuleInfo(
+                name = UpdateManager.NAME,
+                className = UpdateManager.NAME,
+                canOverrideExistingModule = false,
+                needsEagerInit = false,
+                isCxxModule = false,
+                isTurboModule = true
+            )
+        )
+    }
+}

File diff suppressed because it is too large
+ 0 - 0
bundle/android/common.android.bundle


File diff suppressed because it is too large
+ 0 - 0
config/bundleCommonInfo.json


+ 3 - 1
package.json

@@ -36,11 +36,13 @@
     "react-native-bundle-splitter": "^3.0.1",
     "react-native-copilot": "^3.3.3",
     "react-native-device-info": "^14.0.4",
+    "react-native-fs": "^2.20.0",
     "react-native-gesture-handler": "^2.27.2",
     "react-native-root-siblings": "^5.0.1",
     "react-native-safe-area-context": "^5.5.2",
     "react-native-storage": "^1.0.1",
-    "react-native-toast-message": "^2.3.3"
+    "react-native-toast-message": "^2.3.3",
+    "react-native-zip-archive": "^7.0.2"
   },
   "devDependencies": {
     "@babel/core": "^7.25.2",

+ 3 - 1
specs/NativeUpdateManager.ts

@@ -2,9 +2,11 @@ import type { TurboModule } from 'react-native';
 import { TurboModuleRegistry } from 'react-native';
 
 export interface Spec extends TurboModule {
+  reload(): void;
+  // 好像用不到
   update(name: string): void;
 }
 
 export default TurboModuleRegistry.getEnforcing<Spec>(
-  'NavigationManager',
+  'UpdateManager',
 ) as Spec;

+ 27 - 3
src/app/screens/main/MainViewScreen.tsx

@@ -1,4 +1,4 @@
-import React from 'react';
+import React, { useState } from 'react';
 import {
   Button,
   Linking,
@@ -14,10 +14,15 @@ import { Apps, NavigationPushByName } from '@common/NavigationHelper.ts';
 import { showMessage } from '@common/ToastHelper.ts';
 import Alert from '@common/components/Alert.tsx';
 import { version_common } from '@common/common.ts';
+import RNFS from 'react-native-fs';
+import { downloadToFile } from '@common/UpdateHelper.ts';
+import UpdateManager from '../../../../specs/NativeUpdateManager.ts';
 
 type Props = StackScreenProps<MainParamList, 'MainView'>;
 
 export default function WebViewScreen(props: Props) {
+  const [progress, setProgress] = useState('');
+
   return (
     <View style={styles.container}>
       <ScrollView>
@@ -72,7 +77,9 @@ export default function WebViewScreen(props: Props) {
             }}
           />
           <View style={{ height: 45 }} />
-          <Text>基础包版本号{version_common}</Text>
+          <Text>
+            基础包版本号{version_common}({progress})
+          </Text>
           <Button
             title={'基础包前台更新'}
             onPress={() => {
@@ -82,7 +89,22 @@ export default function WebViewScreen(props: Props) {
                   有新版本可以更新,确定更新吗?
                 </Text>,
                 {
-                  action: () => {},
+                  action: () => {
+                    downloadToFile(
+                      'https://download-api.51trust.com/ywx-android-sdk/common.android.zip',
+                      'common.android.zip',
+                      (bytesWritten, contentLength) => {
+                        setProgress(
+                          `进度: ${(
+                            (bytesWritten / contentLength) *
+                            100
+                          ).toFixed(2)}%`,
+                        );
+                      },
+                    ).then(() => {
+                      UpdateManager.reload();
+                    });
+                  },
                 },
                 {
                   action: () => {},
@@ -90,6 +112,8 @@ export default function WebViewScreen(props: Props) {
               );
             }}
           />
+          <View style={{ height: 45 }} />
+          <Text>基础包版本号{RNFS.ExternalDirectoryPath}</Text>
         </>
       </ScrollView>
     </View>

+ 43 - 0
src/common/UpdateHelper.ts

@@ -0,0 +1,43 @@
+import RNFS from 'react-native-fs';
+import { unzip } from 'react-native-zip-archive';
+
+export const downloadToFile = async (
+  fileUrl: string,
+  fileName: string,
+  listener?: (bytesWritten: number, contentLength: number) => void,
+) => {
+  // /storage/emulated/0/Android/data/com.trust.ywx/files/bundles/android/common.android.bundle
+  const downloadDest = `${RNFS.ExternalDirectoryPath}/bundles/android/${fileName}`;
+
+  const fileExists = await RNFS.exists(downloadDest);
+  if (fileExists) {
+    await RNFS.unlink(downloadDest);
+  }
+
+  const options = {
+    fromUrl: fileUrl,
+    toFile: downloadDest,
+    progress: (res: any) => {
+      listener && listener(res.bytesWritten, res.contentLength);
+      console.log(
+        `进度: ${((res.bytesWritten / res.contentLength) * 100).toFixed(2)}%`,
+      );
+    },
+  };
+
+  try {
+    const res = await RNFS.downloadFile(options).promise;
+    console.log('文件已下载到沙盒:', downloadDest, res);
+    const exists = await RNFS.exists(downloadDest);
+    if (exists) {
+      const result = await unzip(
+        downloadDest,
+        `${RNFS.ExternalDirectoryPath}/bundles/android/`,
+      );
+      console.log('文件已解压到沙盒:', downloadDest, result);
+    }
+    return downloadDest;
+  } catch (error) {
+    console.error('下载失败:', error);
+  }
+};

+ 2 - 0
src/common/common.ts

@@ -6,12 +6,14 @@ import 'react-native';
 import '@react-navigation/native';
 import 'react-native-gesture-handler';
 import '@react-navigation/stack';
+import 'react-native-fs';
 // 路由懒加载已经内存管理等,提高加载效率
 import 'react-native-bundle-splitter';
 // 获取设备信息
 import 'react-native-device-info';
 // 应用间路由工具
 import '@common/NavigationHelper';
+import '@common/UpdateHelper.ts';
 // 弹出相关
 import '@common/ToastHelper.ts';
 import '@common/components/Alert.tsx';

+ 23 - 0
yarn.lock

@@ -2710,6 +2710,11 @@ balanced-match@^1.0.0:
   resolved "https://nexus-inner.51trust.com/repository/npm/balanced-match/-/balanced-match-1.0.2.tgz"
   integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
 
+base-64@^0.1.0:
+  version "0.1.0"
+  resolved "https://nexus-inner.51trust.com/repository/npm/base-64/-/base-64-0.1.0.tgz#780a99c84e7d600260361511c4877613bf24f6bb"
+  integrity sha512-Y5gU45svrR5tI2Vt/X9GPd3L0HNIKzGu202EjxrXMpuc2V2CiKgemAbUUsqYmZJvPtCXoUKjNZwBJzsNScUbXA==
+
 base64-js@^1.3.1, base64-js@^1.5.1:
   version "1.5.1"
   resolved "https://nexus-inner.51trust.com/repository/npm/base64-js/-/base64-js-1.5.1.tgz"
@@ -6238,6 +6243,14 @@ react-native-device-info@^14.0.4:
   resolved "https://nexus-inner.51trust.com/repository/npm/react-native-device-info/-/react-native-device-info-14.0.4.tgz#56b24ace9ff29a66bdfc667209086421ed6cfdce"
   integrity sha512-NX0wMAknSDBeFnEnSFQ8kkAcQrFHrG4Cl0mVjoD+0++iaKrOupiGpBXqs8xR0SeJyPC5zpdPl4h/SaBGly6UxA==
 
+react-native-fs@^2.20.0:
+  version "2.20.0"
+  resolved "https://nexus-inner.51trust.com/repository/npm/react-native-fs/-/react-native-fs-2.20.0.tgz#05a9362b473bfc0910772c0acbb73a78dbc810f6"
+  integrity sha512-VkTBzs7fIDUiy/XajOSNk0XazFE9l+QlMAce7lGuebZcag5CnjszB+u4BdqzwaQOdcYb5wsJIsqq4kxInIRpJQ==
+  dependencies:
+    base-64 "^0.1.0"
+    utf8 "^3.0.0"
+
 react-native-gesture-handler@^2.27.2:
   version "2.27.2"
   resolved "https://nexus-inner.51trust.com/repository/npm/react-native-gesture-handler/-/react-native-gesture-handler-2.27.2.tgz#d52e839e9cb225e75c9b6fce7438979ca6917512"
@@ -6270,6 +6283,11 @@ react-native-toast-message@^2.3.3:
   resolved "https://nexus-inner.51trust.com/repository/npm/react-native-toast-message/-/react-native-toast-message-2.3.3.tgz#e301508d386a9902ff6b4559ecc6674f8cfdf97a"
   integrity sha512-4IIUHwUPvKHu4gjD0Vj2aGQzqPATiblL1ey8tOqsxOWRPGGu52iIbL8M/mCz4uyqecvPdIcMY38AfwRuUADfQQ==
 
+react-native-zip-archive@^7.0.2:
+  version "7.0.2"
+  resolved "https://nexus-inner.51trust.com/repository/npm/react-native-zip-archive/-/react-native-zip-archive-7.0.2.tgz#f8c488b4f5ab1605bff1f366ac101fe0dce6fd1d"
+  integrity sha512-msCRJMcwH6NVZ2/zoC+1nvA0wlpYRnMxteQywS9nt4BzXn48tZpaVtE519QEZn0xe3ygvgsWx5cdPoE9Jx3bsg==
+
 react-native@0.80.1:
   version "0.80.1"
   resolved "https://nexus-inner.51trust.com/repository/npm/react-native/-/react-native-0.80.1.tgz"
@@ -7253,6 +7271,11 @@ use-sync-external-store@^1.5.0:
   resolved "https://nexus-inner.51trust.com/repository/npm/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz#55122e2a3edd2a6c106174c27485e0fd59bcfca0"
   integrity sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==
 
+utf8@^3.0.0:
+  version "3.0.0"
+  resolved "https://nexus-inner.51trust.com/repository/npm/utf8/-/utf8-3.0.0.tgz#f052eed1364d696e769ef058b183df88c87f69d1"
+  integrity sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==
+
 util-deprecate@^1.0.1:
   version "1.0.2"
   resolved "https://nexus-inner.51trust.com/repository/npm/util-deprecate/-/util-deprecate-1.0.2.tgz"

Some files were not shown because too many files changed in this diff