@onekeyfe/react-native-background-thread 3.0.89 → 3.0.91
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.
- package/android/src/main/cpp/cpp-adapter.cpp +215 -31
- package/android/src/main/java/com/backgroundthread/BackgroundThreadManager.kt +701 -106
- package/ios/BackgroundRunnerReactNativeDelegate.h +7 -1
- package/ios/BackgroundRunnerReactNativeDelegate.mm +213 -46
- package/ios/BackgroundThreadManager.h +14 -0
- package/ios/BackgroundThreadManager.mm +175 -14
- package/package.json +1 -1
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package com.backgroundthread
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
|
+
import android.content.Context
|
|
4
5
|
import android.content.Intent
|
|
5
6
|
import android.net.Uri
|
|
6
7
|
import android.os.Handler
|
|
@@ -11,6 +12,7 @@ import com.facebook.react.ReactPackage
|
|
|
11
12
|
import com.facebook.proguard.annotations.DoNotStrip
|
|
12
13
|
import com.facebook.react.ReactInstanceEventListener
|
|
13
14
|
import com.facebook.react.bridge.JSBundleLoader
|
|
15
|
+
import com.facebook.react.bridge.JavaScriptModule
|
|
14
16
|
import com.facebook.react.bridge.ReactApplicationContext
|
|
15
17
|
import com.facebook.react.bridge.ReactContext
|
|
16
18
|
import com.facebook.react.common.annotations.UnstableReactNativeAPI
|
|
@@ -18,13 +20,17 @@ import com.facebook.react.defaults.DefaultComponentsRegistry
|
|
|
18
20
|
import com.facebook.react.defaults.DefaultReactHostDelegate
|
|
19
21
|
import com.facebook.react.defaults.DefaultTurboModuleManagerDelegate
|
|
20
22
|
import com.facebook.react.fabric.ComponentFactory
|
|
23
|
+
import com.facebook.react.interfaces.TaskInterface
|
|
21
24
|
import com.facebook.react.runtime.ReactHostImpl
|
|
22
25
|
import com.facebook.react.runtime.hermes.HermesInstance
|
|
23
26
|
import com.facebook.react.shell.MainReactPackage
|
|
24
27
|
import java.io.File
|
|
25
28
|
import java.lang.ref.WeakReference
|
|
29
|
+
import java.util.concurrent.CountDownLatch
|
|
26
30
|
import java.util.concurrent.TimeUnit
|
|
27
31
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
32
|
+
import java.util.concurrent.atomic.AtomicLong
|
|
33
|
+
import java.util.concurrent.atomic.AtomicReference
|
|
28
34
|
|
|
29
35
|
/**
|
|
30
36
|
* Singleton manager for the background React Native runtime.
|
|
@@ -36,9 +42,38 @@ import java.util.concurrent.atomic.AtomicBoolean
|
|
|
36
42
|
* - Cross-runtime communication via SharedRPC onWrite notifications
|
|
37
43
|
*/
|
|
38
44
|
class BackgroundThreadManager private constructor() {
|
|
45
|
+
// OneKey patch: the prebuilt RN AAR exposes the legacy six-argument HMRClient
|
|
46
|
+
// interface, while the patched JS client accepts an exact bundle URL as argument seven.
|
|
47
|
+
private interface HMRClient : JavaScriptModule {
|
|
48
|
+
fun setup(
|
|
49
|
+
platform: String?,
|
|
50
|
+
bundleEntry: String?,
|
|
51
|
+
host: String?,
|
|
52
|
+
port: Int,
|
|
53
|
+
isEnabled: Boolean,
|
|
54
|
+
scheme: String?,
|
|
55
|
+
fullBundleUrlOverride: String?,
|
|
56
|
+
)
|
|
57
|
+
}
|
|
39
58
|
|
|
59
|
+
// OneKey patch: background uses the same local dev-vendor HBC as main,
|
|
60
|
+
// then evaluates its own modulesOnly Metro delta in the isolated runtime.
|
|
61
|
+
private data class DevVendorConfig(
|
|
62
|
+
val commonAssetName: String,
|
|
63
|
+
val fingerprint: String,
|
|
64
|
+
val backgroundHMREnabled: Boolean,
|
|
65
|
+
val runtimeGeneration: Long = 0,
|
|
66
|
+
)
|
|
40
67
|
private var bgReactHost: ReactHostImpl? = null
|
|
41
68
|
private var reactPackages: List<ReactPackage> = emptyList()
|
|
69
|
+
private val backgroundRuntimeGeneration = AtomicLong(0)
|
|
70
|
+
private val backgroundHMRRestartInFlight = AtomicBoolean(false)
|
|
71
|
+
|
|
72
|
+
@Volatile
|
|
73
|
+
private var lastBackgroundEntryURL: String? = null
|
|
74
|
+
|
|
75
|
+
@Volatile
|
|
76
|
+
private var lastDevVendorConfig: DevVendorConfig? = null
|
|
42
77
|
|
|
43
78
|
// Tracks the last resumed Activity so we can replay it onto the bg
|
|
44
79
|
// ReactContext as soon as the bg host finishes initializing (covers the
|
|
@@ -59,7 +94,18 @@ class BackgroundThreadManager private constructor() {
|
|
|
59
94
|
// full process restart in that case.
|
|
60
95
|
@Volatile
|
|
61
96
|
private var mainReactHost: ReactHost? = null
|
|
62
|
-
private
|
|
97
|
+
private val runnerState = AtomicReference(BackgroundRunnerState.IDLE)
|
|
98
|
+
|
|
99
|
+
@Volatile
|
|
100
|
+
private var runnerStartFailureMessage: String? = null
|
|
101
|
+
|
|
102
|
+
private enum class BackgroundRunnerState {
|
|
103
|
+
IDLE,
|
|
104
|
+
STARTING,
|
|
105
|
+
RUNNING,
|
|
106
|
+
FAILED,
|
|
107
|
+
DESTROYING,
|
|
108
|
+
}
|
|
63
109
|
|
|
64
110
|
companion object {
|
|
65
111
|
private const val MODULE_NAME = "background"
|
|
@@ -101,10 +147,19 @@ class BackgroundThreadManager private constructor() {
|
|
|
101
147
|
|
|
102
148
|
// ── JNI declarations ────────────────────────────────────────────────────
|
|
103
149
|
|
|
104
|
-
private external fun nativeInstallSharedBridge(
|
|
150
|
+
private external fun nativeInstallSharedBridge(
|
|
151
|
+
runtimePtr: Long,
|
|
152
|
+
isMain: Boolean,
|
|
153
|
+
runtimeGeneration: Long,
|
|
154
|
+
)
|
|
105
155
|
private external fun nativeSetupErrorHandler(runtimePtr: Long)
|
|
106
156
|
private external fun nativeDestroy()
|
|
107
|
-
private external fun nativeExecuteWork(
|
|
157
|
+
private external fun nativeExecuteWork(
|
|
158
|
+
runtimePtr: Long,
|
|
159
|
+
workId: Long,
|
|
160
|
+
isMain: Boolean,
|
|
161
|
+
runtimeGeneration: Long,
|
|
162
|
+
)
|
|
108
163
|
|
|
109
164
|
/**
|
|
110
165
|
* Evaluate the segment at [segmentPath] into the BACKGROUND runtime on its
|
|
@@ -139,7 +194,11 @@ class BackgroundThreadManager private constructor() {
|
|
|
139
194
|
* released and the JS promise resolves immediately instead of leaking until
|
|
140
195
|
* teardown or the bg watchdog. Exactly-once on the native side.
|
|
141
196
|
*/
|
|
142
|
-
private external fun nativeDropScheduledWork(
|
|
197
|
+
private external fun nativeDropScheduledWork(
|
|
198
|
+
isMain: Boolean,
|
|
199
|
+
workId: Long,
|
|
200
|
+
runtimeGeneration: Long,
|
|
201
|
+
)
|
|
143
202
|
|
|
144
203
|
/**
|
|
145
204
|
* Synchronously mark the SharedRPC listener for `runtimeId` as dead
|
|
@@ -147,7 +206,19 @@ class BackgroundThreadManager private constructor() {
|
|
|
147
206
|
* SharedRPC::invalidate (cpp/SharedRPC.cpp) for the cross-runtime
|
|
148
207
|
* correctness rationale.
|
|
149
208
|
*/
|
|
150
|
-
private external fun nativeInvalidateSharedRpc(
|
|
209
|
+
private external fun nativeInvalidateSharedRpc(
|
|
210
|
+
runtimeId: String,
|
|
211
|
+
runtimeGeneration: Long,
|
|
212
|
+
): Boolean
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Quiesce background-native work and release JSI callbacks while this call
|
|
216
|
+
* still runs on the outgoing background runtime's JS thread. Destroying
|
|
217
|
+
* those callbacks from the UI or teardown worker thread is unsafe.
|
|
218
|
+
*/
|
|
219
|
+
private external fun nativeInvalidateBackgroundRuntimeOnJSThread(
|
|
220
|
+
runtimeGeneration: Long,
|
|
221
|
+
): Boolean
|
|
151
222
|
|
|
152
223
|
// ── SharedBridge ────────────────────────────────────────────────────────
|
|
153
224
|
|
|
@@ -183,7 +254,7 @@ class BackgroundThreadManager private constructor() {
|
|
|
183
254
|
val ptr = context.javaScriptContextHolder?.get() ?: 0L
|
|
184
255
|
if (ptr != 0L) {
|
|
185
256
|
mainRuntimePtr = ptr
|
|
186
|
-
nativeInstallSharedBridge(ptr, true)
|
|
257
|
+
nativeInstallSharedBridge(ptr, true, 0)
|
|
187
258
|
BTLogger.info("SharedBridge installed in main runtime")
|
|
188
259
|
} else {
|
|
189
260
|
BTLogger.warn("Main runtime pointer is 0, cannot install SharedBridge")
|
|
@@ -332,7 +403,77 @@ class BackgroundThreadManager private constructor() {
|
|
|
332
403
|
}
|
|
333
404
|
}
|
|
334
405
|
}
|
|
335
|
-
|
|
406
|
+
private fun createDevVendorBundleLoader(
|
|
407
|
+
appContext: android.content.Context,
|
|
408
|
+
entryURL: String,
|
|
409
|
+
config: DevVendorConfig,
|
|
410
|
+
): JSBundleLoader {
|
|
411
|
+
return object : JSBundleLoader() {
|
|
412
|
+
override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
|
|
413
|
+
val totalStart = System.nanoTime()
|
|
414
|
+
delegate.loadScriptFromAssets(
|
|
415
|
+
appContext.assets,
|
|
416
|
+
"assets://${config.commonAssetName}",
|
|
417
|
+
false,
|
|
418
|
+
)
|
|
419
|
+
val assertionFile = File(appContext.cacheDir, "onekey-dev-vendor-assert-background.js")
|
|
420
|
+
val quotedEntryURL = org.json.JSONObject.quote(entryURL)
|
|
421
|
+
assertionFile.writeText(
|
|
422
|
+
"if(globalThis.__ONEKEY_DEV_VENDOR_FINGERPRINT__!==\"${config.fingerprint}\")" +
|
|
423
|
+
"{throw new Error(\"Dev-vendor common fingerprint mismatch\");}" +
|
|
424
|
+
"globalThis.__ONEKEY_DEV_VENDOR_FULL_BUNDLE_URL__=$quotedEntryURL;" +
|
|
425
|
+
"globalThis.__ONEKEY_RUNTIME_TARGET__=\"background\";" +
|
|
426
|
+
"globalThis.__ONEKEY_BACKGROUND_RUNTIME_GENERATION__=${config.runtimeGeneration};",
|
|
427
|
+
)
|
|
428
|
+
delegate.loadScriptFromFile(
|
|
429
|
+
assertionFile.absolutePath,
|
|
430
|
+
entryURL,
|
|
431
|
+
false,
|
|
432
|
+
)
|
|
433
|
+
val deltaFile = File(
|
|
434
|
+
appContext.cacheDir,
|
|
435
|
+
"onekey-dev-vendor-background-${config.fingerprint.take(12)}.bundle",
|
|
436
|
+
)
|
|
437
|
+
val temporaryFile = File.createTempFile(
|
|
438
|
+
".${deltaFile.name}-",
|
|
439
|
+
".tmp",
|
|
440
|
+
deltaFile.parentFile,
|
|
441
|
+
)
|
|
442
|
+
try {
|
|
443
|
+
val connection = java.net.URL(entryURL).openConnection() as java.net.HttpURLConnection
|
|
444
|
+
try {
|
|
445
|
+
connection.connectTimeout = 15_000
|
|
446
|
+
connection.readTimeout = 60_000
|
|
447
|
+
val statusCode = connection.responseCode
|
|
448
|
+
if (statusCode !in 200..299) {
|
|
449
|
+
throw IllegalStateException(
|
|
450
|
+
"Dev-vendor background delta returned HTTP $statusCode",
|
|
451
|
+
)
|
|
452
|
+
}
|
|
453
|
+
connection.inputStream.use { input ->
|
|
454
|
+
temporaryFile.outputStream().use { output -> input.copyTo(output) }
|
|
455
|
+
}
|
|
456
|
+
} finally {
|
|
457
|
+
connection.disconnect()
|
|
458
|
+
}
|
|
459
|
+
android.system.Os.rename(
|
|
460
|
+
temporaryFile.absolutePath,
|
|
461
|
+
deltaFile.absolutePath,
|
|
462
|
+
)
|
|
463
|
+
} catch (error: Exception) {
|
|
464
|
+
temporaryFile.delete()
|
|
465
|
+
throw RuntimeException("Failed to download dev-vendor background delta", error)
|
|
466
|
+
}
|
|
467
|
+
delegate.loadScriptFromFile(deltaFile.absolutePath, entryURL, false)
|
|
468
|
+
val totalMs = (System.nanoTime() - totalStart) / 1_000_000.0
|
|
469
|
+
BTLogger.info(
|
|
470
|
+
"[DevVendor] background common.hbc + Metro delta loaded in " +
|
|
471
|
+
"${String.format("%.1f", totalMs)}ms",
|
|
472
|
+
)
|
|
473
|
+
return entryURL
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
336
477
|
private fun createLocalFileBundleLoader(localPath: String, sourceURL: String): JSBundleLoader {
|
|
337
478
|
return object : JSBundleLoader() {
|
|
338
479
|
override fun loadScript(delegate: com.facebook.react.bridge.JSBundleLoaderDelegate): String {
|
|
@@ -347,115 +488,332 @@ class BackgroundThreadManager private constructor() {
|
|
|
347
488
|
}
|
|
348
489
|
}
|
|
349
490
|
|
|
350
|
-
@OptIn(UnstableReactNativeAPI::class)
|
|
351
491
|
fun startBackgroundRunnerWithEntryURL(context: ReactApplicationContext, entryURL: String) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
492
|
+
ensureBackgroundRunnerWithEntryURL(context.applicationContext, entryURL)
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
496
|
+
fun ensureBackgroundRunnerWithEntryURL(context: Context, entryURL: String): Boolean {
|
|
497
|
+
return ensureBackgroundRunner(context, entryURL, null)
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
501
|
+
fun ensureBackgroundRunnerWithDevVendor(
|
|
502
|
+
context: Context,
|
|
503
|
+
entryURL: String,
|
|
504
|
+
commonAssetName: String,
|
|
505
|
+
fingerprint: String,
|
|
506
|
+
backgroundHMREnabled: Boolean,
|
|
507
|
+
): Boolean {
|
|
508
|
+
return ensureBackgroundRunner(
|
|
509
|
+
context,
|
|
510
|
+
entryURL,
|
|
511
|
+
DevVendorConfig(commonAssetName, fingerprint, backgroundHMREnabled),
|
|
512
|
+
)
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
@OptIn(UnstableReactNativeAPI::class)
|
|
516
|
+
private fun ensureBackgroundRunner(
|
|
517
|
+
context: Context,
|
|
518
|
+
entryURL: String,
|
|
519
|
+
devVendorConfig: DevVendorConfig?,
|
|
520
|
+
): Boolean {
|
|
521
|
+
if (!runnerState.compareAndSet(BackgroundRunnerState.IDLE, BackgroundRunnerState.STARTING)) {
|
|
522
|
+
BTLogger.info("Background runner start coalesced: state=${getBackgroundRunnerState()}")
|
|
523
|
+
return false
|
|
355
524
|
}
|
|
525
|
+
|
|
526
|
+
runnerStartFailureMessage = null
|
|
527
|
+
val runtimeGeneration = backgroundRuntimeGeneration.incrementAndGet()
|
|
528
|
+
val activeDevVendorConfig =
|
|
529
|
+
devVendorConfig?.copy(runtimeGeneration = runtimeGeneration)
|
|
530
|
+
lastBackgroundEntryURL = entryURL
|
|
531
|
+
lastDevVendorConfig = devVendorConfig
|
|
356
532
|
val bgStartTime = System.nanoTime()
|
|
357
533
|
BTLogger.info("[SplitBundle] background runner starting with entryURL: $entryURL")
|
|
358
534
|
|
|
359
535
|
val appContext = context.applicationContext
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
reactPackages
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
536
|
+
try {
|
|
537
|
+
val packages =
|
|
538
|
+
if (reactPackages.isNotEmpty()) {
|
|
539
|
+
reactPackages
|
|
540
|
+
} else {
|
|
541
|
+
BTLogger.warn("No ReactPackages registered for background runtime; call setReactPackages(...) from host before start. Falling back to MainReactPackage only.")
|
|
542
|
+
listOf(MainReactPackage())
|
|
543
|
+
}
|
|
367
544
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
545
|
+
val localBundlePath = resolveLocalBundlePath(entryURL)
|
|
546
|
+
val bundleLoader =
|
|
547
|
+
when {
|
|
548
|
+
activeDevVendorConfig != null && isRemoteBundleUrl(entryURL) ->
|
|
549
|
+
createDevVendorBundleLoader(appContext, entryURL, activeDevVendorConfig)
|
|
550
|
+
|
|
551
|
+
// Debug mode: remote URL — use single bundle (Metro dev server)
|
|
552
|
+
isRemoteBundleUrl(entryURL) -> createDownloadedBundleLoader(appContext, entryURL)
|
|
553
|
+
|
|
554
|
+
// OTA / local file path — try sequential loading with common bundle
|
|
555
|
+
localBundlePath != null -> {
|
|
556
|
+
val commonPath = resolveCommonBundlePath(localBundlePath)
|
|
557
|
+
if (commonPath != null) {
|
|
558
|
+
BTLogger.info("Using sequential file bundle loader: common=$commonPath, entry=$localBundlePath")
|
|
559
|
+
createSequentialFileBundleLoader(commonPath, localBundlePath, entryURL)
|
|
560
|
+
} else {
|
|
561
|
+
BTLogger.info("No common bundle found for OTA path, using single bundle: $localBundlePath")
|
|
562
|
+
createLocalFileBundleLoader(localBundlePath, entryURL)
|
|
563
|
+
}
|
|
383
564
|
}
|
|
384
|
-
}
|
|
385
565
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
566
|
+
// Assets-based loading — try sequential loading with common.bundle in assets
|
|
567
|
+
entryURL.startsWith("assets://") -> {
|
|
568
|
+
val entryAssetName = entryURL.removePrefix("assets://")
|
|
569
|
+
if (hasCommonBundleInAssets(appContext)) {
|
|
570
|
+
BTLogger.info("Using sequential asset bundle loader: common=common.bundle, entry=$entryAssetName")
|
|
571
|
+
createSequentialAssetBundleLoader(appContext, "common.bundle", entryAssetName)
|
|
572
|
+
} else {
|
|
573
|
+
BTLogger.info("No common.bundle in assets, using single bundle: $entryURL")
|
|
574
|
+
JSBundleLoader.createAssetLoader(appContext, entryURL, true)
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// Bare filename (e.g. "background.bundle") — treat as asset
|
|
579
|
+
else -> {
|
|
580
|
+
if (hasCommonBundleInAssets(appContext)) {
|
|
581
|
+
BTLogger.info("Using sequential asset bundle loader: common=common.bundle, entry=$entryURL")
|
|
582
|
+
createSequentialAssetBundleLoader(appContext, "common.bundle", entryURL)
|
|
583
|
+
} else {
|
|
584
|
+
BTLogger.info("No common.bundle in assets, using single bundle: assets://$entryURL")
|
|
585
|
+
JSBundleLoader.createAssetLoader(appContext, "assets://$entryURL", true)
|
|
586
|
+
}
|
|
395
587
|
}
|
|
396
588
|
}
|
|
397
589
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
590
|
+
val delegate = DefaultReactHostDelegate(
|
|
591
|
+
jsMainModulePath = MODULE_NAME,
|
|
592
|
+
jsBundleLoader = bundleLoader,
|
|
593
|
+
reactPackages = packages,
|
|
594
|
+
jsRuntimeFactory = HermesInstance(),
|
|
595
|
+
turboModuleManagerDelegateBuilder = DefaultTurboModuleManagerDelegate.Builder(),
|
|
596
|
+
)
|
|
597
|
+
|
|
598
|
+
val componentFactory = ComponentFactory()
|
|
599
|
+
DefaultComponentsRegistry.register(componentFactory)
|
|
600
|
+
|
|
601
|
+
val host = ReactHostImpl(
|
|
602
|
+
appContext,
|
|
603
|
+
delegate,
|
|
604
|
+
componentFactory,
|
|
605
|
+
true, /* allowPackagerServerAccess */
|
|
606
|
+
false, /* useDevSupport */
|
|
607
|
+
)
|
|
608
|
+
bgReactHost = host
|
|
609
|
+
|
|
610
|
+
host.addReactInstanceEventListener(object : ReactInstanceEventListener {
|
|
611
|
+
override fun onReactContextInitialized(context: ReactContext) {
|
|
612
|
+
if (backgroundRuntimeGeneration.get() != runtimeGeneration) {
|
|
613
|
+
BTLogger.info(
|
|
614
|
+
"[BackgroundHMR] ignored stale ReactContext generation=$runtimeGeneration",
|
|
615
|
+
)
|
|
616
|
+
return
|
|
617
|
+
}
|
|
618
|
+
val initMs = (System.nanoTime() - bgStartTime) / 1_000_000.0
|
|
619
|
+
BTLogger.info("[SplitBundle] background ReactContext initialized in ${String.format("%.1f", initMs)}ms")
|
|
620
|
+
// Replay the most recent Activity resume so TurboModules on the
|
|
621
|
+
// bg host can see getCurrentActivity()/ActivityEventListeners
|
|
622
|
+
// from the very first call, even when the bg host finishes
|
|
623
|
+
// initializing after the Activity is already resumed.
|
|
624
|
+
replayLastResumedActivityOnUi()
|
|
625
|
+
context.runOnJSQueueThread {
|
|
626
|
+
try {
|
|
627
|
+
if (
|
|
628
|
+
backgroundRuntimeGeneration.get() != runtimeGeneration ||
|
|
629
|
+
bgReactHost?.currentReactContext !== context
|
|
630
|
+
) {
|
|
631
|
+
BTLogger.info(
|
|
632
|
+
"[BackgroundHMR] skipped stale binding install generation=$runtimeGeneration",
|
|
633
|
+
)
|
|
634
|
+
return@runOnJSQueueThread
|
|
635
|
+
}
|
|
636
|
+
val ptr = context.javaScriptContextHolder?.get() ?: 0L
|
|
637
|
+
if (ptr != 0L) {
|
|
638
|
+
bgRuntimePtr = ptr
|
|
639
|
+
nativeInstallSharedBridge(ptr, false, runtimeGeneration)
|
|
640
|
+
nativeSetupErrorHandler(ptr)
|
|
641
|
+
BTLogger.info("SharedBridge and error handler installed in background runtime")
|
|
642
|
+
} else {
|
|
643
|
+
BTLogger.error("Background runtime pointer is 0")
|
|
644
|
+
}
|
|
645
|
+
} catch (e: Exception) {
|
|
646
|
+
BTLogger.error("Error installing bindings in background runtime: ${e.message}")
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
if (activeDevVendorConfig?.backgroundHMREnabled == true) {
|
|
650
|
+
setupBackgroundHMRClient(context, entryURL, runtimeGeneration)
|
|
406
651
|
}
|
|
407
652
|
}
|
|
408
|
-
}
|
|
653
|
+
})
|
|
409
654
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
655
|
+
watchBackgroundRunnerStart(host.start(), bgStartTime, runtimeGeneration)
|
|
656
|
+
return true
|
|
657
|
+
} catch (t: Throwable) {
|
|
658
|
+
markBackgroundRunnerFailed(t, bgStartTime, runtimeGeneration)
|
|
659
|
+
return false
|
|
660
|
+
}
|
|
661
|
+
}
|
|
417
662
|
|
|
418
|
-
|
|
419
|
-
|
|
663
|
+
private fun setupBackgroundHMRClient(
|
|
664
|
+
context: ReactContext,
|
|
665
|
+
entryURL: String,
|
|
666
|
+
runtimeGeneration: Long,
|
|
667
|
+
) {
|
|
668
|
+
try {
|
|
669
|
+
val uri = Uri.parse(entryURL)
|
|
670
|
+
val hmrRegistrationUri = uri.buildUpon()
|
|
671
|
+
.path("/apps/mobile/background.bundle")
|
|
672
|
+
.build()
|
|
673
|
+
val scheme = hmrRegistrationUri.scheme ?: "http"
|
|
674
|
+
val port = if (hmrRegistrationUri.port != -1) {
|
|
675
|
+
hmrRegistrationUri.port
|
|
676
|
+
} else if (scheme == "https") {
|
|
677
|
+
443
|
|
678
|
+
} else {
|
|
679
|
+
80
|
|
680
|
+
}
|
|
681
|
+
val path = hmrRegistrationUri.path?.removePrefix("/")
|
|
682
|
+
?: "apps/mobile/background.bundle"
|
|
683
|
+
context.getJSModule(HMRClient::class.java).setup(
|
|
684
|
+
"android",
|
|
685
|
+
path,
|
|
686
|
+
hmrRegistrationUri.host,
|
|
687
|
+
port,
|
|
688
|
+
true,
|
|
689
|
+
scheme,
|
|
690
|
+
hmrRegistrationUri.toString(),
|
|
691
|
+
)
|
|
692
|
+
BTLogger.info(
|
|
693
|
+
"[BackgroundHMR] client setup queued generation=$runtimeGeneration " +
|
|
694
|
+
"downloadURL=$entryURL registrationURL=$hmrRegistrationUri",
|
|
695
|
+
)
|
|
696
|
+
} catch (t: Throwable) {
|
|
697
|
+
BTLogger.error("[BackgroundHMR] client setup failed: ${t.message}")
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
private fun watchBackgroundRunnerStart(
|
|
702
|
+
task: TaskInterface<Void>,
|
|
703
|
+
bgStartTime: Long,
|
|
704
|
+
runtimeGeneration: Long,
|
|
705
|
+
) {
|
|
706
|
+
Thread {
|
|
707
|
+
try {
|
|
708
|
+
task.waitForCompletion()
|
|
709
|
+
if (
|
|
710
|
+
runnerState.get() != BackgroundRunnerState.STARTING ||
|
|
711
|
+
backgroundRuntimeGeneration.get() != runtimeGeneration
|
|
712
|
+
) {
|
|
713
|
+
return@Thread
|
|
714
|
+
}
|
|
715
|
+
when {
|
|
716
|
+
task.isFaulted() -> markBackgroundRunnerFailed(
|
|
717
|
+
task.getError() ?: RuntimeException("Background ReactHost start faulted"),
|
|
718
|
+
bgStartTime,
|
|
719
|
+
runtimeGeneration,
|
|
720
|
+
)
|
|
721
|
+
task.isCancelled() -> markBackgroundRunnerFailed(
|
|
722
|
+
RuntimeException("Background ReactHost start cancelled"),
|
|
723
|
+
bgStartTime,
|
|
724
|
+
runtimeGeneration,
|
|
725
|
+
)
|
|
726
|
+
runnerState.compareAndSet(
|
|
727
|
+
BackgroundRunnerState.STARTING,
|
|
728
|
+
BackgroundRunnerState.RUNNING,
|
|
729
|
+
) -> {
|
|
730
|
+
val startMs = (System.nanoTime() - bgStartTime) / 1_000_000.0
|
|
731
|
+
BTLogger.info("Background runner start task completed in ${String.format("%.1f", startMs)}ms")
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
} catch (t: Throwable) {
|
|
735
|
+
markBackgroundRunnerFailed(t, bgStartTime, runtimeGeneration)
|
|
736
|
+
}
|
|
737
|
+
}.apply {
|
|
738
|
+
isDaemon = true
|
|
739
|
+
name = "OneKey-BgThread-StartWatch"
|
|
740
|
+
start()
|
|
741
|
+
}
|
|
742
|
+
}
|
|
420
743
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
744
|
+
private fun markBackgroundRunnerFailed(
|
|
745
|
+
t: Throwable,
|
|
746
|
+
bgStartTime: Long,
|
|
747
|
+
runtimeGeneration: Long,
|
|
748
|
+
) {
|
|
749
|
+
if (
|
|
750
|
+
backgroundRuntimeGeneration.get() != runtimeGeneration ||
|
|
751
|
+
!runnerState.compareAndSet(BackgroundRunnerState.STARTING, BackgroundRunnerState.FAILED)
|
|
752
|
+
) {
|
|
753
|
+
return
|
|
754
|
+
}
|
|
755
|
+
val startMs = (System.nanoTime() - bgStartTime) / 1_000_000.0
|
|
756
|
+
runnerStartFailureMessage = t.message ?: t.javaClass.simpleName
|
|
757
|
+
bgRuntimePtr = 0
|
|
758
|
+
BTLogger.error(
|
|
759
|
+
"Background runner start failed after ${String.format("%.1f", startMs)}ms: " +
|
|
760
|
+
runnerStartFailureMessage
|
|
427
761
|
)
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
762
|
+
val failedHost = bgReactHost
|
|
763
|
+
val destroyTask = try {
|
|
764
|
+
failedHost?.destroy("Background runner start failed", t as? Exception)
|
|
765
|
+
} catch (destroyError: Throwable) {
|
|
766
|
+
BTLogger.error("Failed to destroy faulted background runner: ${destroyError.message}")
|
|
767
|
+
null
|
|
768
|
+
}
|
|
769
|
+
if (destroyTask == null) {
|
|
770
|
+
if (failedHost == null) {
|
|
771
|
+
runnerState.compareAndSet(
|
|
772
|
+
BackgroundRunnerState.FAILED,
|
|
773
|
+
BackgroundRunnerState.IDLE,
|
|
774
|
+
)
|
|
775
|
+
}
|
|
776
|
+
return
|
|
777
|
+
}
|
|
778
|
+
Thread {
|
|
779
|
+
try {
|
|
780
|
+
destroyTask.waitForCompletion()
|
|
781
|
+
if (destroyTask.isFaulted() || destroyTask.isCancelled()) {
|
|
782
|
+
BTLogger.error(
|
|
783
|
+
"Faulted background runner teardown did not complete cleanly " +
|
|
784
|
+
"(faulted=${destroyTask.isFaulted()}, " +
|
|
785
|
+
"cancelled=${destroyTask.isCancelled()}): " +
|
|
786
|
+
destroyTask.getError()?.message,
|
|
787
|
+
)
|
|
788
|
+
return@Thread
|
|
789
|
+
}
|
|
790
|
+
if (
|
|
791
|
+
runnerState.compareAndSet(
|
|
792
|
+
BackgroundRunnerState.FAILED,
|
|
793
|
+
BackgroundRunnerState.DESTROYING,
|
|
794
|
+
)
|
|
795
|
+
) {
|
|
796
|
+
Handler(Looper.getMainLooper()).post {
|
|
797
|
+
if (bgReactHost === failedHost) {
|
|
798
|
+
bgReactHost = null
|
|
449
799
|
}
|
|
450
|
-
|
|
451
|
-
BTLogger.
|
|
800
|
+
runnerState.set(BackgroundRunnerState.IDLE)
|
|
801
|
+
BTLogger.info(
|
|
802
|
+
"Faulted background runner teardown completed; retry is available",
|
|
803
|
+
)
|
|
452
804
|
}
|
|
453
805
|
}
|
|
806
|
+
} catch (destroyError: Throwable) {
|
|
807
|
+
BTLogger.error(
|
|
808
|
+
"Failed to await faulted background runner teardown: " +
|
|
809
|
+
destroyError.message,
|
|
810
|
+
)
|
|
454
811
|
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
812
|
+
}.apply {
|
|
813
|
+
isDaemon = true
|
|
814
|
+
name = "OneKey-BgThread-FailedHostDestroy"
|
|
815
|
+
start()
|
|
816
|
+
}
|
|
459
817
|
}
|
|
460
818
|
|
|
461
819
|
/**
|
|
@@ -463,9 +821,26 @@ class BackgroundThreadManager private constructor() {
|
|
|
463
821
|
* Routes to main or background runtime's JS queue thread, then calls nativeExecuteWork.
|
|
464
822
|
*/
|
|
465
823
|
@DoNotStrip
|
|
466
|
-
fun scheduleOnJSThread(
|
|
467
|
-
|
|
468
|
-
|
|
824
|
+
fun scheduleOnJSThread(
|
|
825
|
+
isMain: Boolean,
|
|
826
|
+
workId: Long,
|
|
827
|
+
runtimeGeneration: Long,
|
|
828
|
+
): Boolean {
|
|
829
|
+
val backgroundState = runnerState.get()
|
|
830
|
+
val context = if (isMain) {
|
|
831
|
+
mainReactContext
|
|
832
|
+
} else if (
|
|
833
|
+
backgroundState == BackgroundRunnerState.STARTING ||
|
|
834
|
+
backgroundState == BackgroundRunnerState.RUNNING
|
|
835
|
+
) {
|
|
836
|
+
bgReactHost?.currentReactContext
|
|
837
|
+
} else {
|
|
838
|
+
null
|
|
839
|
+
}
|
|
840
|
+
BTLogger.info(
|
|
841
|
+
"scheduleOnJSThread: isMain=$isMain, workId=$workId, " +
|
|
842
|
+
"generation=$runtimeGeneration, context=${context != null}",
|
|
843
|
+
)
|
|
469
844
|
if (context == null) {
|
|
470
845
|
BTLogger.error("scheduleOnJSThread: context is null! isMain=$isMain, mainCtx=${mainReactContext != null}, bgHost=${bgReactHost != null}, bgCtx=${bgReactHost?.currentReactContext != null}")
|
|
471
846
|
// The just-enqueued native work will never reach the JS thread.
|
|
@@ -479,13 +854,27 @@ class BackgroundThreadManager private constructor() {
|
|
|
479
854
|
}
|
|
480
855
|
return try {
|
|
481
856
|
val posted = context.runOnJSQueueThread {
|
|
482
|
-
|
|
483
|
-
|
|
857
|
+
if (
|
|
858
|
+
!isMain &&
|
|
859
|
+
(
|
|
860
|
+
backgroundRuntimeGeneration.get() != runtimeGeneration ||
|
|
861
|
+
bgReactHost?.currentReactContext !== context
|
|
862
|
+
)
|
|
863
|
+
) {
|
|
864
|
+
BTLogger.info(
|
|
865
|
+
"scheduleOnJSThread: dropped stale background workId=$workId " +
|
|
866
|
+
"generation=$runtimeGeneration active=${backgroundRuntimeGeneration.get()}",
|
|
867
|
+
)
|
|
868
|
+
nativeDropScheduledWork(isMain, workId, runtimeGeneration)
|
|
869
|
+
return@runOnJSQueueThread
|
|
870
|
+
}
|
|
871
|
+
// Re-read ptr only after proving this Runnable still belongs to
|
|
872
|
+
// the active context and generation.
|
|
484
873
|
val ptr = if (isMain) mainRuntimePtr else bgRuntimePtr
|
|
485
874
|
BTLogger.info("scheduleOnJSThread runOnJSQueueThread: isMain=$isMain, workId=$workId, ptr=$ptr")
|
|
486
875
|
if (ptr != 0L) {
|
|
487
876
|
try {
|
|
488
|
-
nativeExecuteWork(ptr, workId)
|
|
877
|
+
nativeExecuteWork(ptr, workId, isMain, runtimeGeneration)
|
|
489
878
|
} catch (e: Exception) {
|
|
490
879
|
BTLogger.error("Error executing work on JS thread: ${e.message}")
|
|
491
880
|
}
|
|
@@ -501,7 +890,7 @@ class BackgroundThreadManager private constructor() {
|
|
|
501
890
|
// runtime recovers and main has no JS retry net).
|
|
502
891
|
// drainPendingBgEvals inside the native fn is
|
|
503
892
|
// gated to !isMain, so settling bg evals only happens for bg.
|
|
504
|
-
nativeDropScheduledWork(isMain, workId)
|
|
893
|
+
nativeDropScheduledWork(isMain, workId, runtimeGeneration)
|
|
505
894
|
}
|
|
506
895
|
}
|
|
507
896
|
if (!posted) {
|
|
@@ -541,7 +930,7 @@ class BackgroundThreadManager private constructor() {
|
|
|
541
930
|
path: String,
|
|
542
931
|
onComplete: (code: String?, message: String?) -> Unit
|
|
543
932
|
) {
|
|
544
|
-
if (!
|
|
933
|
+
if (!isBackgroundStarted) {
|
|
545
934
|
// Bg runtime not started yet → retryable (the loader will re-attempt
|
|
546
935
|
// once the bg host is up).
|
|
547
936
|
onComplete("SPLIT_BUNDLE_NO_RUNTIME", "Background runtime not started")
|
|
@@ -950,6 +1339,11 @@ class BackgroundThreadManager private constructor() {
|
|
|
950
1339
|
fun restart(context: ReactApplicationContext, mode: String, reason: String, promise: com.facebook.react.bridge.Promise) {
|
|
951
1340
|
val isUi = mode == "ui"
|
|
952
1341
|
val isAll = mode == "all"
|
|
1342
|
+
val isBackgroundHMR = mode == "background"
|
|
1343
|
+
if (isBackgroundHMR) {
|
|
1344
|
+
restartBackgroundForHMR(context.applicationContext, reason, promise)
|
|
1345
|
+
return
|
|
1346
|
+
}
|
|
953
1347
|
if (!isUi && !isAll) {
|
|
954
1348
|
promise.reject(
|
|
955
1349
|
"BG_RESTART_ERROR",
|
|
@@ -961,9 +1355,12 @@ class BackgroundThreadManager private constructor() {
|
|
|
961
1355
|
BTLogger.info("restart: mode=$mode reason=$reason")
|
|
962
1356
|
|
|
963
1357
|
try {
|
|
964
|
-
nativeInvalidateSharedRpc("main")
|
|
1358
|
+
nativeInvalidateSharedRpc("main", 0)
|
|
965
1359
|
if (isAll) {
|
|
966
|
-
nativeInvalidateSharedRpc(
|
|
1360
|
+
nativeInvalidateSharedRpc(
|
|
1361
|
+
"background",
|
|
1362
|
+
backgroundRuntimeGeneration.incrementAndGet(),
|
|
1363
|
+
)
|
|
967
1364
|
}
|
|
968
1365
|
} catch (t: Throwable) {
|
|
969
1366
|
// Invalidate is best-effort; not fatal if the JNI call somehow
|
|
@@ -1071,6 +1468,188 @@ class BackgroundThreadManager private constructor() {
|
|
|
1071
1468
|
// any further code here is unreachable
|
|
1072
1469
|
}
|
|
1073
1470
|
|
|
1471
|
+
private fun restartBackgroundForHMR(
|
|
1472
|
+
context: Context,
|
|
1473
|
+
reason: String,
|
|
1474
|
+
promise: com.facebook.react.bridge.Promise,
|
|
1475
|
+
) {
|
|
1476
|
+
if (!BuildConfig.DEBUG) {
|
|
1477
|
+
promise.reject(
|
|
1478
|
+
"BG_RESTART_ERROR",
|
|
1479
|
+
"Background HMR restart is unavailable in release builds",
|
|
1480
|
+
)
|
|
1481
|
+
return
|
|
1482
|
+
}
|
|
1483
|
+
val generation = reason
|
|
1484
|
+
.removePrefix("onekey-bg-hmr:")
|
|
1485
|
+
.substringBefore(':')
|
|
1486
|
+
.toLongOrNull()
|
|
1487
|
+
if (!reason.startsWith("onekey-bg-hmr:") || generation == null) {
|
|
1488
|
+
promise.reject(
|
|
1489
|
+
"BG_RESTART_ERROR",
|
|
1490
|
+
"Background HMR restart is missing its runtime generation",
|
|
1491
|
+
)
|
|
1492
|
+
return
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
Handler(Looper.getMainLooper()).post {
|
|
1496
|
+
val activeGeneration = backgroundRuntimeGeneration.get()
|
|
1497
|
+
if (generation != activeGeneration) {
|
|
1498
|
+
BTLogger.info(
|
|
1499
|
+
"[BackgroundHMR] ignored stale restart requested=$generation active=$activeGeneration",
|
|
1500
|
+
)
|
|
1501
|
+
promise.resolve(null)
|
|
1502
|
+
return@post
|
|
1503
|
+
}
|
|
1504
|
+
val entryURL = lastBackgroundEntryURL
|
|
1505
|
+
val config = lastDevVendorConfig
|
|
1506
|
+
if (
|
|
1507
|
+
entryURL == null ||
|
|
1508
|
+
config?.backgroundHMREnabled != true ||
|
|
1509
|
+
!backgroundHMRRestartInFlight.compareAndSet(false, true)
|
|
1510
|
+
) {
|
|
1511
|
+
BTLogger.info("[BackgroundHMR] restart coalesced or disabled")
|
|
1512
|
+
promise.resolve(null)
|
|
1513
|
+
return@post
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
val outgoingHost = bgReactHost
|
|
1517
|
+
val outgoingContext = outgoingHost?.currentReactContext
|
|
1518
|
+
val invalidatedGeneration = backgroundRuntimeGeneration.incrementAndGet()
|
|
1519
|
+
runnerState.set(BackgroundRunnerState.DESTROYING)
|
|
1520
|
+
bgRuntimePtr = 0
|
|
1521
|
+
val runtimeInvalidated = CountDownLatch(1)
|
|
1522
|
+
val runtimeInvalidationSucceeded = AtomicBoolean(false)
|
|
1523
|
+
val invalidationScheduled = try {
|
|
1524
|
+
outgoingContext?.runOnJSQueueThread {
|
|
1525
|
+
try {
|
|
1526
|
+
nativeInvalidateBackgroundRuntimeOnJSThread(
|
|
1527
|
+
invalidatedGeneration,
|
|
1528
|
+
)
|
|
1529
|
+
runtimeInvalidationSucceeded.set(true)
|
|
1530
|
+
} catch (t: Throwable) {
|
|
1531
|
+
BTLogger.error(
|
|
1532
|
+
"[BackgroundHMR] JS-thread invalidation failed: ${t.message}",
|
|
1533
|
+
)
|
|
1534
|
+
try {
|
|
1535
|
+
nativeInvalidateSharedRpc("background", invalidatedGeneration)
|
|
1536
|
+
runtimeInvalidationSucceeded.set(true)
|
|
1537
|
+
} catch (fallbackError: Throwable) {
|
|
1538
|
+
BTLogger.error(
|
|
1539
|
+
"[BackgroundHMR] fallback invalidation failed: " +
|
|
1540
|
+
fallbackError.message,
|
|
1541
|
+
)
|
|
1542
|
+
}
|
|
1543
|
+
} finally {
|
|
1544
|
+
runtimeInvalidated.countDown()
|
|
1545
|
+
}
|
|
1546
|
+
} == true
|
|
1547
|
+
} catch (t: Throwable) {
|
|
1548
|
+
BTLogger.error(
|
|
1549
|
+
"[BackgroundHMR] failed to schedule JS-thread invalidation: ${t.message}",
|
|
1550
|
+
)
|
|
1551
|
+
false
|
|
1552
|
+
}
|
|
1553
|
+
if (!invalidationScheduled) {
|
|
1554
|
+
try {
|
|
1555
|
+
// Safe fallback for a context whose JS queue is already
|
|
1556
|
+
// unavailable. This retains the old off-thread callback
|
|
1557
|
+
// disposal safeguards, but still quiesces SharedRPC.
|
|
1558
|
+
nativeInvalidateSharedRpc("background", invalidatedGeneration)
|
|
1559
|
+
runtimeInvalidationSucceeded.set(true)
|
|
1560
|
+
} catch (t: Throwable) {
|
|
1561
|
+
BTLogger.error(
|
|
1562
|
+
"[BackgroundHMR] SharedRPC invalidate failed: ${t.message}",
|
|
1563
|
+
)
|
|
1564
|
+
}
|
|
1565
|
+
runtimeInvalidated.countDown()
|
|
1566
|
+
}
|
|
1567
|
+
promise.resolve(null)
|
|
1568
|
+
|
|
1569
|
+
Thread {
|
|
1570
|
+
try {
|
|
1571
|
+
runtimeInvalidated.await()
|
|
1572
|
+
} catch (t: Throwable) {
|
|
1573
|
+
BTLogger.error(
|
|
1574
|
+
"[BackgroundHMR] outgoing runtime invalidation wait failed: ${t.message}",
|
|
1575
|
+
)
|
|
1576
|
+
backgroundHMRRestartInFlight.set(false)
|
|
1577
|
+
return@Thread
|
|
1578
|
+
}
|
|
1579
|
+
if (!runtimeInvalidationSucceeded.get()) {
|
|
1580
|
+
BTLogger.error(
|
|
1581
|
+
"[BackgroundHMR] replacement blocked: runtime invalidation failed",
|
|
1582
|
+
)
|
|
1583
|
+
backgroundHMRRestartInFlight.set(false)
|
|
1584
|
+
return@Thread
|
|
1585
|
+
}
|
|
1586
|
+
val destroyTask = try {
|
|
1587
|
+
outgoingHost?.destroy("Background HMR restart", null)
|
|
1588
|
+
} catch (t: Throwable) {
|
|
1589
|
+
BTLogger.error(
|
|
1590
|
+
"[BackgroundHMR] outgoing host destroy threw: ${t.message}",
|
|
1591
|
+
)
|
|
1592
|
+
null
|
|
1593
|
+
}
|
|
1594
|
+
if (outgoingHost != null && destroyTask == null) {
|
|
1595
|
+
backgroundHMRRestartInFlight.set(false)
|
|
1596
|
+
return@Thread
|
|
1597
|
+
}
|
|
1598
|
+
if (destroyTask != null) {
|
|
1599
|
+
try {
|
|
1600
|
+
destroyTask.waitForCompletion()
|
|
1601
|
+
} catch (t: Throwable) {
|
|
1602
|
+
BTLogger.error(
|
|
1603
|
+
"[BackgroundHMR] outgoing host destroy wait failed: ${t.message}",
|
|
1604
|
+
)
|
|
1605
|
+
backgroundHMRRestartInFlight.set(false)
|
|
1606
|
+
return@Thread
|
|
1607
|
+
}
|
|
1608
|
+
if (destroyTask.isFaulted() || destroyTask.isCancelled()) {
|
|
1609
|
+
BTLogger.error(
|
|
1610
|
+
"[BackgroundHMR] outgoing host destroy did not complete cleanly " +
|
|
1611
|
+
"(faulted=${destroyTask.isFaulted()}, " +
|
|
1612
|
+
"cancelled=${destroyTask.isCancelled()}): " +
|
|
1613
|
+
destroyTask.getError()?.message,
|
|
1614
|
+
)
|
|
1615
|
+
backgroundHMRRestartInFlight.set(false)
|
|
1616
|
+
return@Thread
|
|
1617
|
+
}
|
|
1618
|
+
}
|
|
1619
|
+
Handler(Looper.getMainLooper()).post {
|
|
1620
|
+
if (bgReactHost !== outgoingHost) {
|
|
1621
|
+
BTLogger.error(
|
|
1622
|
+
"[BackgroundHMR] replacement aborted: outgoing host changed",
|
|
1623
|
+
)
|
|
1624
|
+
backgroundHMRRestartInFlight.set(false)
|
|
1625
|
+
return@post
|
|
1626
|
+
}
|
|
1627
|
+
bgReactHost = null
|
|
1628
|
+
if (
|
|
1629
|
+
!runnerState.compareAndSet(
|
|
1630
|
+
BackgroundRunnerState.DESTROYING,
|
|
1631
|
+
BackgroundRunnerState.IDLE,
|
|
1632
|
+
)
|
|
1633
|
+
) {
|
|
1634
|
+
BTLogger.error(
|
|
1635
|
+
"[BackgroundHMR] replacement aborted: unexpected state=" +
|
|
1636
|
+
getBackgroundRunnerState(),
|
|
1637
|
+
)
|
|
1638
|
+
backgroundHMRRestartInFlight.set(false)
|
|
1639
|
+
return@post
|
|
1640
|
+
}
|
|
1641
|
+
BTLogger.info("[BackgroundHMR] starting replacement background runtime")
|
|
1642
|
+
ensureBackgroundRunner(context, entryURL, config)
|
|
1643
|
+
backgroundHMRRestartInFlight.set(false)
|
|
1644
|
+
}
|
|
1645
|
+
}.apply {
|
|
1646
|
+
isDaemon = true
|
|
1647
|
+
name = "OneKey-BgThread-HMRRestart"
|
|
1648
|
+
start()
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
|
|
1074
1653
|
/**
|
|
1075
1654
|
* Returns true if Runtime.exit(0) was reached (in which case the process
|
|
1076
1655
|
* is now terminating and any code after the call site is unreachable).
|
|
@@ -1097,9 +1676,24 @@ class BackgroundThreadManager private constructor() {
|
|
|
1097
1676
|
|
|
1098
1677
|
// ── Lifecycle ───────────────────────────────────────────────────────────
|
|
1099
1678
|
|
|
1100
|
-
val isBackgroundStarted: Boolean
|
|
1679
|
+
val isBackgroundStarted: Boolean
|
|
1680
|
+
get() = when (runnerState.get()) {
|
|
1681
|
+
BackgroundRunnerState.STARTING, BackgroundRunnerState.RUNNING -> true
|
|
1682
|
+
else -> false
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
fun getBackgroundRunnerState(): String = when (runnerState.get()) {
|
|
1686
|
+
BackgroundRunnerState.IDLE -> "idle"
|
|
1687
|
+
BackgroundRunnerState.STARTING -> "starting"
|
|
1688
|
+
BackgroundRunnerState.RUNNING -> "running"
|
|
1689
|
+
BackgroundRunnerState.FAILED -> "failed"
|
|
1690
|
+
BackgroundRunnerState.DESTROYING -> "destroying"
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
fun getBackgroundRunnerFailureMessage(): String? = runnerStartFailureMessage
|
|
1101
1694
|
|
|
1102
1695
|
fun destroy() {
|
|
1696
|
+
runnerState.set(BackgroundRunnerState.DESTROYING)
|
|
1103
1697
|
nativeDestroy()
|
|
1104
1698
|
bgRuntimePtr = 0
|
|
1105
1699
|
mainRuntimePtr = 0
|
|
@@ -1107,7 +1701,8 @@ class BackgroundThreadManager private constructor() {
|
|
|
1107
1701
|
mainReactHost = null
|
|
1108
1702
|
bgReactHost?.destroy("BackgroundThreadManager destroyed", null)
|
|
1109
1703
|
bgReactHost = null
|
|
1110
|
-
|
|
1704
|
+
runnerStartFailureMessage = null
|
|
1705
|
+
runnerState.set(BackgroundRunnerState.IDLE)
|
|
1111
1706
|
lastResumedActivityRef = WeakReference(null)
|
|
1112
1707
|
}
|
|
1113
1708
|
}
|