Skip to content

Commit

Permalink
Create callback mechanism to run code post app startup
Browse files Browse the repository at this point in the history
  • Loading branch information
bidetofevil committed Nov 13, 2024
1 parent 05296c1 commit 55d7be3
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,20 @@ interface AppStartupDataCollector {
/**
* Set the time for when the startup Activity begins to render as well as its name
*/
fun startupActivityResumed(activityName: String, timestampMs: Long? = null)
fun startupActivityResumed(
activityName: String,
collectionCompleteCallback: (() -> Unit)? = null,
timestampMs: Long? = null,
)

/**
* Set the time for when the startup Activity has finished rendering its first frame as well as its name
*/
fun firstFrameRendered(activityName: String, timestampMs: Long? = null)
fun firstFrameRendered(
activityName: String,
collectionCompleteCallback: (() -> Unit)? = null,

Check warning on line 54 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupDataCollector.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/AppStartupDataCollector.kt#L54

Added line #L54 was not covered by tests
timestampMs: Long? = null,
)

/**
* Set an arbitrary time interval during startup that is of note
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,19 +128,27 @@ internal class AppStartupTraceEmitter(
startupActivityInitEndMs = timestampMs ?: nowMs()
}

override fun startupActivityResumed(activityName: String, timestampMs: Long?) {
override fun startupActivityResumed(
activityName: String,
collectionCompleteCallback: (() -> Unit)?,
timestampMs: Long?
) {
startupActivityName = activityName
startupActivityResumedMs = timestampMs ?: nowMs()
if (!endWithFrameDraw) {
dataCollectionComplete()
dataCollectionComplete(collectionCompleteCallback)
}
}

override fun firstFrameRendered(activityName: String, timestampMs: Long?) {
override fun firstFrameRendered(
activityName: String,
collectionCompleteCallback: (() -> Unit)?,
timestampMs: Long?
) {
startupActivityName = activityName
firstFrameRenderedMs = timestampMs ?: nowMs()
if (endWithFrameDraw) {
dataCollectionComplete()
dataCollectionComplete(collectionCompleteCallback)
}
}

Expand All @@ -153,7 +161,7 @@ internal class AppStartupTraceEmitter(
/**
* Called when app startup is considered complete, i.e. the data can be used and any additional updates can be ignored
*/
private fun dataCollectionComplete() {
private fun dataCollectionComplete(callback: (() -> Unit)?) {
if (!startupRecorded.get()) {
synchronized(startupRecorded) {
if (!startupRecorded.get()) {
Expand All @@ -166,6 +174,7 @@ internal class AppStartupTraceEmitter(
)
}
}
callback?.invoke()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,11 @@ class StartupTracker(
decorView.onNextDraw {
if (!isFirstDraw) {
isFirstDraw = true
val callback =
{ appStartupDataCollector.firstFrameRendered(activityName = activityName) }
val callback = {
appStartupDataCollector.firstFrameRendered(
activityName = activityName

Check warning on line 69 in embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/StartupTracker.kt

View check run for this annotation

Codecov / codecov/patch

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/capture/startup/StartupTracker.kt#L67-L69

Added lines #L67 - L69 were not covered by tests
)
}
decorView.viewTreeObserver.registerFrameCommitCallback(callback)
}
}
Expand Down Expand Up @@ -98,7 +101,9 @@ class StartupTracker(

override fun onActivityResumed(activity: Activity) {
if (activity.observeForStartup()) {
appStartupDataCollector.startupActivityResumed(activityName = activity.localClassName)
appStartupDataCollector.startupActivityResumed(
activityName = activity.localClassName
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import io.embrace.android.embracesdk.internal.spans.findAttributeValue
import io.embrace.android.embracesdk.internal.utils.BuildVersionChecker
import io.embrace.android.embracesdk.internal.worker.BackgroundWorker
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -36,6 +37,7 @@ import org.robolectric.annotation.Config
@RunWith(AndroidJUnit4::class)
internal class AppStartupTraceEmitterTest {
private var startupService: StartupService? = null
private var dataCollectionCompletedCallbackInvoked: Boolean = false
private lateinit var clock: FakeClock
private lateinit var spanSink: SpanSink
private lateinit var spanService: SpanService
Expand Down Expand Up @@ -70,7 +72,8 @@ internal class AppStartupTraceEmitterTest {
@Test
fun `no crashes if startup service not available in T`() {
startupService = null
appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME)
appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback)
assertTrue(dataCollectionCompletedCallbackInvoked)
}

@Config(sdk = [Build.VERSION_CODES.TIRAMISU])
Expand Down Expand Up @@ -101,7 +104,8 @@ internal class AppStartupTraceEmitterTest {
@Test
fun `no crashes if startup service not available in S`() {
startupService = null
appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME)
appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback)
assertTrue(dataCollectionCompletedCallbackInvoked)
}

@Config(sdk = [Build.VERSION_CODES.S])
Expand Down Expand Up @@ -132,7 +136,8 @@ internal class AppStartupTraceEmitterTest {
@Test
fun `no crashes if startup service not available in P`() {
startupService = null
appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME)
appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback)
assertTrue(dataCollectionCompletedCallbackInvoked)
}

@Config(sdk = [Build.VERSION_CODES.P])
Expand All @@ -157,7 +162,8 @@ internal class AppStartupTraceEmitterTest {
@Test
fun `no crashes if startup service not available in M`() {
startupService = null
appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME)
appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback)
assertTrue(dataCollectionCompletedCallbackInvoked)
}

@Config(sdk = [Build.VERSION_CODES.M])
Expand All @@ -182,7 +188,8 @@ internal class AppStartupTraceEmitterTest {
@Test
fun `no crashes if startup service not available in L`() {
startupService = null
appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME)
appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback)
assertTrue(dataCollectionCompletedCallbackInvoked)
}

@Config(sdk = [Build.VERSION_CODES.LOLLIPOP])
Expand All @@ -203,6 +210,10 @@ internal class AppStartupTraceEmitterTest {
verifyWarmStartWithResumeWithoutAppInitEvents()
}

private fun dataCollectionCompletedCallback() {
dataCollectionCompletedCallbackInvoked = true
}

private fun verifyColdStartWithRender(processCreateDelayMs: Long? = null) {
clock.tick(100L)
appStartupTraceEmitter.applicationInitStart()
Expand Down Expand Up @@ -508,11 +519,11 @@ internal class AppStartupTraceEmitterTest {
}

private fun startupActivityRender(renderFrame: Boolean = true): Pair<Long, Long> {
appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME)
appStartupTraceEmitter.startupActivityResumed(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback)
val resumed = clock.now()
if (renderFrame) {
clock.tick(199L)
appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME)
appStartupTraceEmitter.firstFrameRendered(STARTUP_ACTIVITY_NAME, ::dataCollectionCompletedCallback)
}
return Pair(resumed, clock.now())
}
Expand Down Expand Up @@ -548,6 +559,7 @@ internal class AppStartupTraceEmitterTest {
)
assertEquals("false", attrs.findAttributeValue("embrace-init-in-foreground"))
assertEquals("main", attrs.findAttributeValue("embrace-init-thread-name"))
assertTrue(dataCollectionCompletedCallbackInvoked)
}

private fun assertChildSpan(span: EmbraceSpanData, expectedStartTimeNanos: Long, expectedEndTimeNanos: Long) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,20 @@ class FakeAppStartupDataCollector(
startupActivityInitEndMs = timestampMs ?: clock.now()
}

override fun startupActivityResumed(activityName: String, timestampMs: Long?) {
override fun startupActivityResumed(
activityName: String,
collectionCompleteCallback: (() -> Unit)?,
timestampMs: Long?
) {
startupActivityName = activityName
startupActivityResumedMs = timestampMs ?: clock.now()
}

override fun firstFrameRendered(activityName: String, timestampMs: Long?) {
override fun firstFrameRendered(
activityName: String,
collectionCompleteCallback: (() -> Unit)?,
timestampMs: Long?
) {
startupActivityName = activityName
firstFrameRenderedMs = timestampMs ?: clock.now()
}
Expand Down

0 comments on commit 55d7be3

Please sign in to comment.