@@ -41,10 +41,6 @@ class SentryFlutterPlugin :
4141 ActivityAware {
4242 private lateinit var channel: MethodChannel
4343 private lateinit var context: Context
44- private lateinit var sentryFlutter: SentryFlutter
45-
46- private var activity: WeakReference <Activity >? = null
47- private var pluginRegistrationTime: Long? = null
4844
4945 override fun onAttachedToEngine (flutterPluginBinding : FlutterPlugin .FlutterPluginBinding ) {
5046 pluginRegistrationTime = System .currentTimeMillis()
@@ -65,7 +61,6 @@ class SentryFlutterPlugin :
6561 when (call.method) {
6662 " initNativeSdk" -> initNativeSdk(call, result)
6763 " closeNativeSdk" -> closeNativeSdk(result)
68- " fetchNativeAppStart" -> fetchNativeAppStart(result)
6964 " setContexts" -> setContexts(call.argument(" key" ), call.argument(" value" ), result)
7065 " removeContexts" -> removeContexts(call.argument(" key" ), result)
7166 " setUser" -> setUser(call.argument(" user" ), result)
@@ -75,7 +70,6 @@ class SentryFlutterPlugin :
7570 " removeExtra" -> removeExtra(call.argument(" key" ), result)
7671 " setTag" -> setTag(call.argument(" key" ), call.argument(" value" ), result)
7772 " removeTag" -> removeTag(call.argument(" key" ), result)
78- " displayRefreshRate" -> displayRefreshRate(result)
7973 " nativeCrash" -> crash()
8074 " setReplayConfig" -> setReplayConfig(call, result)
8175 " captureReplay" -> captureReplay(result)
@@ -151,106 +145,6 @@ class SentryFlutterPlugin :
151145 }
152146 }
153147
154- private fun fetchNativeAppStart (result : Result ) {
155- if (! sentryFlutter.autoPerformanceTracingEnabled) {
156- result.success(null )
157- return
158- }
159-
160- val appStartMetrics = AppStartMetrics .getInstance()
161-
162- if (! appStartMetrics.isAppLaunchedInForeground ||
163- appStartMetrics.appStartTimeSpan.durationMs > APP_START_MAX_DURATION_MS
164- ) {
165- Log .w(
166- " Sentry" ,
167- " Invalid app start data: app not launched in foreground or app start took too long (>60s)" ,
168- )
169- result.success(null )
170- return
171- }
172-
173- val appStartTimeSpan = appStartMetrics.appStartTimeSpan
174- val appStartTime = appStartTimeSpan.startTimestamp
175- val isColdStart = appStartMetrics.appStartType == AppStartMetrics .AppStartType .COLD
176-
177- if (appStartTime == null ) {
178- Log .w(" Sentry" , " App start won't be sent due to missing appStartTime" )
179- result.success(null )
180- } else {
181- val appStartTimeMillis = DateUtils .nanosToMillis(appStartTime.nanoTimestamp().toDouble())
182- val item =
183-
184- mutableMapOf<String , Any ?>(
185- " pluginRegistrationTime" to pluginRegistrationTime,
186- " appStartTime" to appStartTimeMillis,
187- " isColdStart" to isColdStart,
188- )
189-
190- val androidNativeSpans = mutableMapOf<String , Any ?>()
191-
192- val processInitSpan =
193- TimeSpan ().apply {
194- description = " Process Initialization"
195- setStartUnixTimeMs(appStartTimeSpan.startTimestampMs)
196- setStartedAt(appStartTimeSpan.startUptimeMs)
197- setStoppedAt(appStartMetrics.classLoadedUptimeMs)
198- }
199- processInitSpan.addToMap(androidNativeSpans)
200-
201- val applicationOnCreateSpan = appStartMetrics.applicationOnCreateTimeSpan
202- applicationOnCreateSpan.addToMap(androidNativeSpans)
203-
204- val contentProviderSpans = appStartMetrics.contentProviderOnCreateTimeSpans
205- contentProviderSpans.forEach { span ->
206- span.addToMap(androidNativeSpans)
207- }
208-
209- appStartMetrics.activityLifecycleTimeSpans.forEach { span ->
210- span.onCreate.addToMap(androidNativeSpans)
211- span.onStart.addToMap(androidNativeSpans)
212- }
213-
214- item[" nativeSpanTimes" ] = androidNativeSpans
215-
216- result.success(item)
217- }
218- }
219-
220- private fun displayRefreshRate (result : Result ) {
221- var refreshRate: Int? = null
222-
223- if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .R ) {
224- val display = activity?.get()?.display
225- if (display != null ) {
226- refreshRate = display.refreshRate.toInt()
227- }
228- } else {
229- val display =
230- activity
231- ?.get()
232- ?.window
233- ?.windowManager
234- ?.defaultDisplay
235- if (display != null ) {
236- refreshRate = display.refreshRate.toInt()
237- }
238- }
239-
240- result.success(refreshRate)
241- }
242-
243- private fun TimeSpan.addToMap (map : MutableMap <String , Any ?>) {
244- if (startTimestamp == null ) return
245-
246- description?.let { description ->
247- map[description] =
248- mapOf<String , Any ?>(
249- " startTimestampMsSinceEpoch" to startTimestampMs,
250- " stopTimestampMsSinceEpoch" to projectedStopTimestampMs,
251- )
252- }
253- }
254148 private fun setContexts (
255149 key : String? ,
256150 value : Any? ,
@@ -374,23 +268,137 @@ class SentryFlutterPlugin :
374268 result.success(" " )
375269 }
376270
271+ @Suppress(" TooManyFunctions" )
377272 companion object {
378273 @SuppressLint(" StaticFieldLeak" )
379274 private var replay: ReplayIntegration ? = null
380275
381276 @SuppressLint(" StaticFieldLeak" )
382277 private var applicationContext: Context ? = null
383278
279+ @SuppressLint(" StaticFieldLeak" )
280+ private var activity: WeakReference <Activity >? = null
281+
282+ private var pluginRegistrationTime: Long? = null
283+
284+ private lateinit var sentryFlutter: SentryFlutter
285+
384286 private const val NATIVE_CRASH_WAIT_TIME = 500L
385287
386288 @Suppress(" unused" ) // Used by native/jni bindings
387289 @JvmStatic
388290 fun privateSentryGetReplayIntegration (): ReplayIntegration ? = replay
389291
292+ @Suppress(" unused" ) // Used by native/jni bindings
293+ @JvmStatic
294+ fun getDisplayRefreshRate (): Int? {
295+ var refreshRate: Int? = null
296+
297+ if (Build .VERSION .SDK_INT >= Build .VERSION_CODES .R ) {
298+ val display = activity?.get()?.display
299+ if (display != null ) {
300+ refreshRate = display.refreshRate.toInt()
301+ }
302+ } else {
303+ val display =
304+ activity
305+ ?.get()
306+ ?.window
307+ ?.windowManager
308+ ?.defaultDisplay
309+ if (display != null ) {
310+ refreshRate = display.refreshRate.toInt()
311+ }
312+ }
313+
314+ return refreshRate
315+ }
316+
317+ @Suppress(" unused" , " ReturnCount" , " TooGenericExceptionCaught" ) // Used by native/jni bindings
318+ @JvmStatic
319+ fun fetchNativeAppStartAsBytes (): ByteArray? {
320+ if (! sentryFlutter.autoPerformanceTracingEnabled) {
321+ return null
322+ }
323+
324+ val appStartMetrics = AppStartMetrics .getInstance()
325+
326+ if (! appStartMetrics.isAppLaunchedInForeground ||
327+ appStartMetrics.appStartTimeSpan.durationMs > APP_START_MAX_DURATION_MS
328+ ) {
329+ Log .w(
330+ " Sentry" ,
331+ " Invalid app start data: app not launched in foreground or app start took too long (>60s)" ,
332+ )
333+ return null
334+ }
335+
336+ val appStartTimeSpan = appStartMetrics.appStartTimeSpan
337+ val appStartTime = appStartTimeSpan.startTimestamp
338+ val isColdStart = appStartMetrics.appStartType == AppStartMetrics .AppStartType .COLD
339+
340+ if (appStartTime == null ) {
341+ Log .w(" Sentry" , " App start won't be sent due to missing appStartTime" )
342+ return null
343+ }
344+
345+ val appStartTimeMillis = DateUtils .nanosToMillis(appStartTime.nanoTimestamp().toDouble())
346+ val item =
347+ mutableMapOf<String , Any ?>(
348+ " pluginRegistrationTime" to pluginRegistrationTime,
349+ " appStartTime" to appStartTimeMillis,
350+ " isColdStart" to isColdStart,
351+ )
352+
353+ val androidNativeSpans = mutableMapOf<String , Any ?>()
354+
355+ val processInitSpan =
356+ TimeSpan ().apply {
357+ description = " Process Initialization"
358+ setStartUnixTimeMs(appStartTimeSpan.startTimestampMs)
359+ setStartedAt(appStartTimeSpan.startUptimeMs)
360+ setStoppedAt(appStartMetrics.classLoadedUptimeMs)
361+ }
362+ addTimeSpanToMap(processInitSpan, androidNativeSpans)
363+
364+ val applicationOnCreateSpan = appStartMetrics.applicationOnCreateTimeSpan
365+ addTimeSpanToMap(applicationOnCreateSpan, androidNativeSpans)
366+
367+ val contentProviderSpans = appStartMetrics.contentProviderOnCreateTimeSpans
368+ contentProviderSpans.forEach { span ->
369+ addTimeSpanToMap(span, androidNativeSpans)
370+ }
371+
372+ appStartMetrics.activityLifecycleTimeSpans.forEach { span ->
373+ addTimeSpanToMap(span.onCreate, androidNativeSpans)
374+ addTimeSpanToMap(span.onStart, androidNativeSpans)
375+ }
376+
377+ item[" nativeSpanTimes" ] = androidNativeSpans
378+
379+ val json = JSONObject (item).toString()
380+ return json.toByteArray(Charsets .UTF_8 )
381+ }
382+
383+ private fun addTimeSpanToMap (
384+ span : TimeSpan ,
385+ map : MutableMap <String , Any ?>,
386+ ) {
387+ if (span.startTimestamp == null ) return
388+
389+ span.description?.let { description ->
390+ map[description] =
391+ mapOf<String , Any ?>(
392+ " startTimestampMsSinceEpoch" to span.startTimestampMs,
393+ " stopTimestampMsSinceEpoch" to span.projectedStopTimestampMs,
394+ )
395+ }
396+ }
397+
390398 @JvmStatic
391399 fun getApplicationContext (): Context ? = applicationContext
392400
393- @Suppress(" unused" ) // Used by native/jni bindings
401+ @Suppress(" unused" , " ReturnCount " , " TooGenericExceptionCaught " ) // Used by native/jni bindings
394402 @JvmStatic
395403 fun loadContextsAsBytes (): ByteArray? {
396404 val options = ScopesAdapter .getInstance().options
@@ -405,11 +413,16 @@ class SentryFlutterPlugin :
405413 options,
406414 currentScope,
407415 )
408- val json = JSONObject (serializedScope).toString()
409- return json.toByteArray(Charsets .UTF_8 )
416+ try {
417+ val json = JSONObject (serializedScope).toString()
418+ return json.toByteArray(Charsets .UTF_8 )
419+ } catch (e: Exception ) {
420+ Log .e(" Sentry" , " Failed to serialize scope" , e)
421+ return null
422+ }
410423 }
411424
412- @Suppress(" unused" ) // Used by native/jni bindings
425+ @Suppress(" unused" , " TooGenericExceptionCaught " ) // Used by native/jni bindings
413426 @JvmStatic
414427 fun loadDebugImagesAsBytes (addresses : Set <String >): ByteArray? {
415428 val options = ScopesAdapter .getInstance().options as SentryAndroidOptions
@@ -428,8 +441,13 @@ class SentryFlutterPlugin :
428441 .serialize()
429442 }
430443
431- val json = JSONArray (debugImages).toString()
432- return json.toByteArray(Charsets .UTF_8 )
444+ try {
445+ val json = JSONArray (debugImages).toString()
446+ return json.toByteArray(Charsets .UTF_8 )
447+ } catch (e: Exception ) {
448+ Log .e(" Sentry" , " Failed to serialize debug images" , e)
449+ return null
450+ }
433451 }
434452
435453 private fun List<DebugImage>?.serialize () = this ?.map { it.serialize() }
0 commit comments