├── .buckconfig ├── .editorconfig ├── .eslintrc.js ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .prettierrc.js ├── .watchmanconfig ├── App.js ├── README.md ├── README_CH.md ├── __tests__ └── App-test.js ├── android ├── app │ ├── BUCK │ ├── build.gradle │ ├── build_defs.bzl │ ├── debug.keystore │ ├── proguard-rules.pro │ └── src │ │ ├── debug │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── reactnativedynamic │ │ │ └── ReactNativeFlipper.java │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ ├── business.android.bundle │ │ ├── common.android.bundle │ │ └── drawable-mdpi │ │ │ └── node_modules_reactnative_libraries_newappscreen_components_logo.png │ │ ├── java │ │ └── com │ │ │ └── reactnativedynamic │ │ │ ├── MainActivity.java │ │ │ ├── MainApplication.java │ │ │ └── core │ │ │ ├── DynamicReactActivity.java │ │ │ ├── DynamicReactNativeHost.java │ │ │ ├── LogUtil.java │ │ │ ├── ReactAppRuntime.java │ │ │ ├── bridge │ │ │ ├── BridgeUtil.java │ │ │ └── ReactActivityDelegate.java │ │ │ ├── model │ │ │ ├── ItemLog.java │ │ │ └── RnBundle.java │ │ │ ├── packages │ │ │ ├── CustomLogPackages.java │ │ │ └── client │ │ │ │ ├── LogModule.java │ │ │ │ ├── TimeRecord.java │ │ │ │ └── TimeUtils.java │ │ │ └── utils │ │ │ ├── FileUtils.java │ │ │ ├── LoadScriptListener.java │ │ │ ├── ReactUtil.java │ │ │ ├── ScriptLoadUtil.java │ │ │ └── UpdateProgressListener.java │ │ └── res │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── strings.xml │ │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle ├── app.json ├── babel.config.js ├── common-entry.js ├── compile ├── common-modules-android.json ├── common-modules-index-map-android.json ├── common-modules-index-map-ios.json ├── common-modules-ios.json ├── metro-base.js └── split-common.js ├── dist ├── base │ └── common.ios.bundle └── business │ ├── assets │ └── app.json │ └── business.ios.bundle ├── index.js ├── ios ├── Controller │ ├── HomeViewController.h │ └── HomeViewController.m ├── Core │ ├── BridgeManager.h │ ├── BridgeManager.m │ └── RCTBridge+Extension.h ├── Podfile ├── Podfile.lock ├── ReactNativeDynamic.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── ReactNativeDynamic.xcscheme ├── ReactNativeDynamic.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── ReactNativeDynamic │ ├── AppDelegate.h │ ├── AppDelegate.m │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ └── Contents.json │ │ └── Contents.json │ ├── Info.plist │ ├── LaunchScreen.storyboard │ └── main.m └── ReactNativeDynamicTests │ ├── Info.plist │ └── ReactNativeDynamicTests.m ├── metro.base.config.js ├── metro.business.config.js ├── package-lock.json ├── package.json └── yarn.lock /.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Windows files 2 | [*.bat] 3 | end_of_line = crlf 4 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native-community', 4 | }; 5 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore polyfills 9 | node_modules/react-native/Libraries/polyfills/.* 10 | 11 | ; Flow doesn't support platforms 12 | .*/Libraries/Utilities/LoadingView.js 13 | 14 | [untyped] 15 | .*/node_modules/@react-native-community/cli/.*/.* 16 | 17 | [include] 18 | 19 | [libs] 20 | node_modules/react-native/interface.js 21 | node_modules/react-native/flow/ 22 | 23 | [options] 24 | emoji=true 25 | 26 | esproposal.optional_chaining=enable 27 | esproposal.nullish_coalescing=enable 28 | 29 | exact_by_default=true 30 | 31 | module.file_ext=.js 32 | module.file_ext=.json 33 | module.file_ext=.ios.js 34 | 35 | munge_underscores=true 36 | 37 | module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' 38 | module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' 39 | 40 | suppress_type=$FlowIssue 41 | suppress_type=$FlowFixMe 42 | suppress_type=$FlowFixMeProps 43 | suppress_type=$FlowFixMeState 44 | 45 | [lints] 46 | sketchy-null-number=warn 47 | sketchy-null-mixed=warn 48 | sketchy-number=warn 49 | untyped-type-import=warn 50 | nonstrict-import=warn 51 | deprecated-type=warn 52 | unsafe-getters-setters=warn 53 | unnecessary-invariant=warn 54 | signature-verification-failure=warn 55 | 56 | [strict] 57 | deprecated-type 58 | nonstrict-import 59 | sketchy-null 60 | unclear-type 61 | unsafe-getters-setters 62 | untyped-import 63 | untyped-type-import 64 | 65 | [version] 66 | ^0.137.0 67 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Windows files should use crlf line endings 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | *.bat text eol=crlf 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | 32 | # node.js 33 | # 34 | node_modules/ 35 | npm-debug.log 36 | yarn-error.log 37 | 38 | # BUCK 39 | buck-out/ 40 | \.buckd/ 41 | *.keystore 42 | !debug.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://docs.fastlane.tools/best-practices/source-control/ 50 | 51 | */fastlane/report.xml 52 | */fastlane/Preview.html 53 | */fastlane/screenshots 54 | 55 | # Bundle artifact 56 | *.jsbundle 57 | 58 | # CocoaPods 59 | /ios/Pods/ 60 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | arrowParens: 'avoid', 7 | }; 8 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * https://github.com/facebook/react-native 4 | * 5 | * @format 6 | * @flow strict-local 7 | */ 8 | 9 | import React from 'react'; 10 | import { 11 | SafeAreaView, 12 | ScrollView, 13 | StatusBar, 14 | StyleSheet, 15 | Text, 16 | useColorScheme, 17 | View, 18 | } from 'react-native'; 19 | 20 | import { 21 | Colors, 22 | DebugInstructions, 23 | Header, 24 | LearnMoreLinks, 25 | ReloadInstructions, 26 | } from 'react-native/Libraries/NewAppScreen'; 27 | 28 | const Section = ({children, title}) => { 29 | const isDarkMode = useColorScheme() === 'dark'; 30 | return ( 31 | 32 | 39 | {title} 40 | 41 | 48 | {children} 49 | 50 | 51 | ); 52 | }; 53 | 54 | const App = () => { 55 | const isDarkMode = useColorScheme() === 'dark'; 56 | 57 | const backgroundStyle = { 58 | backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, 59 | }; 60 | 61 | return ( 62 | 63 | 64 | 67 |
68 | 72 |
73 | Edit App.js to change this 74 | screen and then come back to see your edits. 75 |
76 |
77 | 78 |
79 |
80 | 81 |
82 |
83 | Read the docs to discover what to do next: 84 |
85 | 86 |
87 | 88 | 89 | ); 90 | }; 91 | 92 | const styles = StyleSheet.create({ 93 | sectionContainer: { 94 | marginTop: 32, 95 | paddingHorizontal: 24, 96 | }, 97 | sectionTitle: { 98 | fontSize: 24, 99 | fontWeight: '600', 100 | }, 101 | sectionDescription: { 102 | marginTop: 8, 103 | fontSize: 18, 104 | fontWeight: '400', 105 | }, 106 | highlight: { 107 | fontWeight: '700', 108 | }, 109 | }); 110 | 111 | export default App; 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-dynamic-load 2 | [中文](./README_CH.md) 3 | 4 | react-native dynamic load bundle from remote; 5 | ## Support 6 | 7 | - iOS/Android dynamic load jsbundle/common bundle; 8 | - iOS/Android supports simultaneous loading of multiple bundles 9 | 10 | ## Implementation logic 11 | 12 | ### Split Bundle 13 | 14 | How to split bundle ? Click [ReactNative Subcontracting Program Introduction](https://dev.to/mrgaogang/react-native-sdk-upgrade-issues-and-split-jsbundle-46k9) 15 | 16 | ```bash 17 | 18 | # ios 19 | 20 | npm run build:ios 21 | 22 | # android 23 | 24 | npm run build:android 25 | 26 | ``` 27 | 28 | It will be automatically split bundle. If you want the packaged product to be a digital type, you can set `moduleIdByIndex=true` in `compile/metro-base.js` 29 | 30 | ### iOS client dynamic load 31 | 32 | ```objc 33 | // Load the jsbundle basic package when the application starts 34 | [BridgeManager.instance loadBaseBundleWithLaunchOptions:launchOptions]; 35 | 36 | // Load business packages when needed 37 | // Here is just the way to load the local bundle. If it is online, you can download it using http first and then load it locally 38 | [BridgeManager.instance 39 | loadBusinessBundle:@"business.ios" 40 | moduleName:@"ReactNativeDynamic" 41 | callback:^(BOOL succeed) { 42 | if (succeed) { 43 | RCTRootView *rootView = [[RCTRootView alloc] 44 | initWithBridge:BridgeManager.instance.commonBridge 45 | moduleName:@"ReactNativeDynamic" 46 | initialProperties:nil]; 47 | self.view = rootView; 48 | } 49 | NSLog(@"%d",succeed); 50 | }]; 51 | ``` 52 | 53 | ### Android client dynamic load 54 | 55 | ```java 56 | // Load the jsbundle basic package when the application starts 57 | SoLoader.init(this, /* native exopackage */ false); 58 | ReactAppRuntime.init(this); 59 | 60 | // your activity 61 | public class MainActivity extends DynamicReactActivity { 62 | 63 | @Override 64 | protected RnBundle getBundle(){ 65 | RnBundle bundle = new RnBundle(); 66 | bundle.scriptType = ScriptType.ASSET; 67 | bundle.scriptPath = "business.android.bundle"; 68 | bundle.scriptUrl = "business.android.bundle"; 69 | bundle.appName = "ReactNativeDynamic"; 70 | return bundle; 71 | } 72 | } 73 | 74 | 75 | ``` 76 | -------------------------------------------------------------------------------- /README_CH.md: -------------------------------------------------------------------------------- 1 | # react-native-dynamic-load 2 | 3 | react-native dynamic load bundle from remote;(单例模式 React Native 分包加载) 4 | 5 | ## Support 6 | 7 | - iOS/Android 动态加载 jsbundle/common bundle; 8 | - iOS/Android 支持多 bundle 同时加载 9 | 10 | ## 实现逻辑 11 | 12 | ### 分包 13 | 14 | 如何进行分包?点击[ReactNative 分包方案介绍](https://blog.gaogangsever.cn/react/RNSDK%E5%8D%87%E7%BA%A7%E5%8F%8A%E5%88%86%E5%8C%85%E6%96%B9%E6%A1%88.html#%E9%97%AE%E9%A2%98-1-rn-%E5%A6%82%E4%BD%95%E8%BF%9B%E8%A1%8C%E5%88%86%E5%8C%85) 15 | 16 | ```bash 17 | 18 | # ios运行 19 | 20 | npm run build:ios 21 | 22 | # android运行 23 | 24 | npm run build:android 25 | 26 | ``` 27 | 28 | 会自动进行分包,如果想要打包后的产物为数字类型,则在`compile/metro-base.js`中设置`moduleIdByIndex=true`即可 29 | 30 | ### iOS 客户端动态加载 31 | 32 | ```objc 33 | // 在应用启动的时候加载 jsbundle 基础包 34 | [BridgeManager.instance loadBaseBundleWithLaunchOptions:launchOptions]; 35 | 36 | // 在需要的时候加载业务包 37 | // 此处只是使用加载本地的bundle的方式,如果是在线的方式,可以先使用http下载然后加载本地 38 | [BridgeManager.instance 39 | loadBusinessBundle:@"business.ios" 40 | moduleName:@"ReactNativeDynamic" 41 | callback:^(BOOL succeed) { 42 | if (succeed) { 43 | RCTRootView *rootView = [[RCTRootView alloc] 44 | initWithBridge:BridgeManager.instance.commonBridge 45 | moduleName:@"ReactNativeDynamic" 46 | initialProperties:nil]; 47 | self.view = rootView; 48 | } 49 | NSLog(@"%d",succeed); 50 | }]; 51 | ``` 52 | 53 | ### Android 客户端动态加载 54 | 55 | ```java 56 | // 应用启动的时候 57 | SoLoader.init(this, /* native exopackage */ false); 58 | ReactAppRuntime.init(this); 59 | 60 | // 你的activity 61 | public class MainActivity extends DynamicReactActivity { 62 | 63 | @Override 64 | protected RnBundle getBundle(){ 65 | RnBundle bundle = new RnBundle(); 66 | bundle.scriptType = ScriptType.ASSET; 67 | bundle.scriptPath = "business.android.bundle"; 68 | bundle.scriptUrl = "business.android.bundle"; 69 | bundle.appName = "ReactNativeDynamic"; 70 | return bundle; 71 | } 72 | } 73 | 74 | 75 | ``` 76 | -------------------------------------------------------------------------------- /__tests__/App-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from '../App'; 8 | 9 | // Note: test renderer must be required after react-native. 10 | import renderer from 'react-test-renderer'; 11 | 12 | it('renders correctly', () => { 13 | renderer.create(); 14 | }); 15 | -------------------------------------------------------------------------------- /android/app/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.reactnativedynamic", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.reactnativedynamic", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /android/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: "com.android.application" 2 | 3 | import com.android.build.OutputFile 4 | 5 | /** 6 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets 7 | * and bundleReleaseJsAndAssets). 8 | * These basically call `react-native bundle` with the correct arguments during the Android build 9 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the 10 | * bundle directly from the development server. Below you can see all the possible configurations 11 | * and their defaults. If you decide to add a configuration block, make sure to add it before the 12 | * `apply from: "../../node_modules/react-native/react.gradle"` line. 13 | * 14 | * project.ext.react = [ 15 | * // the name of the generated asset file containing your JS bundle 16 | * bundleAssetName: "index.android.bundle", 17 | * 18 | * // the entry file for bundle generation. If none specified and 19 | * // "index.android.js" exists, it will be used. Otherwise "index.js" is 20 | * // default. Can be overridden with ENTRY_FILE environment variable. 21 | * entryFile: "index.android.js", 22 | * 23 | * // https://reactnative.dev/docs/performance#enable-the-ram-format 24 | * bundleCommand: "ram-bundle", 25 | * 26 | * // whether to bundle JS and assets in debug mode 27 | * bundleInDebug: false, 28 | * 29 | * // whether to bundle JS and assets in release mode 30 | * bundleInRelease: true, 31 | * 32 | * // whether to bundle JS and assets in another build variant (if configured). 33 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants 34 | * // The configuration property can be in the following formats 35 | * // 'bundleIn${productFlavor}${buildType}' 36 | * // 'bundleIn${buildType}' 37 | * // bundleInFreeDebug: true, 38 | * // bundleInPaidRelease: true, 39 | * // bundleInBeta: true, 40 | * 41 | * // whether to disable dev mode in custom build variants (by default only disabled in release) 42 | * // for example: to disable dev mode in the staging build type (if configured) 43 | * devDisabledInStaging: true, 44 | * // The configuration property can be in the following formats 45 | * // 'devDisabledIn${productFlavor}${buildType}' 46 | * // 'devDisabledIn${buildType}' 47 | * 48 | * // the root of your project, i.e. where "package.json" lives 49 | * root: "../../", 50 | * 51 | * // where to put the JS bundle asset in debug mode 52 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug", 53 | * 54 | * // where to put the JS bundle asset in release mode 55 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release", 56 | * 57 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 58 | * // require('./image.png')), in debug mode 59 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug", 60 | * 61 | * // where to put drawable resources / React Native assets, e.g. the ones you use via 62 | * // require('./image.png')), in release mode 63 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release", 64 | * 65 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means 66 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to 67 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle 68 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/ 69 | * // for example, you might want to remove it from here. 70 | * inputExcludes: ["android/**", "ios/**"], 71 | * 72 | * // override which node gets called and with what additional arguments 73 | * nodeExecutableAndArgs: ["node"], 74 | * 75 | * // supply additional arguments to the packager 76 | * extraPackagerArgs: [] 77 | * ] 78 | */ 79 | 80 | project.ext.react = [ 81 | enableHermes: false, // clean and rebuild if changing 82 | ] 83 | 84 | apply from: "../../node_modules/react-native/react.gradle" 85 | 86 | /** 87 | * Set this to true to create two separate APKs instead of one: 88 | * - An APK that only works on ARM devices 89 | * - An APK that only works on x86 devices 90 | * The advantage is the size of the APK is reduced by about 4MB. 91 | * Upload all the APKs to the Play Store and people will download 92 | * the correct one based on the CPU architecture of their device. 93 | */ 94 | def enableSeparateBuildPerCPUArchitecture = false 95 | 96 | /** 97 | * Run Proguard to shrink the Java bytecode in release builds. 98 | */ 99 | def enableProguardInReleaseBuilds = false 100 | 101 | /** 102 | * The preferred build flavor of JavaScriptCore. 103 | * 104 | * For example, to use the international variant, you can use: 105 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'` 106 | * 107 | * The international variant includes ICU i18n library and necessary data 108 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that 109 | * give correct results when using with locales other than en-US. Note that 110 | * this variant is about 6MiB larger per architecture than default. 111 | */ 112 | def jscFlavor = 'org.webkit:android-jsc:+' 113 | 114 | /** 115 | * Whether to enable the Hermes VM. 116 | * 117 | * This should be set on project.ext.react and mirrored here. If it is not set 118 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode 119 | * and the benefits of using Hermes will therefore be sharply reduced. 120 | */ 121 | def enableHermes = project.ext.react.get("enableHermes", false); 122 | 123 | android { 124 | ndkVersion rootProject.ext.ndkVersion 125 | 126 | compileSdkVersion rootProject.ext.compileSdkVersion 127 | 128 | compileOptions { 129 | sourceCompatibility JavaVersion.VERSION_1_8 130 | targetCompatibility JavaVersion.VERSION_1_8 131 | } 132 | 133 | defaultConfig { 134 | applicationId "com.reactnativedynamic" 135 | minSdkVersion rootProject.ext.minSdkVersion 136 | targetSdkVersion rootProject.ext.targetSdkVersion 137 | versionCode 1 138 | versionName "1.0" 139 | } 140 | splits { 141 | abi { 142 | reset() 143 | enable enableSeparateBuildPerCPUArchitecture 144 | universalApk false // If true, also generate a universal APK 145 | include "armeabi-v7a", "x86", "arm64-v8a", "x86_64" 146 | } 147 | } 148 | signingConfigs { 149 | debug { 150 | storeFile file('debug.keystore') 151 | storePassword 'android' 152 | keyAlias 'androiddebugkey' 153 | keyPassword 'android' 154 | } 155 | } 156 | buildTypes { 157 | debug { 158 | signingConfig signingConfigs.debug 159 | } 160 | release { 161 | // Caution! In production, you need to generate your own keystore file. 162 | // see https://reactnative.dev/docs/signed-apk-android. 163 | signingConfig signingConfigs.debug 164 | minifyEnabled enableProguardInReleaseBuilds 165 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro" 166 | } 167 | } 168 | 169 | // applicationVariants are e.g. debug, release 170 | applicationVariants.all { variant -> 171 | variant.outputs.each { output -> 172 | // For each separate APK per architecture, set a unique version code as described here: 173 | // https://developer.android.com/studio/build/configure-apk-splits.html 174 | // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc. 175 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4] 176 | def abi = output.getFilter(OutputFile.ABI) 177 | if (abi != null) { // null for the universal-debug, universal-release variants 178 | output.versionCodeOverride = 179 | defaultConfig.versionCode * 1000 + versionCodes.get(abi) 180 | } 181 | 182 | } 183 | } 184 | } 185 | 186 | dependencies { 187 | implementation fileTree(dir: "libs", include: ["*.jar"]) 188 | //noinspection GradleDynamicVersion 189 | implementation "com.facebook.react:react-native:+" // From node_modules 190 | implementation 'com.google.code.gson:gson:2.8.5' // json转换 191 | 192 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" 193 | 194 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") { 195 | exclude group:'com.facebook.fbjni' 196 | } 197 | 198 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") { 199 | exclude group:'com.facebook.flipper' 200 | exclude group:'com.squareup.okhttp3', module:'okhttp' 201 | } 202 | 203 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") { 204 | exclude group:'com.facebook.flipper' 205 | } 206 | 207 | if (enableHermes) { 208 | def hermesPath = "../../node_modules/hermes-engine/android/"; 209 | debugImplementation files(hermesPath + "hermes-debug.aar") 210 | releaseImplementation files(hermesPath + "hermes-release.aar") 211 | } else { 212 | implementation jscFlavor 213 | } 214 | } 215 | 216 | // Run this once to be able to run the application with BUCK 217 | // puts all compile dependencies into folder libs for BUCK to use 218 | task copyDownloadableDepsToLibs(type: Copy) { 219 | from configurations.compile 220 | into 'libs' 221 | } 222 | 223 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) 224 | -------------------------------------------------------------------------------- /android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/debug.keystore -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /android/app/src/debug/java/com/reactnativedynamic/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.reactnativedynamic; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin; 21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | public class ReactNativeFlipper { 28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 29 | if (FlipperUtils.shouldEnableFlipper(context)) { 30 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 31 | 32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 33 | client.addPlugin(new ReactFlipperPlugin()); 34 | client.addPlugin(new DatabasesFlipperPlugin(context)); 35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 36 | client.addPlugin(CrashReporterPlugin.getInstance()); 37 | 38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 39 | NetworkingModule.setCustomClientBuilder( 40 | new NetworkingModule.CustomClientBuilder() { 41 | @Override 42 | public void apply(OkHttpClient.Builder builder) { 43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 44 | } 45 | }); 46 | client.addPlugin(networkFlipperPlugin); 47 | client.start(); 48 | 49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 50 | // Hence we run if after all native modules have been initialized 51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 52 | if (reactContext == null) { 53 | reactInstanceManager.addReactInstanceEventListener( 54 | new ReactInstanceManager.ReactInstanceEventListener() { 55 | @Override 56 | public void onReactContextInitialized(ReactContext reactContext) { 57 | reactInstanceManager.removeReactInstanceEventListener(this); 58 | reactContext.runOnNativeModulesQueueThread( 59 | new Runnable() { 60 | @Override 61 | public void run() { 62 | client.addPlugin(new FrescoFlipperPlugin()); 63 | } 64 | }); 65 | } 66 | }); 67 | } else { 68 | client.addPlugin(new FrescoFlipperPlugin()); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 13 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /android/app/src/main/assets/business.android.bundle: -------------------------------------------------------------------------------- 1 | !(function(n){var e=(function(){function n(n,e){return n}function e(n){var e={};return n.forEach(function(n,r){e[n]=!0}),e}function r(n,r,u){if(n.formatValueCalls++,n.formatValueCalls>200)return"[TOO BIG formatValueCalls "+n.formatValueCalls+" exceeded limit of 200]";var f=t(n,r);if(f)return f;var c=Object.keys(r),s=e(c);if(d(r)&&(c.indexOf('message')>=0||c.indexOf('description')>=0))return o(r);if(0===c.length){if(v(r)){var g=r.name?': '+r.name:'';return n.stylize('[Function'+g+']','special')}if(p(r))return n.stylize(RegExp.prototype.toString.call(r),'regexp');if(y(r))return n.stylize(Date.prototype.toString.call(r),'date');if(d(r))return o(r)}var h,b,m='',j=!1,O=['{','}'];(h=r,Array.isArray(h)&&(j=!0,O=['[',']']),v(r))&&(m=' [Function'+(r.name?': '+r.name:'')+']');return p(r)&&(m=' '+RegExp.prototype.toString.call(r)),y(r)&&(m=' '+Date.prototype.toUTCString.call(r)),d(r)&&(m=' '+o(r)),0!==c.length||j&&0!=r.length?u<0?p(r)?n.stylize(RegExp.prototype.toString.call(r),'regexp'):n.stylize('[Object]','special'):(n.seen.push(r),b=j?i(n,r,u,s,c):c.map(function(e){return l(n,r,u,s,e,j)}),n.seen.pop(),a(b,m,O)):O[0]+m+O[1]}function t(n,e){if(s(e))return n.stylize('undefined','undefined');if('string'==typeof e){var r="'"+JSON.stringify(e).replace(/^"|"$/g,'').replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return n.stylize(r,'string')}return c(e)?n.stylize(''+e,'number'):u(e)?n.stylize(''+e,'boolean'):f(e)?n.stylize('null','null'):void 0}function o(n){return'['+Error.prototype.toString.call(n)+']'}function i(n,e,r,t,o){for(var i=[],a=0,u=e.length;a-1&&(u=l?u.split('\n').map(function(n){return' '+n}).join('\n').substr(2):'\n'+u.split('\n').map(function(n){return' '+n}).join('\n')):u=n.stylize('[Circular]','special')),s(a)){if(l&&i.match(/^\d+$/))return u;(a=JSON.stringify(''+i)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(a=a.substr(1,a.length-2),a=n.stylize(a,'name')):(a=a.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),a=n.stylize(a,'string'))}return a+': '+u}function a(n,e,r){return n.reduce(function(n,e){return 0,e.indexOf('\n')>=0&&0,n+e.replace(/\u001b\[\d\d?m/g,'').length+1},0)>60?r[0]+(''===e?'':e+'\n ')+' '+n.join(',\n ')+' '+r[1]:r[0]+e+' '+n.join(', ')+' '+r[1]}function u(n){return'boolean'==typeof n}function f(n){return null===n}function c(n){return'number'==typeof n}function s(n){return void 0===n}function p(n){return g(n)&&'[object RegExp]'===h(n)}function g(n){return'object'==typeof n&&null!==n}function y(n){return g(n)&&'[object Date]'===h(n)}function d(n){return g(n)&&('[object Error]'===h(n)||n instanceof Error)}function v(n){return'function'==typeof n}function h(n){return Object.prototype.toString.call(n)}function b(n,e){return Object.prototype.hasOwnProperty.call(n,e)}return function(e,t){return r({seen:[],formatValueCalls:0,stylize:n},e,t.depth)}})(),r='(index)',t={trace:0,info:1,warn:2,error:3},o=[];o[t.trace]='debug',o[t.info]='log',o[t.warn]='warning',o[t.error]='error';var i=1;function l(r){return function(){var l;l=1===arguments.length&&'string'==typeof arguments[0]?arguments[0]:Array.prototype.map.call(arguments,function(n){return e(n,{depth:10})}).join(', ');var a=arguments[0],u=r;'string'==typeof a&&'Warning: '===a.slice(0,9)&&u>=t.error&&(u=t.warn),n.__inspectorLog&&n.__inspectorLog(o[u],l,[].slice.call(arguments),i),s.length&&(l=p('',l)),n.nativeLoggingHook(l,u)}}function a(n,e){return Array.apply(null,Array(e)).map(function(){return n})}var u="\u2502",f="\u2510",c="\u2518",s=[];function p(n,e){return s.join('')+n+' '+(e||'')}if(n.nativeLoggingHook){n.console;n.console={error:l(t.error),info:l(t.info),log:l(t.info),warn:l(t.warn),trace:l(t.trace),debug:l(t.trace),table:function(e){if(!Array.isArray(e)){var o=e;for(var i in e=[],o)if(o.hasOwnProperty(i)){var l=o[i];l[r]=i,e.push(l)}}if(0!==e.length){var u=Object.keys(e[0]).sort(),f=[],c=[];u.forEach(function(n,r){c[r]=n.length;for(var t=0;t';return function(){for(var r=arguments.length,u=new Array(r),e=0;e 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | package com.reactnativedynamic.core; 9 | 10 | import android.app.AlertDialog; 11 | import android.content.Intent; 12 | import android.graphics.Color; 13 | import android.os.Bundle; 14 | import android.view.Gravity; 15 | import android.view.KeyEvent; 16 | import android.view.ViewGroup; 17 | import android.widget.TextView; 18 | 19 | import com.facebook.react.ReactInstanceManager; 20 | import com.facebook.react.ReactNativeHost; 21 | import com.facebook.react.bridge.CatalystInstance; 22 | import com.facebook.react.bridge.ReactContext; 23 | import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; 24 | import com.facebook.react.modules.core.PermissionAwareActivity; 25 | import com.facebook.react.modules.core.PermissionListener; 26 | import com.reactnativedynamic.core.bridge.ReactActivityDelegate; 27 | import com.reactnativedynamic.core.utils.FileUtils; 28 | import com.reactnativedynamic.core.utils.LoadScriptListener; 29 | import com.reactnativedynamic.core.model.RnBundle; 30 | import com.reactnativedynamic.core.utils.ScriptLoadUtil; 31 | import com.reactnativedynamic.core.utils.UpdateProgressListener; 32 | import com.reactnativedynamic.core.packages.client.TimeRecord; 33 | 34 | 35 | import java.io.File; 36 | import java.util.Date; 37 | 38 | import javax.annotation.Nullable; 39 | 40 | /** 41 | * 异步加载业务bundle的activity 42 | */ 43 | public abstract class DynamicReactActivity extends androidx.fragment.app.FragmentActivity 44 | implements DefaultHardwareBackBtnHandler, PermissionAwareActivity { 45 | 46 | public enum ScriptType {ASSET, FILE, NETWORK} 47 | 48 | private final ReactActivityDelegate mDelegate; 49 | protected boolean bundleLoaded = false; 50 | private AlertDialog mProgressDialog; 51 | 52 | protected DynamicReactActivity() { 53 | mDelegate = createReactActivityDelegate(); 54 | } 55 | 56 | /** 57 | * Returns the name of the main component registered from JavaScript. 58 | * This is used to schedule rendering of the component. 59 | * e.g. "MoviesApp" 60 | */ 61 | final private @Nullable 62 | String getMainComponentNameInner() { 63 | if (!bundleLoaded && 64 | getBundle().scriptType == ScriptType.NETWORK) { 65 | return null; 66 | } 67 | return getBundle().appName; 68 | } 69 | 70 | 71 | /** 72 | * Called at construction time, override if you have a custom delegate implementation. 73 | */ 74 | protected ReactActivityDelegate createReactActivityDelegate() { 75 | return new ReactActivityDelegate(this, getMainComponentNameInner()); 76 | } 77 | 78 | @Override 79 | protected void onCreate(Bundle savedInstanceState) { 80 | super.onCreate(savedInstanceState); 81 | TimeRecord.mDynamicLog.startActivityTime = new Date().getTime(); 82 | 83 | final ReactInstanceManager manager = DynamicReactNativeHost.getInstance().getReactInstanceManager(); 84 | mDelegate.onCreateReactDelegate(null); 85 | 86 | if (!manager.hasStartedCreatingInitialContext() 87 | || ScriptLoadUtil.getCatalystInstance(getReactNativeHost()) == null) { 88 | //由于下面是异步执行,所以需要在此处创建ReactDelegate 89 | 90 | manager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() { 91 | @Override 92 | public void onReactContextInitialized(ReactContext context) { 93 | loadScript(new LoadScriptListener() { 94 | @Override 95 | public void onLoadComplete(boolean success, String scriptPath) { 96 | bundleLoaded = success; 97 | TimeRecord.mDynamicLog.resLoadTime = new Date().getTime(); 98 | if (success) 99 | runApp(scriptPath); 100 | } 101 | }); 102 | manager.removeReactInstanceEventListener(this); 103 | } 104 | }); 105 | DynamicReactNativeHost.getInstance().getReactInstanceManager().createReactContextInBackground(); 106 | } else { 107 | loadScript(new LoadScriptListener() { 108 | @Override 109 | public void onLoadComplete(boolean success, String scriptPath) { 110 | bundleLoaded = success; 111 | TimeRecord.mDynamicLog.resLoadTime = new Date().getTime(); 112 | if (success) 113 | runApp(scriptPath); 114 | } 115 | }); 116 | } 117 | 118 | } 119 | 120 | protected abstract RnBundle getBundle(); 121 | 122 | protected void runApp(String scriptPath) { 123 | if (scriptPath != null) { 124 | scriptPath = "file://" + scriptPath.substring(0, scriptPath.lastIndexOf(File.separator) + 1); 125 | } 126 | final String path = scriptPath; 127 | final RnBundle bundle = getBundle(); 128 | final ReactInstanceManager reactInstanceManager = DynamicReactNativeHost.getInstance().getReactInstanceManager(); 129 | if (bundle.scriptType == ScriptType.NETWORK) {//如果是网络加载的话,此时正在子线程 130 | runOnUiThread(new Runnable() { 131 | @Override 132 | public void run() { 133 | ScriptLoadUtil.setJsBundleAssetPath( 134 | reactInstanceManager.getCurrentReactContext(), 135 | path); 136 | mDelegate.loadApp(getMainComponentNameInner()); 137 | } 138 | }); 139 | } else {//主线程运行 140 | ScriptLoadUtil.setJsBundleAssetPath( 141 | reactInstanceManager.getCurrentReactContext(), 142 | path); 143 | mDelegate.loadApp(getMainComponentNameInner()); 144 | } 145 | } 146 | 147 | protected void loadScript(final LoadScriptListener loadListener) { 148 | final RnBundle bundle = getBundle(); 149 | /** all buz module is loaded when in debug mode*/ 150 | if (ScriptLoadUtil.MULTI_DEBUG) {//当设置成debug模式时,所有需要的业务代码已经都加载好了 151 | loadListener.onLoadComplete(true, null); 152 | return; 153 | } 154 | ScriptType pathType = bundle.scriptType; 155 | String scriptPath = bundle.scriptUrl; 156 | final CatalystInstance instance = ScriptLoadUtil.getCatalystInstance(getReactNativeHost()); 157 | if (pathType == ScriptType.ASSET) { 158 | ScriptLoadUtil.loadScriptFromAsset(getApplicationContext(), instance, scriptPath, false); 159 | loadListener.onLoadComplete(true, null); 160 | } else if (pathType == ScriptType.FILE) { 161 | File scriptFile = new File(getApplicationContext().getFilesDir() 162 | + File.separator +/*ScriptLoadUtil.REACT_DIR+File.separator+*/scriptPath); 163 | scriptPath = scriptFile.getAbsolutePath(); 164 | ScriptLoadUtil.loadScriptFromFile(scriptPath, instance, scriptPath, false); 165 | loadListener.onLoadComplete(true, scriptPath); 166 | } else if (pathType == ScriptType.NETWORK) { 167 | AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this); 168 | dialogBuilder.setTitle("Loading jsBundle"); 169 | dialogBuilder.setCancelable(false); 170 | final TextView tvv = new TextView(this); 171 | tvv.setText("conneting");//由于demo中把文件放在了github上,所以http建立连接要花好几秒时间 172 | tvv.setTextColor(Color.BLACK); 173 | tvv.setGravity(Gravity.CENTER); 174 | tvv.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 175 | dialogBuilder.setView(tvv); 176 | mProgressDialog = dialogBuilder.create(); 177 | mProgressDialog.show(); 178 | //由于downloadRNBundle里面的md5参数由组件名代替了,实际开发中需要用到md5校验的需要自己修改 179 | FileUtils.downloadRNBundle(this.getApplicationContext(), scriptPath, bundle.appName, new UpdateProgressListener() { 180 | @Override 181 | public void updateProgressChange(final int precent) { 182 | runOnUiThread(new Runnable() { 183 | @Override 184 | public void run() { 185 | if (tvv != null) { 186 | tvv.setText(String.valueOf(precent)); 187 | } 188 | } 189 | }); 190 | } 191 | 192 | @Override 193 | public void complete(boolean success) { 194 | if (mProgressDialog != null) { 195 | runOnUiThread(new Runnable() { 196 | @Override 197 | public void run() { 198 | mProgressDialog.dismiss(); 199 | mProgressDialog = null; 200 | } 201 | }); 202 | } 203 | if (!success) { 204 | loadListener.onLoadComplete(false, null); 205 | return; 206 | } 207 | TimeRecord.mDynamicLog.resDownloadTime = new Date().getTime(); 208 | String info = FileUtils.getCurrentPackageMd5(getApplicationContext()); 209 | String bundlePath = FileUtils.getPackageFolderPath(getApplicationContext(), info); 210 | System.out.println("是否已经下载完成了 ======== " + bundlePath); 211 | 212 | String jsBundleFilePath = FileUtils.appendPathComponent(bundlePath, bundle.scriptPath); 213 | System.out.println("jsBundleFilePath ======== " + jsBundleFilePath); 214 | 215 | File bundleFile = new File(jsBundleFilePath); 216 | if (bundleFile != null && bundleFile.exists()) { 217 | System.out.println("文件存在 = " + success); 218 | ScriptLoadUtil.loadScriptFromFile(jsBundleFilePath, instance, jsBundleFilePath, false); 219 | } else { 220 | success = false; 221 | } 222 | loadListener.onLoadComplete(success, jsBundleFilePath); 223 | } 224 | }); 225 | } 226 | } 227 | 228 | protected void initView() { 229 | mDelegate.onCreate(null); 230 | } 231 | 232 | @Override 233 | protected void onPause() { 234 | super.onPause(); 235 | mDelegate.onPause(); 236 | } 237 | 238 | @Override 239 | protected void onResume() { 240 | super.onResume(); 241 | mDelegate.onResume(); 242 | } 243 | 244 | @Override 245 | protected void onDestroy() { 246 | super.onDestroy(); 247 | mDelegate.onDestroy(); 248 | } 249 | 250 | @Override 251 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 252 | super.onActivityResult(requestCode, resultCode, data); 253 | mDelegate.onActivityResult(requestCode, resultCode, data); 254 | } 255 | 256 | @Override 257 | public boolean onKeyDown(int keyCode, KeyEvent event) { 258 | return mDelegate.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event); 259 | } 260 | 261 | @Override 262 | public boolean onKeyUp(int keyCode, KeyEvent event) { 263 | return mDelegate.onKeyUp(keyCode, event) || super.onKeyUp(keyCode, event); 264 | } 265 | 266 | @Override 267 | public boolean onKeyLongPress(int keyCode, KeyEvent event) { 268 | return mDelegate.onKeyLongPress(keyCode, event) || super.onKeyLongPress(keyCode, event); 269 | } 270 | 271 | @Override 272 | public void onBackPressed() { 273 | if (!mDelegate.onBackPressed()) { 274 | super.onBackPressed(); 275 | } 276 | } 277 | 278 | @Override 279 | public void invokeDefaultOnBackPressed() { 280 | super.onBackPressed(); 281 | } 282 | 283 | @Override 284 | public void onNewIntent(Intent intent) { 285 | if (!mDelegate.onNewIntent(intent)) { 286 | super.onNewIntent(intent); 287 | } 288 | } 289 | 290 | @Override 291 | public void requestPermissions( 292 | String[] permissions, 293 | int requestCode, 294 | PermissionListener listener) { 295 | mDelegate.requestPermissions(permissions, requestCode, listener); 296 | } 297 | 298 | @Override 299 | public void onRequestPermissionsResult( 300 | int requestCode, 301 | String[] permissions, 302 | int[] grantResults) { 303 | mDelegate.onRequestPermissionsResult(requestCode, permissions, grantResults); 304 | } 305 | 306 | protected final ReactNativeHost getReactNativeHost() { 307 | return mDelegate.getReactNativeHost(); 308 | } 309 | 310 | protected final ReactInstanceManager getReactInstanceManager() { 311 | return mDelegate.getReactInstanceManager(); 312 | } 313 | 314 | protected final void loadApp(String appKey) { 315 | mDelegate.loadApp(appKey); 316 | } 317 | } 318 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/DynamicReactNativeHost.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | import com.facebook.react.ReactNativeHost; 6 | import com.facebook.react.ReactPackage; 7 | import com.facebook.react.shell.MainReactPackage; 8 | import com.reactnativedynamic.core.utils.ScriptLoadUtil; 9 | import com.reactnativedynamic.core.packages.CustomLogPackages; 10 | 11 | import java.util.Arrays; 12 | import java.util.List; 13 | 14 | /** 15 | * Author: alexganggao 16 | * Created by: 2021/8/5 17 | * Description: 18 | */ 19 | public class DynamicReactNativeHost extends ReactNativeHost { 20 | 21 | protected DynamicReactNativeHost() { 22 | super(ReactAppRuntime.getApplication()); 23 | } 24 | 25 | @Override 26 | public boolean getUseDeveloperSupport() { 27 | return ScriptLoadUtil.MULTI_DEBUG;//是否是debug模式 28 | } 29 | 30 | @Override 31 | protected List getPackages() { 32 | return Arrays.asList( 33 | new MainReactPackage(), 34 | new CustomLogPackages() 35 | ); 36 | } 37 | 38 | @Nullable 39 | @Override 40 | protected String getBundleAssetName() { 41 | return "common.android.bundle"; 42 | } 43 | 44 | @Override 45 | protected String getJSMainModuleName() { 46 | return ""; 47 | } 48 | 49 | private static class SingletonHostHolder { 50 | private static final DynamicReactNativeHost INSTANCE = new DynamicReactNativeHost(); 51 | } 52 | 53 | public static final DynamicReactNativeHost getInstance() { 54 | return DynamicReactNativeHost.SingletonHostHolder.INSTANCE; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/LogUtil.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core; 2 | 3 | import android.util.Log; 4 | 5 | /** 6 | * Author: alexganggao 7 | * Created by: 2021/8/5 8 | * Description: 9 | */ 10 | public class LogUtil { 11 | private static String buildWholeMessage(String format, Object... args) { 12 | if(args != null && args.length != 0) { 13 | return "[now]"+ String.format(format, args); 14 | } else { 15 | return "App: " + "[T_" + Thread.currentThread().getName() + "]" + format; 16 | } 17 | } 18 | public static int i(String tag, String format, Object... args) { 19 | Log.i(tag, buildWholeMessage(format, args)); 20 | 21 | return 0; 22 | } 23 | public static int e(String tag, String format, Object... args) { 24 | Log.e(tag, buildWholeMessage(format, args)); 25 | 26 | return 0; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/ReactAppRuntime.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core; 2 | 3 | import android.app.Application; 4 | 5 | /** 6 | * Author: alexganggao 7 | * Created by: 2021/8/5 8 | * Description: 9 | */ 10 | 11 | 12 | public class ReactAppRuntime { 13 | private static class SingletonHolder { 14 | private static final ReactAppRuntime INSTANCE = new ReactAppRuntime(); 15 | } 16 | 17 | private ReactAppRuntime() { 18 | } 19 | 20 | private Application mApplication; 21 | 22 | public static void init(Application application) { 23 | SingletonHolder.INSTANCE.mApplication = application; 24 | DynamicReactNativeHost.getInstance(); 25 | } 26 | 27 | public static final ReactAppRuntime getInstance() { 28 | return SingletonHolder.INSTANCE; 29 | } 30 | 31 | public static Application getApplication() { 32 | return getInstance().mApplication; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/bridge/BridgeUtil.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.bridge; 2 | 3 | import android.content.Context; 4 | 5 | import com.facebook.react.bridge.CatalystInstance; 6 | import com.facebook.react.bridge.CatalystInstanceImpl; 7 | 8 | 9 | public class BridgeUtil { 10 | 11 | public static void loadScriptFromAsset(Context context, 12 | CatalystInstance instance, 13 | String assetName,boolean loadSynchronously) { 14 | String source = assetName; 15 | if(!assetName.startsWith("assets://")) { 16 | source = "assets://" + assetName; 17 | } 18 | ((CatalystInstanceImpl)instance).loadScriptFromAssets(context.getAssets(), source,loadSynchronously); 19 | } 20 | 21 | 22 | public static void loadScriptFromFile(String fileName, 23 | CatalystInstance instance, 24 | String sourceUrl,boolean loadSynchronously) { 25 | ((CatalystInstanceImpl)instance).loadScriptFromFile(fileName, sourceUrl,loadSynchronously); 26 | } 27 | } 28 | 29 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/bridge/ReactActivityDelegate.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.bridge; 2 | 3 | // Copyright (c) Facebook, Inc. and its affiliates. 4 | 5 | // This source code is licensed under the MIT license found in the 6 | // LICENSE file in the root directory of this source tree. 7 | 8 | 9 | import android.annotation.TargetApi; 10 | import android.app.Activity; 11 | import android.content.Context; 12 | import android.content.Intent; 13 | import android.os.Build; 14 | import android.os.Bundle; 15 | import android.view.KeyEvent; 16 | 17 | import androidx.annotation.Nullable; 18 | 19 | import com.facebook.infer.annotation.Assertions; 20 | import com.facebook.react.ReactActivity; 21 | import com.facebook.react.ReactApplication; 22 | import com.facebook.react.ReactDelegate; 23 | import com.facebook.react.ReactFragmentActivity; 24 | import com.facebook.react.ReactInstanceManager; 25 | import com.facebook.react.ReactNativeHost; 26 | import com.facebook.react.ReactRootView; 27 | import com.facebook.react.bridge.Callback; 28 | import com.facebook.react.modules.core.PermissionListener; 29 | import com.reactnativedynamic.core.DynamicReactNativeHost; 30 | 31 | /** 32 | * Delegate class for {@link ReactActivity} and {@link ReactFragmentActivity}. You can subclass this 33 | * to provide custom implementations for e.g. {@link #getReactNativeHost()}, if your Application 34 | * class doesn't implement {@link ReactApplication}. 35 | */ 36 | public class ReactActivityDelegate { 37 | 38 | private final @Nullable 39 | Activity mActivity; 40 | private final @Nullable 41 | String mMainComponentName; 42 | 43 | private @Nullable 44 | PermissionListener mPermissionListener; 45 | private @Nullable 46 | Callback mPermissionsCallback; 47 | private ReactDelegate mReactDelegate; 48 | 49 | @Deprecated 50 | public ReactActivityDelegate(Activity activity, @Nullable String mainComponentName) { 51 | mActivity = activity; 52 | mMainComponentName = mainComponentName; 53 | } 54 | 55 | public ReactActivityDelegate(ReactActivity activity, @Nullable String mainComponentName) { 56 | mActivity = activity; 57 | mMainComponentName = mainComponentName; 58 | } 59 | 60 | public @Nullable 61 | Bundle getLaunchOptions() { 62 | return null; 63 | } 64 | 65 | public ReactRootView createRootView() { 66 | return new ReactRootView(getContext()); 67 | } 68 | 69 | /** 70 | * Get the {@link ReactNativeHost} used by this app. By default, assumes {@link 71 | * Activity#getApplication()} is an instance of {@link ReactApplication} and calls {@link 72 | * ReactApplication#getReactNativeHost()}. Override this method if your application class does not 73 | * implement {@code ReactApplication} or you simply have a different mechanism for storing a 74 | * {@code ReactNativeHost}, e.g. as a static field somewhere. 75 | */ 76 | public ReactNativeHost getReactNativeHost() { 77 | return DynamicReactNativeHost.getInstance(); 78 | } 79 | 80 | public ReactInstanceManager getReactInstanceManager() { 81 | return mReactDelegate.getReactInstanceManager(); 82 | } 83 | 84 | public String getMainComponentName() { 85 | return mMainComponentName; 86 | } 87 | 88 | public void onCreate(Bundle savedInstanceState) { 89 | String mainComponentName = getMainComponentName(); 90 | mReactDelegate = 91 | new ReactDelegate( 92 | getPlainActivity(), getReactNativeHost(), mainComponentName, getLaunchOptions()) { 93 | @Override 94 | public ReactRootView createRootView() { 95 | return ReactActivityDelegate.this.createRootView(); 96 | } 97 | }; 98 | if (mMainComponentName != null) { 99 | loadApp(mainComponentName); 100 | } 101 | } 102 | 103 | public void onCreateReactDelegate(Bundle savedInstanceState) { 104 | String mainComponentName = getMainComponentName(); 105 | if (mReactDelegate == null) { 106 | mReactDelegate = 107 | new ReactDelegate( 108 | getPlainActivity(), getReactNativeHost(), mainComponentName, getLaunchOptions()) { 109 | @Override 110 | public ReactRootView createRootView() { 111 | return ReactActivityDelegate.this.createRootView(); 112 | } 113 | }; 114 | } 115 | 116 | 117 | } 118 | 119 | public void loadApp(String appKey) { 120 | mReactDelegate.loadApp(appKey); 121 | getPlainActivity().setContentView(mReactDelegate.getReactRootView()); 122 | } 123 | 124 | public void onPause() { 125 | mReactDelegate.onHostPause(); 126 | } 127 | 128 | public void onResume() { 129 | mReactDelegate.onHostResume(); 130 | 131 | if (mPermissionsCallback != null) { 132 | mPermissionsCallback.invoke(); 133 | mPermissionsCallback = null; 134 | } 135 | } 136 | 137 | public void onDestroy() { 138 | mReactDelegate.onHostDestroy(); 139 | } 140 | 141 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 142 | mReactDelegate.onActivityResult(requestCode, resultCode, data, true); 143 | } 144 | 145 | public boolean onKeyDown(int keyCode, KeyEvent event) { 146 | if (getReactNativeHost().hasInstance() 147 | && getReactNativeHost().getUseDeveloperSupport() 148 | && keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { 149 | event.startTracking(); 150 | return true; 151 | } 152 | return false; 153 | } 154 | 155 | public boolean onKeyUp(int keyCode, KeyEvent event) { 156 | return mReactDelegate.shouldShowDevMenuOrReload(keyCode, event); 157 | } 158 | 159 | public boolean onKeyLongPress(int keyCode, KeyEvent event) { 160 | if (getReactNativeHost().hasInstance() 161 | && getReactNativeHost().getUseDeveloperSupport() 162 | && keyCode == KeyEvent.KEYCODE_MEDIA_FAST_FORWARD) { 163 | getReactNativeHost().getReactInstanceManager().showDevOptionsDialog(); 164 | return true; 165 | } 166 | return false; 167 | } 168 | 169 | public boolean onBackPressed() { 170 | return mReactDelegate.onBackPressed(); 171 | } 172 | 173 | public boolean onNewIntent(Intent intent) { 174 | if (getReactNativeHost().hasInstance()) { 175 | getReactNativeHost().getReactInstanceManager().onNewIntent(intent); 176 | return true; 177 | } 178 | return false; 179 | } 180 | 181 | public void onWindowFocusChanged(boolean hasFocus) { 182 | if (getReactNativeHost().hasInstance()) { 183 | getReactNativeHost().getReactInstanceManager().onWindowFocusChange(hasFocus); 184 | } 185 | } 186 | 187 | @TargetApi(Build.VERSION_CODES.M) 188 | public void requestPermissions( 189 | String[] permissions, int requestCode, PermissionListener listener) { 190 | mPermissionListener = listener; 191 | getPlainActivity().requestPermissions(permissions, requestCode); 192 | } 193 | 194 | public void onRequestPermissionsResult( 195 | final int requestCode, final String[] permissions, final int[] grantResults) { 196 | mPermissionsCallback = 197 | new Callback() { 198 | @Override 199 | public void invoke(Object... args) { 200 | if (mPermissionListener != null 201 | && mPermissionListener.onRequestPermissionsResult( 202 | requestCode, permissions, grantResults)) { 203 | mPermissionListener = null; 204 | } 205 | } 206 | }; 207 | } 208 | 209 | public Context getContext() { 210 | return Assertions.assertNotNull(mActivity); 211 | } 212 | 213 | public Activity getPlainActivity() { 214 | return ((Activity) getContext()); 215 | } 216 | } 217 | 218 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/model/ItemLog.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.model; 2 | 3 | public class ItemLog { 4 | public Long clickTime = Long.valueOf(0); 5 | public Long willTime = Long.valueOf(0); 6 | public Long initTime = Long.valueOf(0);;//组件初始化时间 7 | public Long resDownloadTime = Long.valueOf(0);; 8 | public Long resLoadTime = Long.valueOf(0);; 9 | public Long startActivityTime = Long.valueOf(0);;//activity启动开始 10 | public Long initCreateViewTime = Long.valueOf(0);; 11 | public Long createViewEndTime = Long.valueOf(0);; 12 | public Long startAppTime = Long.valueOf(0);; 13 | public Long startRenderTime = Long.valueOf(0);; 14 | public Long componentMountedTime = Long.valueOf(0);; 15 | public Long updatedTime = Long.valueOf(0);;//更新结束时间 16 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/model/RnBundle.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.model; 2 | 3 | import com.reactnativedynamic.core.DynamicReactActivity; 4 | 5 | /*** 6 | * 加载rn bundle的信息 7 | */ 8 | public class RnBundle { 9 | /** 10 | * 如果是网络下载的方式,则需要设置下载的路径,默认可以写自己的bundle名称 11 | */ 12 | public String scriptPath; 13 | /** 14 | * 网络,文件还是assets 15 | */ 16 | public DynamicReactActivity.ScriptType scriptType; 17 | /*** 18 | * 必须传递的,不管是网络,文件还是assets,都需要传递脚本url 19 | */ 20 | public String scriptUrl; 21 | /** 22 | * 必须传递: 工程下面的app.json中的Name字段 23 | */ 24 | public String appName; 25 | } 26 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/packages/CustomLogPackages.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.packages; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import com.facebook.react.ReactPackage; 7 | import com.facebook.react.bridge.NativeModule; 8 | import com.facebook.react.bridge.ReactApplicationContext; 9 | import com.facebook.react.uimanager.ViewManager; 10 | import com.reactnativedynamic.core.packages.client.LogModule; 11 | 12 | import java.util.ArrayList; 13 | import java.util.Collections; 14 | import java.util.List; 15 | 16 | public class CustomLogPackages implements ReactPackage { 17 | @NonNull 18 | @Override 19 | public List createNativeModules(@NonNull ReactApplicationContext reactContext) { 20 | List modules = new ArrayList<>(); 21 | 22 | modules.add(new LogModule(reactContext)); 23 | return modules; 24 | } 25 | 26 | @NonNull 27 | @Override 28 | public List createViewManagers(@NonNull ReactApplicationContext reactContext) { 29 | return Collections.emptyList(); 30 | } 31 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/packages/client/LogModule.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.packages.client; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import com.facebook.react.bridge.Callback; 7 | import com.facebook.react.bridge.ReactApplicationContext; 8 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 9 | import com.facebook.react.bridge.ReactMethod; 10 | import com.google.gson.Gson; 11 | import com.reactnativedynamic.MainApplication; 12 | 13 | import java.util.Date; 14 | import java.util.HashMap; 15 | 16 | public class LogModule extends ReactContextBaseJavaModule { 17 | public LogModule(@NonNull ReactApplicationContext reactContext) { 18 | super(reactContext); 19 | } 20 | 21 | @NonNull 22 | @Override 23 | public String getName() { 24 | return "LogModule"; 25 | } 26 | 27 | @ReactMethod 28 | public void log(String type) { 29 | System.out.println("type = " + type); 30 | if (!MainApplication.isDynamic) { 31 | switch (type) { 32 | case "mounted": 33 | TimeRecord.mStaticLog.componentMountedTime = new Date().getTime(); 34 | break; 35 | case "render": 36 | TimeRecord.mStaticLog.startRenderTime = new Date().getTime(); 37 | break; 38 | case "constructor": 39 | TimeRecord.mStaticLog.initTime = new Date().getTime(); 40 | break; 41 | 42 | case "updated": 43 | TimeRecord.mStaticLog.updatedTime = new Date().getTime(); 44 | break; 45 | } 46 | } else { 47 | switch (type) { 48 | case "mounted": 49 | TimeRecord.mDynamicLog.componentMountedTime = new Date().getTime(); 50 | break; 51 | case "render": 52 | TimeRecord.mDynamicLog.startRenderTime = new Date().getTime(); 53 | break; 54 | case "constructor": 55 | TimeRecord.mDynamicLog.initTime = new Date().getTime(); 56 | break; 57 | case "updated": 58 | TimeRecord.mDynamicLog.updatedTime = new Date().getTime(); 59 | break; 60 | } 61 | } 62 | 63 | } 64 | 65 | 66 | @ReactMethod 67 | public void showDynamicTimes(Callback callback) { 68 | boolean isDynamic =MainApplication.isDynamic; 69 | HashMap hashMap = new HashMap<>(); 70 | hashMap.put("&user button click time", isDynamic ? TimeRecord.mDynamicLog.clickTime : TimeRecord.mStaticLog.clickTime); 71 | hashMap.put("&init create view time", isDynamic ? TimeRecord.mDynamicLog.initCreateViewTime : TimeRecord.mStaticLog.initCreateViewTime); 72 | hashMap.put("&constructor time", isDynamic ? TimeRecord.mDynamicLog.initTime : TimeRecord.mStaticLog.initTime); 73 | hashMap.put("&init create view end time", isDynamic ? TimeRecord.mDynamicLog.createViewEndTime : TimeRecord.mStaticLog.createViewEndTime); 74 | hashMap.put("&start app time", isDynamic ? TimeRecord.mDynamicLog.startAppTime : TimeRecord.mStaticLog.startAppTime); 75 | hashMap.put("&render end time", isDynamic ? TimeRecord.mDynamicLog.startRenderTime : TimeRecord.mStaticLog.startRenderTime); 76 | hashMap.put("&mounted time", isDynamic ? TimeRecord.mDynamicLog.componentMountedTime : TimeRecord.mStaticLog.componentMountedTime); 77 | hashMap.put("&res download end time", isDynamic ? TimeRecord.mDynamicLog.resDownloadTime : 0); 78 | hashMap.put("&resource load time", isDynamic ? TimeRecord.mDynamicLog.resLoadTime : TimeRecord.mStaticLog.resLoadTime); 79 | 80 | hashMap.put("资源下载时间", isDynamic ? TimeUtils.formatTime(TimeRecord.mDynamicLog.resDownloadTime, TimeRecord.mDynamicLog.clickTime) : 0); 81 | hashMap.put("分包加载时间", isDynamic ? TimeUtils.formatTime(TimeRecord.mDynamicLog.resLoadTime, TimeRecord.mDynamicLog.resDownloadTime) : 0); 82 | hashMap.put("RN应用启动耗时", isDynamic ? TimeUtils.formatTime(TimeRecord.mDynamicLog.initTime, TimeRecord.mDynamicLog.resLoadTime) : TimeUtils.formatTime(TimeRecord.mStaticLog.initTime, TimeRecord.mStaticLog.startActivityTime)); 83 | hashMap.put("首屏视图渲染时间", isDynamic ? TimeUtils.formatTime(TimeRecord.mDynamicLog.updatedTime!=0?TimeRecord.mDynamicLog.updatedTime:TimeRecord.mDynamicLog.startRenderTime, TimeRecord.mDynamicLog.clickTime) : TimeUtils.formatTime(TimeRecord.mStaticLog.updatedTime, TimeRecord.mStaticLog.componentMountedTime)); 84 | 85 | callback.invoke(new Gson().toJson(hashMap)); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/packages/client/TimeRecord.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.packages.client; 2 | 3 | 4 | import com.reactnativedynamic.core.model.ItemLog; 5 | 6 | public class TimeRecord { 7 | public static ItemLog mStaticLog =new ItemLog(); 8 | public static ItemLog mDynamicLog =new ItemLog(); 9 | } -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/packages/client/TimeUtils.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.packages.client; 2 | 3 | import java.util.Date; 4 | 5 | public class TimeUtils { 6 | public static void logTime(String tag) { 7 | System.out.println("-------" + tag + "----------:" + new Date().getTime()); 8 | } 9 | 10 | public static Long formatTime(Long time1, Long time2) { 11 | System.out.println("时间:time1 = " + time1 + ", time2 = " + time2); 12 | return Math.abs(time1 - time2); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.utils; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.res.AssetManager; 6 | import android.util.Log; 7 | 8 | import java.io.BufferedInputStream; 9 | import java.io.BufferedOutputStream; 10 | import java.io.BufferedReader; 11 | import java.io.Closeable; 12 | import java.io.File; 13 | import java.io.FileInputStream; 14 | import java.io.FileNotFoundException; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.InputStream; 18 | import java.io.InputStreamReader; 19 | import java.io.OutputStream; 20 | import java.io.PrintWriter; 21 | import java.lang.ref.WeakReference; 22 | import java.net.HttpURLConnection; 23 | import java.net.MalformedURLException; 24 | import java.net.URL; 25 | import java.util.ArrayList; 26 | import java.util.zip.ZipEntry; 27 | import java.util.zip.ZipInputStream; 28 | 29 | public class FileUtils { 30 | public static final String PACKAGE_FILE_NAME = "app.json"; 31 | public static final String PACKAGE_HASH_KEY = "md5"; 32 | public static final String UNZIPPED_FOLDER_NAME = "unzipped"; 33 | public static final String FOLDER_RN = "bundles";//目标目录 34 | public static final String REACT_NATIVE_FILE = "index.android.bundle"; 35 | 36 | public static final String STATUS_FILE = "code_rn.json"; 37 | 38 | public static final int DOWNLOAD_BUFFER_SIZE = 1024 * 256; 39 | private static final int WRITE_BUFFER_SIZE = 1024 * 8; 40 | private static WeakReference rnActivityRef; 41 | private static final String TAG = "FileUtils"; 42 | 43 | public static final String BUNDLE_DOWNLOADPATH = "bundle-downloaded";//下载目录 44 | public static final String RN_NAME = "rnbundle";//临时下载名 45 | 46 | public static String appendPathComponent(String basePath, String appendPathComponent) { 47 | return new File(basePath, appendPathComponent).getAbsolutePath(); 48 | } 49 | 50 | public static void downloadRNBundle(final Context context,final String url,final String md5,final UpdateProgressListener listener){ 51 | final String downloadPath = context.getFilesDir()+ File.separator+BUNDLE_DOWNLOADPATH; 52 | final String fileName = RN_NAME; 53 | new Thread(new Runnable() { 54 | @Override 55 | public void run() { 56 | boolean result = true; 57 | try { 58 | boolean tmpRet = FileUtils.downloadFile(url, downloadPath, fileName,listener); 59 | if(!tmpRet){ 60 | result = false; 61 | }else { 62 | String filePath = downloadPath+ File.separator+fileName; 63 | String successStr = FileUtils.processRnPackage(context, md5, filePath); 64 | if (!"success".equals(successStr)) { 65 | result = false; 66 | } 67 | } 68 | }catch (Exception e){ 69 | result = false; 70 | }finally { 71 | if(listener!=null){ 72 | listener.complete(result); 73 | } 74 | } 75 | } 76 | }).start(); 77 | } 78 | 79 | /** 80 | * 下载后的解压操作,这里是没有md5校验的,md5只作为一个目录来使用,实际中请自行添加md5校验 81 | * @param context 82 | * @param md5 下载的文件的md5,这个可由服务端提供,这里的demo由于没服务端因此只当中目录 83 | * @param path 下载完的bundle的路径 84 | * @return 85 | */ 86 | public static String processRnPackage(Context context,String md5,String path){ 87 | System.out.println("文件解压: " + ", md5 = " + md5 + ", path = " + path); 88 | String ret = "failed"; 89 | try { 90 | File downloadFile = new File(path); 91 | if(downloadFile.exists()){ 92 | System.out.println("下载的文件存在"); 93 | } 94 | String newUpdateHash = md5; 95 | String newUpdateFolderPath = getPackageFolderPath(context, newUpdateHash); 96 | String newUpdateMetadataPath = appendPathComponent(newUpdateFolderPath, PACKAGE_FILE_NAME); 97 | if (fileAtPathExists(newUpdateFolderPath)) { 98 | // This removes any stale data in newPackageFolderPath that could have been left 99 | // uncleared due to a crash or error during the download or install process. 100 | deleteDirectoryAtPath(newUpdateFolderPath); 101 | } 102 | // Unzip the downloaded file and then delete the zip 103 | String unzippedFolderPath = appendPathComponent(getRNCodePath(context), UNZIPPED_FOLDER_NAME); 104 | System.out.println("解压路径:" + unzippedFolderPath+" "); 105 | FileUtils.unzipFile(downloadFile, unzippedFolderPath); 106 | FileUtils.deleteFileOrFolderSilently(downloadFile); 107 | 108 | FileUtils.copyDirectoryContents(unzippedFolderPath, newUpdateFolderPath); 109 | FileUtils.deleteFileAtPathSilently(unzippedFolderPath); 110 | String relativeBundlePath = newUpdateFolderPath; 111 | FileUtils.writeStringToFile(md5, appendPathComponent(getRNCodePath(context),STATUS_FILE));//用该文件判断当前最新版本 112 | FileUtils.writeStringToFile(md5, newUpdateMetadataPath); 113 | ret = "success"; 114 | }catch (Exception e){ 115 | Log.e(TAG,"react native 解压bundle失败"); 116 | e.printStackTrace(); 117 | } 118 | return ret; 119 | } 120 | 121 | public static boolean downloadFile(String url,String fileForder,String fileName,UpdateProgressListener listener) throws IOException{ 122 | System.out.println("文件开始下载:url = " + url + ", fileForder = " + fileForder + ", fileName = " + fileName + ", listener = " + listener); 123 | String downloadUrlString = url; 124 | HttpURLConnection connection = null; 125 | BufferedInputStream bin = null; 126 | FileOutputStream fos = null; 127 | BufferedOutputStream bout = null; 128 | File downloadFile = null; 129 | boolean result = false; 130 | // Download the file while checking if it is a zip and notifying client of progress. 131 | try { 132 | URL downloadUrl = new URL(downloadUrlString); 133 | connection = (HttpURLConnection) (downloadUrl.openConnection()); 134 | connection.setRequestProperty("Accept-Encoding", "identity"); 135 | bin = new BufferedInputStream(connection.getInputStream()); 136 | 137 | long totalBytes = connection.getContentLength(); 138 | long receivedBytes = 0; 139 | 140 | File downloadFolder = new File(fileForder); 141 | downloadFolder.mkdirs(); 142 | downloadFile = new File(downloadFolder, fileName); 143 | fos = new FileOutputStream(downloadFile); 144 | bout = new BufferedOutputStream(fos, DOWNLOAD_BUFFER_SIZE); 145 | byte[] data = new byte[DOWNLOAD_BUFFER_SIZE]; 146 | byte[] header = new byte[4]; 147 | 148 | int numBytesRead = 0; 149 | while ((numBytesRead = bin.read(data, 0, DOWNLOAD_BUFFER_SIZE)) >= 0) { 150 | if (receivedBytes < 4) { 151 | for (int i = 0; i < numBytesRead; i++) { 152 | int headerOffset = (int) (receivedBytes) + i; 153 | if (headerOffset >= 4) { 154 | break; 155 | } 156 | 157 | header[headerOffset] = data[i]; 158 | } 159 | } 160 | 161 | receivedBytes += numBytesRead; 162 | bout.write(data, 0, numBytesRead); 163 | listener.updateProgressChange((int)(receivedBytes*100/totalBytes)); 164 | } 165 | 166 | if (totalBytes != receivedBytes) { 167 | throw new IOException("Received " + receivedBytes + " bytes, expected " + totalBytes); 168 | } 169 | result = true; 170 | System.out.println("文件下载成功:url = " + url + ", fileForder = " + fileForder + ", fileName = " + fileName + ", listener = " + listener); 171 | } catch (MalformedURLException e) { 172 | throw new IOException(downloadUrlString, e); 173 | } finally { 174 | try { 175 | if (bout != null) bout.close(); 176 | if (fos != null) fos.close(); 177 | if (bin != null) bin.close(); 178 | if (connection != null) connection.disconnect(); 179 | } catch (IOException e) { 180 | throw new IOException("Error closing IO resources.", e); 181 | } 182 | } 183 | return result; 184 | } 185 | 186 | public static void copyDirectoryContents(String sourceDirectoryPath, String destinationDirectoryPath) throws IOException { 187 | File sourceDir = new File(sourceDirectoryPath); 188 | File destDir = new File(destinationDirectoryPath); 189 | if (!destDir.exists()) { 190 | destDir.mkdir(); 191 | } 192 | 193 | for (File sourceFile : sourceDir.listFiles()) { 194 | if (sourceFile.isDirectory()) { 195 | copyDirectoryContents( 196 | appendPathComponent(sourceDirectoryPath, sourceFile.getName()), 197 | appendPathComponent(destinationDirectoryPath, sourceFile.getName())); 198 | } else { 199 | File destFile = new File(destDir, sourceFile.getName()); 200 | FileInputStream fromFileStream = null; 201 | BufferedInputStream fromBufferedStream = null; 202 | FileOutputStream destStream = null; 203 | byte[] buffer = new byte[WRITE_BUFFER_SIZE]; 204 | try { 205 | fromFileStream = new FileInputStream(sourceFile); 206 | fromBufferedStream = new BufferedInputStream(fromFileStream); 207 | destStream = new FileOutputStream(destFile); 208 | int bytesRead; 209 | while ((bytesRead = fromBufferedStream.read(buffer)) > 0) { 210 | destStream.write(buffer, 0, bytesRead); 211 | } 212 | } finally { 213 | try { 214 | if (fromFileStream != null) fromFileStream.close(); 215 | if (fromBufferedStream != null) fromBufferedStream.close(); 216 | if (destStream != null) destStream.close(); 217 | } catch (IOException e) { 218 | throw new IOException("Error closing IO resources.", e); 219 | } 220 | } 221 | } 222 | } 223 | } 224 | 225 | public static void deleteDirectoryAtPath(String directoryPath) { 226 | if (directoryPath == null) { 227 | Log.e(TAG,"deleteDirectoryAtPath attempted with null directoryPath"); 228 | return; 229 | } 230 | File file = new File(directoryPath); 231 | if (file.exists()) { 232 | deleteFileOrFolderSilently(file); 233 | } 234 | } 235 | 236 | public static void deleteFileAtPathSilently(String path) { 237 | deleteFileOrFolderSilently(new File(path)); 238 | } 239 | 240 | public static void deleteFileOrFolderSilently(File file) { 241 | if (file.isDirectory()) { 242 | File[] files = file.listFiles(); 243 | for (File fileEntry : files) { 244 | if (fileEntry.isDirectory()) { 245 | deleteFileOrFolderSilently(fileEntry); 246 | } else { 247 | fileEntry.delete(); 248 | } 249 | } 250 | } 251 | 252 | if (!file.delete()) { 253 | Log.e(TAG,"Error deleting file " + file.getName()); 254 | } 255 | } 256 | 257 | public static String getRNCodePath(Context context) { 258 | String codePath = appendPathComponent(context.getFilesDir().getAbsolutePath(), FOLDER_RN); 259 | return codePath; 260 | } 261 | 262 | public static String getPackageFolderPath(Context context,String packageHash) { 263 | return appendPathComponent(getRNCodePath(context), packageHash); 264 | } 265 | 266 | public static String getCurrentPackageMd5(Context context) { 267 | String statusFilePath = appendPathComponent(getRNCodePath(context), "code_rn.json"); 268 | String content = null; 269 | try { 270 | content = readFileToString(statusFilePath); 271 | }catch (Exception e){ 272 | e.printStackTrace(); 273 | } 274 | 275 | if (content == null) { 276 | return content; 277 | } else { 278 | content.replace('\n', ' '); 279 | return content.trim(); 280 | } 281 | } 282 | 283 | public static boolean fileAtPathExists(String filePath) { 284 | return new File(filePath).exists(); 285 | } 286 | 287 | public static void moveFile(File fileToMove, String newFolderPath, String newFileName) throws IOException{ 288 | File newFolder = new File(newFolderPath); 289 | if (!newFolder.exists()) { 290 | newFolder.mkdirs(); 291 | } 292 | 293 | File newFilePath = new File(newFolderPath, newFileName); 294 | if (!fileToMove.renameTo(newFilePath)) { 295 | throw new IOException("Unable to move file from " + 296 | fileToMove.getAbsolutePath() + " to " + newFilePath.getAbsolutePath() + "."); 297 | } 298 | } 299 | 300 | public static String readFileToString(String filePath) throws IOException { 301 | FileInputStream fin = null; 302 | BufferedReader reader = null; 303 | try { 304 | File fl = new File(filePath); 305 | fin = new FileInputStream(fl); 306 | reader = new BufferedReader(new InputStreamReader(fin)); 307 | StringBuilder sb = new StringBuilder(); 308 | String line = null; 309 | while ((line = reader.readLine()) != null) { 310 | sb.append(line).append("\n"); 311 | } 312 | 313 | return sb.toString(); 314 | } finally { 315 | if (reader != null) reader.close(); 316 | if (fin != null) fin.close(); 317 | } 318 | } 319 | 320 | public static void unzipFile(File zipFile, String destination) throws IOException { 321 | FileInputStream fileStream = null; 322 | BufferedInputStream bufferedStream = null; 323 | ZipInputStream zipStream = null; 324 | try { 325 | fileStream = new FileInputStream(zipFile); 326 | bufferedStream = new BufferedInputStream(fileStream); 327 | zipStream = new ZipInputStream(bufferedStream); 328 | ZipEntry entry; 329 | 330 | File destinationFolder = new File(destination); 331 | if (destinationFolder.exists()) { 332 | deleteFileOrFolderSilently(destinationFolder); 333 | } 334 | 335 | destinationFolder.mkdirs(); 336 | 337 | byte[] buffer = new byte[WRITE_BUFFER_SIZE]; 338 | while ((entry = zipStream.getNextEntry()) != null) { 339 | String fileName = entry.getName(); 340 | System.out.println("解压文件 = " + fileName); 341 | File file = new File(destinationFolder, fileName); 342 | if (entry.isDirectory()) { 343 | file.mkdirs(); 344 | } else { 345 | File parent = file.getParentFile(); 346 | if (!parent.exists()) { 347 | parent.mkdirs(); 348 | } 349 | 350 | FileOutputStream fout = new FileOutputStream(file); 351 | try { 352 | int numBytesRead; 353 | while ((numBytesRead = zipStream.read(buffer)) != -1) { 354 | fout.write(buffer, 0, numBytesRead); 355 | } 356 | } finally { 357 | fout.close(); 358 | } 359 | } 360 | long time = entry.getTime(); 361 | if (time > 0) { 362 | file.setLastModified(time); 363 | } 364 | } 365 | } finally { 366 | try { 367 | if (zipStream != null) zipStream.close(); 368 | if (bufferedStream != null) bufferedStream.close(); 369 | if (fileStream != null) fileStream.close(); 370 | } catch (IOException e) { 371 | throw new IOException("Error closing IO resources.", e); 372 | } 373 | } 374 | } 375 | 376 | public static void writeStringToFile(String content, String filePath) throws IOException { 377 | PrintWriter out = null; 378 | try { 379 | out = new PrintWriter(filePath); 380 | out.print(content); 381 | } finally { 382 | if (out != null) out.close(); 383 | } 384 | } 385 | 386 | /** 387 | * 388 | * @param context 389 | * @param assetFilePath 390 | * @param destPath 391 | */ 392 | public static boolean copyAssetFile(Context context,String assetFilePath, String destPath,boolean overWrite){ 393 | AssetManager assetManager = context.getAssets(); 394 | File fileDir = context.getFilesDir(); 395 | String absoluteDestPath = fileDir.getAbsolutePath()+File.separator+destPath; 396 | try { 397 | ArrayList files = getAssetsFilePath(context,assetFilePath,null); 398 | for(int i=0;i getAssetsFilePath(Context context, String oriPath, ArrayList paths) throws IOException{ 427 | 428 | if (paths == null) paths = new ArrayList<>(); 429 | 430 | String[] list = context.getAssets().list(oriPath); 431 | for (String l : list) { 432 | int length = context.getAssets().list(l).length; 433 | String desPath = oriPath.equals("") ? l : oriPath + "/" + l; 434 | if (length == 0) { 435 | paths.add(desPath); 436 | } else { 437 | getAssetsFilePath(context, desPath, paths); 438 | } 439 | } 440 | return paths; 441 | 442 | } 443 | 444 | 445 | private static boolean writeFileFromIS(File file, InputStream is) { 446 | if (file == null || is == null) return false; 447 | if (!createOrExistsFile(file)) return false; 448 | 449 | OutputStream os = null; 450 | try { 451 | os = new BufferedOutputStream(new FileOutputStream(file)); 452 | byte data[] = new byte[1024]; 453 | int len; 454 | while ((len = is.read(data, 0, 1024)) != -1) { 455 | os.write(data, 0, len); 456 | } 457 | return true; 458 | } catch (IOException e) { 459 | e.printStackTrace(); 460 | return false; 461 | } finally { 462 | closeIO(is, os); 463 | } 464 | } 465 | 466 | 467 | 468 | private static boolean createOrExistsFile(File file) { 469 | if (file == null) return false; 470 | if (file.exists()) return file.isFile(); 471 | if (!createOrExistsDir(file.getParentFile())) return false; 472 | try { 473 | return file.createNewFile(); 474 | } catch (IOException e) { 475 | e.printStackTrace(); 476 | return false; 477 | } 478 | } 479 | 480 | 481 | private static boolean createOrExistsDir(File file) { 482 | return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); 483 | } 484 | 485 | 486 | private static void closeIO(Closeable... closeables) { 487 | if (closeables == null) return; 488 | for (Closeable closeable : closeables) { 489 | if (closeable != null) { 490 | try { 491 | closeable.close(); 492 | } catch (IOException e) { 493 | e.printStackTrace(); 494 | } 495 | } 496 | } 497 | } 498 | } 499 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/utils/LoadScriptListener.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.utils; 2 | 3 | public interface LoadScriptListener { 4 | public void onLoadComplete(boolean success,String scriptPath); 5 | } 6 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/utils/ReactUtil.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.utils; 2 | 3 | 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | import com.facebook.react.bridge.CatalystInstance; 8 | 9 | public class ReactUtil { 10 | 11 | @Nullable 12 | public static String getSourceUrl(CatalystInstance instance) { 13 | return instance.getSourceURL(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/utils/ScriptLoadUtil.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.utils; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.Nullable; 7 | 8 | import com.facebook.react.ReactInstanceManager; 9 | import com.facebook.react.ReactNativeHost; 10 | import com.facebook.react.ReactRootView; 11 | import com.facebook.react.bridge.CatalystInstance; 12 | import com.facebook.react.bridge.ReactContext; 13 | import com.facebook.react.modules.core.DeviceEventManagerModule; 14 | import com.reactnativedynamic.core.bridge.BridgeUtil; 15 | 16 | import java.lang.reflect.Field; 17 | import java.lang.reflect.InvocationTargetException; 18 | import java.lang.reflect.Method; 19 | import java.util.HashSet; 20 | import java.util.Set; 21 | 22 | 23 | public class ScriptLoadUtil { 24 | protected final static String TAG = "ScriptLoadUtil"; 25 | public static final String REACT_DIR = "react_bundles"; 26 | public static final boolean MULTI_DEBUG = false;//需要debug的时候设置成true,你也可以设置成跟BuildConfig.DEBUG一致 27 | private static Set sLoadedScript = new HashSet<>(); 28 | 29 | public static void recreateReactContextInBackgroundInner(ReactInstanceManager manager) { 30 | try {//recreateReactContextInBackground replace this 31 | Method method = ReactInstanceManager.class.getDeclaredMethod("recreateReactContextInBackgroundInner"); 32 | method.setAccessible(true); 33 | method.invoke(manager); 34 | } catch (NoSuchMethodException e) { 35 | e.printStackTrace(); 36 | } catch (IllegalAccessException e) { 37 | e.printStackTrace(); 38 | } catch (InvocationTargetException e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | 43 | public static void moveToResumedLifecycleState(ReactInstanceManager manager, boolean force) { 44 | try { 45 | Method method = ReactInstanceManager.class.getDeclaredMethod("moveToResumedLifecycleState", boolean.class); 46 | method.setAccessible(true); 47 | method.invoke(manager, force); 48 | } catch (NoSuchMethodException e) { 49 | e.printStackTrace(); 50 | } catch (IllegalAccessException e) { 51 | e.printStackTrace(); 52 | } catch (InvocationTargetException e) { 53 | e.printStackTrace(); 54 | } 55 | } 56 | 57 | public static void setJsModuleName(ReactRootView rootView, String moduleName) { 58 | try { 59 | Field field = ReactRootView.class.getDeclaredField("mJSModuleName"); 60 | field.setAccessible(true); 61 | field.set(rootView, moduleName); 62 | } catch (NoSuchFieldException e) { 63 | e.printStackTrace(); 64 | } catch (IllegalAccessException e) { 65 | e.printStackTrace(); 66 | } 67 | } 68 | 69 | @Nullable 70 | public static CatalystInstance getCatalystInstance(ReactNativeHost host) { 71 | ReactInstanceManager manager = host.getReactInstanceManager(); 72 | if (manager == null) { 73 | Log.e(TAG,"manager is null!!"); 74 | return null; 75 | } 76 | 77 | ReactContext context = manager.getCurrentReactContext(); 78 | if (context == null) { 79 | Log.e(TAG,"context is null!!"); 80 | return null; 81 | } 82 | return context.getCatalystInstance(); 83 | } 84 | 85 | public static void setJsBundleAssetPath(ReactContext reactContext,String bundleAssetPath){ 86 | reactContext 87 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) 88 | .emit("sm-bundle-changed", bundleAssetPath); 89 | } 90 | 91 | @Nullable 92 | public static String getSourceUrl(CatalystInstance instance) { 93 | return ReactUtil.getSourceUrl(instance); 94 | } 95 | 96 | public static void loadScriptFromAsset(Context context, 97 | CatalystInstance instance, 98 | String assetName,boolean isSync) { 99 | if (sLoadedScript.contains(assetName)) { 100 | return; 101 | } 102 | BridgeUtil.loadScriptFromAsset(context,instance,assetName,isSync); 103 | sLoadedScript.add(assetName); 104 | } 105 | 106 | public static void loadScriptFromFile(String fileName, 107 | CatalystInstance instance, 108 | String sourceUrl,boolean isSync) { 109 | if (sLoadedScript.contains(sourceUrl)) { 110 | return; 111 | } 112 | BridgeUtil.loadScriptFromFile(fileName,instance,sourceUrl,isSync); 113 | sLoadedScript.add(sourceUrl); 114 | } 115 | 116 | public static void clearLoadedRecord(){ 117 | if(sLoadedScript!=null){ 118 | sLoadedScript.clear(); 119 | } 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /android/app/src/main/java/com/reactnativedynamic/core/utils/UpdateProgressListener.java: -------------------------------------------------------------------------------- 1 | package com.reactnativedynamic.core.utils; 2 | 3 | public interface UpdateProgressListener { 4 | public void updateProgressChange(int precent); 5 | public void complete(boolean success); 6 | } 7 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | ReactNativeDynamic 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "29.0.3" 6 | minSdkVersion = 21 7 | compileSdkVersion = 29 8 | targetSdkVersion = 29 9 | ndkVersion = "20.1.5948944" 10 | } 11 | repositories { 12 | google() 13 | jcenter() 14 | } 15 | dependencies { 16 | classpath("com.android.tools.build:gradle:4.1.0") 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | mavenLocal() 25 | maven { 26 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 27 | url("$rootDir/../node_modules/react-native/android") 28 | } 29 | maven { 30 | // Android JSC is installed from npm 31 | url("$rootDir/../node_modules/jsc-android/dist") 32 | } 33 | 34 | google() 35 | jcenter() 36 | maven { url 'https://www.jitpack.io' } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.75.1 29 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MrGaoGang/react-native-dynamic-load/5834003f7e226e803d0352d0e022973c1f69ba4b/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'ReactNativeDynamic' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactNativeDynamic", 3 | "displayName": "ReactNativeDynamic" 4 | } -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /common-entry.js: -------------------------------------------------------------------------------- 1 | import 'react'; 2 | import 'react-native'; 3 | require('react-native/Libraries/Core/checkNativeVersion'); 4 | -------------------------------------------------------------------------------- /compile/common-modules-android.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /compile/common-modules-ios.json: -------------------------------------------------------------------------------- 1 | [] -------------------------------------------------------------------------------- /compile/metro-base.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const md5 = require('crypto-js/md5'); 3 | 4 | const buildType = process.argv[4]; 5 | const {name: appName} = require('../app.json'); 6 | const commonModulesFileName = `${__dirname}/common-modules-${buildType}.json`; 7 | const commonModulesIndexMapFileName = `${__dirname}/common-modules-index-map-${buildType}.json`; 8 | const pathM = require('path'); 9 | const pathSep = pathM.sep; 10 | const base = process.cwd(); 11 | 12 | // 使用名称作为id 13 | const commonAndroid = require(`${base}/compile/common-modules-android.json`); 14 | const commonIOS = require(`${base}/compile/common-modules-ios.json`); 15 | const commonModules = buildType === 'ios' ? commonIOS : commonAndroid; 16 | 17 | // 使用数字作为id 18 | const commonModulesIndexMap = 19 | buildType === 'ios' 20 | ? require(`${base}/compile/common-modules-index-map-ios.json`) 21 | : require(`${base}/compile/common-modules-index-map-android.json`); 22 | // 针对id使用数字,随机生成 23 | const randomNum = +`${+new Date()}`.slice(-9); 24 | // 是否使用index作为表示 25 | const moduleIdByIndex = true; 26 | 27 | function clearFileInfo() { 28 | if (moduleIdByIndex) { 29 | fs.writeFileSync(commonModulesIndexMapFileName, '{}'); 30 | } else { 31 | fs.writeFileSync(commonModulesFileName, '[]'); 32 | } 33 | } 34 | 35 | function createModuleIdFactory() { 36 | let nextId = randomNum; 37 | const fileToIdMap = new Map(); 38 | 39 | return path => { 40 | if (!moduleIdByIndex) { 41 | const name = getModuleIdByName(base, path); 42 | return name; 43 | } 44 | // 公共包的Id 45 | const relPath = pathM.relative(base, path); 46 | if (commonModulesIndexMap[relPath]) { 47 | return commonModulesIndexMap[relPath]; 48 | } 49 | // 业务包的Id 50 | let id = fileToIdMap.get(path); 51 | if (typeof id !== 'number') { 52 | id = nextId + 1; 53 | nextId = nextId + 1; 54 | fileToIdMap.set(path, id); 55 | } 56 | return id; 57 | }; 58 | } 59 | 60 | function processModuleFilter() { 61 | return module => { 62 | const {path} = module; 63 | const relPath = pathM.relative(base, path); 64 | if ( 65 | path.indexOf('__prelude__') !== -1 || 66 | path.indexOf('/node_modules/react-native/Libraries/polyfills') !== -1 || 67 | path.indexOf('source-map') !== -1 || 68 | path.indexOf('/node_modules/metro-runtime/src/polyfills/require.js') !== 69 | -1 70 | ) { 71 | return false; 72 | } 73 | // 使用name的情况 74 | if (!moduleIdByIndex) { 75 | if (commonModules.includes(relPath)) { 76 | return false; 77 | } 78 | } else { 79 | // 使用id的情况 80 | if (commonModulesIndexMap[relPath]) { 81 | return false; 82 | } 83 | } 84 | 85 | return true; 86 | }; 87 | } 88 | /** 89 | * 公共包分包,id创建,如果是以id则从0开始,否则按照路径名称开始 90 | */ 91 | function createCommonModuleIdFactory() { 92 | let nextId = 0; 93 | const fileToIdMap = new Map(); 94 | 95 | return path => { 96 | if (!moduleIdByIndex) { 97 | const name = getModuleIdByName(base, path); 98 | const relPath = pathM.relative(base, path); 99 | if (!commonModules.includes(relPath)) { 100 | // 记录路径 101 | commonModules.push(relPath); 102 | fs.writeFileSync(commonModulesFileName, JSON.stringify(commonModules)); 103 | } 104 | return name; 105 | } 106 | let id = fileToIdMap.get(path); 107 | 108 | if (typeof id !== 'number') { 109 | id = nextId + 1; 110 | nextId = nextId + 1; 111 | fileToIdMap.set(path, id); 112 | const relPath = pathM.relative(base, path); 113 | if (!commonModulesIndexMap[relPath]) { 114 | // 记录路径和id的关系 115 | commonModulesIndexMap[relPath] = id; 116 | fs.writeFileSync( 117 | commonModulesIndexMapFileName, 118 | JSON.stringify(commonModulesIndexMap), 119 | ); 120 | } 121 | } 122 | return id; 123 | }; 124 | } 125 | 126 | /** 根据模块路径返回moduleId,优点是简单且确保唯一,缺点是无法使用rambundle打包方式*/ 127 | function getModuleIdByName(projectRootPath, path) { 128 | let name = ''; 129 | if ( 130 | path.indexOf( 131 | `node_modules${pathSep}react-native${pathSep}Libraries${pathSep}`, 132 | ) > 0 133 | ) { 134 | // 这里是react native 自带的库,因其一般不会改变路径,所以可直接截取最后的文件名称 135 | name = path.substr(path.lastIndexOf(pathSep) + 1); 136 | } else if (path.indexOf(`${projectRootPath + pathSep}node_modules`) === 0) { 137 | // 针对node_modules的,加上node_modules前缀 138 | /* 139 | 这里是react native 自带库以外的其他库,因是绝对路径,带有设备信息, 140 | 为了避免重复名称,可以保留node_modules直至结尾 141 | 如node_modules/xxx.js 需要将设备信息截掉 142 | */ 143 | name = path.substr(projectRootPath.length + 1); 144 | } else if (path.indexOf(`${projectRootPath}`) === 0) { 145 | // 针对本项目的,加上项目特定前缀 146 | name = appName + pathSep + path.substr(projectRootPath.length + 1); 147 | } 148 | name = name.replace('.js', ''); 149 | name = name.replace('.png', ''); 150 | const regExp = 151 | pathSep === '\\' ? new RegExp('\\\\', 'gm') : new RegExp(pathSep, 'gm'); 152 | name = name.replace(regExp, '_'); // 把path中的/换成下划线 153 | name = name.replace('@', '_'); // 把path中的/换成下划线 154 | 155 | return name; 156 | } 157 | 158 | module.exports = { 159 | createModuleIdFactory, 160 | processModuleFilter, 161 | clearFileInfo, 162 | moduleIdByIndex, 163 | createCommonModuleIdFactory, 164 | }; 165 | -------------------------------------------------------------------------------- /compile/split-common.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const filePath = process.argv[2]; 3 | console.log('正在处理文件:' + filePath); 4 | if (filePath) { 5 | if (fs.existsSync(filePath)) { 6 | try { 7 | fs.readFile( 8 | filePath, 9 | { 10 | encoding: 'utf8', 11 | }, 12 | function (err, data) { 13 | if (err) { 14 | // check and handle err 15 | console.log(filePath, '文件读取失败'); 16 | } 17 | console.log(filePath, '删除最后一行成功~'); 18 | const datas = data.split('\n'); 19 | const linesExceptFirst = datas.slice(0, datas.length - 1).join('\n'); 20 | fs.writeFileSync(filePath, linesExceptFirst); 21 | }, 22 | ); 23 | } catch (error) { 24 | console.log(filePath, '文件读取失败'); 25 | console.error(error); 26 | } 27 | } else { 28 | console.log(filePath, '文件不存在'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /dist/business/assets/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactNativeDynamic", 3 | "displayName": "ReactNativeDynamic" 4 | } -------------------------------------------------------------------------------- /dist/business/business.ios.bundle: -------------------------------------------------------------------------------- 1 | !(function(n){var e=(function(){function n(n,e){return n}function e(n){var e={};return n.forEach(function(n,r){e[n]=!0}),e}function r(n,r,u){if(n.formatValueCalls++,n.formatValueCalls>200)return"[TOO BIG formatValueCalls "+n.formatValueCalls+" exceeded limit of 200]";var f=t(n,r);if(f)return f;var c=Object.keys(r),s=e(c);if(d(r)&&(c.indexOf('message')>=0||c.indexOf('description')>=0))return o(r);if(0===c.length){if(v(r)){var g=r.name?': '+r.name:'';return n.stylize('[Function'+g+']','special')}if(p(r))return n.stylize(RegExp.prototype.toString.call(r),'regexp');if(y(r))return n.stylize(Date.prototype.toString.call(r),'date');if(d(r))return o(r)}var h,b,m='',j=!1,O=['{','}'];(h=r,Array.isArray(h)&&(j=!0,O=['[',']']),v(r))&&(m=' [Function'+(r.name?': '+r.name:'')+']');return p(r)&&(m=' '+RegExp.prototype.toString.call(r)),y(r)&&(m=' '+Date.prototype.toUTCString.call(r)),d(r)&&(m=' '+o(r)),0!==c.length||j&&0!=r.length?u<0?p(r)?n.stylize(RegExp.prototype.toString.call(r),'regexp'):n.stylize('[Object]','special'):(n.seen.push(r),b=j?i(n,r,u,s,c):c.map(function(e){return l(n,r,u,s,e,j)}),n.seen.pop(),a(b,m,O)):O[0]+m+O[1]}function t(n,e){if(s(e))return n.stylize('undefined','undefined');if('string'==typeof e){var r="'"+JSON.stringify(e).replace(/^"|"$/g,'').replace(/'/g,"\\'").replace(/\\"/g,'"')+"'";return n.stylize(r,'string')}return c(e)?n.stylize(''+e,'number'):u(e)?n.stylize(''+e,'boolean'):f(e)?n.stylize('null','null'):void 0}function o(n){return'['+Error.prototype.toString.call(n)+']'}function i(n,e,r,t,o){for(var i=[],a=0,u=e.length;a-1&&(u=l?u.split('\n').map(function(n){return' '+n}).join('\n').substr(2):'\n'+u.split('\n').map(function(n){return' '+n}).join('\n')):u=n.stylize('[Circular]','special')),s(a)){if(l&&i.match(/^\d+$/))return u;(a=JSON.stringify(''+i)).match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)?(a=a.substr(1,a.length-2),a=n.stylize(a,'name')):(a=a.replace(/'/g,"\\'").replace(/\\"/g,'"').replace(/(^"|"$)/g,"'"),a=n.stylize(a,'string'))}return a+': '+u}function a(n,e,r){return n.reduce(function(n,e){return 0,e.indexOf('\n')>=0&&0,n+e.replace(/\u001b\[\d\d?m/g,'').length+1},0)>60?r[0]+(''===e?'':e+'\n ')+' '+n.join(',\n ')+' '+r[1]:r[0]+e+' '+n.join(', ')+' '+r[1]}function u(n){return'boolean'==typeof n}function f(n){return null===n}function c(n){return'number'==typeof n}function s(n){return void 0===n}function p(n){return g(n)&&'[object RegExp]'===h(n)}function g(n){return'object'==typeof n&&null!==n}function y(n){return g(n)&&'[object Date]'===h(n)}function d(n){return g(n)&&('[object Error]'===h(n)||n instanceof Error)}function v(n){return'function'==typeof n}function h(n){return Object.prototype.toString.call(n)}function b(n,e){return Object.prototype.hasOwnProperty.call(n,e)}return function(e,t){return r({seen:[],formatValueCalls:0,stylize:n},e,t.depth)}})(),r='(index)',t={trace:0,info:1,warn:2,error:3},o=[];o[t.trace]='debug',o[t.info]='log',o[t.warn]='warning',o[t.error]='error';var i=1;function l(r){return function(){var l;l=1===arguments.length&&'string'==typeof arguments[0]?arguments[0]:Array.prototype.map.call(arguments,function(n){return e(n,{depth:10})}).join(', ');var a=arguments[0],u=r;'string'==typeof a&&'Warning: '===a.slice(0,9)&&u>=t.error&&(u=t.warn),n.__inspectorLog&&n.__inspectorLog(o[u],l,[].slice.call(arguments),i),s.length&&(l=p('',l)),n.nativeLoggingHook(l,u)}}function a(n,e){return Array.apply(null,Array(e)).map(function(){return n})}var u="\u2502",f="\u2510",c="\u2518",s=[];function p(n,e){return s.join('')+n+' '+(e||'')}if(n.nativeLoggingHook){n.console;n.console={error:l(t.error),info:l(t.info),log:l(t.info),warn:l(t.warn),trace:l(t.trace),debug:l(t.trace),table:function(e){if(!Array.isArray(e)){var o=e;for(var i in e=[],o)if(o.hasOwnProperty(i)){var l=o[i];l[r]=i,e.push(l)}}if(0!==e.length){var u=Object.keys(e[0]).sort(),f=[],c=[];u.forEach(function(n,r){c[r]=n.length;for(var t=0;t';return function(){for(var r=arguments.length,u=new Array(r),e=0;e App); 9 | -------------------------------------------------------------------------------- /ios/Controller/HomeViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.h 3 | // ReactNativeDynamic 4 | // 5 | // Created by mrgaogang on 2021/6/22. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface HomeViewController : UIViewController 13 | 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /ios/Controller/HomeViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // HomeViewController.m 3 | // ReactNativeDynamic 4 | // 5 | // Created by mrgaogang on 2021/6/22. 6 | // 7 | 8 | #import "BridgeManager.h" 9 | #import "HomeViewController.h" 10 | #import 11 | 12 | @interface HomeViewController () 13 | 14 | @end 15 | 16 | @implementation HomeViewController 17 | 18 | - (void)viewDidLoad { 19 | [super viewDidLoad]; 20 | // 此处只是使用加载本地的bundle的方式,如果是在线的方式,可以先使用http下载然后加载本地 21 | [BridgeManager.instance 22 | loadBusinessBundle:@"business.ios" 23 | moduleName:@"ReactNativeDynamic" 24 | callback:^(BOOL succeed) { 25 | if (succeed) { 26 | RCTRootView *rootView = [[RCTRootView alloc] 27 | initWithBridge:BridgeManager.instance.commonBridge 28 | moduleName:@"ReactNativeDynamic" 29 | initialProperties:nil]; 30 | self.view = rootView; 31 | } 32 | NSLog(@"%d", succeed); 33 | }]; 34 | } 35 | 36 | /* 37 | #pragma mark - Navigation 38 | 39 | // In a storyboard-based application, you will often want to do a little 40 | preparation before navigation 41 | - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { 42 | // Get the new view controller using [segue destinationViewController]. 43 | // Pass the selected object to the new view controller. 44 | } 45 | */ 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /ios/Core/BridgeManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // BridgeManager.h 3 | // ReactNativeDynamic 4 | // 5 | // Created by mrgaogang on 2021/6/22. 6 | // 7 | 8 | #import 9 | #import "RCTBridge+Extension.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef enum { 14 | RNBundleLoadStatus_Init = 0, 15 | RNBundleLoadStatus_Loading = 1, 16 | RNBundleLoadedStatus_Successed = 2, 17 | RNBundleLoadStatus_Failed = 3 18 | } RNBundleLoadStatus; 19 | 20 | typedef void(^RNBusinessLoadCallback)(BOOL succeed); 21 | 22 | 23 | 24 | @interface BridgeManager : NSObject 25 | 26 | @property (nonatomic, strong) RCTBridge *commonBridge; 27 | 28 | @property(nonatomic,strong) NSMutableArray *loadedScripts; 29 | 30 | @property(nonatomic,assign) RNBundleLoadStatus commonStatus; 31 | 32 | - (void) loadBaseBundleWithLaunchOptions:(NSDictionary *)launchOptions; 33 | 34 | - (void)loadBusinessBundle:(NSString*)path moduleName:(NSString*) moduleName callback:(RNBusinessLoadCallback) callback; 35 | 36 | +(instancetype)instance; 37 | 38 | @end 39 | 40 | NS_ASSUME_NONNULL_END 41 | -------------------------------------------------------------------------------- /ios/Core/BridgeManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // BridgeManager.m 3 | // ReactNativeDynamic 4 | // 5 | // Created by mrgaogang on 2021/6/22. 6 | // 7 | 8 | #import "BridgeManager.h" 9 | #import "RCTBridge+Extension.h" 10 | @interface BridgeManager () 11 | @property(nonatomic, strong) NSMutableArray *pendingQueue; 12 | 13 | @end 14 | @implementation BridgeManager 15 | 16 | - (instancetype)init { 17 | if (self = [super init]) { 18 | [self initManager]; 19 | } 20 | return self; 21 | } 22 | 23 | + (instancetype)instance { 24 | static BridgeManager *manager = nil; 25 | static dispatch_once_t onceToken; 26 | dispatch_once(&onceToken, ^{ 27 | manager = [[BridgeManager alloc] init]; 28 | }); 29 | return manager; 30 | } 31 | 32 | - (void)initManager { 33 | // success listener 34 | [[NSNotificationCenter defaultCenter] 35 | addObserver:self 36 | selector:@selector(onJSDidLoad:) 37 | name:RCTJavaScriptDidLoadNotification 38 | object:nil]; 39 | // error listener 40 | [[NSNotificationCenter defaultCenter] 41 | addObserver:self 42 | selector:@selector(onJSLoadError:) 43 | name:RCTJavaScriptDidFailToLoadNotification 44 | object:nil]; 45 | 46 | self.pendingQueue = [[NSMutableArray alloc] initWithCapacity:1]; 47 | } 48 | 49 | - (void)loadBaseBundleWithLaunchOptions:(NSDictionary *)launchOptions { 50 | 51 | if (self.commonBridge == nil) { 52 | self.commonStatus = RNBundleLoadStatus_Init; 53 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self 54 | launchOptions:launchOptions]; 55 | self.commonBridge = bridge; 56 | self.commonStatus = RNBundleLoadStatus_Loading; 57 | } 58 | } 59 | 60 | #pragma mask - js listener 61 | - (void)onJSDidLoad:(NSNotification *)notification { 62 | // base lib load success! 63 | NSLog(@"base bundle load success!"); 64 | [self _baseBundleLoad:nil]; 65 | } 66 | 67 | - (void)onJSLoadError:(NSNotification *)notification { 68 | // base lib load failed! 69 | NSLog(@"base bundle load faild!"); 70 | [self _baseBundleLoad: [NSError new]]; 71 | } 72 | 73 | // load business js bundle 74 | - (void)loadBusinessBundle:(NSString *)path 75 | moduleName:(NSString *)moduleName 76 | callback:(RNBusinessLoadCallback)callback { 77 | if (self.commonBridge == nil) { 78 | NSLog(@"basic jsbundle not loaded before business bundle!"); 79 | return; 80 | } 81 | if (self.loadedScripts == nil) { 82 | self.loadedScripts = [NSMutableArray array]; 83 | } 84 | 85 | NSString *jsCodeLocationBuz = 86 | [[NSBundle mainBundle] URLForResource:path withExtension:@"bundle"] 87 | .path; 88 | NSError *error = nil; 89 | // you can replace to you own file location 90 | NSData *sourceBuz = [NSData dataWithContentsOfFile:jsCodeLocationBuz 91 | options:NSDataReadingMappedIfSafe 92 | error:&error]; 93 | 94 | if (error == nil) { // load source success 95 | RNBusinessLoadCallback blk = ^(BOOL successed) { 96 | if (successed) { 97 | if ([self.loadedScripts indexOfObject:moduleName] == NSNotFound) { 98 | [self.loadedScripts addObject:moduleName]; 99 | [self.commonBridge.batchedBridge executeSourceCode:sourceBuz sync:NO]; 100 | }else{ 101 | // has loaded 102 | NSLog(@"bundle %@ has loaded",path); 103 | } 104 | callback(YES); 105 | 106 | } else { 107 | callback(successed); 108 | } 109 | }; 110 | 111 | if (self.commonStatus == RNBundleLoadedStatus_Successed) { 112 | blk(YES); 113 | } else { // wait for base bundle load success 114 | [self.pendingQueue addObject:blk]; 115 | } 116 | } else { 117 | callback(NO); 118 | } 119 | 120 | 121 | } 122 | 123 | - (void)loadScriptWithBridge:(RCTBridge *)bridge 124 | path:(NSString *)path 125 | moduleName:(NSString *)moduleName 126 | callback:(RNBusinessLoadCallback)callback { 127 | } 128 | 129 | #pragma mark - Queue 130 | 131 | - (void)_processPendingQueue { 132 | 133 | if (self.commonStatus == RNBundleLoadedStatus_Successed) { 134 | for (RNBusinessLoadCallback blk in self.pendingQueue) { 135 | blk(YES); 136 | } 137 | } else if (self.commonStatus == 138 | RNBundleLoadStatus_Failed) { // in case load failed 139 | for (RNBusinessLoadCallback blk in self.pendingQueue) { 140 | blk(NO); 141 | } 142 | } 143 | 144 | [self.pendingQueue removeAllObjects]; // clean up 145 | } 146 | 147 | - (void)_baseBundleLoad:(NSError *)error { 148 | 149 | static int retry = 0; 150 | if (error && retry > 0) { 151 | retry--; 152 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5 * NSEC_PER_SEC), 153 | dispatch_get_main_queue(), ^{ 154 | [self.commonBridge reload]; 155 | }); 156 | } else { 157 | self.commonStatus = 158 | error ? RNBundleLoadStatus_Failed : RNBundleLoadedStatus_Successed; 159 | [self _processPendingQueue]; 160 | } 161 | } 162 | 163 | #pragma mark - RCTBridgeDelegate 164 | 165 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge { 166 | NSString *bundlePath = [[NSBundle mainBundle] pathForResource:@"common.ios" 167 | ofType:@"bundle"]; 168 | return [NSURL fileURLWithPath:bundlePath]; 169 | } 170 | 171 | // load base js bundle 172 | - (void)loadSourceForBridge:(RCTBridge *)bridge 173 | onProgress:(RCTSourceLoadProgressBlock)onProgress 174 | onComplete:(RCTSourceLoadBlock)loadCallback { 175 | 176 | __weak typeof(self) weakSelf = self; 177 | 178 | [RCTJavaScriptLoader loadBundleAtURL:[self sourceURLForBridge:bridge] 179 | onProgress:onProgress 180 | onComplete:^(NSError *error, RCTSource *source) { 181 | NSLog(@"common lib src did load, error:%@", 182 | error.localizedDescription); 183 | loadCallback(error, source); 184 | [weakSelf _baseBundleLoad:error]; 185 | }]; 186 | } 187 | 188 | 189 | - (void)dealloc { 190 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 191 | } 192 | @end 193 | -------------------------------------------------------------------------------- /ios/Core/RCTBridge+Extension.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTBridge+Private.h 3 | // ReactNativeDynamic 4 | // 5 | // Created by mrgaogang on 2021/6/22. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface RCTBridge (Extension) 13 | -(void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync; 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../node_modules/react-native/scripts/react_native_pods' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | platform :ios, '10.0' 5 | 6 | target 'ReactNativeDynamic' do 7 | config = use_native_modules! 8 | 9 | use_react_native!( 10 | :path => config[:reactNativePath], 11 | # to enable hermes on iOS, change `false` to `true` and then install pods 12 | :hermes_enabled => false 13 | ) 14 | 15 | target 'ReactNativeDynamicTests' do 16 | inherit! :complete 17 | # Pods for testing 18 | end 19 | 20 | # Enables Flipper. 21 | # 22 | # Note that if you have use_frameworks! enabled, Flipper will not work and 23 | # you should disable the next line. 24 | use_flipper!() 25 | 26 | post_install do |installer| 27 | react_native_post_install(installer) 28 | end 29 | end -------------------------------------------------------------------------------- /ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - boost-for-react-native (1.63.0) 3 | - CocoaAsyncSocket (7.6.5) 4 | - DoubleConversion (1.1.6) 5 | - FBLazyVector (0.64.2) 6 | - FBReactNativeSpec (0.64.2): 7 | - RCT-Folly (= 2020.01.13.00) 8 | - RCTRequired (= 0.64.2) 9 | - RCTTypeSafety (= 0.64.2) 10 | - React-Core (= 0.64.2) 11 | - React-jsi (= 0.64.2) 12 | - ReactCommon/turbomodule/core (= 0.64.2) 13 | - Flipper (0.75.1): 14 | - Flipper-Folly (~> 2.5) 15 | - Flipper-RSocket (~> 1.3) 16 | - Flipper-DoubleConversion (1.1.7) 17 | - Flipper-Folly (2.5.3): 18 | - boost-for-react-native 19 | - Flipper-DoubleConversion 20 | - Flipper-Glog 21 | - libevent (~> 2.1.12) 22 | - OpenSSL-Universal (= 1.1.180) 23 | - Flipper-Glog (0.3.6) 24 | - Flipper-PeerTalk (0.0.4) 25 | - Flipper-RSocket (1.3.1): 26 | - Flipper-Folly (~> 2.5) 27 | - FlipperKit (0.75.1): 28 | - FlipperKit/Core (= 0.75.1) 29 | - FlipperKit/Core (0.75.1): 30 | - Flipper (~> 0.75.1) 31 | - FlipperKit/CppBridge 32 | - FlipperKit/FBCxxFollyDynamicConvert 33 | - FlipperKit/FBDefines 34 | - FlipperKit/FKPortForwarding 35 | - FlipperKit/CppBridge (0.75.1): 36 | - Flipper (~> 0.75.1) 37 | - FlipperKit/FBCxxFollyDynamicConvert (0.75.1): 38 | - Flipper-Folly (~> 2.5) 39 | - FlipperKit/FBDefines (0.75.1) 40 | - FlipperKit/FKPortForwarding (0.75.1): 41 | - CocoaAsyncSocket (~> 7.6) 42 | - Flipper-PeerTalk (~> 0.0.4) 43 | - FlipperKit/FlipperKitHighlightOverlay (0.75.1) 44 | - FlipperKit/FlipperKitLayoutPlugin (0.75.1): 45 | - FlipperKit/Core 46 | - FlipperKit/FlipperKitHighlightOverlay 47 | - FlipperKit/FlipperKitLayoutTextSearchable 48 | - YogaKit (~> 1.18) 49 | - FlipperKit/FlipperKitLayoutTextSearchable (0.75.1) 50 | - FlipperKit/FlipperKitNetworkPlugin (0.75.1): 51 | - FlipperKit/Core 52 | - FlipperKit/FlipperKitReactPlugin (0.75.1): 53 | - FlipperKit/Core 54 | - FlipperKit/FlipperKitUserDefaultsPlugin (0.75.1): 55 | - FlipperKit/Core 56 | - FlipperKit/SKIOSNetworkPlugin (0.75.1): 57 | - FlipperKit/Core 58 | - FlipperKit/FlipperKitNetworkPlugin 59 | - glog (0.3.5) 60 | - libevent (2.1.12) 61 | - OpenSSL-Universal (1.1.180) 62 | - RCT-Folly (2020.01.13.00): 63 | - boost-for-react-native 64 | - DoubleConversion 65 | - glog 66 | - RCT-Folly/Default (= 2020.01.13.00) 67 | - RCT-Folly/Default (2020.01.13.00): 68 | - boost-for-react-native 69 | - DoubleConversion 70 | - glog 71 | - RCTRequired (0.64.2) 72 | - RCTTypeSafety (0.64.2): 73 | - FBLazyVector (= 0.64.2) 74 | - RCT-Folly (= 2020.01.13.00) 75 | - RCTRequired (= 0.64.2) 76 | - React-Core (= 0.64.2) 77 | - React (0.64.2): 78 | - React-Core (= 0.64.2) 79 | - React-Core/DevSupport (= 0.64.2) 80 | - React-Core/RCTWebSocket (= 0.64.2) 81 | - React-RCTActionSheet (= 0.64.2) 82 | - React-RCTAnimation (= 0.64.2) 83 | - React-RCTBlob (= 0.64.2) 84 | - React-RCTImage (= 0.64.2) 85 | - React-RCTLinking (= 0.64.2) 86 | - React-RCTNetwork (= 0.64.2) 87 | - React-RCTSettings (= 0.64.2) 88 | - React-RCTText (= 0.64.2) 89 | - React-RCTVibration (= 0.64.2) 90 | - React-callinvoker (0.64.2) 91 | - React-Core (0.64.2): 92 | - glog 93 | - RCT-Folly (= 2020.01.13.00) 94 | - React-Core/Default (= 0.64.2) 95 | - React-cxxreact (= 0.64.2) 96 | - React-jsi (= 0.64.2) 97 | - React-jsiexecutor (= 0.64.2) 98 | - React-perflogger (= 0.64.2) 99 | - Yoga 100 | - React-Core/CoreModulesHeaders (0.64.2): 101 | - glog 102 | - RCT-Folly (= 2020.01.13.00) 103 | - React-Core/Default 104 | - React-cxxreact (= 0.64.2) 105 | - React-jsi (= 0.64.2) 106 | - React-jsiexecutor (= 0.64.2) 107 | - React-perflogger (= 0.64.2) 108 | - Yoga 109 | - React-Core/Default (0.64.2): 110 | - glog 111 | - RCT-Folly (= 2020.01.13.00) 112 | - React-cxxreact (= 0.64.2) 113 | - React-jsi (= 0.64.2) 114 | - React-jsiexecutor (= 0.64.2) 115 | - React-perflogger (= 0.64.2) 116 | - Yoga 117 | - React-Core/DevSupport (0.64.2): 118 | - glog 119 | - RCT-Folly (= 2020.01.13.00) 120 | - React-Core/Default (= 0.64.2) 121 | - React-Core/RCTWebSocket (= 0.64.2) 122 | - React-cxxreact (= 0.64.2) 123 | - React-jsi (= 0.64.2) 124 | - React-jsiexecutor (= 0.64.2) 125 | - React-jsinspector (= 0.64.2) 126 | - React-perflogger (= 0.64.2) 127 | - Yoga 128 | - React-Core/RCTActionSheetHeaders (0.64.2): 129 | - glog 130 | - RCT-Folly (= 2020.01.13.00) 131 | - React-Core/Default 132 | - React-cxxreact (= 0.64.2) 133 | - React-jsi (= 0.64.2) 134 | - React-jsiexecutor (= 0.64.2) 135 | - React-perflogger (= 0.64.2) 136 | - Yoga 137 | - React-Core/RCTAnimationHeaders (0.64.2): 138 | - glog 139 | - RCT-Folly (= 2020.01.13.00) 140 | - React-Core/Default 141 | - React-cxxreact (= 0.64.2) 142 | - React-jsi (= 0.64.2) 143 | - React-jsiexecutor (= 0.64.2) 144 | - React-perflogger (= 0.64.2) 145 | - Yoga 146 | - React-Core/RCTBlobHeaders (0.64.2): 147 | - glog 148 | - RCT-Folly (= 2020.01.13.00) 149 | - React-Core/Default 150 | - React-cxxreact (= 0.64.2) 151 | - React-jsi (= 0.64.2) 152 | - React-jsiexecutor (= 0.64.2) 153 | - React-perflogger (= 0.64.2) 154 | - Yoga 155 | - React-Core/RCTImageHeaders (0.64.2): 156 | - glog 157 | - RCT-Folly (= 2020.01.13.00) 158 | - React-Core/Default 159 | - React-cxxreact (= 0.64.2) 160 | - React-jsi (= 0.64.2) 161 | - React-jsiexecutor (= 0.64.2) 162 | - React-perflogger (= 0.64.2) 163 | - Yoga 164 | - React-Core/RCTLinkingHeaders (0.64.2): 165 | - glog 166 | - RCT-Folly (= 2020.01.13.00) 167 | - React-Core/Default 168 | - React-cxxreact (= 0.64.2) 169 | - React-jsi (= 0.64.2) 170 | - React-jsiexecutor (= 0.64.2) 171 | - React-perflogger (= 0.64.2) 172 | - Yoga 173 | - React-Core/RCTNetworkHeaders (0.64.2): 174 | - glog 175 | - RCT-Folly (= 2020.01.13.00) 176 | - React-Core/Default 177 | - React-cxxreact (= 0.64.2) 178 | - React-jsi (= 0.64.2) 179 | - React-jsiexecutor (= 0.64.2) 180 | - React-perflogger (= 0.64.2) 181 | - Yoga 182 | - React-Core/RCTSettingsHeaders (0.64.2): 183 | - glog 184 | - RCT-Folly (= 2020.01.13.00) 185 | - React-Core/Default 186 | - React-cxxreact (= 0.64.2) 187 | - React-jsi (= 0.64.2) 188 | - React-jsiexecutor (= 0.64.2) 189 | - React-perflogger (= 0.64.2) 190 | - Yoga 191 | - React-Core/RCTTextHeaders (0.64.2): 192 | - glog 193 | - RCT-Folly (= 2020.01.13.00) 194 | - React-Core/Default 195 | - React-cxxreact (= 0.64.2) 196 | - React-jsi (= 0.64.2) 197 | - React-jsiexecutor (= 0.64.2) 198 | - React-perflogger (= 0.64.2) 199 | - Yoga 200 | - React-Core/RCTVibrationHeaders (0.64.2): 201 | - glog 202 | - RCT-Folly (= 2020.01.13.00) 203 | - React-Core/Default 204 | - React-cxxreact (= 0.64.2) 205 | - React-jsi (= 0.64.2) 206 | - React-jsiexecutor (= 0.64.2) 207 | - React-perflogger (= 0.64.2) 208 | - Yoga 209 | - React-Core/RCTWebSocket (0.64.2): 210 | - glog 211 | - RCT-Folly (= 2020.01.13.00) 212 | - React-Core/Default (= 0.64.2) 213 | - React-cxxreact (= 0.64.2) 214 | - React-jsi (= 0.64.2) 215 | - React-jsiexecutor (= 0.64.2) 216 | - React-perflogger (= 0.64.2) 217 | - Yoga 218 | - React-CoreModules (0.64.2): 219 | - FBReactNativeSpec (= 0.64.2) 220 | - RCT-Folly (= 2020.01.13.00) 221 | - RCTTypeSafety (= 0.64.2) 222 | - React-Core/CoreModulesHeaders (= 0.64.2) 223 | - React-jsi (= 0.64.2) 224 | - React-RCTImage (= 0.64.2) 225 | - ReactCommon/turbomodule/core (= 0.64.2) 226 | - React-cxxreact (0.64.2): 227 | - boost-for-react-native (= 1.63.0) 228 | - DoubleConversion 229 | - glog 230 | - RCT-Folly (= 2020.01.13.00) 231 | - React-callinvoker (= 0.64.2) 232 | - React-jsi (= 0.64.2) 233 | - React-jsinspector (= 0.64.2) 234 | - React-perflogger (= 0.64.2) 235 | - React-runtimeexecutor (= 0.64.2) 236 | - React-jsi (0.64.2): 237 | - boost-for-react-native (= 1.63.0) 238 | - DoubleConversion 239 | - glog 240 | - RCT-Folly (= 2020.01.13.00) 241 | - React-jsi/Default (= 0.64.2) 242 | - React-jsi/Default (0.64.2): 243 | - boost-for-react-native (= 1.63.0) 244 | - DoubleConversion 245 | - glog 246 | - RCT-Folly (= 2020.01.13.00) 247 | - React-jsiexecutor (0.64.2): 248 | - DoubleConversion 249 | - glog 250 | - RCT-Folly (= 2020.01.13.00) 251 | - React-cxxreact (= 0.64.2) 252 | - React-jsi (= 0.64.2) 253 | - React-perflogger (= 0.64.2) 254 | - React-jsinspector (0.64.2) 255 | - React-perflogger (0.64.2) 256 | - React-RCTActionSheet (0.64.2): 257 | - React-Core/RCTActionSheetHeaders (= 0.64.2) 258 | - React-RCTAnimation (0.64.2): 259 | - FBReactNativeSpec (= 0.64.2) 260 | - RCT-Folly (= 2020.01.13.00) 261 | - RCTTypeSafety (= 0.64.2) 262 | - React-Core/RCTAnimationHeaders (= 0.64.2) 263 | - React-jsi (= 0.64.2) 264 | - ReactCommon/turbomodule/core (= 0.64.2) 265 | - React-RCTBlob (0.64.2): 266 | - FBReactNativeSpec (= 0.64.2) 267 | - RCT-Folly (= 2020.01.13.00) 268 | - React-Core/RCTBlobHeaders (= 0.64.2) 269 | - React-Core/RCTWebSocket (= 0.64.2) 270 | - React-jsi (= 0.64.2) 271 | - React-RCTNetwork (= 0.64.2) 272 | - ReactCommon/turbomodule/core (= 0.64.2) 273 | - React-RCTImage (0.64.2): 274 | - FBReactNativeSpec (= 0.64.2) 275 | - RCT-Folly (= 2020.01.13.00) 276 | - RCTTypeSafety (= 0.64.2) 277 | - React-Core/RCTImageHeaders (= 0.64.2) 278 | - React-jsi (= 0.64.2) 279 | - React-RCTNetwork (= 0.64.2) 280 | - ReactCommon/turbomodule/core (= 0.64.2) 281 | - React-RCTLinking (0.64.2): 282 | - FBReactNativeSpec (= 0.64.2) 283 | - React-Core/RCTLinkingHeaders (= 0.64.2) 284 | - React-jsi (= 0.64.2) 285 | - ReactCommon/turbomodule/core (= 0.64.2) 286 | - React-RCTNetwork (0.64.2): 287 | - FBReactNativeSpec (= 0.64.2) 288 | - RCT-Folly (= 2020.01.13.00) 289 | - RCTTypeSafety (= 0.64.2) 290 | - React-Core/RCTNetworkHeaders (= 0.64.2) 291 | - React-jsi (= 0.64.2) 292 | - ReactCommon/turbomodule/core (= 0.64.2) 293 | - React-RCTSettings (0.64.2): 294 | - FBReactNativeSpec (= 0.64.2) 295 | - RCT-Folly (= 2020.01.13.00) 296 | - RCTTypeSafety (= 0.64.2) 297 | - React-Core/RCTSettingsHeaders (= 0.64.2) 298 | - React-jsi (= 0.64.2) 299 | - ReactCommon/turbomodule/core (= 0.64.2) 300 | - React-RCTText (0.64.2): 301 | - React-Core/RCTTextHeaders (= 0.64.2) 302 | - React-RCTVibration (0.64.2): 303 | - FBReactNativeSpec (= 0.64.2) 304 | - RCT-Folly (= 2020.01.13.00) 305 | - React-Core/RCTVibrationHeaders (= 0.64.2) 306 | - React-jsi (= 0.64.2) 307 | - ReactCommon/turbomodule/core (= 0.64.2) 308 | - React-runtimeexecutor (0.64.2): 309 | - React-jsi (= 0.64.2) 310 | - ReactCommon/turbomodule/core (0.64.2): 311 | - DoubleConversion 312 | - glog 313 | - RCT-Folly (= 2020.01.13.00) 314 | - React-callinvoker (= 0.64.2) 315 | - React-Core (= 0.64.2) 316 | - React-cxxreact (= 0.64.2) 317 | - React-jsi (= 0.64.2) 318 | - React-perflogger (= 0.64.2) 319 | - Yoga (1.14.0) 320 | - YogaKit (1.18.1): 321 | - Yoga (~> 1.14) 322 | 323 | DEPENDENCIES: 324 | - DoubleConversion (from `../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec`) 325 | - FBLazyVector (from `../node_modules/react-native/Libraries/FBLazyVector`) 326 | - FBReactNativeSpec (from `../node_modules/react-native/React/FBReactNativeSpec`) 327 | - Flipper (~> 0.75.1) 328 | - Flipper-DoubleConversion (= 1.1.7) 329 | - Flipper-Folly (~> 2.5.3) 330 | - Flipper-Glog (= 0.3.6) 331 | - Flipper-PeerTalk (~> 0.0.4) 332 | - Flipper-RSocket (~> 1.3) 333 | - FlipperKit (~> 0.75.1) 334 | - FlipperKit/Core (~> 0.75.1) 335 | - FlipperKit/CppBridge (~> 0.75.1) 336 | - FlipperKit/FBCxxFollyDynamicConvert (~> 0.75.1) 337 | - FlipperKit/FBDefines (~> 0.75.1) 338 | - FlipperKit/FKPortForwarding (~> 0.75.1) 339 | - FlipperKit/FlipperKitHighlightOverlay (~> 0.75.1) 340 | - FlipperKit/FlipperKitLayoutPlugin (~> 0.75.1) 341 | - FlipperKit/FlipperKitLayoutTextSearchable (~> 0.75.1) 342 | - FlipperKit/FlipperKitNetworkPlugin (~> 0.75.1) 343 | - FlipperKit/FlipperKitReactPlugin (~> 0.75.1) 344 | - FlipperKit/FlipperKitUserDefaultsPlugin (~> 0.75.1) 345 | - FlipperKit/SKIOSNetworkPlugin (~> 0.75.1) 346 | - glog (from `../node_modules/react-native/third-party-podspecs/glog.podspec`) 347 | - RCT-Folly (from `../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec`) 348 | - RCTRequired (from `../node_modules/react-native/Libraries/RCTRequired`) 349 | - RCTTypeSafety (from `../node_modules/react-native/Libraries/TypeSafety`) 350 | - React (from `../node_modules/react-native/`) 351 | - React-callinvoker (from `../node_modules/react-native/ReactCommon/callinvoker`) 352 | - React-Core (from `../node_modules/react-native/`) 353 | - React-Core/DevSupport (from `../node_modules/react-native/`) 354 | - React-Core/RCTWebSocket (from `../node_modules/react-native/`) 355 | - React-CoreModules (from `../node_modules/react-native/React/CoreModules`) 356 | - React-cxxreact (from `../node_modules/react-native/ReactCommon/cxxreact`) 357 | - React-jsi (from `../node_modules/react-native/ReactCommon/jsi`) 358 | - React-jsiexecutor (from `../node_modules/react-native/ReactCommon/jsiexecutor`) 359 | - React-jsinspector (from `../node_modules/react-native/ReactCommon/jsinspector`) 360 | - React-perflogger (from `../node_modules/react-native/ReactCommon/reactperflogger`) 361 | - React-RCTActionSheet (from `../node_modules/react-native/Libraries/ActionSheetIOS`) 362 | - React-RCTAnimation (from `../node_modules/react-native/Libraries/NativeAnimation`) 363 | - React-RCTBlob (from `../node_modules/react-native/Libraries/Blob`) 364 | - React-RCTImage (from `../node_modules/react-native/Libraries/Image`) 365 | - React-RCTLinking (from `../node_modules/react-native/Libraries/LinkingIOS`) 366 | - React-RCTNetwork (from `../node_modules/react-native/Libraries/Network`) 367 | - React-RCTSettings (from `../node_modules/react-native/Libraries/Settings`) 368 | - React-RCTText (from `../node_modules/react-native/Libraries/Text`) 369 | - React-RCTVibration (from `../node_modules/react-native/Libraries/Vibration`) 370 | - React-runtimeexecutor (from `../node_modules/react-native/ReactCommon/runtimeexecutor`) 371 | - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) 372 | - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) 373 | 374 | SPEC REPOS: 375 | trunk: 376 | - boost-for-react-native 377 | - CocoaAsyncSocket 378 | - Flipper 379 | - Flipper-DoubleConversion 380 | - Flipper-Folly 381 | - Flipper-Glog 382 | - Flipper-PeerTalk 383 | - Flipper-RSocket 384 | - FlipperKit 385 | - libevent 386 | - OpenSSL-Universal 387 | - YogaKit 388 | 389 | EXTERNAL SOURCES: 390 | DoubleConversion: 391 | :podspec: "../node_modules/react-native/third-party-podspecs/DoubleConversion.podspec" 392 | FBLazyVector: 393 | :path: "../node_modules/react-native/Libraries/FBLazyVector" 394 | FBReactNativeSpec: 395 | :path: "../node_modules/react-native/React/FBReactNativeSpec" 396 | glog: 397 | :podspec: "../node_modules/react-native/third-party-podspecs/glog.podspec" 398 | RCT-Folly: 399 | :podspec: "../node_modules/react-native/third-party-podspecs/RCT-Folly.podspec" 400 | RCTRequired: 401 | :path: "../node_modules/react-native/Libraries/RCTRequired" 402 | RCTTypeSafety: 403 | :path: "../node_modules/react-native/Libraries/TypeSafety" 404 | React: 405 | :path: "../node_modules/react-native/" 406 | React-callinvoker: 407 | :path: "../node_modules/react-native/ReactCommon/callinvoker" 408 | React-Core: 409 | :path: "../node_modules/react-native/" 410 | React-CoreModules: 411 | :path: "../node_modules/react-native/React/CoreModules" 412 | React-cxxreact: 413 | :path: "../node_modules/react-native/ReactCommon/cxxreact" 414 | React-jsi: 415 | :path: "../node_modules/react-native/ReactCommon/jsi" 416 | React-jsiexecutor: 417 | :path: "../node_modules/react-native/ReactCommon/jsiexecutor" 418 | React-jsinspector: 419 | :path: "../node_modules/react-native/ReactCommon/jsinspector" 420 | React-perflogger: 421 | :path: "../node_modules/react-native/ReactCommon/reactperflogger" 422 | React-RCTActionSheet: 423 | :path: "../node_modules/react-native/Libraries/ActionSheetIOS" 424 | React-RCTAnimation: 425 | :path: "../node_modules/react-native/Libraries/NativeAnimation" 426 | React-RCTBlob: 427 | :path: "../node_modules/react-native/Libraries/Blob" 428 | React-RCTImage: 429 | :path: "../node_modules/react-native/Libraries/Image" 430 | React-RCTLinking: 431 | :path: "../node_modules/react-native/Libraries/LinkingIOS" 432 | React-RCTNetwork: 433 | :path: "../node_modules/react-native/Libraries/Network" 434 | React-RCTSettings: 435 | :path: "../node_modules/react-native/Libraries/Settings" 436 | React-RCTText: 437 | :path: "../node_modules/react-native/Libraries/Text" 438 | React-RCTVibration: 439 | :path: "../node_modules/react-native/Libraries/Vibration" 440 | React-runtimeexecutor: 441 | :path: "../node_modules/react-native/ReactCommon/runtimeexecutor" 442 | ReactCommon: 443 | :path: "../node_modules/react-native/ReactCommon" 444 | Yoga: 445 | :path: "../node_modules/react-native/ReactCommon/yoga" 446 | 447 | SPEC CHECKSUMS: 448 | boost-for-react-native: 39c7adb57c4e60d6c5479dd8623128eb5b3f0f2c 449 | CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99 450 | DoubleConversion: cf9b38bf0b2d048436d9a82ad2abe1404f11e7de 451 | FBLazyVector: e686045572151edef46010a6f819ade377dfeb4b 452 | FBReactNativeSpec: 873e66e236c7d677003395ef31b94e837f66a636 453 | Flipper: d3da1aa199aad94455ae725e9f3aa43f3ec17021 454 | Flipper-DoubleConversion: 38631e41ef4f9b12861c67d17cb5518d06badc41 455 | Flipper-Folly: 755929a4f851b2fb2c347d533a23f191b008554c 456 | Flipper-Glog: 1dfd6abf1e922806c52ceb8701a3599a79a200a6 457 | Flipper-PeerTalk: 116d8f857dc6ef55c7a5a75ea3ceaafe878aadc9 458 | Flipper-RSocket: 127954abe8b162fcaf68d2134d34dc2bd7076154 459 | FlipperKit: 8a20b5c5fcf9436cac58551dc049867247f64b00 460 | glog: 73c2498ac6884b13ede40eda8228cb1eee9d9d62 461 | libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913 462 | OpenSSL-Universal: 1aa4f6a6ee7256b83db99ec1ccdaa80d10f9af9b 463 | RCT-Folly: ec7a233ccc97cc556cf7237f0db1ff65b986f27c 464 | RCTRequired: 6d3e854f0e7260a648badd0d44fc364bc9da9728 465 | RCTTypeSafety: c1f31d19349c6b53085766359caac425926fafaa 466 | React: bda6b6d7ae912de97d7a61aa5c160db24aa2ad69 467 | React-callinvoker: 9840ea7e8e88ed73d438edb725574820b29b5baa 468 | React-Core: b5e385da7ce5f16a220fc60fd0749eae2c6120f0 469 | React-CoreModules: 17071a4e2c5239b01585f4aa8070141168ab298f 470 | React-cxxreact: 9be7b6340ed9f7c53e53deca7779f07cd66525ba 471 | React-jsi: 67747b9722f6dab2ffe15b011bcf6b3f2c3f1427 472 | React-jsiexecutor: 80c46bd381fd06e418e0d4f53672dc1d1945c4c3 473 | React-jsinspector: cc614ec18a9ca96fd275100c16d74d62ee11f0ae 474 | React-perflogger: 25373e382fed75ce768a443822f07098a15ab737 475 | React-RCTActionSheet: af7796ba49ffe4ca92e7277a5d992d37203f7da5 476 | React-RCTAnimation: 6a2e76ab50c6f25b428d81b76a5a45351c4d77aa 477 | React-RCTBlob: 02a2887023e0eed99391b6445b2e23a2a6f9226d 478 | React-RCTImage: ce5bf8e7438f2286d9b646a05d6ab11f38b0323d 479 | React-RCTLinking: ccd20742de14e020cb5f99d5c7e0bf0383aefbd9 480 | React-RCTNetwork: dfb9d089ab0753e5e5f55fc4b1210858f7245647 481 | React-RCTSettings: b14aef2d83699e48b410fb7c3ba5b66cd3291ae2 482 | React-RCTText: 41a2e952dd9adc5caf6fb68ed46b275194d5da5f 483 | React-RCTVibration: 24600e3b1aaa77126989bc58b6747509a1ba14f3 484 | React-runtimeexecutor: a9904c6d0218fb9f8b19d6dd88607225927668f9 485 | ReactCommon: 149906e01aa51142707a10665185db879898e966 486 | Yoga: 575c581c63e0d35c9a83f4b46d01d63abc1100ac 487 | YogaKit: f782866e155069a2cca2517aafea43200b01fd5a 488 | 489 | PODFILE CHECKSUM: 4f93f3b781d035f9d1a2e08e0c777b5644a96f44 490 | 491 | COCOAPODS: 1.10.1 492 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamic.xcodeproj/xcshareddata/xcschemes/ReactNativeDynamic.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamic.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamic.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamic/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : UIResponder 5 | 6 | @property (nonatomic, strong) UIWindow *window; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamic/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | #import 5 | #import 6 | 7 | #import "BridgeManager.h" 8 | #import "HomeViewController.h" 9 | 10 | 11 | @implementation AppDelegate 12 | 13 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 14 | { 15 | 16 | // 在应用启动的时候加载 jsbundle 基础包 17 | [BridgeManager.instance loadBaseBundleWithLaunchOptions:launchOptions]; 18 | 19 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 20 | UIViewController *rootViewController = [[HomeViewController alloc] init]; 21 | self.window.rootViewController = rootViewController; 22 | [self.window makeKeyAndVisible]; 23 | return YES; 24 | } 25 | 26 | 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamic/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /ios/ReactNativeDynamic/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamic/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | ReactNativeDynamic 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamic/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamic/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamicTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/ReactNativeDynamicTests/ReactNativeDynamicTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface ReactNativeDynamicTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation ReactNativeDynamicTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 38 | if (level >= RCTLogLevelError) { 39 | redboxError = message; 40 | } 41 | }); 42 | #endif 43 | 44 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 45 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 46 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | 48 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 49 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 50 | return YES; 51 | } 52 | return NO; 53 | }]; 54 | } 55 | 56 | #ifdef DEBUG 57 | RCTSetLogFunction(RCTDefaultLogFunction); 58 | #endif 59 | 60 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 61 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 62 | } 63 | 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /metro.base.config.js: -------------------------------------------------------------------------------- 1 | const metroCfg = require('./compile/metro-base'); 2 | metroCfg.clearFileInfo(); 3 | module.exports = { 4 | serializer: { 5 | createModuleIdFactory: metroCfg.createCommonModuleIdFactory, 6 | }, 7 | transformer: { 8 | getTransformOptions: async () => ({ 9 | transform: { 10 | experimentalImportSupport: false, 11 | inlineRequires: true, 12 | }, 13 | }), 14 | }, 15 | }; 16 | -------------------------------------------------------------------------------- /metro.business.config.js: -------------------------------------------------------------------------------- 1 | const { 2 | createModuleIdFactory, 3 | processModuleFilter, 4 | } = require('./compile/metro-base'); 5 | 6 | module.exports = { 7 | serializer: { 8 | createModuleIdFactory: createModuleIdFactory, 9 | processModuleFilter: processModuleFilter(), 10 | }, 11 | transformer: { 12 | getTransformOptions: async () => ({ 13 | transform: { 14 | experimentalImportSupport: false, 15 | inlineRequires: true, 16 | }, 17 | }), 18 | }, 19 | }; 20 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ReactNativeDynamic", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "build:base:ios": "react-native bundle --platform ios --dev false --entry-file common-entry.js --bundle-output dist/base/common.ios.bundle --assets-dest dist/base --config ./metro.base.config.js --reset-cache && node ./compile/split-common.js dist/base/common.ios.bundle", 10 | "build:bus:ios": "react-native bundle --platform ios --dev false --entry-file index.js --bundle-output dist/business/business.ios.bundle --assets-dest dist/business --config ./metro.business.config.js --reset-cache", 11 | "build:ios": "npm run build:base:ios && npm run build:bus:ios", 12 | "build:base:android": "react-native bundle --platform android --dev false --entry-file common-entry.js --bundle-output android/app/src/main/assets/common.android.bundle --assets-dest android/app/src/main/assets --config ./metro.base.config.js --reset-cache && node ./compile/split-common.js android/app/src/main/assets/common.android.bundle ", 13 | "build:bus:android": "react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/business.android.bundle --assets-dest android/app/src/main/assets --config ./metro.business.config.js --reset-cache", 14 | "build:android": " npm run build:base:android && npm run build:bus:android", 15 | "test": "jest", 16 | "lint": "eslint ." 17 | }, 18 | "dependencies": { 19 | "crypto-js": "^4.1.1", 20 | "react": "17.0.1", 21 | "react-native": "0.64.2" 22 | }, 23 | "devDependencies": { 24 | "@babel/core": "^7.14.6", 25 | "@babel/runtime": "^7.14.6", 26 | "@react-native-community/eslint-config": "^3.0.0", 27 | "babel-jest": "^27.0.2", 28 | "eslint": "^7.29.0", 29 | "jest": "^27.0.4", 30 | "metro-react-native-babel-preset": "^0.66.0", 31 | "react-test-renderer": "17.0.1" 32 | }, 33 | "jest": { 34 | "preset": "react-native" 35 | } 36 | } 37 | --------------------------------------------------------------------------------