зеркало из https://github.com/mozilla/glean.git
Only allow the registration of lifecycle observers on the main thread
This commit is contained in:
Родитель
f2e6c3ab45
Коммит
808c8d2a4c
|
@ -91,6 +91,7 @@ The following steps are required for applications using the Glean SDK, but not l
|
|||
|
||||
The Glean SDK should only be initialized from the main application, not individual libraries. If you are adding Glean support to a library, you can safely skip this section.
|
||||
Please also note that the Glean SDK does not support use across multiple processes, and must only be initialized on the application's main process. Initializing in other processes is a no-op.
|
||||
Additionally, Glean must be initialized on the main (UI) thread of the applications main process. Failure to do so will throw an `IllegalThreadStateException`.
|
||||
|
||||
Before any data collection can take place, the Glean SDK **must** be initialized from the application.
|
||||
An excellent place to perform this operation is within the `onCreate` method of the class that extends Android's `Application` class.
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.content.Context
|
|||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import android.os.Process
|
||||
import androidx.annotation.MainThread
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import com.sun.jna.StringArray
|
||||
|
@ -33,6 +34,7 @@ import mozilla.telemetry.glean.private.RecordedExperimentData
|
|||
import mozilla.telemetry.glean.scheduler.GleanLifecycleObserver
|
||||
import mozilla.telemetry.glean.scheduler.PingUploadWorker
|
||||
import mozilla.telemetry.glean.scheduler.MetricsPingScheduler
|
||||
import mozilla.telemetry.glean.utils.ThreadUtils
|
||||
import org.json.JSONObject
|
||||
|
||||
/**
|
||||
|
@ -94,6 +96,8 @@ open class GleanInternalAPI internal constructor () {
|
|||
* A LifecycleObserver will be added to send pings when the application goes
|
||||
* into the background.
|
||||
*
|
||||
* This method must be called from the main thread.
|
||||
*
|
||||
* @param applicationContext [Context] to access application features, such
|
||||
* as shared preferences
|
||||
* @param configuration A Glean [Configuration] object with global settings.
|
||||
|
@ -101,10 +105,17 @@ open class GleanInternalAPI internal constructor () {
|
|||
@Suppress("ReturnCount", "LongMethod")
|
||||
@JvmOverloads
|
||||
@Synchronized
|
||||
@MainThread
|
||||
fun initialize(
|
||||
applicationContext: Context,
|
||||
configuration: Configuration = Configuration()
|
||||
) {
|
||||
// Glean initialization must be called on the main thread, or lifecycle
|
||||
// registration may fail. This is also enforced at build time by the
|
||||
// @MainThread decorator, but this run time check is also performed to
|
||||
// be extra certain.
|
||||
ThreadUtils.assertOnUiThread()
|
||||
|
||||
// In certain situations Glean.initialize may be called from a process other than the main
|
||||
// process. In this case we want initialize to be a no-op and just return.
|
||||
if (!isMainProcess(applicationContext)) {
|
||||
|
@ -193,6 +204,8 @@ open class GleanInternalAPI internal constructor () {
|
|||
Dispatchers.API.flushQueuedInitialTasks()
|
||||
|
||||
// At this point, all metrics and events can be recorded.
|
||||
// This should only be called from the main thread. This is enforced by
|
||||
// the @MainThread decorator and the `assertOnUiThread` call.
|
||||
ProcessLifecycleOwner.get().lifecycle.addObserver(gleanLifecycleObserver)
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import mozilla.telemetry.glean.GleanMetrics.Pings
|
|||
import mozilla.telemetry.glean.utils.getISOTimeString
|
||||
import mozilla.telemetry.glean.utils.parseISOTimeString
|
||||
import mozilla.telemetry.glean.private.TimeUnit
|
||||
import mozilla.telemetry.glean.utils.ThreadUtils
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit as AndroidTimeUnit
|
||||
|
@ -64,6 +65,15 @@ internal class MetricsPingScheduler(
|
|||
}
|
||||
|
||||
init {
|
||||
// This should only be called from the main thread.
|
||||
// We can't enforce this at build time here, since the @MainThread
|
||||
// decorator can not be applied to a contructor. However, in practice
|
||||
// this is only called from Glean.initialize which does have that
|
||||
// decorator. For good measure, we also perform this run time check
|
||||
// here.
|
||||
// See https://bugzilla.mozilla.org/show_bug.cgi?id=1581556
|
||||
ThreadUtils.assertOnUiThread()
|
||||
|
||||
// When performing the data migration from glean-ac, this scheduler might be
|
||||
// provided with a date the 'metrics' ping was last sent. If so, save that in
|
||||
// the new storage and use it in this scheduler.
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
package mozilla.telemetry.glean.utils
|
||||
|
||||
import android.os.Looper
|
||||
|
||||
object ThreadUtils {
|
||||
private val uiThread = Looper.getMainLooper().thread
|
||||
|
||||
/**
|
||||
* Assert that this code is run on the main (UI) thread.
|
||||
*/
|
||||
fun assertOnUiThread() {
|
||||
val currentThread = Thread.currentThread()
|
||||
val currentThreadId = currentThread.id
|
||||
val expectedThreadId = uiThread.id
|
||||
|
||||
if (currentThreadId == expectedThreadId) {
|
||||
return
|
||||
}
|
||||
|
||||
throw IllegalThreadStateException("Expected UI thread, but running on " + currentThread.name)
|
||||
}
|
||||
}
|
|
@ -10,6 +10,7 @@ import androidx.lifecycle.LifecycleOwner
|
|||
import androidx.lifecycle.LifecycleRegistry
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import kotlinx.coroutines.Dispatchers as KotlinDispatchers
|
||||
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import mozilla.telemetry.glean.GleanMetrics.GleanInternalMetrics
|
||||
|
@ -522,4 +523,13 @@ class GleanTest {
|
|||
|
||||
assertFalse(Glean.isMainProcess(context))
|
||||
}
|
||||
|
||||
@Test(expected = IllegalThreadStateException::class)
|
||||
fun `Glean initialize must be called on the main thread`() {
|
||||
runBlocking(KotlinDispatchers.IO) {
|
||||
val context: Context = ApplicationProvider.getApplicationContext()
|
||||
|
||||
Glean.initialize(context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче