Skip to content

Commit 6e29df1

Browse files
committed
feat(rooting): moved root detection to a dedicated background thread so that it doesn't block startup
1 parent 870d249 commit 6e29df1

15 files changed

Lines changed: 89 additions & 32 deletions

File tree

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
* Added `NativeOutOfMemoryPlugin` as a new way to report `OutOfMemoryError`s that uses pre-allocated memory in the NDK module instead of allocating an `Event` object. When used `OutOfMemoryError`s will be more reliably reported, but will not be passed to `OnErrorCallback`s (`OnSendCallback` works as expected).
88
[#2384](https://github.com/bugsnag/bugsnag-android/pull/2384)
99
* Added `appHangCooldownMillis` to the AppHangPlugin to control the number of AppHang errors produced when the app is performance constrained
10-
[]()
10+
[#2389](https://github.com/bugsnag/bugsnag-android/pull/2389)
11+
* Moved root/jailbreak detection onto a background thread so that it no longer blocks startup (this should improve startup performance in most common cases)
12+
[#2391](https://github.com/bugsnag/bugsnag-android/pull/2391)
1113

1214
## 6.24.0 (2026-02-11)
1315

bugsnag-android-core/api/bugsnag-android-core.api

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -877,6 +877,10 @@ public final class com/bugsnag/android/StateEvent$StartSession : com/bugsnag/and
877877
public final fun getUnhandledCount ()I
878878
}
879879

880+
public final class com/bugsnag/android/StateEvent$SynchronizeState : com/bugsnag/android/StateEvent {
881+
public static final field INSTANCE Lcom/bugsnag/android/StateEvent$SynchronizeState;
882+
}
883+
880884
public final class com/bugsnag/android/StateEvent$UpdateContext : com/bugsnag/android/StateEvent {
881885
public final field context Ljava/lang/String;
882886
public fun <init> (Ljava/lang/String;)V
@@ -1179,13 +1183,15 @@ public final class com/bugsnag/android/internal/TaskType : java/lang/Enum {
11791183
public abstract interface class com/bugsnag/android/internal/dag/Provider {
11801184
public abstract fun get ()Ljava/lang/Object;
11811185
public abstract fun getOrNull ()Ljava/lang/Object;
1186+
public abstract fun isComplete ()Z
11821187
}
11831188

11841189
public abstract class com/bugsnag/android/internal/dag/RunnableProvider : com/bugsnag/android/internal/dag/Provider, java/lang/Runnable {
11851190
public fun <init> ()V
11861191
public fun get ()Ljava/lang/Object;
11871192
public fun getOrNull ()Ljava/lang/Object;
11881193
public abstract fun invoke ()Ljava/lang/Object;
1194+
public fun isComplete ()Z
11891195
public final fun run ()V
11901196
}
11911197

@@ -1197,6 +1203,7 @@ public final class com/bugsnag/android/internal/dag/ValueProvider : com/bugsnag/
11971203
public fun get ()Ljava/lang/Object;
11981204
public fun getOrNull ()Ljava/lang/Object;
11991205
public fun hashCode ()I
1206+
public fun isComplete ()Z
12001207
public fun toString ()Ljava/lang/String;
12011208
}
12021209

bugsnag-android-core/detekt-baseline.xml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<ID>LongParameterList:AppWithState.kt$AppWithState$( binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, buildUuid: Provider&lt;String?>?, type: String?, versionCode: Number?, /** * The number of milliseconds the application was running before the event occurred */ var duration: Number?, /** * The number of milliseconds the application was running in the foreground before the * event occurred */ var durationInForeground: Number?, /** * Whether the application was in the foreground when the event occurred */ var inForeground: Boolean?, /** * Whether the application was launching when the event occurred */ var isLaunching: Boolean? )</ID>
1616
<ID>LongParameterList:AppWithState.kt$AppWithState$( binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, buildUuid: String?, type: String?, versionCode: Number?, /** * The number of milliseconds the application was running before the event occurred */ duration: Number?, /** * The number of milliseconds the application was running in the foreground before the * event occurred */ durationInForeground: Number?, /** * Whether the application was in the foreground when the event occurred */ inForeground: Boolean?, /** * Whether the application was launching when the event occurred */ isLaunching: Boolean? )</ID>
1717
<ID>LongParameterList:AppWithState.kt$AppWithState$( config: ImmutableConfig, binaryArch: String?, id: String?, releaseStage: String?, version: String?, codeBundleId: String?, duration: Number?, durationInForeground: Number?, inForeground: Boolean?, isLaunching: Boolean? )</ID>
18-
<ID>LongParameterList:DataCollectionModule.kt$DataCollectionModule$( contextModule: ContextModule, configModule: ConfigModule, systemServiceModule: SystemServiceModule, trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, deviceIdStore: Provider&lt;DeviceIdStore>, memoryTrimState: MemoryTrimState )</ID>
18+
<ID>LongParameterList:DataCollectionModule.kt$DataCollectionModule$( contextModule: ContextModule, configModule: ConfigModule, systemServiceModule: SystemServiceModule, trackerModule: TrackerModule, bgTaskService: BackgroundTaskService, connectivity: Connectivity, deviceIdStore: Provider&lt;DeviceIdStore>, memoryTrimState: MemoryTrimState, clientObservable: ClientObservable )</ID>
1919
<ID>LongParameterList:Device.kt$Device$( buildInfo: DeviceBuildInfo, /** * The Application Binary Interface used */ var cpuAbi: Array&lt;String>?, /** * Whether the device has been jailbroken */ var jailbroken: Boolean?, /** * A UUID generated by Bugsnag and used for the individual application on a device */ var id: String?, /** * The IETF language tag of the locale used */ var locale: String?, /** * The total number of bytes of memory on the device */ var totalMemory: Long?, /** * A collection of names and their versions of the primary languages, frameworks or * runtimes that the application is running on */ runtimeVersions: MutableMap&lt;String, Any>? )</ID>
2020
<ID>LongParameterList:DeviceBuildInfo.kt$DeviceBuildInfo$( val manufacturer: String?, val model: String?, val osVersion: String?, val apiLevel: Int?, val osBuild: String?, val fingerprint: String?, val tags: String?, val brand: String?, val cpuAbis: Array&lt;String>? )</ID>
2121
<ID>LongParameterList:DeviceDataCollector.kt$DeviceDataCollector$( private val connectivity: Connectivity, private val appContext: Context, resources: Resources, private val deviceIdStore: Provider&lt;DeviceIdStore.DeviceIds?>, private val buildInfo: DeviceBuildInfo, private val dataDirectory: File, private val rootedFuture: Provider&lt;Boolean>?, private val bgTaskService: BackgroundTaskService, private val logger: Logger )</ID>
@@ -84,7 +84,6 @@
8484
<ID>SwallowedException:ConnectivityCompat.kt$ConnectivityLegacy$e: NullPointerException</ID>
8585
<ID>SwallowedException:ContextExtensions.kt$exc: RuntimeException</ID>
8686
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$e: Throwable</ID>
87-
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$exc: Exception</ID>
8887
<ID>SwallowedException:DeviceDataCollector.kt$DeviceDataCollector$exception: Exception</ID>
8988
<ID>SwallowedException:DeviceIdFilePersistence.kt$DeviceIdFilePersistence$exc: OverlappingFileLockException</ID>
9089
<ID>SwallowedException:EventStore.kt$EventStore$exception: RejectedExecutionException</ID>

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ public Client(@NonNull Context androidContext, @NonNull String apiKey) {
128128
* @param configuration a configuration for the Client
129129
*/
130130
public Client(@NonNull Context androidContext, @NonNull final Configuration configuration) {
131-
ContextModule contextModule = new ContextModule(androidContext, bgTaskService);
131+
ContextModule contextModule = new ContextModule(androidContext);
132132
appContext = contextModule.getCtx();
133133

134134
notifier = configuration.getNotifier();
@@ -186,7 +186,7 @@ public Unit invoke(Boolean hasConnection, String networkState) {
186186

187187
// lookup system services
188188
final SystemServiceModule systemServiceModule =
189-
new SystemServiceModule(contextModule, bgTaskService);
189+
new SystemServiceModule(contextModule);
190190

191191
// setup further state trackers and data collection
192192
TrackerModule trackerModule = new TrackerModule(configModule,
@@ -195,7 +195,7 @@ public Unit invoke(Boolean hasConnection, String networkState) {
195195
DataCollectionModule dataCollectionModule = new DataCollectionModule(contextModule,
196196
configModule, systemServiceModule, trackerModule,
197197
bgTaskService, connectivity, storageModule.getDeviceIdStore(),
198-
memoryTrimState);
198+
memoryTrimState, clientObservable);
199199

200200
// load the device + user information
201201
userState = storageModule.loadUser(configuration.getUser());

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,8 @@ internal class ClientObservable : BaseObservable() {
3131
fun postNdkDeliverPending() {
3232
updateState { StateEvent.DeliverPending }
3333
}
34+
35+
fun postSynchronizeState() {
36+
updateState { StateEvent.SynchronizeState }
37+
}
3438
}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.bugsnag.android
22

33
import android.os.Environment
44
import com.bugsnag.android.internal.BackgroundTaskService
5+
import com.bugsnag.android.internal.RootDetectionProvider
56
import com.bugsnag.android.internal.dag.BackgroundDependencyModule
67
import com.bugsnag.android.internal.dag.ConfigModule
78
import com.bugsnag.android.internal.dag.ContextModule
@@ -20,7 +21,8 @@ internal class DataCollectionModule(
2021
bgTaskService: BackgroundTaskService,
2122
connectivity: Connectivity,
2223
deviceIdStore: Provider<DeviceIdStore>,
23-
memoryTrimState: MemoryTrimState
24+
memoryTrimState: MemoryTrimState,
25+
clientObservable: ClientObservable
2426
) : BackgroundDependencyModule(bgTaskService) {
2527

2628
private val ctx = contextModule.ctx
@@ -41,10 +43,8 @@ internal class DataCollectionModule(
4143
)
4244
}
4345

44-
private val rootDetection = provider {
45-
val rootDetector = RootDetector(logger = logger, deviceBuildInfo = deviceBuildInfo)
46-
rootDetector.isRooted()
47-
}
46+
private val rootDetection = RootDetectionProvider(deviceBuildInfo, clientObservable, logger)
47+
.apply { start() }
4848

4949
val deviceDataCollector = provider {
5050
DeviceDataCollector(

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,9 +122,10 @@ internal class DeviceDataCollector(
122122
}
123123

124124
private fun checkIsRooted(): Boolean {
125+
val rooted = rootedFuture ?: return false
125126
return try {
126-
rootedFuture != null && rootedFuture.get()
127-
} catch (exc: Exception) {
127+
rooted.isComplete && rooted.get()
128+
} catch (_: Exception) {
128129
false
129130
}
130131
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,4 +82,6 @@ sealed class StateEvent { // JvmField allows direct field access optimizations
8282
) : StateEvent()
8383

8484
object ClearFeatureFlags : StateEvent()
85+
86+
object SynchronizeState : StateEvent()
8587
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.bugsnag.android.internal
2+
3+
import com.bugsnag.android.ClientObservable
4+
import com.bugsnag.android.DeviceBuildInfo
5+
import com.bugsnag.android.Logger
6+
import com.bugsnag.android.RootDetector
7+
import com.bugsnag.android.internal.dag.RunnableProvider
8+
9+
internal class RootDetectionProvider(
10+
private val deviceBuildInfo: DeviceBuildInfo,
11+
private val clientObservable: ClientObservable,
12+
private val logger: Logger,
13+
) : RunnableProvider<Boolean>() {
14+
var isRooted: Boolean = false
15+
private set
16+
17+
fun start() {
18+
// root detection can take 100+ms so we always have a dedicated background thread for it
19+
// we fire an event once we're finished to let any downstream notifiers know the result
20+
val worker = Thread(this, "Bugsnag Worker")
21+
worker.priority = Thread.MIN_PRIORITY
22+
worker.isDaemon = true
23+
worker.start()
24+
}
25+
26+
override fun invoke(): Boolean {
27+
val rootDetector = RootDetector(logger = logger, deviceBuildInfo = deviceBuildInfo)
28+
isRooted = rootDetector.isRooted()
29+
clientObservable.postSynchronizeState()
30+
return isRooted
31+
}
32+
}

bugsnag-android-core/src/main/java/com/bugsnag/android/internal/dag/ConfigModule.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@ internal class ConfigModule(
1414
configuration: Configuration,
1515
connectivity: Connectivity,
1616
bgTaskExecutor: BackgroundTaskService
17-
) : BackgroundDependencyModule(bgTaskExecutor) {
17+
) : DependencyModule {
1818
val config = sanitiseConfiguration(contextModule.ctx, configuration, connectivity, bgTaskExecutor)
1919
}

0 commit comments

Comments
 (0)