|
11 | 11 | import com.facebook.react.bridge.ReactMarker;
|
12 | 12 | import com.facebook.react.bridge.ReactMarkerConstants;
|
13 | 13 | import com.facebook.react.uimanager.events.RCTEventEmitter;
|
| 14 | +import android.os.Process; |
| 15 | +import android.os.SystemClock; |
| 16 | +import java.math.BigInteger; |
| 17 | +import android.util.Log; |
14 | 18 |
|
15 | 19 | @ReactModule(name = NativeInstrumentationModule.NAME)
|
16 | 20 | public class NativeInstrumentationModule extends ReactContextBaseJavaModule implements RCTEventEmitter {
|
17 |
| - public static final String NAME = "NativeInstrumentation"; |
18 |
| - private static Long startTime = null; |
19 |
| - |
20 |
| - private static Double cachedStartStartupTime = null; |
21 |
| - private static Double cachedEndStartupTime = null; |
22 |
| - private static Double cachedStartupDuration = null; |
| 21 | + private static boolean hasAppRestarted = false; |
| 22 | + private static int bundleLoadCounter = 0; |
23 | 23 |
|
24 | 24 | static {
|
25 | 25 | ReactMarker.addListener((name, tag, instanceKey) -> {
|
26 |
| - long currentTime = System.currentTimeMillis(); |
27 |
| - |
28 | 26 | if (name == ReactMarkerConstants.PRE_RUN_JS_BUNDLE_START) {
|
29 |
| - android.util.Log.d(NAME, String.format("JS bundle load started at: %d", currentTime)); |
30 |
| - initializeNativeInstrumentation(); |
| 27 | + if (!hasAppRestarted) { |
| 28 | + if (bundleLoadCounter > 0) { |
| 29 | + hasAppRestarted = true; |
| 30 | + } |
| 31 | + bundleLoadCounter++; |
| 32 | + } |
31 | 33 | }
|
32 | 34 | });
|
33 | 35 | }
|
34 | 36 |
|
35 | 37 | public NativeInstrumentationModule(ReactApplicationContext reactContext) {
|
36 | 38 | super(reactContext);
|
37 |
| - android.util.Log.d(NAME, "Module constructor called"); |
38 | 39 | }
|
39 | 40 |
|
40 | 41 | @Override
|
41 | 42 | public String getName() {
|
42 | 43 | return NAME;
|
43 | 44 | }
|
44 | 45 |
|
45 |
| - public static void initializeNativeInstrumentation() { |
46 |
| - android.util.Log.d(NAME, "Initializing native instrumentation..."); |
47 |
| - cachedStartStartupTime = null; |
48 |
| - cachedEndStartupTime = null; |
49 |
| - cachedStartupDuration = null; |
50 |
| - startTime = System.currentTimeMillis(); |
51 |
| - android.util.Log.d(NAME, String.format("Initialized with start time: %d (previous metrics cleared)", startTime)); |
52 |
| - } |
| 46 | + @ReactMethod(isBlockingSynchronousMethod = true) |
| 47 | + public double getStartupTimeSync() throws Exception { |
| 48 | + try { |
| 49 | + long currentTime = System.currentTimeMillis(); |
| 50 | + long processStartTime = Process.getStartUptimeMillis(); |
| 51 | + long currentUptime = SystemClock.uptimeMillis(); |
53 | 52 |
|
54 |
| - /** |
55 |
| - * Creates a fresh WritableMap with startup metrics. |
56 |
| - * Note: Each WritableMap can only be consumed once when passed through the React Native bridge. |
57 |
| - * This method ensures we always create a new instance for each request. |
58 |
| - * |
59 |
| - * Each map can be consumed once by the JS side (i.e., going through the bridge). |
60 |
| - * |
61 |
| - * @return A new WritableMap instance containing the startup metrics |
62 |
| - */ |
63 |
| - private WritableMap createStartupMetricsMap(double startStartupTime, double endStartupTime, double startupDuration) { |
64 |
| - WritableMap params = Arguments.createMap(); |
65 |
| - params.putDouble("startStartupTime", startStartupTime); |
66 |
| - params.putDouble("endStartupTime", endStartupTime); |
67 |
| - params.putDouble("startupDuration", startupDuration); |
68 |
| - return params; |
69 |
| - } |
| 53 | + long startupTime = currentTime - currentUptime + processStartTime; |
70 | 54 |
|
71 |
| - @ReactMethod |
72 |
| - public void getStartupTime(Promise promise) { |
73 |
| - android.util.Log.d(NAME, "Getting startup time..."); |
| 55 | + return BigInteger.valueOf(startupTime).doubleValue(); |
| 56 | + } catch (Exception e) { |
| 57 | + Log.e(NAME, "Error calculating startup time", e); |
74 | 58 |
|
75 |
| - if (startTime == null) { |
76 |
| - android.util.Log.e(NAME, "Error: Start time was not initialized"); |
77 |
| - promise.reject("NO_START_TIME", "[NativeInstrumentation] Start time was not initialized"); |
78 |
| - return; |
| 59 | + throw e; |
79 | 60 | }
|
| 61 | + } |
80 | 62 |
|
81 |
| - if (cachedStartupDuration != null) { |
82 |
| - android.util.Log.d(NAME, "Returning cached metrics"); |
83 |
| - promise.resolve(createStartupMetricsMap(cachedStartStartupTime, cachedEndStartupTime, cachedStartupDuration)); |
84 |
| - return; |
85 |
| - } |
| 63 | + @ReactMethod |
| 64 | + public void getStartupTime(Promise promise) { |
| 65 | + try { |
| 66 | + WritableMap response = Arguments.createMap(); |
86 | 67 |
|
87 |
| - long endTime = System.currentTimeMillis(); |
88 |
| - double duration = (endTime - startTime) / 1000.0; |
| 68 | + double startupTime = getStartupTimeSync(); |
89 | 69 |
|
90 |
| - android.util.Log.d(NAME, String.format( |
91 |
| - "Calculating metrics - Start: %d, End: %d, Duration: %f seconds", |
92 |
| - startTime, endTime, duration |
93 |
| - )); |
| 70 | + response.putDouble("startupTime", startupTime); |
94 | 71 |
|
95 |
| - cachedStartStartupTime = (double) startTime; |
96 |
| - cachedEndStartupTime = (double) endTime; |
97 |
| - cachedStartupDuration = duration; |
| 72 | + promise.resolve(response); |
| 73 | + } catch (Exception e) { |
| 74 | + promise.reject("STARTUP_TIME_ERROR", "Failed to get startup time: " + e.getMessage(), e); |
| 75 | + } |
| 76 | + } |
98 | 77 |
|
99 |
| - android.util.Log.d(NAME, "Metrics cached and being returned"); |
100 |
| - promise.resolve(createStartupMetricsMap(cachedStartStartupTime, cachedEndStartupTime, cachedStartupDuration)); |
| 78 | + @ReactMethod |
| 79 | + public void getHasAppRestarted(Promise promise) { |
| 80 | + promise.resolve(hasAppRestarted); |
101 | 81 | }
|
102 | 82 |
|
103 | 83 | @ReactMethod
|
|
0 commit comments