Skip to content

Commit

Permalink
fix: changes to hopefully improve startup and ANRs [WPB-6048] (#2607)
Browse files Browse the repository at this point in the history
Co-authored-by: Yamil Medina <yamilmedina@users.noreply.github.com>
  • Loading branch information
saleniuk and yamilmedina committed Jan 23, 2024
1 parent d16ce74 commit 5ddbbe0
Show file tree
Hide file tree
Showing 12 changed files with 214 additions and 91 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Expand Up @@ -70,6 +70,7 @@ dependencies {
implementation(libs.androidx.splashscreen)
implementation(libs.androidx.exifInterface)
implementation(libs.androidx.biometric)
implementation(libs.androidx.startup)

implementation(libs.ktx.dateTime)
implementation(libs.material)
Expand Down
2 changes: 1 addition & 1 deletion app/src/dev/kotlin/com/wire/android/util/DataDogLogger.kt
Expand Up @@ -29,8 +29,8 @@ object DataDogLogger : LogWriter() {

private val logger = Logger.Builder()
.setNetworkInfoEnabled(true)
.setLogcatLogsEnabled(true)
.setLogcatLogsEnabled(false) // we already use platformLogWriter() along with DataDogLogger, don't need duplicates in LogCat
.setDatadogLogsEnabled(true)
.setBundleWithTraceEnabled(true)
.setLoggerName("DATADOG")
.build()
Expand Down
Expand Up @@ -29,8 +29,8 @@ object DataDogLogger : LogWriter() {

private val logger = Logger.Builder()
.setNetworkInfoEnabled(true)
.setLogcatLogsEnabled(true)
.setLogcatLogsEnabled(false) // we already use platformLogWriter() along with DataDogLogger, don't need duplicates in LogCat
.setDatadogLogsEnabled(true)
.setBundleWithTraceEnabled(true)
.setLoggerName("DATADOG")
.build()
Expand Down
39 changes: 17 additions & 22 deletions app/src/main/AndroidManifest.xml
Expand Up @@ -227,19 +227,36 @@
android:host="e2ei"
android:scheme="wire" />
</intent-filter>

</activity>
<!--
We make a custom Firebase init, because the app selects the Firebase project at a runtime,
so we need to remove default FirebaseInitProvider and create our custom FirebaseInitializer.
-->
<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove" />

<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">

<!--
We create custom WorkManager Configuration.Provider and initialize on-demand,
so we need to remove default WorkManagerInitializer.
-->
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />

<meta-data
android:name="com.wire.android.initializer.FirebaseInitializer"
android:value="androidx.startup" />

</provider>

<provider
Expand Down Expand Up @@ -301,28 +318,6 @@
android:exported="false"
android:foregroundServiceType="phoneCall" />

<!-- If you want to disable android.startup completely. -->
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
tools:node="remove" />

<provider
android:name="com.google.firebase.provider.FirebaseInitProvider"
android:authorities="${applicationId}.firebaseinitprovider"
tools:node="remove" />
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<!-- If you are using androidx.startup to initialize other components -->
<meta-data
android:name="androidx.work.WorkManagerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>

</application>

</manifest>
85 changes: 39 additions & 46 deletions app/src/main/kotlin/com/wire/android/WireApplication.kt
Expand Up @@ -24,21 +24,19 @@ import android.os.Build
import android.os.StrictMode
import androidx.work.Configuration
import co.touchlab.kermit.platformLogWriter
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.wire.android.datastore.GlobalDataStore
import com.wire.android.di.ApplicationScope
import com.wire.android.di.KaliumCoreLogic
import com.wire.android.util.DataDogLogger
import com.wire.android.util.LogFileWriter
import com.wire.android.util.extension.isGoogleServicesAvailable
import com.wire.android.util.getGitBuildId
import com.wire.android.util.lifecycle.ConnectionPolicyManager
import com.wire.android.workmanager.WireWorkerFactory
import com.wire.kalium.logger.KaliumLogLevel
import com.wire.kalium.logger.KaliumLogger
import com.wire.kalium.logic.CoreLogger
import com.wire.kalium.logic.CoreLogic
import dagger.Lazy
import dagger.hilt.android.HiltAndroidApp
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
Expand All @@ -50,29 +48,29 @@ class WireApplication : Application(), Configuration.Provider {

@Inject
@KaliumCoreLogic
lateinit var coreLogic: CoreLogic
lateinit var coreLogic: Lazy<CoreLogic>

@Inject
lateinit var logFileWriter: LogFileWriter
lateinit var logFileWriter: Lazy<LogFileWriter>

@Inject
lateinit var connectionPolicyManager: ConnectionPolicyManager
lateinit var connectionPolicyManager: Lazy<ConnectionPolicyManager>

@Inject
lateinit var wireWorkerFactory: WireWorkerFactory
lateinit var wireWorkerFactory: Lazy<WireWorkerFactory>

@Inject
lateinit var globalObserversManager: GlobalObserversManager
lateinit var globalObserversManager: Lazy<GlobalObserversManager>

@Inject
lateinit var globalDataStore: GlobalDataStore
lateinit var globalDataStore: Lazy<GlobalDataStore>

@Inject
@ApplicationScope
lateinit var globalAppScope: CoroutineScope
override fun getWorkManagerConfiguration(): Configuration {
return Configuration.Builder()
.setWorkerFactory(wireWorkerFactory)
.setWorkerFactory(wireWorkerFactory.get())
.build()
}

Expand All @@ -81,23 +79,19 @@ class WireApplication : Application(), Configuration.Provider {

enableStrictMode()

if (this.isGoogleServicesAvailable()) {
val firebaseOptions = FirebaseOptions.Builder()
.setApplicationId(BuildConfig.FIREBASE_APP_ID)
.setGcmSenderId(BuildConfig.FIREBASE_PUSH_SENDER_ID)
.setApiKey(BuildConfig.GOOGLE_API_KEY)
.setProjectId(BuildConfig.FCM_PROJECT_ID)
.build()
FirebaseApp.initializeApp(this, firebaseOptions)
}
globalAppScope.launch {
initializeApplicationLoggingFrameworks()

initializeApplicationLoggingFrameworks()
connectionPolicyManager.startObservingAppLifecycle()
appLogger.i("$TAG app lifecycle")
connectionPolicyManager.get().startObservingAppLifecycle()

// TODO: Can be handled in one of Sync steps
coreLogic.updateApiVersionsScheduler.schedulePeriodicApiVersionUpdate()
appLogger.i("$TAG api version update")
// TODO: Can be handled in one of Sync steps
coreLogic.get().updateApiVersionsScheduler.schedulePeriodicApiVersionUpdate()

globalObserversManager.observe()
appLogger.i("$TAG global observers")
globalObserversManager.get().observe()
}
}

private fun enableStrictMode() {
Expand All @@ -121,29 +115,27 @@ class WireApplication : Application(), Configuration.Provider {
}
}

private fun initializeApplicationLoggingFrameworks() {
globalAppScope.launch {
// 1. Datadog should be initialized first
ExternalLoggerManager.initDatadogLogger(applicationContext, globalDataStore)
// 2. Initialize our internal logging framework
val isLoggingEnabled = globalDataStore.isLoggingEnabled().first()
val config = if (isLoggingEnabled) {
KaliumLogger.Config.DEFAULT.apply {
setLogLevel(KaliumLogLevel.VERBOSE)
setLogWriterList(listOf(DataDogLogger, platformLogWriter()))
}
} else {
KaliumLogger.Config.disabled()
private suspend fun initializeApplicationLoggingFrameworks() {
// 1. Datadog should be initialized first
ExternalLoggerManager.initDatadogLogger(applicationContext, globalDataStore.get())
// 2. Initialize our internal logging framework
val isLoggingEnabled = globalDataStore.get().isLoggingEnabled().first()
val config = if (isLoggingEnabled) {
KaliumLogger.Config.DEFAULT.apply {
setLogLevel(KaliumLogLevel.VERBOSE)
setLogWriterList(listOf(DataDogLogger, platformLogWriter()))
}
// 2. Initialize our internal logging framework
AppLogger.init(config)
CoreLogger.init(config)
// 3. Initialize our internal FILE logging framework
logFileWriter.start()
// 4. Everything ready, now we can log device info
appLogger.i("Logger enabled")
logDeviceInformation()
} else {
KaliumLogger.Config.disabled()
}
// 2. Initialize our internal logging framework
AppLogger.init(config)
CoreLogger.init(config)
// 3. Initialize our internal FILE logging framework
logFileWriter.get().start()
// 4. Everything ready, now we can log device info
appLogger.i("Logger enabled")
logDeviceInformation()
}

private fun logDeviceInformation() {
Expand All @@ -169,7 +161,7 @@ class WireApplication : Application(), Configuration.Provider {
override fun onLowMemory() {
super.onLowMemory()
appLogger.w("onLowMemory called - Stopping logging, buckling the seatbelt and hoping for the best!")
logFileWriter.stop()
logFileWriter.get().stop()
}

private companion object {
Expand All @@ -190,5 +182,6 @@ class WireApplication : Application(), Configuration.Provider {
values().firstOrNull { it.level == value } ?: TRIM_MEMORY_UNKNOWN
}
}
private const val TAG = "WireApplication"
}
}
@@ -0,0 +1,40 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.initializer

import android.content.Context
import androidx.startup.Initializer
import com.google.firebase.FirebaseApp
import com.google.firebase.FirebaseOptions
import com.wire.android.BuildConfig
import com.wire.android.util.extension.isGoogleServicesAvailable

class FirebaseInitializer : Initializer<Unit> {
override fun create(context: Context) {
if (context.isGoogleServicesAvailable()) {
val firebaseOptions = FirebaseOptions.Builder()
.setApplicationId(BuildConfig.FIREBASE_APP_ID)
.setGcmSenderId(BuildConfig.FIREBASE_PUSH_SENDER_ID)
.setApiKey(BuildConfig.GOOGLE_API_KEY)
.setProjectId(BuildConfig.FCM_PROJECT_ID)
.build()
FirebaseApp.initializeApp(context, firebaseOptions)
}
}
override fun dependencies(): List<Class<out Initializer<*>>> = emptyList() // no dependencies on other libraries
}
@@ -0,0 +1,37 @@
/*
* Wire
* Copyright (C) 2024 Wire Swiss GmbH
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.wire.android.initializer

import android.content.Context
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent

@EntryPoint
@InstallIn(SingletonComponent::class)
interface InitializerEntryPoint {

companion object {
// a helper method to resolve the InitializerEntryPoint from the context
fun resolve(context: Context): InitializerEntryPoint {
val appContext = context.applicationContext ?: throw IllegalStateException()
return EntryPointAccessors.fromApplication(appContext, InitializerEntryPoint::class.java)
}
}
}

0 comments on commit 5ddbbe0

Please sign in to comment.