@onekeyfe/react-native-background-thread 1.1.51 → 1.1.53
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -108,6 +108,108 @@ class BackgroundThreadManager private constructor() {
|
|
|
108
108
|
return null
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* Creates a JSBundleLoader that loads two bundles sequentially from Android assets:
|
|
113
|
+
* first the common bundle (polyfills + shared modules), then the
|
|
114
|
+
* entry-specific bundle (entry-only modules + require(entryId)).
|
|
115
|
+
*/
|
|
116
|
+
private fun createSequentialAssetBundleLoader(
|
|
117
|
+
appContext: android.content.Context,
|
|
118
|
+
commonAssetName: String,
|
|
119
|
+
entryAssetName: String
|
|
120
|
+
): JSBundleLoader {
|
|
121
|
+
return object : JSBundleLoader() {
|
|
122
|
+
override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
|
|
123
|
+
val totalStart = System.nanoTime()
|
|
124
|
+
|
|
125
|
+
// Step 1: Load common bundle (polyfills + shared modules)
|
|
126
|
+
val commonStart = System.nanoTime()
|
|
127
|
+
delegate.loadScriptFromAssets(appContext.assets, "assets://$commonAssetName", false)
|
|
128
|
+
val commonMs = (System.nanoTime() - commonStart) / 1_000_000.0
|
|
129
|
+
BTLogger.info("[SplitBundle] common bundle loaded from assets in ${String.format("%.1f", commonMs)}ms: $commonAssetName")
|
|
130
|
+
|
|
131
|
+
// Step 2: Load entry-specific bundle
|
|
132
|
+
val entryStart = System.nanoTime()
|
|
133
|
+
delegate.loadScriptFromAssets(appContext.assets, "assets://$entryAssetName", false)
|
|
134
|
+
val entryMs = (System.nanoTime() - entryStart) / 1_000_000.0
|
|
135
|
+
BTLogger.info("[SplitBundle] entry bundle loaded from assets in ${String.format("%.1f", entryMs)}ms: $entryAssetName")
|
|
136
|
+
|
|
137
|
+
val totalMs = (System.nanoTime() - totalStart) / 1_000_000.0
|
|
138
|
+
BTLogger.info("[SplitBundle] sequential asset load total: ${String.format("%.1f", totalMs)}ms (common=${String.format("%.1f", commonMs)}ms + entry=${String.format("%.1f", entryMs)}ms)")
|
|
139
|
+
|
|
140
|
+
return "assets://$entryAssetName"
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Creates a JSBundleLoader that loads two bundles sequentially from local files:
|
|
147
|
+
* first the common bundle, then the entry-specific bundle.
|
|
148
|
+
*/
|
|
149
|
+
private fun createSequentialFileBundleLoader(
|
|
150
|
+
commonPath: String,
|
|
151
|
+
entryPath: String,
|
|
152
|
+
entrySourceURL: String
|
|
153
|
+
): JSBundleLoader {
|
|
154
|
+
return object : JSBundleLoader() {
|
|
155
|
+
override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
|
|
156
|
+
val totalStart = System.nanoTime()
|
|
157
|
+
|
|
158
|
+
// Step 1: Load common bundle (polyfills + shared modules)
|
|
159
|
+
val commonFile = File(commonPath)
|
|
160
|
+
if (!commonFile.exists()) {
|
|
161
|
+
BTLogger.error("Common bundle file does not exist: $commonPath")
|
|
162
|
+
throw RuntimeException("Common bundle file does not exist: $commonPath")
|
|
163
|
+
}
|
|
164
|
+
BTLogger.info("[SplitBundle] common bundle file: ${commonFile.length() / 1024}KB")
|
|
165
|
+
val commonStart = System.nanoTime()
|
|
166
|
+
delegate.loadScriptFromFile(commonFile.absolutePath, "common.bundle", false)
|
|
167
|
+
val commonMs = (System.nanoTime() - commonStart) / 1_000_000.0
|
|
168
|
+
BTLogger.info("[SplitBundle] common bundle loaded from file in ${String.format("%.1f", commonMs)}ms: $commonPath")
|
|
169
|
+
|
|
170
|
+
// Step 2: Load entry-specific bundle
|
|
171
|
+
val entryFile = File(entryPath)
|
|
172
|
+
if (!entryFile.exists()) {
|
|
173
|
+
BTLogger.error("Entry bundle file does not exist: $entryPath")
|
|
174
|
+
throw RuntimeException("Entry bundle file does not exist: $entryPath")
|
|
175
|
+
}
|
|
176
|
+
BTLogger.info("[SplitBundle] entry bundle file: ${entryFile.length() / 1024}KB")
|
|
177
|
+
val entryStart = System.nanoTime()
|
|
178
|
+
delegate.loadScriptFromFile(entryFile.absolutePath, entrySourceURL, false)
|
|
179
|
+
val entryMs = (System.nanoTime() - entryStart) / 1_000_000.0
|
|
180
|
+
BTLogger.info("[SplitBundle] entry bundle loaded from file in ${String.format("%.1f", entryMs)}ms: $entryPath")
|
|
181
|
+
|
|
182
|
+
val totalMs = (System.nanoTime() - totalStart) / 1_000_000.0
|
|
183
|
+
BTLogger.info("[SplitBundle] sequential file load total: ${String.format("%.1f", totalMs)}ms (common=${String.format("%.1f", commonMs)}ms + entry=${String.format("%.1f", entryMs)}ms)")
|
|
184
|
+
|
|
185
|
+
return entrySourceURL
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check if common.bundle exists in the Android assets directory.
|
|
192
|
+
*/
|
|
193
|
+
private fun hasCommonBundleInAssets(appContext: android.content.Context): Boolean {
|
|
194
|
+
return try {
|
|
195
|
+
appContext.assets.open("common.bundle").close()
|
|
196
|
+
true
|
|
197
|
+
} catch (e: Exception) {
|
|
198
|
+
false
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Resolve the common bundle path for OTA (file-based) loading.
|
|
204
|
+
* Looks for common.bundle in the same directory as the entry bundle.
|
|
205
|
+
*/
|
|
206
|
+
private fun resolveCommonBundlePath(entryBundlePath: String): String? {
|
|
207
|
+
val entryFile = File(entryBundlePath)
|
|
208
|
+
val parentDir = entryFile.parentFile ?: return null
|
|
209
|
+
val commonFile = File(parentDir, "common.bundle")
|
|
210
|
+
return if (commonFile.exists()) commonFile.absolutePath else null
|
|
211
|
+
}
|
|
212
|
+
|
|
111
213
|
private fun createDownloadedBundleLoader(appContext: android.content.Context, entryURL: String): JSBundleLoader {
|
|
112
214
|
return object : JSBundleLoader() {
|
|
113
215
|
override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
|
|
@@ -149,7 +251,8 @@ class BackgroundThreadManager private constructor() {
|
|
|
149
251
|
BTLogger.warn("Background runner already started")
|
|
150
252
|
return
|
|
151
253
|
}
|
|
152
|
-
|
|
254
|
+
val bgStartTime = System.nanoTime()
|
|
255
|
+
BTLogger.info("[SplitBundle] background runner starting with entryURL: $entryURL")
|
|
153
256
|
|
|
154
257
|
val appContext = context.applicationContext
|
|
155
258
|
val packages =
|
|
@@ -163,10 +266,43 @@ class BackgroundThreadManager private constructor() {
|
|
|
163
266
|
val localBundlePath = resolveLocalBundlePath(entryURL)
|
|
164
267
|
val bundleLoader =
|
|
165
268
|
when {
|
|
269
|
+
// Debug mode: remote URL — use single bundle (Metro dev server)
|
|
166
270
|
isRemoteBundleUrl(entryURL) -> createDownloadedBundleLoader(appContext, entryURL)
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
271
|
+
|
|
272
|
+
// OTA / local file path — try sequential loading with common bundle
|
|
273
|
+
localBundlePath != null -> {
|
|
274
|
+
val commonPath = resolveCommonBundlePath(localBundlePath)
|
|
275
|
+
if (commonPath != null) {
|
|
276
|
+
BTLogger.info("Using sequential file bundle loader: common=$commonPath, entry=$localBundlePath")
|
|
277
|
+
createSequentialFileBundleLoader(commonPath, localBundlePath, entryURL)
|
|
278
|
+
} else {
|
|
279
|
+
BTLogger.info("No common bundle found for OTA path, using single bundle: $localBundlePath")
|
|
280
|
+
createLocalFileBundleLoader(localBundlePath, entryURL)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Assets-based loading — try sequential loading with common.bundle in assets
|
|
285
|
+
entryURL.startsWith("assets://") -> {
|
|
286
|
+
val entryAssetName = entryURL.removePrefix("assets://")
|
|
287
|
+
if (hasCommonBundleInAssets(appContext)) {
|
|
288
|
+
BTLogger.info("Using sequential asset bundle loader: common=common.bundle, entry=$entryAssetName")
|
|
289
|
+
createSequentialAssetBundleLoader(appContext, "common.bundle", entryAssetName)
|
|
290
|
+
} else {
|
|
291
|
+
BTLogger.info("No common.bundle in assets, using single bundle: $entryURL")
|
|
292
|
+
JSBundleLoader.createAssetLoader(appContext, entryURL, true)
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Bare filename (e.g. "background.bundle") — treat as asset
|
|
297
|
+
else -> {
|
|
298
|
+
if (hasCommonBundleInAssets(appContext)) {
|
|
299
|
+
BTLogger.info("Using sequential asset bundle loader: common=common.bundle, entry=$entryURL")
|
|
300
|
+
createSequentialAssetBundleLoader(appContext, "common.bundle", entryURL)
|
|
301
|
+
} else {
|
|
302
|
+
BTLogger.info("No common.bundle in assets, using single bundle: assets://$entryURL")
|
|
303
|
+
JSBundleLoader.createAssetLoader(appContext, "assets://$entryURL", true)
|
|
304
|
+
}
|
|
305
|
+
}
|
|
170
306
|
}
|
|
171
307
|
|
|
172
308
|
val delegate = DefaultReactHostDelegate(
|
|
@@ -191,7 +327,8 @@ class BackgroundThreadManager private constructor() {
|
|
|
191
327
|
|
|
192
328
|
host.addReactInstanceEventListener(object : ReactInstanceEventListener {
|
|
193
329
|
override fun onReactContextInitialized(context: ReactContext) {
|
|
194
|
-
|
|
330
|
+
val initMs = (System.nanoTime() - bgStartTime) / 1_000_000.0
|
|
331
|
+
BTLogger.info("[SplitBundle] background ReactContext initialized in ${String.format("%.1f", initMs)}ms")
|
|
195
332
|
context.runOnJSQueueThread {
|
|
196
333
|
try {
|
|
197
334
|
val ptr = context.javaScriptContextHolder?.get() ?: 0L
|
|
@@ -170,6 +170,8 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
170
170
|
|
|
171
171
|
- (NSURL *)bundleURL
|
|
172
172
|
{
|
|
173
|
+
// When _jsBundleSource is set (dev mode or explicit override), use it as-is.
|
|
174
|
+
// This is a single full bundle (not split), so DON'T use common+entry strategy.
|
|
173
175
|
if (!_jsBundleSource.empty()) {
|
|
174
176
|
NSString *jsBundleSourceNS = [NSString stringWithUTF8String:_jsBundleSource.c_str()];
|
|
175
177
|
NSURL *resolvedURL = resolveBundleSourceURL(jsBundleSourceNS);
|
|
@@ -180,11 +182,19 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
180
182
|
[BTLogger warn:[NSString stringWithFormat:@"Unable to resolve custom jsBundleSource=%@", jsBundleSourceNS]];
|
|
181
183
|
}
|
|
182
184
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
185
|
+
// Default: load common bundle (shared polyfills + modules).
|
|
186
|
+
// The background entry bundle is loaded later in hostDidStart:.
|
|
187
|
+
NSURL *commonURL = resolveMainBundleResourceURL(@"common.jsbundle");
|
|
188
|
+
if (commonURL) {
|
|
189
|
+
return commonURL;
|
|
186
190
|
}
|
|
187
|
-
return [[NSBundle mainBundle] URLForResource:@"
|
|
191
|
+
return [[NSBundle mainBundle] URLForResource:@"common" withExtension:@"jsbundle"];
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
- (NSString *)resolveBackgroundEntryBundlePath
|
|
195
|
+
{
|
|
196
|
+
NSURL *url = resolveMainBundleResourceURL(@"background.bundle");
|
|
197
|
+
return url.path;
|
|
188
198
|
}
|
|
189
199
|
|
|
190
200
|
- (void)hostDidStart:(RCTHost *)host
|
|
@@ -203,6 +213,28 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
203
213
|
return;
|
|
204
214
|
}
|
|
205
215
|
|
|
216
|
+
// When _jsBundleSource is set, the bundle loaded in bundleURL was already
|
|
217
|
+
// a full single bundle (dev mode / explicit override), so skip entry loading.
|
|
218
|
+
BOOL isSplitBundle = _jsBundleSource.empty();
|
|
219
|
+
|
|
220
|
+
// Read the background entry bundle data before entering the executor block
|
|
221
|
+
// (only needed in split-bundle mode).
|
|
222
|
+
NSData *bgBundleData = nil;
|
|
223
|
+
NSString *bgBundleSourceURL = nil;
|
|
224
|
+
if (isSplitBundle) {
|
|
225
|
+
NSString *bgBundlePath = [self resolveBackgroundEntryBundlePath];
|
|
226
|
+
if (bgBundlePath) {
|
|
227
|
+
bgBundleData = [NSData dataWithContentsOfFile:bgBundlePath];
|
|
228
|
+
bgBundleSourceURL = bgBundlePath.lastPathComponent ?: @"background.bundle";
|
|
229
|
+
[BTLogger info:[NSString stringWithFormat:@"Background entry bundle loaded from %@ (%lu bytes)",
|
|
230
|
+
bgBundlePath, (unsigned long)bgBundleData.length]];
|
|
231
|
+
} else {
|
|
232
|
+
[BTLogger warn:@"Background entry bundle not found, __setupBackgroundRPCHandler may not be defined"];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
CFAbsoluteTime bgStartTime = CFAbsoluteTimeGetCurrent();
|
|
237
|
+
|
|
206
238
|
[_rctInstance callFunctionOnBufferedRuntimeExecutor:[=](jsi::Runtime &runtime) {
|
|
207
239
|
[self setupErrorHandler:runtime];
|
|
208
240
|
|
|
@@ -218,6 +250,22 @@ static NSURL *resolveBundleSourceURL(NSString *jsBundleSourceNS)
|
|
|
218
250
|
};
|
|
219
251
|
SharedRPC::install(runtime, std::move(bgExecutor), "background");
|
|
220
252
|
[BTLogger info:@"SharedStore and SharedRPC installed in background runtime"];
|
|
253
|
+
|
|
254
|
+
// In split-bundle mode, evaluate the background entry bundle now.
|
|
255
|
+
// This must happen BEFORE invokeOptionalGlobalFunction since the entry
|
|
256
|
+
// bundle defines __setupBackgroundRPCHandler.
|
|
257
|
+
if (isSplitBundle && bgBundleData && bgBundleData.length > 0) {
|
|
258
|
+
CFAbsoluteTime bgEvalStart = CFAbsoluteTimeGetCurrent();
|
|
259
|
+
auto buffer = std::make_shared<jsi::StringBuffer>(
|
|
260
|
+
std::string(static_cast<const char *>(bgBundleData.bytes), bgBundleData.length));
|
|
261
|
+
runtime.evaluateJavaScript(std::move(buffer), [bgBundleSourceURL UTF8String]);
|
|
262
|
+
double bgEvalMs = (CFAbsoluteTimeGetCurrent() - bgEvalStart) * 1000.0;
|
|
263
|
+
[BTLogger info:[NSString stringWithFormat:@"[SplitBundle] bg entry evaluated in %.1fms", bgEvalMs]];
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
double bgTotalMs = (CFAbsoluteTimeGetCurrent() - bgStartTime) * 1000.0;
|
|
267
|
+
[BTLogger info:[NSString stringWithFormat:@"[SplitBundle] bg hostDidStart total setup in %.1fms", bgTotalMs]];
|
|
268
|
+
|
|
221
269
|
invokeOptionalGlobalFunction(runtime, "__setupBackgroundRPCHandler");
|
|
222
270
|
}];
|
|
223
271
|
}
|