Skip to content

Commit 1b49171

Browse files
authored
Convenient preconditions specific for Crashlytics concurrency (#6125)
1 parent 856457a commit 1b49171

File tree

2 files changed

+108
-18
lines changed

2 files changed

+108
-18
lines changed

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/CrashlyticsRegistrar.java

Lines changed: 22 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
package com.google.firebase.crashlytics;
1616

17+
import static com.google.firebase.crashlytics.internal.CrashlyticsPreconditions.StrictLevel.ASSERT;
18+
1719
import com.google.firebase.FirebaseApp;
1820
import com.google.firebase.analytics.connector.AnalyticsConnector;
1921
import com.google.firebase.annotations.concurrent.Background;
@@ -24,7 +26,8 @@
2426
import com.google.firebase.components.Dependency;
2527
import com.google.firebase.components.Qualified;
2628
import com.google.firebase.crashlytics.internal.CrashlyticsNativeComponent;
27-
import com.google.firebase.inject.Deferred;
29+
import com.google.firebase.crashlytics.internal.CrashlyticsPreconditions;
30+
import com.google.firebase.crashlytics.internal.Logger;
2831
import com.google.firebase.installations.FirebaseInstallationsApi;
2932
import com.google.firebase.platforminfo.LibraryVersionComponent;
3033
import com.google.firebase.remoteconfig.interop.FirebaseRemoteConfigInterop;
@@ -66,26 +69,27 @@ public List<Component<?>> getComponents() {
6669
}
6770

6871
private FirebaseCrashlytics buildCrashlytics(ComponentContainer container) {
69-
FirebaseApp app = container.get(FirebaseApp.class);
70-
71-
Deferred<CrashlyticsNativeComponent> nativeComponent =
72-
container.getDeferred(CrashlyticsNativeComponent.class);
72+
// TODO(mrober): Make this a build time configuration. Do not release like this.
73+
CrashlyticsPreconditions.setStrictLevel(ASSERT); // Kill the process on violation for debugging.
7374

74-
Deferred<AnalyticsConnector> analyticsConnector =
75-
container.getDeferred(AnalyticsConnector.class);
75+
// CrashlyticsPreconditions.checkMainThread();
76+
long startTime = System.currentTimeMillis();
7677

77-
FirebaseInstallationsApi firebaseInstallations = container.get(FirebaseInstallationsApi.class);
78+
FirebaseCrashlytics crashlytics =
79+
FirebaseCrashlytics.init(
80+
container.get(FirebaseApp.class),
81+
container.get(FirebaseInstallationsApi.class),
82+
container.getDeferred(CrashlyticsNativeComponent.class),
83+
container.getDeferred(AnalyticsConnector.class),
84+
container.getDeferred(FirebaseRemoteConfigInterop.class),
85+
container.get(backgroundExecutorService),
86+
container.get(blockingExecutorService));
7887

79-
Deferred<FirebaseRemoteConfigInterop> remoteConfigInterop =
80-
container.getDeferred(FirebaseRemoteConfigInterop.class);
88+
long duration = System.currentTimeMillis() - startTime;
89+
if (duration > 30) {
90+
Logger.getLogger().i("Initializing Crashlytics blocked main for " + duration + " ms");
91+
}
8192

82-
return FirebaseCrashlytics.init(
83-
app,
84-
firebaseInstallations,
85-
nativeComponent,
86-
analyticsConnector,
87-
remoteConfigInterop,
88-
container.get(backgroundExecutorService),
89-
container.get(blockingExecutorService));
93+
return crashlytics;
9094
}
9195
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.firebase.crashlytics.internal
18+
19+
import android.os.Build
20+
import android.os.Looper
21+
import com.google.firebase.crashlytics.internal.CrashlyticsPreconditions.StrictLevel.ASSERT
22+
import com.google.firebase.crashlytics.internal.CrashlyticsPreconditions.StrictLevel.NONE
23+
import com.google.firebase.crashlytics.internal.CrashlyticsPreconditions.StrictLevel.THROW
24+
import com.google.firebase.crashlytics.internal.CrashlyticsPreconditions.StrictLevel.WARN
25+
26+
/**
27+
* Convenient preconditions specific for Crashlytics concurrency.
28+
*
29+
* Use GMS Core's [com.google.android.gms.common.internal.Preconditions] for general preconditions.
30+
*/
31+
internal object CrashlyticsPreconditions {
32+
private val threadName
33+
get() = Thread.currentThread().name
34+
35+
// TODO(mrober): Make this a build time configuration.
36+
@JvmStatic var strictLevel: StrictLevel = NONE
37+
38+
@JvmStatic
39+
fun checkMainThread() =
40+
checkThread(::isMainThread) { "Must be called on the main thread, was called on $threadName." }
41+
42+
@JvmStatic
43+
fun checkBlockingThread() =
44+
checkThread(::isBlockingThread) {
45+
"Must be called on a blocking thread, was called on $threadName."
46+
}
47+
48+
@JvmStatic
49+
fun checkBackgroundThread() =
50+
checkThread(::isBackgroundThread) {
51+
"Must be called on a background thread, was called on $threadName."
52+
}
53+
54+
private fun isMainThread() =
55+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
56+
Looper.getMainLooper().isCurrentThread
57+
} else {
58+
Looper.getMainLooper() == Looper.myLooper()
59+
}
60+
61+
private fun isBlockingThread() = threadName.contains("Firebase Blocking Thread #")
62+
63+
// TODO(mrober): Remove the Crashlytics thread when fully migrated to Firebase common threads.
64+
private fun isBackgroundThread() =
65+
threadName.contains("Firebase Background Thread #") ||
66+
threadName.contains("Crashlytics Exception Handler")
67+
68+
private fun checkThread(isCorrectThread: () -> Boolean, failureMessage: () -> String) {
69+
if (strictLevel.level >= WARN.level && !isCorrectThread()) {
70+
Logger.getLogger().w(failureMessage())
71+
assert(strictLevel.level < ASSERT.level, failureMessage)
72+
check(strictLevel.level < THROW.level, failureMessage)
73+
}
74+
}
75+
76+
enum class StrictLevel(val level: Int) : Comparable<StrictLevel> {
77+
/** Do not check for violations. */
78+
NONE(0),
79+
/** Log violations as warnings. */
80+
WARN(1),
81+
/** Throw an exception on violation. */
82+
THROW(2),
83+
/** Kill the process on violation. Useful for debugging. */
84+
ASSERT(3),
85+
}
86+
}

0 commit comments

Comments
 (0)