@onekeyfe/react-native-perf-stats 3.0.27 → 3.0.29
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/java/com/margelo/nitro/reactnativeperfstats/ReactNativePerfStats.kt +5 -1
- package/ios/ReactNativePerfStats.swift +65 -27
- package/lib/typescript/src/ReactNativePerfStats.nitro.d.ts +7 -3
- package/lib/typescript/src/ReactNativePerfStats.nitro.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ReactNativePerfStats.nitro.ts +7 -3
package/android/src/main/java/com/margelo/nitro/reactnativeperfstats/ReactNativePerfStats.kt
CHANGED
|
@@ -593,7 +593,11 @@ internal object Overlay : Application.ActivityLifecycleCallbacks {
|
|
|
593
593
|
val view = overlayView ?: return
|
|
594
594
|
if (attachedToWindowManager) {
|
|
595
595
|
try {
|
|
596
|
-
|
|
596
|
+
// Use the view's own context, not currentActivity. onActivityDestroyed
|
|
597
|
+
// posts detach() to the main handler and then clears currentActivity
|
|
598
|
+
// synchronously, so by the time this runs currentActivity may already
|
|
599
|
+
// be null and the overlay would otherwise leak.
|
|
600
|
+
val wm = view.context.getSystemService(Context.WINDOW_SERVICE)
|
|
597
601
|
as? WindowManager
|
|
598
602
|
wm?.removeView(view)
|
|
599
603
|
} catch (e: Exception) {
|
|
@@ -412,19 +412,43 @@ private final class JsFpsHolder {
|
|
|
412
412
|
|
|
413
413
|
// MARK: - Overlay
|
|
414
414
|
//
|
|
415
|
-
// Singleton
|
|
416
|
-
//
|
|
417
|
-
//
|
|
415
|
+
// Singleton overlay rendered on its own dedicated UIWindow at
|
|
416
|
+
// `.alert + 1` windowLevel. The dedicated window is required because
|
|
417
|
+
// React Native's <Modal> presents view controllers via UIKit's modal
|
|
418
|
+
// presentation, which renders above any subview added to the host
|
|
419
|
+
// app's main UIWindow — a UILabel attached to keyWindow ends up behind
|
|
420
|
+
// the modal regardless of view-hierarchy z-order. A separate UIWindow
|
|
421
|
+
// with a higher `windowLevel` sits above modal-presented view
|
|
422
|
+
// controllers and system alerts, so the overlay stays visible.
|
|
423
|
+
//
|
|
424
|
+
// `OverlayPassthroughWindow` overrides `hitTest` to return nil for
|
|
425
|
+
// touches outside the label, letting them fall through to the
|
|
426
|
+
// underlying app windows — otherwise the overlay window would swallow
|
|
427
|
+
// every tap on the screen.
|
|
418
428
|
//
|
|
419
429
|
// Inherits NSObject so UIPanGestureRecognizer's target/action selector
|
|
420
430
|
// dispatch resolves cleanly via Obj-C runtime.
|
|
421
431
|
|
|
432
|
+
private final class OverlayPassthroughWindow: UIWindow {
|
|
433
|
+
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
|
434
|
+
let hit = super.hitTest(point, with: event)
|
|
435
|
+
// Anything that bubbles up to the window itself or its empty
|
|
436
|
+
// root view means the touch missed our overlay subview — pass it
|
|
437
|
+
// through to whatever's below this window.
|
|
438
|
+
if hit === self || hit === self.rootViewController?.view {
|
|
439
|
+
return nil
|
|
440
|
+
}
|
|
441
|
+
return hit
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
422
445
|
private final class Overlay: NSObject {
|
|
423
446
|
static let shared = Overlay()
|
|
424
447
|
|
|
425
448
|
private override init() { super.init() }
|
|
426
449
|
|
|
427
450
|
private var label: UILabel?
|
|
451
|
+
private var overlayWindow: UIWindow?
|
|
428
452
|
private var visible = false
|
|
429
453
|
|
|
430
454
|
// `_lastSample` is written by the Sampler timer thread and read by the
|
|
@@ -484,11 +508,27 @@ private final class Overlay: NSObject {
|
|
|
484
508
|
|
|
485
509
|
private func attach() {
|
|
486
510
|
if label != nil { return }
|
|
487
|
-
guard let
|
|
488
|
-
OneKeyLog.warn(kTag, "No
|
|
511
|
+
guard let scene = currentForegroundScene() else {
|
|
512
|
+
OneKeyLog.warn(kTag, "No foreground UIWindowScene; overlay deferred")
|
|
489
513
|
return
|
|
490
514
|
}
|
|
491
515
|
|
|
516
|
+
// Dedicated host VC keeps the window's view hierarchy minimal —
|
|
517
|
+
// the empty UIView serves only as the parent for the label and
|
|
518
|
+
// as the hitTest sentinel checked by OverlayPassthroughWindow.
|
|
519
|
+
let host = UIViewController()
|
|
520
|
+
host.view.backgroundColor = .clear
|
|
521
|
+
|
|
522
|
+
let window = OverlayPassthroughWindow(windowScene: scene)
|
|
523
|
+
window.frame = scene.coordinateSpace.bounds
|
|
524
|
+
// .alert is 2000 on iOS; +1 puts the overlay above modal-presented
|
|
525
|
+
// controllers, system alerts and action sheets. Status bar is
|
|
526
|
+
// .statusBar (1000), which we stay below intentionally.
|
|
527
|
+
window.windowLevel = UIWindow.Level.alert + 1
|
|
528
|
+
window.backgroundColor = .clear
|
|
529
|
+
window.isHidden = false
|
|
530
|
+
window.rootViewController = host
|
|
531
|
+
|
|
492
532
|
let lbl = UILabel(frame: CGRect(x: 30, y: 100, width: 170, height: 92))
|
|
493
533
|
lbl.backgroundColor = UIColor.black.withAlphaComponent(0.7)
|
|
494
534
|
lbl.layer.cornerRadius = 8
|
|
@@ -503,8 +543,9 @@ private final class Overlay: NSObject {
|
|
|
503
543
|
let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
|
|
504
544
|
lbl.addGestureRecognizer(pan)
|
|
505
545
|
|
|
506
|
-
|
|
546
|
+
host.view.addSubview(lbl)
|
|
507
547
|
label = lbl
|
|
548
|
+
overlayWindow = window
|
|
508
549
|
|
|
509
550
|
if let s = lastSample {
|
|
510
551
|
lbl.text = renderText(s)
|
|
@@ -514,6 +555,13 @@ private final class Overlay: NSObject {
|
|
|
514
555
|
private func detach() {
|
|
515
556
|
label?.removeFromSuperview()
|
|
516
557
|
label = nil
|
|
558
|
+
// Hide before nilling so UIKit can release the window cleanly;
|
|
559
|
+
// assigning nil to rootViewController first avoids a fleeting
|
|
560
|
+
// warning when the window is torn down with a controller still
|
|
561
|
+
// attached.
|
|
562
|
+
overlayWindow?.isHidden = true
|
|
563
|
+
overlayWindow?.rootViewController = nil
|
|
564
|
+
overlayWindow = nil
|
|
517
565
|
}
|
|
518
566
|
|
|
519
567
|
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
|
|
@@ -541,29 +589,19 @@ private final class Overlay: NSObject {
|
|
|
541
589
|
return "CPU: \(cpuStr)\nRAM: \(memStr)\nUI: \(uiStr)\nJS: \(jsStr)"
|
|
542
590
|
}
|
|
543
591
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
windowScene.activationState == .foregroundActive
|
|
551
|
-
else { continue }
|
|
552
|
-
if let kw = windowScene.keyWindow {
|
|
553
|
-
return kw
|
|
554
|
-
}
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
// Fallback: scan all connected scenes
|
|
592
|
+
/// Locate an active foreground `UIWindowScene` to host the overlay
|
|
593
|
+
/// window. Prefers `.foregroundActive`; falls back to the first
|
|
594
|
+
/// connected `UIWindowScene` so we still attach during transient
|
|
595
|
+
/// states like cold launch when no scene is `.foregroundActive` yet.
|
|
596
|
+
private func currentForegroundScene() -> UIWindowScene? {
|
|
597
|
+
var fallback: UIWindowScene?
|
|
558
598
|
for scene in UIApplication.shared.connectedScenes {
|
|
559
|
-
guard let
|
|
560
|
-
if
|
|
561
|
-
return
|
|
562
|
-
}
|
|
563
|
-
if let any = windowScene.windows.first {
|
|
564
|
-
return any
|
|
599
|
+
guard let ws = scene as? UIWindowScene else { continue }
|
|
600
|
+
if ws.activationState == .foregroundActive {
|
|
601
|
+
return ws
|
|
565
602
|
}
|
|
603
|
+
if fallback == nil { fallback = ws }
|
|
566
604
|
}
|
|
567
|
-
return
|
|
605
|
+
return fallback
|
|
568
606
|
}
|
|
569
607
|
}
|
|
@@ -43,9 +43,13 @@ export interface ReactNativePerfStats extends HybridObject<{
|
|
|
43
43
|
/**
|
|
44
44
|
* Show the floating overlay (CPU + RAM) drawn natively.
|
|
45
45
|
*
|
|
46
|
-
* - Android: TextView attached
|
|
47
|
-
*
|
|
48
|
-
*
|
|
46
|
+
* - Android: TextView attached via `WindowManager.addView` using the
|
|
47
|
+
* current Activity's window token (window type
|
|
48
|
+
* `TYPE_APPLICATION_ABOVE_SUB_PANEL`) — no floating-window permission
|
|
49
|
+
* required, and the overlay tracks Activity lifecycle (re-attach on
|
|
50
|
+
* resume, detach on pause/destroy).
|
|
51
|
+
* - iOS: UILabel hosted on a dedicated passthrough UIWindow above the
|
|
52
|
+
* app's key window so it stays above modally-presented controllers.
|
|
49
53
|
* - Draggable on both platforms.
|
|
50
54
|
* - Idempotent. If the sampler is not running, the overlay shows "--"
|
|
51
55
|
* until `start` is called.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ReactNativePerfStats.nitro.d.ts","sourceRoot":"","sources":["../../../src/ReactNativePerfStats.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IACzD;;;;;;;;OAQG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC,+EAA+E;IAC/E,IAAI,IAAI,IAAI,CAAC;IAEb
|
|
1
|
+
{"version":3,"file":"ReactNativePerfStats.nitro.d.ts","sourceRoot":"","sources":["../../../src/ReactNativePerfStats.nitro.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,WAAW,UAAU;IACzB;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,uEAAuE;IACvE,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBACf,SAAQ,YAAY,CAAC;IAAE,GAAG,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAA;CAAE,CAAC;IACzD;;;;;;;;OAQG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAEhC,+EAA+E;IAC/E,IAAI,IAAI,IAAI,CAAC;IAEb;;;;;;;;;;;;;OAaG;IACH,WAAW,IAAI,IAAI,CAAC;IAEpB,gFAAgF;IAChF,WAAW,IAAI,IAAI,CAAC;IAEpB;;;;OAIG;IACH,MAAM,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC;IAE9B;;;;;;;;OAQG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACjC"}
|
package/package.json
CHANGED
|
@@ -45,9 +45,13 @@ export interface ReactNativePerfStats
|
|
|
45
45
|
/**
|
|
46
46
|
* Show the floating overlay (CPU + RAM) drawn natively.
|
|
47
47
|
*
|
|
48
|
-
* - Android: TextView attached
|
|
49
|
-
*
|
|
50
|
-
*
|
|
48
|
+
* - Android: TextView attached via `WindowManager.addView` using the
|
|
49
|
+
* current Activity's window token (window type
|
|
50
|
+
* `TYPE_APPLICATION_ABOVE_SUB_PANEL`) — no floating-window permission
|
|
51
|
+
* required, and the overlay tracks Activity lifecycle (re-attach on
|
|
52
|
+
* resume, detach on pause/destroy).
|
|
53
|
+
* - iOS: UILabel hosted on a dedicated passthrough UIWindow above the
|
|
54
|
+
* app's key window so it stays above modally-presented controllers.
|
|
51
55
|
* - Draggable on both platforms.
|
|
52
56
|
* - Idempotent. If the sampler is not running, the overlay shows "--"
|
|
53
57
|
* until `start` is called.
|