@callstack/react-native-brownfield 5.2.0 → 6.0.0
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/CHANGELOG.md +23 -0
- package/android/build.gradle +1 -0
- package/android/consumer-rules.pro +6 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/BrownfieldPerformance.kt +384 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/BrownfieldPerformanceModule.kt +15 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactDelegateWrapper.kt +8 -5
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfield.kt +78 -23
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeBrownfieldPackage.kt +1 -0
- package/android/src/main/java/com/callstack/reactnativebrownfield/ReactNativeFragment.kt +89 -3
- package/android/src/main/java/com/callstack/reactnativebrownfield/constants/ReactNativeFragmentArgNames.kt +2 -0
- package/android/src/test/java/com/callstack/reactnativebrownfield/PerformanceMetricsTest.kt +54 -0
- package/gradle-plugin/brownfield/src/main/kotlin/com/callstack/react/brownfield/plugin/RNSourceSets.kt +59 -15
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/plugin/AgpCompatibilityMatrixTest.kt +6 -8
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/plugin/LegacyConsumerConfigurationTest.kt +6 -4
- package/gradle-plugin/brownfield/src/test/kotlin/com/callstack/react/brownfield/plugin/RNBundleTaskStubs.kt +27 -0
- package/ios/BrownfieldDisplayMetrics.swift +265 -0
- package/ios/BrownfieldPerformanceBridge.h +8 -0
- package/ios/BrownfieldPerformanceBridge.mm +103 -0
- package/ios/Expo/ExpoHostRuntime.swift +58 -14
- package/ios/Expo/ExpoUpdatesStartup.swift +49 -0
- package/ios/JSBundleLoadObserver.swift +56 -19
- package/ios/JSBundleTimingObserver.h +26 -0
- package/ios/JSBundleTimingObserver.m +140 -0
- package/ios/ReactHostPreloading.swift +7 -3
- package/ios/ReactNativeBrownfield.swift +82 -14
- package/ios/ReactNativeBrownfield.xcodeproj/project.pbxproj +16 -0
- package/ios/Vanilla/ReactNativeHostRuntime.swift +1 -1
- package/ios/Views/ReactNativeView.swift +21 -3
- package/ios/Views/ReactNativeViewController.swift +44 -42
- package/lib/commonjs/expo-config-plugin/android/withAndroidModuleFiles.js +1 -1
- package/lib/commonjs/expo-config-plugin/android/withAndroidModuleFiles.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/podfileHelpers.js +1 -1
- package/lib/commonjs/expo-config-plugin/ios/podfileHelpers.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withBrownfieldIos.js +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withBrownfieldIos.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withIosFrameworkFiles.js +1 -1
- package/lib/commonjs/expo-config-plugin/ios/withIosFrameworkFiles.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js +2 -2
- package/lib/commonjs/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
- package/lib/commonjs/expo-config-plugin/template/android/ReactNativeHostManager.vanilla.kt +35 -0
- package/lib/commonjs/expo-config-plugin/template/android/consumer-rules.pro +7 -0
- package/lib/commonjs/expo-config-plugin/template/ios/FrameworkInterface.vanilla.swift +12 -0
- package/lib/commonjs/expo-config-plugin/template/ios/PodfileTargetBlock.vanilla.rb +5 -0
- package/lib/commonjs/index.js +1 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/scaffold/index.js +2 -0
- package/lib/commonjs/scaffold/index.js.map +1 -0
- package/lib/commonjs/scaffold/projectFiles.js +2 -0
- package/lib/commonjs/scaffold/projectFiles.js.map +1 -0
- package/lib/commonjs/scaffold/xcode.d.js +2 -0
- package/lib/commonjs/scaffold/xcode.d.js.map +1 -0
- package/lib/module/expo-config-plugin/android/withAndroidModuleFiles.js +1 -1
- package/lib/module/expo-config-plugin/android/withAndroidModuleFiles.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/podfileHelpers.js +1 -1
- package/lib/module/expo-config-plugin/ios/podfileHelpers.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/withBrownfieldIos.js +1 -1
- package/lib/module/expo-config-plugin/ios/withBrownfieldIos.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/withIosFrameworkFiles.js +1 -1
- package/lib/module/expo-config-plugin/ios/withIosFrameworkFiles.js.map +1 -1
- package/lib/module/expo-config-plugin/ios/xcodeHelpers.js +2 -2
- package/lib/module/expo-config-plugin/ios/xcodeHelpers.js.map +1 -1
- package/lib/module/expo-config-plugin/template/android/ReactNativeHostManager.vanilla.kt +35 -0
- package/lib/module/expo-config-plugin/template/android/consumer-rules.pro +7 -0
- package/lib/module/expo-config-plugin/template/ios/FrameworkInterface.vanilla.swift +12 -0
- package/lib/module/expo-config-plugin/template/ios/PodfileTargetBlock.vanilla.rb +5 -0
- package/lib/module/index.js +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/module/scaffold/index.js +2 -0
- package/lib/module/scaffold/index.js.map +1 -0
- package/lib/module/scaffold/projectFiles.js +2 -0
- package/lib/module/scaffold/projectFiles.js.map +1 -0
- package/lib/module/scaffold/xcode.d.js +2 -0
- package/lib/module/scaffold/xcode.d.js.map +1 -0
- package/lib/typescript/commonjs/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts +10 -2
- package/lib/typescript/commonjs/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/podfileHelpers.d.ts +4 -2
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/podfileHelpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/withBrownfieldIos.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts +11 -2
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts +6 -2
- package/lib/typescript/commonjs/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/index.d.ts +7 -0
- package/lib/typescript/commonjs/src/index.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/scaffold/index.d.ts +23 -0
- package/lib/typescript/commonjs/src/scaffold/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/src/scaffold/projectFiles.d.ts +63 -0
- package/lib/typescript/commonjs/src/scaffold/projectFiles.d.ts.map +1 -0
- package/lib/typescript/module/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts +10 -2
- package/lib/typescript/module/src/expo-config-plugin/android/withAndroidModuleFiles.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/podfileHelpers.d.ts +4 -2
- package/lib/typescript/module/src/expo-config-plugin/ios/podfileHelpers.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/withBrownfieldIos.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts +11 -2
- package/lib/typescript/module/src/expo-config-plugin/ios/withIosFrameworkFiles.d.ts.map +1 -1
- package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts +6 -2
- package/lib/typescript/module/src/expo-config-plugin/ios/xcodeHelpers.d.ts.map +1 -1
- package/lib/typescript/module/src/index.d.ts +7 -0
- package/lib/typescript/module/src/index.d.ts.map +1 -1
- package/lib/typescript/module/src/scaffold/index.d.ts +23 -0
- package/lib/typescript/module/src/scaffold/index.d.ts.map +1 -0
- package/lib/typescript/module/src/scaffold/projectFiles.d.ts +63 -0
- package/lib/typescript/module/src/scaffold/projectFiles.d.ts.map +1 -0
- package/package.json +16 -2
- package/src/expo-config-plugin/android/withAndroidModuleFiles.ts +33 -5
- package/src/expo-config-plugin/ios/podfileHelpers.ts +24 -6
- package/src/expo-config-plugin/ios/withBrownfieldIos.ts +5 -2
- package/src/expo-config-plugin/ios/withIosFrameworkFiles.ts +21 -6
- package/src/expo-config-plugin/ios/xcodeHelpers.ts +77 -5
- package/src/expo-config-plugin/template/android/ReactNativeHostManager.vanilla.kt +35 -0
- package/src/expo-config-plugin/template/android/consumer-rules.pro +7 -0
- package/src/expo-config-plugin/template/ios/FrameworkInterface.vanilla.swift +12 -0
- package/src/expo-config-plugin/template/ios/PodfileTargetBlock.vanilla.rb +5 -0
- package/src/index.ts +20 -1
- package/src/scaffold/index.ts +395 -0
- package/src/scaffold/projectFiles.ts +243 -0
- package/src/scaffold/xcode.d.ts +4 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @callstack/react-native-brownfield
|
|
2
2
|
|
|
3
|
+
## 6.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- [#494](https://github.com/callstack/react-native-brownfield/pull/494) [`d28810c`](https://github.com/callstack/react-native-brownfield/commit/d28810caa32f727d56915d8e8d9276a82560b231) Thanks [@hurali97](https://github.com/hurali97)! - Add Android startup, TTID/TTFD, and JS/UI responsiveness metrics. Android's
|
|
8
|
+
`OnJSBundleLoaded` callback now receives `JSBundleTimings` instead of a Boolean.
|
|
9
|
+
|
|
10
|
+
### Patch Changes
|
|
11
|
+
|
|
12
|
+
- Updated dependencies []:
|
|
13
|
+
- @callstack/brownfield-cli@6.0.0
|
|
14
|
+
|
|
15
|
+
## 5.3.0
|
|
16
|
+
|
|
17
|
+
### Minor Changes
|
|
18
|
+
|
|
19
|
+
- [#480](https://github.com/callstack/react-native-brownfield/pull/480) [`f17f556`](https://github.com/callstack/react-native-brownfield/commit/f17f55616615a2bcb82d90f358efd838f56f5e67) Thanks [@gedu](https://github.com/gedu)! - add @callstack/create-react-native-brownfield: scaffold Brownfield packaging targets in an existing React Native CLI project (`npm create @callstack/react-native-brownfield`), plus the `@callstack/react-native-brownfield/scaffold` subpath export it uses
|
|
20
|
+
|
|
21
|
+
### Patch Changes
|
|
22
|
+
|
|
23
|
+
- Updated dependencies []:
|
|
24
|
+
- @callstack/brownfield-cli@5.3.0
|
|
25
|
+
|
|
3
26
|
## 5.2.0
|
|
4
27
|
|
|
5
28
|
### Minor Changes
|
package/android/build.gradle
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
-keep class com.callstack.reactnativebrownfield.ReactNativeBrownfield { *; }
|
|
2
2
|
-keep class com.callstack.reactnativebrownfield.ReactNativeBrownfield$Companion { *; }
|
|
3
3
|
-keep interface com.callstack.reactnativebrownfield.OnJSBundleLoaded { *; }
|
|
4
|
+
-keep interface com.callstack.reactnativebrownfield.OnDisplayMetrics { *; }
|
|
5
|
+
-keep class com.callstack.reactnativebrownfield.JSBundleTimings { *; }
|
|
6
|
+
-keep class com.callstack.reactnativebrownfield.BrownfieldDisplayMetrics { *; }
|
|
7
|
+
-keep class com.callstack.reactnativebrownfield.BrownfieldDisplayInterval { *; }
|
|
8
|
+
-keep class com.callstack.reactnativebrownfield.BrownfieldThreadMetrics { *; }
|
|
9
|
+
-keep class com.callstack.reactnativebrownfield.BrownfieldPerformanceModule { *; }
|
|
4
10
|
-keep interface com.callstack.reactnativebrownfield.OnMessageListener { *; }
|
|
5
11
|
-keep class com.callstack.reactnativebrownfield.ReactNativeFragment { *; }
|
|
6
12
|
-keep class com.callstack.reactnativebrownfield.ReactNativeFragment$Companion { *; }
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
package com.callstack.reactnativebrownfield
|
|
2
|
+
|
|
3
|
+
import android.os.Handler
|
|
4
|
+
import android.os.Looper
|
|
5
|
+
import android.os.SystemClock
|
|
6
|
+
import android.view.Choreographer
|
|
7
|
+
import android.view.View
|
|
8
|
+
import com.facebook.react.ReactHost
|
|
9
|
+
import com.facebook.react.ReactInstanceEventListener
|
|
10
|
+
import com.facebook.react.bridge.ReactContext
|
|
11
|
+
import com.facebook.react.bridge.ReactMarker
|
|
12
|
+
import com.facebook.react.bridge.ReactMarkerConstants
|
|
13
|
+
import java.util.Collections
|
|
14
|
+
import java.util.UUID
|
|
15
|
+
|
|
16
|
+
class JSBundleTimings @JvmOverloads constructor(
|
|
17
|
+
val jsBundleLoadTime: Double?,
|
|
18
|
+
val jsBundleEvaluationTime: Double?,
|
|
19
|
+
timeline: List<Map<String, Any>> = emptyList()
|
|
20
|
+
) {
|
|
21
|
+
val timeline: List<Map<String, Any>> = Collections.unmodifiableList(
|
|
22
|
+
timeline.map { Collections.unmodifiableMap(HashMap(it)) }
|
|
23
|
+
)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
fun interface OnDisplayMetrics {
|
|
27
|
+
fun onMetrics(metrics: BrownfieldDisplayMetrics)
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
data class BrownfieldThreadMetrics(
|
|
31
|
+
val fps: Double?,
|
|
32
|
+
val busyRatio: Double?,
|
|
33
|
+
val sampledMs: Double,
|
|
34
|
+
val frameCount: Int
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
data class BrownfieldDisplayInterval(
|
|
38
|
+
val duration: Double,
|
|
39
|
+
val jsThread: BrownfieldThreadMetrics,
|
|
40
|
+
val uiThread: BrownfieldThreadMetrics
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
data class BrownfieldDisplayMetrics(
|
|
44
|
+
val presentationID: String,
|
|
45
|
+
val moduleName: String,
|
|
46
|
+
val initialDisplay: BrownfieldDisplayInterval?,
|
|
47
|
+
val fullDisplay: BrownfieldDisplayInterval?,
|
|
48
|
+
val isFinal: Boolean,
|
|
49
|
+
val isCancelled: Boolean = false
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
internal class BrownfieldFrameSamples {
|
|
53
|
+
private var startNanos: Long? = null
|
|
54
|
+
private var lastNanos = 0L
|
|
55
|
+
private var budgetNanos = 1_000_000_000L / 60
|
|
56
|
+
private var frames = 0
|
|
57
|
+
private var busyNanos = 0L
|
|
58
|
+
|
|
59
|
+
@Synchronized
|
|
60
|
+
fun begin(nowNanos: Long) {
|
|
61
|
+
if (startNanos == null) {
|
|
62
|
+
startNanos = nowNanos
|
|
63
|
+
lastNanos = nowNanos
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@Synchronized
|
|
68
|
+
fun record(nowNanos: Long, frameIntervalNanos: Long) {
|
|
69
|
+
if (startNanos == null) begin(nowNanos)
|
|
70
|
+
budgetNanos = frameIntervalNanos.coerceAtLeast(1_000_000_000L / 240)
|
|
71
|
+
busyNanos += (nowNanos - lastNanos - budgetNanos).coerceAtLeast(0)
|
|
72
|
+
frames += 1
|
|
73
|
+
lastNanos = nowNanos
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
@Synchronized
|
|
77
|
+
fun snapshot(nowNanos: Long): BrownfieldThreadMetrics {
|
|
78
|
+
val start = startNanos
|
|
79
|
+
if (start == null || nowNanos <= start) {
|
|
80
|
+
return BrownfieldThreadMetrics(null, null, 0.0, 0)
|
|
81
|
+
}
|
|
82
|
+
val elapsed = nowNanos - start
|
|
83
|
+
val unfinishedStall = (nowNanos - lastNanos - budgetNanos).coerceAtLeast(0)
|
|
84
|
+
return BrownfieldThreadMetrics(
|
|
85
|
+
fps = frames * 1_000_000_000.0 / elapsed,
|
|
86
|
+
busyRatio = ((busyNanos + unfinishedStall).toDouble() / elapsed).coerceAtMost(1.0),
|
|
87
|
+
sampledMs = elapsed / 1_000_000.0,
|
|
88
|
+
frameCount = frames
|
|
89
|
+
)
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
internal class BrownfieldFrameSampler {
|
|
94
|
+
private val samples = BrownfieldFrameSamples()
|
|
95
|
+
@Volatile private var stopped = false
|
|
96
|
+
private var choreographer: Choreographer? = null
|
|
97
|
+
|
|
98
|
+
private val callback = object : Choreographer.FrameCallback {
|
|
99
|
+
override fun doFrame(frameTimeNanos: Long) {
|
|
100
|
+
if (stopped) return
|
|
101
|
+
val now = SystemClock.elapsedRealtimeNanos()
|
|
102
|
+
val interval = ReactNativeBrownfield.displayFrameIntervalNanos()
|
|
103
|
+
samples.record(now, interval)
|
|
104
|
+
choreographer?.postFrameCallback(this)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fun startOnCurrentLooper() {
|
|
109
|
+
if (stopped || choreographer != null || Looper.myLooper() == null) return
|
|
110
|
+
samples.begin(SystemClock.elapsedRealtimeNanos())
|
|
111
|
+
choreographer = Choreographer.getInstance().also { it.postFrameCallback(callback) }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fun snapshot(nowNanos: Long) = samples.snapshot(nowNanos)
|
|
115
|
+
|
|
116
|
+
fun stop() {
|
|
117
|
+
stopped = true
|
|
118
|
+
choreographer?.removeFrameCallback(callback)
|
|
119
|
+
choreographer = null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
internal object BrownfieldPresentationRegistry {
|
|
124
|
+
private val sessions = mutableMapOf<String, BrownfieldDisplaySession>()
|
|
125
|
+
|
|
126
|
+
@Synchronized fun add(session: BrownfieldDisplaySession) { sessions[session.id] = session }
|
|
127
|
+
@Synchronized fun remove(id: String) { sessions.remove(id) }
|
|
128
|
+
@Synchronized fun mark(id: String) { sessions[id]?.markFullyDisplayed() }
|
|
129
|
+
@Synchronized fun cancelAll() { sessions.values.toList().forEach { it.cancel() }; sessions.clear() }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
internal class BrownfieldDisplaySession(
|
|
133
|
+
val moduleName: String,
|
|
134
|
+
private val waitForFullDisplay: Boolean,
|
|
135
|
+
collectThreadMetrics: Boolean,
|
|
136
|
+
callback: OnDisplayMetrics?
|
|
137
|
+
) {
|
|
138
|
+
val id: String = UUID.randomUUID().toString()
|
|
139
|
+
private val main = Handler(Looper.getMainLooper())
|
|
140
|
+
private val startNanos = SystemClock.elapsedRealtimeNanos()
|
|
141
|
+
private var completion = callback
|
|
142
|
+
private val ui = if (collectThreadMetrics && callback != null) BrownfieldFrameSampler() else null
|
|
143
|
+
private val js = if (collectThreadMetrics && callback != null) BrownfieldFrameSampler() else null
|
|
144
|
+
private var initial: BrownfieldDisplayInterval? = null
|
|
145
|
+
private var marked = false
|
|
146
|
+
private var attached = false
|
|
147
|
+
private var resumed = true
|
|
148
|
+
@Volatile private var finished = callback == null
|
|
149
|
+
private var boundRoot: View? = null
|
|
150
|
+
private var attachmentListener: View.OnAttachStateChangeListener? = null
|
|
151
|
+
private var contextListener: ReactInstanceEventListener? = null
|
|
152
|
+
|
|
153
|
+
init {
|
|
154
|
+
if (!finished) {
|
|
155
|
+
BrownfieldPresentationRegistry.add(this)
|
|
156
|
+
main.post { if (!finished) ui?.startOnCurrentLooper() }
|
|
157
|
+
scheduleJS()
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
private fun scheduleJS() {
|
|
162
|
+
if (js == null || finished) return
|
|
163
|
+
val context = ReactNativeBrownfield.currentReactContext()
|
|
164
|
+
if (context != null) {
|
|
165
|
+
context.runOnJSQueueThread { if (!finished) js.startOnCurrentLooper() }
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
val host = ReactNativeBrownfield.shared.reactHost
|
|
169
|
+
val listener = object : ReactInstanceEventListener {
|
|
170
|
+
override fun onReactContextInitialized(context: ReactContext) {
|
|
171
|
+
host.removeReactInstanceEventListener(this)
|
|
172
|
+
contextListener = null
|
|
173
|
+
context.runOnJSQueueThread { if (!finished) js.startOnCurrentLooper() }
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
contextListener = listener
|
|
177
|
+
host.addReactInstanceEventListener(listener)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
fun bind(root: View, requiresResume: Boolean) {
|
|
181
|
+
resumed = !requiresResume
|
|
182
|
+
val listener = object : View.OnAttachStateChangeListener {
|
|
183
|
+
override fun onViewAttachedToWindow(view: View) {
|
|
184
|
+
attached = true
|
|
185
|
+
main.post { maybeAppear(view) }
|
|
186
|
+
}
|
|
187
|
+
override fun onViewDetachedFromWindow(view: View) {
|
|
188
|
+
if (attached) cancel()
|
|
189
|
+
view.removeOnAttachStateChangeListener(this)
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
boundRoot = root
|
|
193
|
+
attachmentListener = listener
|
|
194
|
+
root.addOnAttachStateChangeListener(listener)
|
|
195
|
+
if (root.isAttachedToWindow) {
|
|
196
|
+
attached = true
|
|
197
|
+
main.post { maybeAppear(root) }
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fun setResumed(value: Boolean) {
|
|
202
|
+
resumed = value
|
|
203
|
+
if (!value && attached && initial == null) cancel() else if (value) main.post { maybeAppear(null) }
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
private fun maybeAppear(root: View?) {
|
|
207
|
+
if (finished || !attached || !resumed || initial != null || (root != null && !root.isAttachedToWindow)) return
|
|
208
|
+
initial = interval()
|
|
209
|
+
if (!waitForFullDisplay || marked) complete()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
fun markFullyDisplayed() {
|
|
213
|
+
main.post {
|
|
214
|
+
if (finished || marked) return@post
|
|
215
|
+
marked = true
|
|
216
|
+
if (initial != null) complete()
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
private fun interval(): BrownfieldDisplayInterval {
|
|
221
|
+
val now = SystemClock.elapsedRealtimeNanos()
|
|
222
|
+
val unavailable = BrownfieldThreadMetrics(null, null, 0.0, 0)
|
|
223
|
+
return BrownfieldDisplayInterval(
|
|
224
|
+
duration = (now - startNanos) / 1_000_000.0,
|
|
225
|
+
jsThread = js?.snapshot(now) ?: unavailable,
|
|
226
|
+
uiThread = ui?.snapshot(now) ?: unavailable
|
|
227
|
+
)
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
private fun complete() {
|
|
231
|
+
val first = initial ?: return
|
|
232
|
+
val full = if (waitForFullDisplay) interval() else first
|
|
233
|
+
finished = true
|
|
234
|
+
val callback = completion
|
|
235
|
+
completion = null
|
|
236
|
+
stop()
|
|
237
|
+
main.post { callback?.onMetrics(BrownfieldDisplayMetrics(id, moduleName, first, full, true)) }
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
fun cancel() {
|
|
241
|
+
main.post {
|
|
242
|
+
if (finished) return@post
|
|
243
|
+
finished = true
|
|
244
|
+
completion = null
|
|
245
|
+
stop()
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
private fun stop() {
|
|
250
|
+
BrownfieldPresentationRegistry.remove(id)
|
|
251
|
+
attachmentListener?.let { boundRoot?.removeOnAttachStateChangeListener(it) }
|
|
252
|
+
attachmentListener = null
|
|
253
|
+
boundRoot = null
|
|
254
|
+
contextListener?.let {
|
|
255
|
+
ReactNativeBrownfield.shared.reactHost.removeReactInstanceEventListener(it)
|
|
256
|
+
}
|
|
257
|
+
contextListener = null
|
|
258
|
+
ui?.stop()
|
|
259
|
+
js?.stop()
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
internal object AndroidJSBundleTimingObserver {
|
|
264
|
+
private val main = Handler(Looper.getMainLooper())
|
|
265
|
+
private var generation = 0
|
|
266
|
+
private var accumulator = JSBundleTimingAccumulator()
|
|
267
|
+
private var result: JSBundleTimings? = null
|
|
268
|
+
private val subscribers = mutableListOf<OnJSBundleLoaded>()
|
|
269
|
+
private var installed = false
|
|
270
|
+
|
|
271
|
+
private val markerListener = ReactMarker.MarkerListener { name, _, instanceKey ->
|
|
272
|
+
val now = SystemClock.elapsedRealtime()
|
|
273
|
+
synchronized(this) {
|
|
274
|
+
when (name) {
|
|
275
|
+
ReactMarkerConstants.DOWNLOAD_START -> accumulator.record(BundleMarker.DOWNLOAD_START, instanceKey, now)
|
|
276
|
+
ReactMarkerConstants.DOWNLOAD_END -> accumulator.record(BundleMarker.DOWNLOAD_END, instanceKey, now)
|
|
277
|
+
ReactMarkerConstants.RUN_JS_BUNDLE_START -> accumulator.record(BundleMarker.EVALUATION_START, instanceKey, now)
|
|
278
|
+
ReactMarkerConstants.RUN_JS_BUNDLE_END -> accumulator.record(BundleMarker.EVALUATION_END, instanceKey, now)
|
|
279
|
+
else -> Unit
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@Synchronized fun install() {
|
|
285
|
+
if (!installed) {
|
|
286
|
+
installed = true
|
|
287
|
+
ReactMarker.addListener(markerListener)
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
@Synchronized fun beginGeneration(): Int {
|
|
292
|
+
generation += 1
|
|
293
|
+
accumulator = JSBundleTimingAccumulator()
|
|
294
|
+
result = null
|
|
295
|
+
subscribers.clear()
|
|
296
|
+
return generation
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
@Synchronized fun subscribe(callback: OnJSBundleLoaded?) {
|
|
300
|
+
if (callback == null) return
|
|
301
|
+
val cached = result
|
|
302
|
+
if (cached != null) main.post { callback(cached) } else subscribers.add(callback)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
fun contextInitialized(context: ReactContext, expectedGeneration: Int) {
|
|
306
|
+
context.runOnJSQueueThread { main.post { complete(expectedGeneration) } }
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
@Synchronized private fun complete(expectedGeneration: Int) {
|
|
310
|
+
if (expectedGeneration != generation || result != null) return
|
|
311
|
+
val timings = accumulator.snapshot()
|
|
312
|
+
result = timings
|
|
313
|
+
val callbacks = subscribers.toList()
|
|
314
|
+
subscribers.clear()
|
|
315
|
+
callbacks.forEach { it(timings) }
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
@Synchronized fun reset() {
|
|
319
|
+
generation += 1
|
|
320
|
+
accumulator = JSBundleTimingAccumulator()
|
|
321
|
+
result = null
|
|
322
|
+
subscribers.clear()
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
internal enum class BundleMarker { DOWNLOAD_START, DOWNLOAD_END, EVALUATION_START, EVALUATION_END }
|
|
327
|
+
|
|
328
|
+
internal class JSBundleTimingAccumulator {
|
|
329
|
+
private data class Endpoint(
|
|
330
|
+
var start: Long? = null,
|
|
331
|
+
var stop: Long? = null,
|
|
332
|
+
var ambiguous: Boolean = false
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
private val download = Endpoint()
|
|
336
|
+
private val evaluation = Endpoint()
|
|
337
|
+
|
|
338
|
+
fun record(marker: BundleMarker, instanceKey: Int, timestampMs: Long) {
|
|
339
|
+
val endpoint = when (marker) {
|
|
340
|
+
BundleMarker.DOWNLOAD_START, BundleMarker.DOWNLOAD_END -> download
|
|
341
|
+
BundleMarker.EVALUATION_START, BundleMarker.EVALUATION_END -> {
|
|
342
|
+
if (instanceKey != 1) return
|
|
343
|
+
evaluation
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
when (marker) {
|
|
347
|
+
BundleMarker.DOWNLOAD_START, BundleMarker.EVALUATION_START -> {
|
|
348
|
+
if (endpoint.start != null || endpoint.stop != null) endpoint.ambiguous = true
|
|
349
|
+
else endpoint.start = timestampMs
|
|
350
|
+
}
|
|
351
|
+
BundleMarker.DOWNLOAD_END, BundleMarker.EVALUATION_END -> {
|
|
352
|
+
if (endpoint.start == null || endpoint.stop != null) endpoint.ambiguous = true
|
|
353
|
+
else endpoint.stop = timestampMs
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
fun snapshot(): JSBundleTimings {
|
|
359
|
+
fun valid(endpoint: Endpoint): Pair<Long, Long>? {
|
|
360
|
+
val start = endpoint.start ?: return null
|
|
361
|
+
val stop = endpoint.stop ?: return null
|
|
362
|
+
return if (!endpoint.ambiguous && stop >= start) start to stop else null
|
|
363
|
+
}
|
|
364
|
+
val downloadPair = valid(download)
|
|
365
|
+
val evaluationPair = valid(evaluation)
|
|
366
|
+
val timeline = listOfNotNull(
|
|
367
|
+
downloadPair?.let { (start, stop) -> mapOf("tag" to "DOWNLOAD", "startMs" to start, "stopMs" to stop) },
|
|
368
|
+
evaluationPair?.let { (start, stop) -> mapOf("tag" to "RUN_JS_BUNDLE", "startMs" to start, "stopMs" to stop) }
|
|
369
|
+
).sortedBy { it["startMs"] as Long }
|
|
370
|
+
return JSBundleTimings(
|
|
371
|
+
downloadPair?.let { (it.second - it.first).toDouble() },
|
|
372
|
+
evaluationPair?.let { (it.second - it.first).toDouble() },
|
|
373
|
+
timeline
|
|
374
|
+
)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
internal fun ReactHost.installBrownfieldPerformanceStop() {
|
|
379
|
+
val stop = {
|
|
380
|
+
AndroidJSBundleTimingObserver.reset()
|
|
381
|
+
BrownfieldPresentationRegistry.cancelAll()
|
|
382
|
+
}
|
|
383
|
+
addBeforeDestroyListener(stop)
|
|
384
|
+
}
|
package/android/src/main/java/com/callstack/reactnativebrownfield/BrownfieldPerformanceModule.kt
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
package com.callstack.reactnativebrownfield
|
|
2
|
+
|
|
3
|
+
import com.facebook.react.bridge.ReactApplicationContext
|
|
4
|
+
import com.facebook.react.bridge.ReactContextBaseJavaModule
|
|
5
|
+
import com.facebook.react.bridge.ReactMethod
|
|
6
|
+
|
|
7
|
+
class BrownfieldPerformanceModule(reactContext: ReactApplicationContext) :
|
|
8
|
+
ReactContextBaseJavaModule(reactContext) {
|
|
9
|
+
override fun getName() = "BrownfieldPerformance"
|
|
10
|
+
|
|
11
|
+
@ReactMethod
|
|
12
|
+
fun markFullyDisplayed(presentationID: String) {
|
|
13
|
+
BrownfieldPresentationRegistry.mark(presentationID)
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -8,14 +8,14 @@ import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
|
|
|
8
8
|
|
|
9
9
|
class ReactDelegateWrapper(
|
|
10
10
|
private val activity: ComponentActivity?,
|
|
11
|
-
resolvedReactHost: ReactHost?,
|
|
12
|
-
moduleName: String,
|
|
13
|
-
|
|
11
|
+
private val resolvedReactHost: ReactHost?,
|
|
12
|
+
private val moduleName: String,
|
|
13
|
+
internal val brownfieldLaunchOptions: Bundle?
|
|
14
14
|
) : ReactDelegate(
|
|
15
15
|
activity = activity!!,
|
|
16
16
|
resolvedReactHost,
|
|
17
17
|
appKey = moduleName,
|
|
18
|
-
launchOptions =
|
|
18
|
+
launchOptions = brownfieldLaunchOptions,
|
|
19
19
|
) {
|
|
20
20
|
private lateinit var hardwareBackHandler: () -> Unit
|
|
21
21
|
private val backBtnHandler = DefaultHardwareBackBtnHandler {
|
|
@@ -33,4 +33,7 @@ class ReactDelegateWrapper(
|
|
|
33
33
|
fun onReactHostResume() {
|
|
34
34
|
super.reactHost?.onHostResume(activity, backBtnHandler)
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
|
|
37
|
+
internal fun withLaunchOptions(launchOptions: Bundle?): ReactDelegateWrapper =
|
|
38
|
+
ReactDelegateWrapper(activity, resolvedReactHost, moduleName, launchOptions)
|
|
39
|
+
}
|
|
@@ -2,6 +2,7 @@ package com.callstack.reactnativebrownfield
|
|
|
2
2
|
|
|
3
3
|
import android.app.Application
|
|
4
4
|
import android.os.Bundle
|
|
5
|
+
import android.view.WindowManager
|
|
5
6
|
import android.widget.FrameLayout
|
|
6
7
|
import androidx.activity.OnBackPressedCallback
|
|
7
8
|
import androidx.fragment.app.FragmentActivity
|
|
@@ -20,7 +21,7 @@ import java.util.concurrent.CopyOnWriteArrayList
|
|
|
20
21
|
import java.util.concurrent.atomic.AtomicBoolean
|
|
21
22
|
|
|
22
23
|
fun interface OnJSBundleLoaded {
|
|
23
|
-
operator fun invoke(
|
|
24
|
+
operator fun invoke(timings: JSBundleTimings)
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
fun interface OnMessageListener {
|
|
@@ -34,6 +35,7 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
34
35
|
private lateinit var instance: ReactNativeBrownfield
|
|
35
36
|
private val initialized = AtomicBoolean()
|
|
36
37
|
private val nativeLibsLoaded = AtomicBoolean()
|
|
38
|
+
private lateinit var applicationContext: Application
|
|
37
39
|
|
|
38
40
|
@JvmStatic
|
|
39
41
|
val shared: ReactNativeBrownfield get() = instance
|
|
@@ -62,7 +64,9 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
62
64
|
reactHost: ReactHost,
|
|
63
65
|
onJSBundleLoaded: OnJSBundleLoaded? = null
|
|
64
66
|
) {
|
|
67
|
+
applicationContext = application
|
|
65
68
|
if (!initialized.getAndSet(true)) {
|
|
69
|
+
AndroidJSBundleTimingObserver.install()
|
|
66
70
|
loadNativeLibs(application)
|
|
67
71
|
installAndPreload(reactHost, onJSBundleLoaded)
|
|
68
72
|
} else {
|
|
@@ -76,14 +80,16 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
76
80
|
onJSBundleLoaded: OnJSBundleLoaded? = null,
|
|
77
81
|
reactHostFactory: () -> ReactHost
|
|
78
82
|
) {
|
|
83
|
+
applicationContext = application
|
|
79
84
|
if (!initialized.getAndSet(true)) {
|
|
85
|
+
AndroidJSBundleTimingObserver.install()
|
|
80
86
|
loadNativeLibs(application)
|
|
81
87
|
installAndPreload(reactHostFactory(), onJSBundleLoaded)
|
|
82
88
|
} else {
|
|
83
89
|
invokeWhenJSBundleLoaded(onJSBundleLoaded)
|
|
84
90
|
}
|
|
85
91
|
}
|
|
86
|
-
|
|
92
|
+
|
|
87
93
|
@JvmStatic
|
|
88
94
|
@JvmOverloads
|
|
89
95
|
fun initialize(
|
|
@@ -91,7 +97,9 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
91
97
|
options: HashMap<String, Any>,
|
|
92
98
|
onJSBundleLoaded: OnJSBundleLoaded? = null
|
|
93
99
|
) {
|
|
100
|
+
applicationContext = application
|
|
94
101
|
if (!initialized.getAndSet(true)) {
|
|
102
|
+
AndroidJSBundleTimingObserver.install()
|
|
95
103
|
loadNativeLibs(application)
|
|
96
104
|
val reactHost = getDefaultReactHost(
|
|
97
105
|
context = application,
|
|
@@ -123,11 +131,11 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
123
131
|
initialize(application, options, onJSBundleLoaded)
|
|
124
132
|
}
|
|
125
133
|
|
|
126
|
-
private fun preloadReactNative(
|
|
134
|
+
private fun preloadReactNative(generation: Int) {
|
|
127
135
|
shared.reactHost.addReactInstanceEventListener(object :
|
|
128
136
|
ReactInstanceEventListener {
|
|
129
137
|
override fun onReactContextInitialized(context: ReactContext) {
|
|
130
|
-
|
|
138
|
+
AndroidJSBundleTimingObserver.contextInitialized(context, generation)
|
|
131
139
|
shared.reactHost.removeReactInstanceEventListener(this)
|
|
132
140
|
}
|
|
133
141
|
})
|
|
@@ -135,10 +143,12 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
135
143
|
}
|
|
136
144
|
|
|
137
145
|
private fun installAndPreload(reactHost: ReactHost, onJSBundleLoaded: OnJSBundleLoaded?) {
|
|
146
|
+
AndroidJSBundleTimingObserver.install()
|
|
147
|
+
val generation = AndroidJSBundleTimingObserver.beginGeneration()
|
|
138
148
|
instance = ReactNativeBrownfield(reactHost)
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
149
|
+
reactHost.installBrownfieldPerformanceStop()
|
|
150
|
+
AndroidJSBundleTimingObserver.subscribe(onJSBundleLoaded)
|
|
151
|
+
preloadReactNative(generation)
|
|
142
152
|
}
|
|
143
153
|
|
|
144
154
|
private fun invokeWhenJSBundleLoaded(onJSBundleLoaded: OnJSBundleLoaded?) {
|
|
@@ -146,18 +156,21 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
146
156
|
return
|
|
147
157
|
}
|
|
148
158
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
onJSBundleLoaded.invoke(true)
|
|
152
|
-
return
|
|
153
|
-
}
|
|
159
|
+
AndroidJSBundleTimingObserver.subscribe(onJSBundleLoaded)
|
|
160
|
+
}
|
|
154
161
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
internal fun currentReactContext(): ReactContext? =
|
|
163
|
+
if (::instance.isInitialized) instance.reactHost.currentReactContext else null
|
|
164
|
+
|
|
165
|
+
internal fun displayFrameIntervalNanos(): Long {
|
|
166
|
+
if (!::applicationContext.isInitialized) return 1_000_000_000L / 60
|
|
167
|
+
@Suppress("DEPRECATION")
|
|
168
|
+
val refreshRate = applicationContext
|
|
169
|
+
.getSystemService(WindowManager::class.java)
|
|
170
|
+
?.defaultDisplay
|
|
171
|
+
?.refreshRate
|
|
172
|
+
?.takeIf { it > 0f } ?: 60f
|
|
173
|
+
return (1_000_000_000.0 / refreshRate).toLong()
|
|
161
174
|
}
|
|
162
175
|
}
|
|
163
176
|
|
|
@@ -215,10 +228,47 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
215
228
|
reactDelegate: ReactDelegateWrapper?,
|
|
216
229
|
launchOptions: Bundle?,
|
|
217
230
|
lifecycleOwner: LifecycleOwner?,
|
|
231
|
+
): FrameLayout {
|
|
232
|
+
return createViewInternal(
|
|
233
|
+
activity, moduleName, reactDelegate, launchOptions, lifecycleOwner, null, false
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@JvmOverloads
|
|
238
|
+
fun createView(
|
|
239
|
+
activity: FragmentActivity?,
|
|
240
|
+
moduleName: String,
|
|
241
|
+
reactDelegate: ReactDelegateWrapper? = null,
|
|
242
|
+
launchOptions: Bundle? = null,
|
|
243
|
+
waitForFullDisplay: Boolean,
|
|
244
|
+
collectThreadMetrics: Boolean = false,
|
|
245
|
+
onMetrics: OnDisplayMetrics? = null,
|
|
246
|
+
): FrameLayout {
|
|
247
|
+
val session = BrownfieldDisplaySession(
|
|
248
|
+
moduleName, waitForFullDisplay, collectThreadMetrics, onMetrics
|
|
249
|
+
)
|
|
250
|
+
val props = Bundle(launchOptions ?: Bundle()).apply {
|
|
251
|
+
putString("brownfieldPresentationID", session.id)
|
|
252
|
+
}
|
|
253
|
+
return createViewInternal(activity, moduleName, reactDelegate, props, activity, session, false)
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
internal fun createViewInternal(
|
|
257
|
+
activity: FragmentActivity?,
|
|
258
|
+
moduleName: String,
|
|
259
|
+
reactDelegate: ReactDelegateWrapper?,
|
|
260
|
+
launchOptions: Bundle?,
|
|
261
|
+
lifecycleOwner: LifecycleOwner?,
|
|
262
|
+
session: BrownfieldDisplaySession?,
|
|
263
|
+
requiresResume: Boolean,
|
|
218
264
|
): FrameLayout {
|
|
219
265
|
val reactHost = shared.reactHost
|
|
220
|
-
val resolvedDelegate =
|
|
221
|
-
reactDelegate
|
|
266
|
+
val resolvedDelegate = when {
|
|
267
|
+
reactDelegate == null -> ReactDelegateWrapper(activity, reactHost, moduleName, launchOptions)
|
|
268
|
+
session != null && reactDelegate.brownfieldLaunchOptions !== launchOptions ->
|
|
269
|
+
reactDelegate.withLaunchOptions(launchOptions)
|
|
270
|
+
else -> reactDelegate
|
|
271
|
+
}
|
|
222
272
|
|
|
223
273
|
val backPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
|
|
224
274
|
override fun handleOnBackPressed() {
|
|
@@ -248,24 +298,29 @@ class ReactNativeBrownfield private constructor(val reactHost: ReactHost) {
|
|
|
248
298
|
* In such a case, we set the lifeCycle observer.
|
|
249
299
|
*/
|
|
250
300
|
if (reactDelegate == null) {
|
|
251
|
-
lifecycleOwner?.lifecycle?.addObserver(getLifeCycleObserver(resolvedDelegate))
|
|
301
|
+
lifecycleOwner?.lifecycle?.addObserver(getLifeCycleObserver(resolvedDelegate, session))
|
|
252
302
|
}
|
|
253
303
|
|
|
254
304
|
resolvedDelegate.loadApp()
|
|
255
|
-
return resolvedDelegate.reactRootView
|
|
305
|
+
return resolvedDelegate.reactRootView!!.also { session?.bind(it, requiresResume) }
|
|
256
306
|
}
|
|
257
307
|
|
|
258
|
-
private fun getLifeCycleObserver(
|
|
308
|
+
private fun getLifeCycleObserver(
|
|
309
|
+
reactDelegate: ReactDelegateWrapper,
|
|
310
|
+
session: BrownfieldDisplaySession?
|
|
311
|
+
): DefaultLifecycleObserver {
|
|
259
312
|
return object : DefaultLifecycleObserver {
|
|
260
313
|
override fun onResume(owner: LifecycleOwner) {
|
|
261
314
|
reactDelegate.onReactHostResume()
|
|
262
315
|
}
|
|
263
316
|
|
|
264
317
|
override fun onPause(owner: LifecycleOwner) {
|
|
318
|
+
session?.cancel()
|
|
265
319
|
reactDelegate.onHostPause()
|
|
266
320
|
}
|
|
267
321
|
|
|
268
322
|
override fun onDestroy(owner: LifecycleOwner) {
|
|
323
|
+
session?.cancel()
|
|
269
324
|
reactDelegate.onHostDestroy()
|
|
270
325
|
owner.lifecycle.removeObserver(this) // Cleanup to avoid leaks
|
|
271
326
|
}
|