From d31715921992f6f2d98325f4e8918d03fa5596e2 Mon Sep 17 00:00:00 2001 From: Matthew Robertson Date: Thu, 25 Jul 2024 15:20:19 -0400 Subject: [PATCH] Convenient preconditions specific for Crashlytics concurrency --- .../crashlytics/CrashlyticsRegistrar.java | 40 +++++---- .../internal/CrashlyticsPreconditions.kt | 86 +++++++++++++++++++ 2 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/CrashlyticsPreconditions.kt diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/CrashlyticsRegistrar.java b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/CrashlyticsRegistrar.java index 15b04da2cc3..9051d22258f 100644 --- a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/CrashlyticsRegistrar.java +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/CrashlyticsRegistrar.java @@ -14,6 +14,8 @@ package com.google.firebase.crashlytics; +import static com.google.firebase.crashlytics.internal.CrashlyticsPreconditions.StrictLevel.ASSERT; + import com.google.firebase.FirebaseApp; import com.google.firebase.analytics.connector.AnalyticsConnector; import com.google.firebase.annotations.concurrent.Background; @@ -24,7 +26,8 @@ import com.google.firebase.components.Dependency; import com.google.firebase.components.Qualified; import com.google.firebase.crashlytics.internal.CrashlyticsNativeComponent; -import com.google.firebase.inject.Deferred; +import com.google.firebase.crashlytics.internal.CrashlyticsPreconditions; +import com.google.firebase.crashlytics.internal.Logger; import com.google.firebase.installations.FirebaseInstallationsApi; import com.google.firebase.platforminfo.LibraryVersionComponent; import com.google.firebase.remoteconfig.interop.FirebaseRemoteConfigInterop; @@ -66,26 +69,27 @@ public List> getComponents() { } private FirebaseCrashlytics buildCrashlytics(ComponentContainer container) { - FirebaseApp app = container.get(FirebaseApp.class); - - Deferred nativeComponent = - container.getDeferred(CrashlyticsNativeComponent.class); + // TODO(mrober): Make this a build time configuration. Do not release like this. + CrashlyticsPreconditions.setStrictLevel(ASSERT); // Kill the process on violation for debugging. - Deferred analyticsConnector = - container.getDeferred(AnalyticsConnector.class); + // CrashlyticsPreconditions.checkMainThread(); + long startTime = System.currentTimeMillis(); - FirebaseInstallationsApi firebaseInstallations = container.get(FirebaseInstallationsApi.class); + FirebaseCrashlytics crashlytics = + FirebaseCrashlytics.init( + container.get(FirebaseApp.class), + container.get(FirebaseInstallationsApi.class), + container.getDeferred(CrashlyticsNativeComponent.class), + container.getDeferred(AnalyticsConnector.class), + container.getDeferred(FirebaseRemoteConfigInterop.class), + container.get(backgroundExecutorService), + container.get(blockingExecutorService)); - Deferred remoteConfigInterop = - container.getDeferred(FirebaseRemoteConfigInterop.class); + long duration = System.currentTimeMillis() - startTime; + if (duration > 30) { + Logger.getLogger().i("Initializing Crashlytics blocked main for " + duration + " ms"); + } - return FirebaseCrashlytics.init( - app, - firebaseInstallations, - nativeComponent, - analyticsConnector, - remoteConfigInterop, - container.get(backgroundExecutorService), - container.get(blockingExecutorService)); + return crashlytics; } } diff --git a/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/CrashlyticsPreconditions.kt b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/CrashlyticsPreconditions.kt new file mode 100644 index 00000000000..256dd724339 --- /dev/null +++ b/firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/CrashlyticsPreconditions.kt @@ -0,0 +1,86 @@ +/* + * Copyright 2024 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.firebase.crashlytics.internal + +import android.os.Build +import android.os.Looper +import com.google.firebase.crashlytics.internal.CrashlyticsPreconditions.StrictLevel.ASSERT +import com.google.firebase.crashlytics.internal.CrashlyticsPreconditions.StrictLevel.NONE +import com.google.firebase.crashlytics.internal.CrashlyticsPreconditions.StrictLevel.THROW +import com.google.firebase.crashlytics.internal.CrashlyticsPreconditions.StrictLevel.WARN + +/** + * Convenient preconditions specific for Crashlytics concurrency. + * + * Use GMS Core's [com.google.android.gms.common.internal.Preconditions] for general preconditions. + */ +internal object CrashlyticsPreconditions { + private val threadName + get() = Thread.currentThread().name + + // TODO(mrober): Make this a build time configuration. + @JvmStatic var strictLevel: StrictLevel = NONE + + @JvmStatic + fun checkMainThread() = + checkThread(::isMainThread) { "Must be called on the main thread, was called on $threadName." } + + @JvmStatic + fun checkBlockingThread() = + checkThread(::isBlockingThread) { + "Must be called on a blocking thread, was called on $threadName." + } + + @JvmStatic + fun checkBackgroundThread() = + checkThread(::isBackgroundThread) { + "Must be called on a background thread, was called on $threadName." + } + + private fun isMainThread() = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + Looper.getMainLooper().isCurrentThread + } else { + Looper.getMainLooper() == Looper.myLooper() + } + + private fun isBlockingThread() = threadName.contains("Firebase Blocking Thread #") + + // TODO(mrober): Remove the Crashlytics thread when fully migrated to Firebase common threads. + private fun isBackgroundThread() = + threadName.contains("Firebase Background Thread #") || + threadName.contains("Crashlytics Exception Handler") + + private fun checkThread(isCorrectThread: () -> Boolean, failureMessage: () -> String) { + if (strictLevel.level >= WARN.level && !isCorrectThread()) { + Logger.getLogger().w(failureMessage()) + assert(strictLevel.level < ASSERT.level, failureMessage) + check(strictLevel.level < THROW.level, failureMessage) + } + } + + enum class StrictLevel(val level: Int) : Comparable { + /** Do not check for violations. */ + NONE(0), + /** Log violations as warnings. */ + WARN(1), + /** Throw an exception on violation. */ + THROW(2), + /** Kill the process on violation. Useful for debugging. */ + ASSERT(3), + } +}