Skip to content

Commit 82d8b8f

Browse files
authored
Merge pull request #2401 from bugsnag/PLAT-15741/bg-build-uuid
Background Build UUID extraction
2 parents 297ad8e + d1f2166 commit 82d8b8f

7 files changed

Lines changed: 93 additions & 24 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
## TBD
4+
5+
### Enhancements
6+
7+
* Build UUIDs derived from dex file signatures no longer block NDK startup, reducing the overall startup time.
8+
[#2401](https://github.com/bugsnag/bugsnag-android/pull/2401)
9+
310
## 6.25.0 (2026-03-02)
411

512
### Enhancements

bugsnag-android-core/src/main/java/com/bugsnag/android/App.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ open class App internal constructor(
5050
var versionCode: Number?
5151
) : JsonStream.Streamable {
5252

53-
private var buildUuidProvider: Provider<String?>? = buildUuid
53+
@get:JvmName("getBuildUuidProvider\$internal")
54+
internal var buildUuidProvider: Provider<String?>? = buildUuid
5455

5556
var buildUuid: String? = null
5657
get() = field ?: buildUuidProvider?.getOrNull()

bugsnag-android-core/src/main/java/com/bugsnag/android/Client.java

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -427,6 +427,25 @@ void setupNdkPlugin() {
427427
clientObservable.postNdkInstall(immutableConfig, lastRunInfoPath, crashes);
428428
syncInitialState();
429429
clientObservable.postNdkDeliverPending();
430+
431+
// If the buildUuid is still being computed (DexBuildIdGenerator running on IO thread),
432+
// schedule a deferred SynchronizeState so the NDK layer picks up the value once ready.
433+
Provider<String> buildUuid = immutableConfig.getBuildUuid();
434+
if (buildUuid != null && !buildUuid.isComplete()) {
435+
try {
436+
bgTaskService.submitTask(TaskType.IO, new Runnable() {
437+
@Override
438+
public void run() {
439+
// This blocks on the IO thread until dex generation finishes,
440+
// then syncs the result to the NDK layer.
441+
immutableConfig.getBuildUuid().getOrNull();
442+
clientObservable.postSynchronizeState();
443+
}
444+
});
445+
} catch (RejectedExecutionException exc) {
446+
logger.w("Failed to schedule deferred NDK build UUID sync", exc);
447+
}
448+
}
430449
}
431450

432451
private boolean setupNdkDirectory() {

bugsnag-android-core/src/main/java/com/bugsnag/android/ClientObservable.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ internal class ClientObservable : BaseObservable() {
1818
conf.apiKey,
1919
conf.enabledErrorTypes.ndkCrashes,
2020
conf.appVersion,
21-
conf.buildUuid?.getOrNull(),
21+
if (conf.buildUuid?.isComplete == true) conf.buildUuid.getOrNull()
22+
else null,
2223
conf.releaseStage,
2324
lastRunInfoPath,
2425
consecutiveLaunchCrashes,

bugsnag-android-core/src/main/java/com/bugsnag/android/NativeInterface.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.bugsnag.android.internal.ImmutableConfig;
44
import com.bugsnag.android.internal.JsonHelper;
5+
import com.bugsnag.android.internal.dag.Provider;
56

67
import android.annotation.SuppressLint;
78

@@ -114,14 +115,20 @@ public static Map<String, String> getUser() {
114115
@NonNull
115116
@SuppressWarnings("unused")
116117
public static Map<String, Object> getApp() {
117-
HashMap<String, Object> data = new HashMap<>();
118118
AppDataCollector source = getClient().getAppDataCollector();
119119
AppWithState app = source.generateAppWithState();
120+
121+
Provider<String> buildUuidProvider = app.getBuildUuidProvider$internal();
122+
String buildUuid = buildUuidProvider != null && buildUuidProvider.isComplete()
123+
? buildUuidProvider.getOrNull()
124+
: null;
125+
126+
HashMap<String, Object> data = new HashMap<>();
120127
data.put("version", app.getVersion());
121128
data.put("releaseStage", app.getReleaseStage());
122129
data.put("id", app.getId());
123130
data.put("type", app.getType());
124-
data.put("buildUUID", app.getBuildUuid());
131+
data.put("buildUUID", buildUuid);
125132
data.put("duration", app.getDuration());
126133
data.put("durationInForeground", app.getDurationInForeground());
127134
data.put("versionCode", app.getVersionCode());

bugsnag-android-core/src/test/java/com/bugsnag/android/ClientObservableTest.kt

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.bugsnag.android
22

33
import com.bugsnag.android.internal.StateObserver
4+
import com.bugsnag.android.internal.convertToImmutableConfig
5+
import com.bugsnag.android.internal.dag.RunnableProvider
6+
import com.bugsnag.android.internal.dag.ValueProvider
47
import org.junit.Assert.assertEquals
8+
import org.junit.Assert.assertNull
59
import org.junit.Assert.assertTrue
610
import org.junit.Test
711

@@ -22,12 +26,61 @@ class ClientObservableTest {
2226

2327
@Test
2428
fun postNdkInstall() {
25-
clientObservable.postNdkInstall(BugsnagTestUtils.generateImmutableConfig(), "/foo", 0)
2629
clientObservable.addObserver(
2730
StateObserver {
2831
assertTrue(it is StateEvent.Install)
2932
}
3033
)
34+
clientObservable.postNdkInstall(BugsnagTestUtils.generateImmutableConfig(), "/foo", 0)
35+
}
36+
37+
@Test
38+
fun postNdkInstallBuildUuidNull() {
39+
// When buildUuid is null, Install event should have null buildUuid
40+
val config = BugsnagTestUtils.generateImmutableConfig()
41+
var installEvent: StateEvent.Install? = null
42+
clientObservable.addObserver {
43+
if (it is StateEvent.Install) installEvent = it
44+
}
45+
clientObservable.postNdkInstall(config, "/foo", 0)
46+
assertNull(installEvent?.buildUuid)
47+
}
48+
49+
@Test
50+
fun postNdkInstallBuildUuidComplete() {
51+
// When buildUuid is a ValueProvider (already complete), Install should include it
52+
val config = convertToImmutableConfig(
53+
BugsnagTestUtils.generateConfiguration(),
54+
ValueProvider("test-uuid")
55+
)
56+
var installEvent: StateEvent.Install? = null
57+
clientObservable.addObserver(
58+
StateObserver {
59+
if (it is StateEvent.Install) installEvent = it
60+
}
61+
)
62+
clientObservable.postNdkInstall(config, "/foo", 0)
63+
assertEquals("test-uuid", installEvent?.buildUuid)
64+
}
65+
66+
@Test
67+
fun postNdkInstallBuildUuidPending() {
68+
// When buildUuid is a pending RunnableProvider, Install should have null buildUuid
69+
val pendingProvider = object : RunnableProvider<String?>() {
70+
override fun invoke(): String? = "deferred-uuid"
71+
}
72+
val config = convertToImmutableConfig(
73+
BugsnagTestUtils.generateConfiguration(),
74+
pendingProvider
75+
)
76+
var installEvent: StateEvent.Install? = null
77+
clientObservable.addObserver(
78+
StateObserver {
79+
if (it is StateEvent.Install) installEvent = it
80+
}
81+
)
82+
clientObservable.postNdkInstall(config, "/foo", 0)
83+
assertNull(installEvent?.buildUuid)
3184
}
3285

3386
@Test

bugsnag-plugin-android-apphang/src/androidTest/java/com/bugsnag/android/LooperMonitorThreadTest.kt

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -52,25 +52,6 @@ class LooperMonitorThreadTest {
5252
assertEquals("no AppHangs expected", 0, appHangCount)
5353
}
5454

55-
@Test
56-
fun testBelowThresholdEvents() {
57-
val countDownLatch = CountDownLatch(10)
58-
val task = object : Runnable {
59-
override fun run() {
60-
JThread.sleep(APP_HANG_THRESHOLD / 2)
61-
countDownLatch.countDown()
62-
63-
if (countDownLatch.count > 0) {
64-
handler.postDelayed(this, 1L)
65-
}
66-
}
67-
}
68-
handler.postDelayed(task, 1)
69-
70-
countDownLatch.await()
71-
assertEquals("no AppHangs expected", 0, appHangCount)
72-
}
73-
7455
@Test
7556
fun appHang() {
7657
val countDownLatch = CountDownLatch(1)

0 commit comments

Comments
 (0)