parent
d5ec200617
commit
d55b525069
|
|
@ -179,7 +179,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
findViewById<TextView>(R.id.tvOpenTestProcessActivity).setOnClickListener {
|
findViewById<TextView>(R.id.tvOpenTestProcessActivity).setOnClickListener {
|
||||||
// TestProcessActivity.startActivity(this)
|
// TestProcessActivity.startActivity(this)
|
||||||
GuruAnalytics.Builder(this)
|
GuruAnalytics.Builder(this, "v3.0.0")
|
||||||
.setBatchLimit(25)
|
.setBatchLimit(25)
|
||||||
.setUploadPeriodInSeconds(60)
|
.setUploadPeriodInSeconds(60)
|
||||||
.setStartUploadDelayInSecond(3)
|
.setStartUploadDelayInSecond(3)
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ class TestProcessActivity : AppCompatActivity() {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_test_process)
|
setContentView(R.layout.activity_test_process)
|
||||||
|
|
||||||
GuruAnalytics.Builder(this)
|
GuruAnalytics.Builder(this, "v3.0.0")
|
||||||
.setBatchLimit(25)
|
.setBatchLimit(25)
|
||||||
.setUploadPeriodInSeconds(60)
|
.setUploadPeriodInSeconds(60)
|
||||||
.setStartUploadDelayInSecond(3)
|
.setStartUploadDelayInSecond(3)
|
||||||
|
|
|
||||||
|
|
@ -20,4 +20,6 @@ kotlin.code.style=official
|
||||||
# Enables namespacing of each library's R class so that its R class includes only the
|
# Enables namespacing of each library's R class so that its R class includes only the
|
||||||
# resources declared in the library itself and none from the library's dependencies,
|
# resources declared in the library itself and none from the library's dependencies,
|
||||||
# thereby reducing the size of the R class for that library
|
# thereby reducing the size of the R class for that library
|
||||||
android.nonTransitiveRClass=true
|
android.nonTransitiveRClass=true
|
||||||
|
|
||||||
|
guruAnalyticsSdkVersion=1.1.2
|
||||||
|
|
@ -30,6 +30,7 @@ android {
|
||||||
versionName "1.0"
|
versionName "1.0"
|
||||||
|
|
||||||
buildConfigField "String", "buildTs", buildTs()
|
buildConfigField "String", "buildTs", buildTs()
|
||||||
|
buildConfigField "String", "guruAnalyticsSdkVersion", "\"v$guruAnalyticsSdkVersion\""
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ publishing { // Repositories *to* which Gradle can publish artifacts
|
||||||
maven(MavenPublication) {
|
maven(MavenPublication) {
|
||||||
groupId 'guru.core.analytics'
|
groupId 'guru.core.analytics'
|
||||||
artifactId 'guru_analytics'
|
artifactId 'guru_analytics'
|
||||||
version '1.1.0' // Your package version
|
// version '1.1.1' // Your package version
|
||||||
|
version = project.findProperty("guruAnalyticsSdkVersion") ?: "unknown"
|
||||||
// artifact publishArtifact //Example: *./target/myJavaClasses.jar*
|
// artifact publishArtifact //Example: *./target/myJavaClasses.jar*
|
||||||
// artifact "build/outputs/aar/aar-test-release.aar"//aar包的目录
|
// artifact "build/outputs/aar/aar-test-release.aar"//aar包的目录
|
||||||
afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) }
|
afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="guru.core.analytics">
|
package="guru.core.analytics">
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
|
||||||
|
|
@ -12,14 +12,18 @@ object Constants {
|
||||||
const val EVENT = "event"
|
const val EVENT = "event"
|
||||||
const val PARAM = "param"
|
const val PARAM = "param"
|
||||||
const val SCREEN = "screen_name"
|
const val SCREEN = "screen_name"
|
||||||
|
const val SESSION_ID = "session_id"
|
||||||
const val ITEM_CATEGORY = "item_category"
|
const val ITEM_CATEGORY = "item_category"
|
||||||
const val ITEM_NAME = "item_name"
|
const val ITEM_NAME = "item_name"
|
||||||
const val VALUE = "value"
|
const val VALUE = "value"
|
||||||
const val FG = "fg"
|
const val FG = "guru_engagement"
|
||||||
const val DURATION = "duration"
|
const val DURATION = "duration"
|
||||||
const val FIRST_OPEN = "first_open"
|
const val FIRST_OPEN = "first_open"
|
||||||
const val ERROR_PROCESS = "error_process"
|
const val ERROR_PROCESS = "error_process"
|
||||||
const val PROCESS = "process"
|
const val PROCESS = "process"
|
||||||
|
const val SESSION_START = "session_start"
|
||||||
|
const val SDK_INIT = "guru_sdk_init"
|
||||||
|
const val SDK_INIT_COMPLETE = "guru_sdk_init_complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
object Ids {
|
object Ids {
|
||||||
|
|
@ -43,7 +47,9 @@ object Constants {
|
||||||
const val SCREEN_W = "screenW"
|
const val SCREEN_W = "screenW"
|
||||||
const val OS_VERSION = "osVersion"
|
const val OS_VERSION = "osVersion"
|
||||||
const val LANGUAGE = "language"
|
const val LANGUAGE = "language"
|
||||||
const val SDK_INFO = "sdkInfo"
|
const val SDK_BUILD_ID = "guruAnalyticsBuildId"
|
||||||
|
const val SDK_VERSION = "guruAnalyticsVersion"
|
||||||
|
const val GURU_SDK_VERSION = "gurusdkVersion"
|
||||||
}
|
}
|
||||||
|
|
||||||
object Properties {
|
object Properties {
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ abstract class GuruAnalytics {
|
||||||
mainProcess: String? = null,
|
mainProcess: String? = null,
|
||||||
isEnableCronet: Boolean? = null,
|
isEnableCronet: Boolean? = null,
|
||||||
uploadIpAddress: List<String>? = null,
|
uploadIpAddress: List<String>? = null,
|
||||||
dnsMode: Int? = null
|
dnsMode: Int? = null,
|
||||||
|
guruSdkVersion: String = ""
|
||||||
)
|
)
|
||||||
|
|
||||||
abstract fun setUploadEventBaseUrl(context: Context, updateEventBaseUrl: String)
|
abstract fun setUploadEventBaseUrl(context: Context, updateEventBaseUrl: String)
|
||||||
|
|
@ -95,7 +96,7 @@ abstract class GuruAnalytics {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder(val context: Context) {
|
class Builder(val context: Context, private val guruSdkVersion: String) {
|
||||||
private val analyticsInfo = AnalyticsInfo()
|
private val analyticsInfo = AnalyticsInfo()
|
||||||
|
|
||||||
fun setBatchLimit(batchLimit: Int?) = apply { analyticsInfo.batchLimit = batchLimit }
|
fun setBatchLimit(batchLimit: Int?) = apply { analyticsInfo.batchLimit = batchLimit }
|
||||||
|
|
@ -162,7 +163,8 @@ abstract class GuruAnalytics {
|
||||||
mainProcess,
|
mainProcess,
|
||||||
isEnableCronet,
|
isEnableCronet,
|
||||||
uploadIpAddress,
|
uploadIpAddress,
|
||||||
dnsMode
|
dnsMode,
|
||||||
|
guruSdkVersion
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return INSTANCE
|
return INSTANCE
|
||||||
|
|
|
||||||
|
|
@ -60,5 +60,7 @@ class PreferencesManager private constructor(
|
||||||
var deviceId: String? by bind("device_id", "")
|
var deviceId: String? by bind("device_id", "")
|
||||||
var adId: String? by bind("ad_id", "")
|
var adId: String? by bind("ad_id", "")
|
||||||
|
|
||||||
|
var sessionDate: String? by bind("session_date", "")
|
||||||
|
var sessionCount: Int? by bind("session_count", 0)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ object GuruAnalyticsAudit {
|
||||||
var enabledCronet: Boolean = false
|
var enabledCronet: Boolean = false
|
||||||
var eventDispatcherStarted: Boolean = false
|
var eventDispatcherStarted: Boolean = false
|
||||||
var fgHelperInitialized: Boolean = false
|
var fgHelperInitialized: Boolean = false
|
||||||
var connectionState: Boolean = false
|
var networkAvailable: Boolean = false
|
||||||
|
|
||||||
// 整体的
|
// 整体的
|
||||||
var total: Int = 0
|
var total: Int = 0
|
||||||
|
|
@ -54,7 +54,7 @@ object GuruAnalyticsAudit {
|
||||||
dnsMode = dnsMode,
|
dnsMode = dnsMode,
|
||||||
eventDispatcherStarted = eventDispatcherStarted,
|
eventDispatcherStarted = eventDispatcherStarted,
|
||||||
fgHelperInitialized = fgHelperInitialized,
|
fgHelperInitialized = fgHelperInitialized,
|
||||||
connectionState = connectionState,
|
connectionState = networkAvailable,
|
||||||
total = total,
|
total = total,
|
||||||
deleted = deleted,
|
deleted = deleted,
|
||||||
uploaded = uploaded,
|
uploaded = uploaded,
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import java.util.*
|
||||||
|
|
||||||
object DeviceInfoStore {
|
object DeviceInfoStore {
|
||||||
|
|
||||||
const val SDK_VERSION = "v1.1.0"
|
var GURU_SDK_VERSION = ""
|
||||||
|
|
||||||
private val deviceInfoSubject: BehaviorSubject<Map<String, Any>> =
|
private val deviceInfoSubject: BehaviorSubject<Map<String, Any>> =
|
||||||
BehaviorSubject.createDefault(
|
BehaviorSubject.createDefault(
|
||||||
|
|
@ -41,7 +41,9 @@ object DeviceInfoStore {
|
||||||
map[Constants.DeviceInfo.SCREEN_W] = AndroidUtils.getWindowWidth(context) ?: 0
|
map[Constants.DeviceInfo.SCREEN_W] = AndroidUtils.getWindowWidth(context) ?: 0
|
||||||
map[Constants.DeviceInfo.OS_VERSION] = Build.VERSION.RELEASE
|
map[Constants.DeviceInfo.OS_VERSION] = Build.VERSION.RELEASE
|
||||||
map[Constants.DeviceInfo.LANGUAGE] = Locale.getDefault().language
|
map[Constants.DeviceInfo.LANGUAGE] = Locale.getDefault().language
|
||||||
map[Constants.DeviceInfo.SDK_INFO] = "${SDK_VERSION}-${BuildConfig.buildTs}"
|
map[Constants.DeviceInfo.SDK_VERSION] = BuildConfig.guruAnalyticsSdkVersion
|
||||||
|
map[Constants.DeviceInfo.SDK_BUILD_ID] = BuildConfig.buildTs
|
||||||
|
map[Constants.DeviceInfo.GURU_SDK_VERSION] = GURU_SDK_VERSION
|
||||||
deviceInfoSubject.onNext(map)
|
deviceInfoSubject.onNext(map)
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_DEVICE_INFO, map)
|
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_DEVICE_INFO, map)
|
||||||
Timber.tag("DeviceInfoStore").i("DeviceInfo: $map")
|
Timber.tag("DeviceInfoStore").i("DeviceInfo: $map")
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ object EventInfoStore {
|
||||||
restoreIds(this)
|
restoreIds(this)
|
||||||
GuruAnalyticsAudit.total = this.eventCountAll ?: 0
|
GuruAnalyticsAudit.total = this.eventCountAll ?: 0
|
||||||
GuruAnalyticsAudit.uploaded = this.eventCountUploaded ?: 0
|
GuruAnalyticsAudit.uploaded = this.eventCountUploaded ?: 0
|
||||||
|
GuruAnalyticsAudit.deleted = this.eventCountDeleted ?: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +164,10 @@ object EventInfoStore {
|
||||||
|
|
||||||
private val supplementEventParamsSubject: BehaviorSubject<Map<String, Any>> =
|
private val supplementEventParamsSubject: BehaviorSubject<Map<String, Any>> =
|
||||||
BehaviorSubject.createDefault(
|
BehaviorSubject.createDefault(
|
||||||
hashMapOf(Constants.Event.SCREEN to "main")
|
hashMapOf(
|
||||||
|
Constants.Event.SCREEN to "main",
|
||||||
|
Constants.Event.SESSION_ID to SESSION.hashCode()
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
private val idsSubject: BehaviorSubject<Map<String, String>> =
|
private val idsSubject: BehaviorSubject<Map<String, String>> =
|
||||||
|
|
@ -184,7 +188,10 @@ object EventInfoStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var supplementEventParams: Map<String, Any>
|
private var supplementEventParams: Map<String, Any>
|
||||||
get() = supplementEventParamsSubject.value ?: hashMapOf(Constants.Event.SCREEN to "main")
|
get() = supplementEventParamsSubject.value ?: hashMapOf(
|
||||||
|
Constants.Event.SCREEN to "main",
|
||||||
|
Constants.Event.SESSION_ID to SESSION.hashCode()
|
||||||
|
)
|
||||||
set(value) {
|
set(value) {
|
||||||
supplementEventParamsSubject.onNext(value)
|
supplementEventParamsSubject.onNext(value)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,11 +37,13 @@ internal class AppLifecycleMonitor internal constructor(context: Context) {
|
||||||
when (event) {
|
when (event) {
|
||||||
Lifecycle.Event.ON_START -> {
|
Lifecycle.Event.ON_START -> {
|
||||||
Timber.d("${TAG}_ON_START")
|
Timber.d("${TAG}_ON_START")
|
||||||
fgHelper.start()
|
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.LIFECYCLE_START)
|
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.LIFECYCLE_START)
|
||||||
}
|
}
|
||||||
|
|
||||||
Lifecycle.Event.ON_RESUME -> Timber.d("${TAG}_ON_RESUME")
|
Lifecycle.Event.ON_RESUME -> {
|
||||||
|
Timber.d("${TAG}_ON_RESUME")
|
||||||
|
fgHelper.start()
|
||||||
|
}
|
||||||
Lifecycle.Event.ON_PAUSE -> {
|
Lifecycle.Event.ON_PAUSE -> {
|
||||||
Timber.d("${TAG}_ON_PAUSE")
|
Timber.d("${TAG}_ON_PAUSE")
|
||||||
fgHelper.stop()
|
fgHelper.stop()
|
||||||
|
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
package guru.core.analytics.impl
|
|
||||||
|
|
||||||
import android.os.SystemClock
|
|
||||||
import guru.core.analytics.data.db.GuruAnalyticsDatabase
|
|
||||||
import guru.core.analytics.data.model.AnalyticsOptions
|
|
||||||
import guru.core.analytics.data.model.EventItem
|
|
||||||
import guru.core.analytics.data.model.GuruAnalyticsAudit
|
|
||||||
import guru.core.analytics.data.store.EventInfoStore
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.concurrent.ConcurrentLinkedQueue
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
|
||||||
|
|
||||||
|
|
||||||
class PendingEvent(
|
|
||||||
val item: EventItem,
|
|
||||||
val options: AnalyticsOptions
|
|
||||||
) {
|
|
||||||
val at = SystemClock.elapsedRealtime()
|
|
||||||
}
|
|
||||||
|
|
||||||
data object EventDispatcher : EventDeliver {
|
|
||||||
|
|
||||||
private val pendingEvents = ConcurrentLinkedQueue<PendingEvent>()
|
|
||||||
|
|
||||||
private val started = AtomicBoolean(false)
|
|
||||||
|
|
||||||
private fun dispatchPendingEvent() {
|
|
||||||
Timber.d("EventDispatcher dispatchPendingEvent ${pendingEvents.size}!")
|
|
||||||
if (pendingEvents.isNotEmpty()) {
|
|
||||||
while (true) {
|
|
||||||
val pendingEvent = pendingEvents.poll() ?: return
|
|
||||||
val event = EventInfoStore.deriveEvent(
|
|
||||||
pendingEvent.item,
|
|
||||||
priority = pendingEvent.options.priority,
|
|
||||||
elapsed = SystemClock.elapsedRealtime() - pendingEvent.at
|
|
||||||
)
|
|
||||||
GuruAnalyticsDatabase.getInstance().eventDao().addEvent(event)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun start() {
|
|
||||||
if (started.compareAndSet(false, true)) {
|
|
||||||
Timber.d("EventDispatcher started!")
|
|
||||||
dispatchPendingEvent()
|
|
||||||
GuruAnalyticsAudit.eventDispatcherStarted = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deliverEvent(item: EventItem, options: AnalyticsOptions) {
|
|
||||||
if (started.get()) {
|
|
||||||
Timber.d("EventDispatcher deliverEvent!")
|
|
||||||
dispatchPendingEvent()
|
|
||||||
val event = EventInfoStore.deriveEvent(item, priority = options.priority)
|
|
||||||
GuruAnalyticsDatabase.getInstance().eventDao().addEvent(event)
|
|
||||||
} else {
|
|
||||||
Timber.d("EventDispatcher deliverEvent pending!")
|
|
||||||
pendingEvents.offer(PendingEvent(item, options))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deliverProperty(name: String, value: String) {
|
|
||||||
EventInfoStore.setUserProperty(name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeProperties(keys: Set<String>) {
|
|
||||||
EventInfoStore.removeUserProperties(keys)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,20 +1,15 @@
|
||||||
package guru.core.analytics.impl
|
package guru.core.analytics.impl
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
|
||||||
import androidx.work.BackoffPolicy
|
import androidx.work.BackoffPolicy
|
||||||
import androidx.work.Constraints
|
import androidx.work.Constraints
|
||||||
import androidx.work.ExistingPeriodicWorkPolicy
|
|
||||||
import androidx.work.ExistingWorkPolicy
|
import androidx.work.ExistingWorkPolicy
|
||||||
import androidx.work.NetworkType
|
import androidx.work.NetworkType
|
||||||
import androidx.work.OneTimeWorkRequestBuilder
|
import androidx.work.OneTimeWorkRequestBuilder
|
||||||
import androidx.work.PeriodicWorkRequestBuilder
|
|
||||||
import androidx.work.SystemClock
|
|
||||||
import androidx.work.WorkManager
|
import androidx.work.WorkManager
|
||||||
import guru.core.analytics.Constants
|
import guru.core.analytics.Constants
|
||||||
import guru.core.analytics.GuruAnalytics
|
import guru.core.analytics.GuruAnalytics
|
||||||
import guru.core.analytics.data.api.GuruRepository
|
import guru.core.analytics.data.api.GuruRepository
|
||||||
import guru.core.analytics.data.api.ServiceLocator
|
|
||||||
import guru.core.analytics.data.db.GuruAnalyticsDatabase
|
import guru.core.analytics.data.db.GuruAnalyticsDatabase
|
||||||
import guru.core.analytics.data.db.model.EventEntity
|
import guru.core.analytics.data.db.model.EventEntity
|
||||||
import guru.core.analytics.data.db.model.EventPriority
|
import guru.core.analytics.data.db.model.EventPriority
|
||||||
|
|
@ -44,6 +39,7 @@ import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
import kotlin.math.ceil
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
internal class EventEngine internal constructor(
|
internal class EventEngine internal constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
|
|
@ -51,21 +47,21 @@ internal class EventEngine internal constructor(
|
||||||
private val uploadPeriodInSeconds: Long = DEFAULT_UPLOAD_PERIOD_IN_SECONDS,
|
private val uploadPeriodInSeconds: Long = DEFAULT_UPLOAD_PERIOD_IN_SECONDS,
|
||||||
private val eventExpiredInDays: Int = DEFAULT_EVENT_EXPIRED_IN_DAYS,
|
private val eventExpiredInDays: Int = DEFAULT_EVENT_EXPIRED_IN_DAYS,
|
||||||
private val guruRepository: GuruRepository
|
private val guruRepository: GuruRepository
|
||||||
) : EventDeliver {
|
) {
|
||||||
|
|
||||||
private val preferencesManager by lazy {
|
private val preferencesManager: PreferencesManager by lazy {
|
||||||
PreferencesManager.getInstance(context)
|
PreferencesManager.getInstance(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val connectivity: Connectivity by lazy {
|
private val connectivity: Connectivity by lazy {
|
||||||
Connectivity(context)
|
Connectivity.of(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val lifecycleMonitor = AppLifecycleMonitor(context)
|
private val lifecycleMonitor = AppLifecycleMonitor(context)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val TAG = "UploadEvents"
|
private const val TAG = "UploadEvents"
|
||||||
const val DEFAULT_UPLOAD_PERIOD_IN_SECONDS = 60L // 接口上传间隔时间 秒
|
const val DEFAULT_UPLOAD_PERIOD_IN_SECONDS = 45L // 接口上传间隔时间 秒
|
||||||
const val DEFAULT_BATCH_LIMIT = 25 // 一次上传event最多数量
|
const val DEFAULT_BATCH_LIMIT = 25 // 一次上传event最多数量
|
||||||
const val DEFAULT_EVENT_EXPIRED_IN_DAYS = 7
|
const val DEFAULT_EVENT_EXPIRED_IN_DAYS = 7
|
||||||
|
|
||||||
|
|
@ -80,7 +76,6 @@ internal class EventEngine internal constructor(
|
||||||
@Volatile
|
@Volatile
|
||||||
var sessionActivated = false
|
var sessionActivated = false
|
||||||
|
|
||||||
|
|
||||||
fun logDebug(message: String, vararg args: Any?) {
|
fun logDebug(message: String, vararg args: Any?) {
|
||||||
if (GuruAnalytics.INSTANCE.isDebug()) {
|
if (GuruAnalytics.INSTANCE.isDebug()) {
|
||||||
Timber.tag(TAG).d(message, args)
|
Timber.tag(TAG).d(message, args)
|
||||||
|
|
@ -101,13 +96,10 @@ internal class EventEngine internal constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
private var compositeDisposable: CompositeDisposable = CompositeDisposable()
|
private var compositeDisposable: CompositeDisposable = CompositeDisposable()
|
||||||
private val pendingEventSubject: PublishSubject<Int> = PublishSubject.create()
|
|
||||||
private val forceTriggerSubject: PublishSubject<String> = PublishSubject.create()
|
|
||||||
internal val started = AtomicBoolean(false)
|
internal val started = AtomicBoolean(false)
|
||||||
private var enableUpload = true
|
private var enableUpload = true
|
||||||
private var latestValidActionTs = 0L
|
private var latestValidActionTs = 0L
|
||||||
|
|
||||||
|
|
||||||
fun setEnableUpload(enable: Boolean) {
|
fun setEnableUpload(enable: Boolean) {
|
||||||
enableUpload = enable
|
enableUpload = enable
|
||||||
val extMap = mapOf("enable" to enable)
|
val extMap = mapOf("enable" to enable)
|
||||||
|
|
@ -116,6 +108,7 @@ internal class EventEngine internal constructor(
|
||||||
|
|
||||||
fun start(startUploadDelay: Long?) {
|
fun start(startUploadDelay: Long?) {
|
||||||
if (started.compareAndSet(false, true)) {
|
if (started.compareAndSet(false, true)) {
|
||||||
|
AnchorAt.recordAt(AnchorAt.SESSION_START)
|
||||||
prepare()
|
prepare()
|
||||||
connectivity.bind()
|
connectivity.bind()
|
||||||
lifecycleMonitor.initialize()
|
lifecycleMonitor.initialize()
|
||||||
|
|
@ -131,8 +124,7 @@ internal class EventEngine internal constructor(
|
||||||
logDebug("session started error!")
|
logDebug("session started error!")
|
||||||
}
|
}
|
||||||
scheduler.scheduleDirect({
|
scheduler.scheduleDirect({
|
||||||
EventDispatcher.start()
|
EventSink.start()
|
||||||
logFirstOpen()
|
|
||||||
startWork()
|
startWork()
|
||||||
val extMap = mapOf("startUploadDelayInSecond" to startUploadDelay)
|
val extMap = mapOf("startUploadDelayInSecond" to startUploadDelay)
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.STATE_START_WORK, extMap)
|
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.STATE_START_WORK, extMap)
|
||||||
|
|
@ -141,16 +133,6 @@ internal class EventEngine internal constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logFirstOpen() {
|
|
||||||
if (preferencesManager.isFirstOpen == true) {
|
|
||||||
GuruAnalytics.INSTANCE.logEvent(
|
|
||||||
Constants.Event.FIRST_OPEN, options = AnalyticsOptions(EventPriority.EMERGENCE)
|
|
||||||
)
|
|
||||||
preferencesManager.isFirstOpen = false
|
|
||||||
|
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_FIRST_OPEN)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun logSessionActive() {
|
private fun logSessionActive() {
|
||||||
|
|
||||||
|
|
@ -172,6 +154,7 @@ internal class EventEngine internal constructor(
|
||||||
logEvent(event)
|
logEvent(event)
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_SESSION_ACTIVE)
|
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_SESSION_ACTIVE)
|
||||||
sessionActivated = true
|
sessionActivated = true
|
||||||
|
|
||||||
}.onErrorReturn {
|
}.onErrorReturn {
|
||||||
Timber.e("active error! $it")
|
Timber.e("active error! $it")
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_SESSION_START_ERROR, it.message)
|
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_SESSION_START_ERROR, it.message)
|
||||||
|
|
@ -185,7 +168,7 @@ internal class EventEngine internal constructor(
|
||||||
private fun dispatchActiveWorker() {
|
private fun dispatchActiveWorker() {
|
||||||
Timber.e("dispatchActiveWorker...")
|
Timber.e("dispatchActiveWorker...")
|
||||||
val constraints = Constraints.Builder()
|
val constraints = Constraints.Builder()
|
||||||
.setRequiredNetworkType(NetworkType.UNMETERED)
|
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||||
.build()
|
.build()
|
||||||
val request = OneTimeWorkRequestBuilder<ActiveWorker>()
|
val request = OneTimeWorkRequestBuilder<ActiveWorker>()
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.MINUTES)
|
.setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.MINUTES)
|
||||||
|
|
@ -209,7 +192,6 @@ internal class EventEngine internal constructor(
|
||||||
private fun startWork() {
|
private fun startWork() {
|
||||||
Timber.d("startWork")
|
Timber.d("startWork")
|
||||||
pollEvents()
|
pollEvents()
|
||||||
forceUpload("startWork")
|
|
||||||
Timber.d("UploadEventDaemon started!!")
|
Timber.d("UploadEventDaemon started!!")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -236,19 +218,25 @@ internal class EventEngine internal constructor(
|
||||||
private fun pollEvents() {
|
private fun pollEvents() {
|
||||||
logDebug("pollEvents()!! $uploadPeriodInSeconds $batchLimit")
|
logDebug("pollEvents()!! $uploadPeriodInSeconds $batchLimit")
|
||||||
val periodFlowable = Flowable.interval(5, uploadPeriodInSeconds, TimeUnit.SECONDS).onBackpressureDrop()
|
val periodFlowable = Flowable.interval(5, uploadPeriodInSeconds, TimeUnit.SECONDS).onBackpressureDrop()
|
||||||
val forceFlowable = forceTriggerSubject.toFlowable(BackpressureStrategy.DROP).map { scene ->
|
val forceFlowable = EventSink.forceFlowable.map { scene ->
|
||||||
logDebug("force trigger: $scene")
|
logDebug("Force Trigger: $scene")
|
||||||
}
|
}
|
||||||
val networkFlowable = connectivity.networkAvailableFlowable
|
compositeDisposable.add(
|
||||||
compositeDisposable.add(Flowable.combineLatest(
|
Flowable.combineLatest(
|
||||||
periodFlowable, forceFlowable, networkFlowable,
|
periodFlowable, forceFlowable,
|
||||||
) { _, _, available ->
|
) { _, _ ->
|
||||||
GuruAnalyticsAudit.connectionState = available
|
val available = try {
|
||||||
val uploadedRate = GuruAnalyticsAudit.uploaded / GuruAnalyticsAudit.total.toFloat()
|
connectivity.isNetworkAvailable()
|
||||||
val ignoreAvailable = !GuruAnalyticsAudit.uploadReady && uploadedRate < 0.6
|
} catch (throwable: Throwable) {
|
||||||
logDebug("enableUpload:$enableUpload && (network:$available || ignoreAvailable: (${ignoreAvailable})[uploaded(${GuruAnalyticsAudit.uploaded}) / total(${GuruAnalyticsAudit.total}) = $uploadedRate])")
|
logInfo("networkAvailable error: $throwable")
|
||||||
return@combineLatest enableUpload && (available || ignoreAvailable)
|
GuruAnalyticsAudit.networkAvailable
|
||||||
}.flatMap { uploadEvents(100) }.subscribe()
|
}
|
||||||
|
GuruAnalyticsAudit.networkAvailable = available
|
||||||
|
val uploadedRate = GuruAnalyticsAudit.sessionUploaded / GuruAnalyticsAudit.sessionTotal.toFloat()
|
||||||
|
val ignoreAvailable = !GuruAnalyticsAudit.uploadReady && uploadedRate < 0.6
|
||||||
|
logDebug("enableUpload:$enableUpload && (network:$available || ignoreAvailable: (${ignoreAvailable})[uploaded(${GuruAnalyticsAudit.uploaded}) / total(${GuruAnalyticsAudit.total}) = $uploadedRate])")
|
||||||
|
return@combineLatest enableUpload && available
|
||||||
|
}.flatMap { uploadEvents(256) }.subscribe()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -286,14 +274,16 @@ internal class EventEngine internal constructor(
|
||||||
val eventDao = GuruAnalyticsDatabase.getInstance().eventDao()
|
val eventDao = GuruAnalyticsDatabase.getInstance().eventDao()
|
||||||
GuruAnalyticsAudit.uploadReady = true
|
GuruAnalyticsAudit.uploadReady = true
|
||||||
logDebug("uploadEvents: $count")
|
logDebug("uploadEvents: $count")
|
||||||
return Flowable.just(count).map { eventDao.loadAndMarkUploadEvents(it) }.onErrorReturn {
|
return Flowable.just(count)
|
||||||
try {
|
.map { eventDao.loadAndMarkUploadEvents(it) }
|
||||||
eventDao.loadAndMarkUploadEvents(batchLimit)
|
.onErrorReturn {
|
||||||
} catch (throwable: Throwable) {
|
try {
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_LOAD_MARK, it.message)
|
eventDao.loadAndMarkUploadEvents(batchLimit)
|
||||||
emptyList()
|
} catch (throwable: Throwable) {
|
||||||
}
|
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_LOAD_MARK, it.message)
|
||||||
}.filter { it.isNotEmpty() }.subscribeOn(dbScheduler).observeOn(scheduler).concatMap { splitEntities(it) }
|
emptyList()
|
||||||
|
}
|
||||||
|
}.filter { it.isNotEmpty() }.subscribeOn(dbScheduler).observeOn(scheduler).concatMap { splitEntities(it) }
|
||||||
.flatMapSingle { uploadEventsInternal(it) }.filter { it.isNotEmpty() }.doOnNext {
|
.flatMapSingle { uploadEventsInternal(it) }.filter { it.isNotEmpty() }.doOnNext {
|
||||||
eventDao.deleteEvents(it)
|
eventDao.deleteEvents(it)
|
||||||
if (GuruAnalytics.INSTANCE.isDebug()) {
|
if (GuruAnalytics.INSTANCE.isDebug()) {
|
||||||
|
|
@ -306,76 +296,42 @@ internal class EventEngine internal constructor(
|
||||||
|
|
||||||
private fun uploadEventsInternal(entities: List<EventEntity>): Single<List<EventEntity>> {
|
private fun uploadEventsInternal(entities: List<EventEntity>): Single<List<EventEntity>> {
|
||||||
val param = ApiParamUtils.generateApiParam(entities)
|
val param = ApiParamUtils.generateApiParam(entities)
|
||||||
return guruRepository.uploadEvents(param).map { true }.observeOn(dbScheduler).doOnError {
|
return guruRepository.uploadEvents(param)
|
||||||
|
.map { true }
|
||||||
|
.observeOn(dbScheduler)
|
||||||
|
.doOnSuccess {
|
||||||
|
// 记录上传成功的数量
|
||||||
|
val eventCountUploaded = preferencesManager.eventCountUploaded ?: 0
|
||||||
|
val uploaded = eventCountUploaded + entities.size
|
||||||
|
preferencesManager.eventCountUploaded = uploaded
|
||||||
|
GuruAnalyticsAudit.uploaded = uploaded
|
||||||
|
GuruAnalyticsAudit.sessionUploaded += entities.size
|
||||||
|
|
||||||
}.doOnSuccess {
|
val extMap = mapOf(
|
||||||
// 记录上传成功的数量
|
"count" to entities.size,
|
||||||
val eventCountUploaded = preferencesManager.eventCountUploaded ?: 0
|
"eventNames" to entities.joinToString(",") { it.event },
|
||||||
val uploaded = eventCountUploaded + entities.size
|
"allUploadedCount" to preferencesManager.eventCountUploaded,
|
||||||
preferencesManager.eventCountUploaded = uploaded
|
)
|
||||||
GuruAnalyticsAudit.uploaded = uploaded
|
logDebug("uploadEvents success: $extMap")
|
||||||
GuruAnalyticsAudit.sessionUploaded += entities.size
|
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_SUCCESS, extMap)
|
||||||
|
latestValidActionTs = android.os.SystemClock.elapsedRealtime()
|
||||||
val extMap = mapOf(
|
}.onErrorReturn {
|
||||||
"count" to entities.size,
|
GuruAnalyticsDatabase.getInstance().eventDao().updateEventDefault(entities)
|
||||||
"eventNames" to entities.joinToString(",") { it.event },
|
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_FAIL, it.message)
|
||||||
"allUploadedCount" to preferencesManager.eventCountUploaded,
|
// val elapsed = android.os.SystemClock.elapsedRealtime()
|
||||||
)
|
// val uploadedRate = GuruAnalyticsAudit.sessionUploaded / max(GuruAnalyticsAudit.sessionTotal, 1)
|
||||||
logDebug("uploadEvents success: $extMap")
|
// val isActiveNetworkMetered = connectivity.isActiveNetworkMetered
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_SUCCESS, extMap)
|
// val exceededValidActionGap = elapsed - latestValidActionTs > SESSION_ACTIVE_INTERVAL
|
||||||
latestValidActionTs = android.os.SystemClock.elapsedRealtime()
|
// logInfo("uploadEvent error $it sessionActivated:$sessionActivated uploadedRate:$uploadedRate isActiveNetworkMetered:$isActiveNetworkMetered gap:${(elapsed - latestValidActionTs) / 1000}s")
|
||||||
}.onErrorReturn {
|
// /// 如果没有激活,并且当前是计费网络,并且距离上次上传成功时间超过15分钟,激活worker
|
||||||
GuruAnalyticsDatabase.getInstance().eventDao().updateEventDefault(entities)
|
// /// 因为这个 worker 会在非计费网络下尝试执行
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_FAIL, it.message)
|
// if (!sessionActivated && uploadedRate < 0.6 && isActiveNetworkMetered && exceededValidActionGap) {
|
||||||
val elapsed = android.os.SystemClock.elapsedRealtime()
|
// logInfo("Metered Network Active! But upload event failed rate > 0.4! So dispatch ActiveWorker")
|
||||||
val uploadedRate = GuruAnalyticsAudit.sessionUploaded / GuruAnalyticsAudit.sessionTotal
|
//// dispatchActiveWorker()
|
||||||
val isActiveNetworkMetered = connectivity.isActiveNetworkMetered
|
// latestValidActionTs = elapsed
|
||||||
val exceededValidActionGap = elapsed - latestValidActionTs > SESSION_ACTIVE_INTERVAL
|
// }
|
||||||
logInfo("uploadEvent error $it sessionActivated:$sessionActivated uploadedRate:$uploadedRate isActiveNetworkMetered:$isActiveNetworkMetered gap:${(elapsed - latestValidActionTs) / 1000}s")
|
false
|
||||||
/// 如果没有激活,并且当前是计费网络,并且距离上次上传成功时间超过15分钟,激活worker
|
}.map { if (it) entities else emptyList() }
|
||||||
/// 因为这个 worker 会在非计费网络下尝试执行
|
|
||||||
if (!sessionActivated && uploadedRate < 0.6 && isActiveNetworkMetered && exceededValidActionGap) {
|
|
||||||
logInfo("Metered Network Active! But upload event failed rate > 0.4! So dispatch ActiveWorker")
|
|
||||||
dispatchActiveWorker()
|
|
||||||
latestValidActionTs = elapsed
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}.map { if (it) entities else emptyList() }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deliverEvent(item: EventItem, options: AnalyticsOptions) {
|
|
||||||
|
|
||||||
// 记录收到的事件数量
|
|
||||||
val delivered = increaseEventCount()
|
|
||||||
pendingEventSubject.onNext(1)
|
|
||||||
if (options.priority == EventPriority.EMERGENCE) {
|
|
||||||
logInfo("EMERGENCE: ${item.eventName} forceUpload")
|
|
||||||
forceUpload("EMERGENCE")
|
|
||||||
} else if (delivered and 0x1F == 0x1F) {
|
|
||||||
logInfo("Already delivered $delivered events!! forceUpload")
|
|
||||||
forceUpload("DELIVERED")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun increaseEventCount(): Int {
|
|
||||||
val eventCountAll = preferencesManager.eventCountAll ?: 0
|
|
||||||
val total = eventCountAll + 1
|
|
||||||
preferencesManager.eventCountAll = total
|
|
||||||
GuruAnalyticsAudit.total = total
|
|
||||||
GuruAnalyticsAudit.sessionTotal++
|
|
||||||
return total
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deliverProperty(name: String, value: String) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeProperties(keys: Set<String>) {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun forceUpload(scene: String = "unknown") {
|
|
||||||
forceTriggerSubject.onNext(scene)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getEventsStatics(): EventStatistic {
|
fun getEventsStatics(): EventStatistic {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,233 @@
|
||||||
|
package guru.core.analytics.impl
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.SystemClock
|
||||||
|
import guru.core.analytics.Constants
|
||||||
|
import guru.core.analytics.data.db.GuruAnalyticsDatabase
|
||||||
|
import guru.core.analytics.data.db.model.EventPriority
|
||||||
|
import guru.core.analytics.data.local.PreferencesManager
|
||||||
|
import guru.core.analytics.data.model.AnalyticsOptions
|
||||||
|
import guru.core.analytics.data.model.EventItem
|
||||||
|
import guru.core.analytics.data.model.GuruAnalyticsAudit
|
||||||
|
import guru.core.analytics.data.store.EventInfoStore
|
||||||
|
import guru.core.analytics.handler.AnalyticsCode
|
||||||
|
import guru.core.analytics.handler.EventHandler
|
||||||
|
import guru.core.analytics.utils.Connectivity
|
||||||
|
import io.reactivex.BackpressureStrategy
|
||||||
|
import io.reactivex.Flowable
|
||||||
|
import io.reactivex.subjects.PublishSubject
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.concurrent.ConcurrentLinkedDeque
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
|
||||||
|
class PendingEvent(
|
||||||
|
val item: EventItem,
|
||||||
|
val options: AnalyticsOptions
|
||||||
|
) {
|
||||||
|
val at = SystemClock.elapsedRealtime()
|
||||||
|
}
|
||||||
|
|
||||||
|
data object AnchorAt {
|
||||||
|
private const val PREDICTED_LAUNCHED = "PREDICTED_LAUNCHED"
|
||||||
|
const val SDK_INIT = "SDK_INIT"
|
||||||
|
const val SDK_INIT_COMPLETED = "SDK_INIT_COMPLETED"
|
||||||
|
const val SESSION_START = "SESSION_START"
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
private val anchorAtMap = mutableMapOf(
|
||||||
|
PREDICTED_LAUNCHED to (SystemClock.elapsedRealtime() - 2000)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun recordAt(name: String) {
|
||||||
|
anchorAtMap[name] = SystemClock.elapsedRealtime()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun elapsedAt(name: String): Long {
|
||||||
|
val current = SystemClock.elapsedRealtime()
|
||||||
|
return current - (anchorAtMap[name] ?: current)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun diffAt(begin: String, end: String): Long {
|
||||||
|
val current = SystemClock.elapsedRealtime()
|
||||||
|
return (anchorAtMap[end] ?: current) - (anchorAtMap[begin] ?: current)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data object EventSink {
|
||||||
|
|
||||||
|
private val forceTriggerSubject: PublishSubject<String> = PublishSubject.create()
|
||||||
|
|
||||||
|
private lateinit var preferencesManager: PreferencesManager
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
private lateinit var connectivity: Connectivity
|
||||||
|
|
||||||
|
private val pendingEvents = ConcurrentLinkedDeque<PendingEvent>()
|
||||||
|
|
||||||
|
private val started = AtomicBoolean(false)
|
||||||
|
|
||||||
|
private val deliverExecutor = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
|
private val latestNetworkAvailable: AtomicBoolean = AtomicBoolean(false)
|
||||||
|
|
||||||
|
private val dateFormat: SimpleDateFormat = SimpleDateFormat("yyyyMMdd", Locale.US)
|
||||||
|
|
||||||
|
val forceFlowable: Flowable<String>
|
||||||
|
get() = forceTriggerSubject.toFlowable(BackpressureStrategy.DROP)
|
||||||
|
|
||||||
|
@SuppressLint("CheckResult")
|
||||||
|
fun initialize(context: Context, host: String) {
|
||||||
|
this.preferencesManager = PreferencesManager.getInstance(context)
|
||||||
|
this.connectivity = Connectivity.of(context)
|
||||||
|
logSdkInit(host)
|
||||||
|
connectivity.networkAvailableFlowable.subscribe {
|
||||||
|
val latest = latestNetworkAvailable.getAndSet(it)
|
||||||
|
if (latest != it) {
|
||||||
|
Timber.d("Network available: $it")
|
||||||
|
forceUpload("NETWORK_AVAILABLE")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatchPendingEvent() {
|
||||||
|
Timber.d("EventDispatcher dispatchPendingEvent ${pendingEvents.size}!")
|
||||||
|
if (pendingEvents.isNotEmpty()) {
|
||||||
|
while (true) {
|
||||||
|
val pendingEvent = pendingEvents.poll() ?: return
|
||||||
|
dispatch(pendingEvent.item, pendingEvent.options, SystemClock.elapsedRealtime() - pendingEvent.at)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
deliver {
|
||||||
|
if (started.compareAndSet(false, true)) {
|
||||||
|
Timber.d("EventDispatcher started!")
|
||||||
|
logSessionStart()
|
||||||
|
logFirstOpen()
|
||||||
|
logSdkInitCompleted()
|
||||||
|
dispatchPendingEvent()
|
||||||
|
GuruAnalyticsAudit.eventDispatcherStarted = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun increaseEventCount(): Int {
|
||||||
|
val eventCountAll = preferencesManager.eventCountAll ?: 0
|
||||||
|
val total = eventCountAll + 1
|
||||||
|
preferencesManager.eventCountAll = total
|
||||||
|
GuruAnalyticsAudit.total = total
|
||||||
|
GuruAnalyticsAudit.sessionTotal++
|
||||||
|
return total
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forceUpload(scene: String = "unknown") {
|
||||||
|
forceTriggerSubject.onNext(scene)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deliverEvent(item: EventItem, options: AnalyticsOptions) {
|
||||||
|
deliver {
|
||||||
|
if (started.get()) {
|
||||||
|
Timber.d("EventDispatcher deliverEvent!")
|
||||||
|
dispatch(item, options)
|
||||||
|
} else {
|
||||||
|
Timber.d("EventDispatcher deliverEvent pending!")
|
||||||
|
pendingEvents.offer(PendingEvent(item, options))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun dispatch(item: EventItem, options: AnalyticsOptions, elapsed: Long = 0L) {
|
||||||
|
val event = EventInfoStore.deriveEvent(item, priority = options.priority, elapsed = elapsed)
|
||||||
|
GuruAnalyticsDatabase.getInstance().eventDao().addEvent(event)
|
||||||
|
val delivered = increaseEventCount()
|
||||||
|
if (options.priority == EventPriority.EMERGENCE) {
|
||||||
|
EventEngine.logInfo("EMERGENCE: ${item.eventName} forceUpload")
|
||||||
|
forceUpload("EMERGENCE")
|
||||||
|
} else if (delivered and 0x1F == 0x1F) {
|
||||||
|
EventEngine.logInfo("Already delivered $delivered events!! forceUpload")
|
||||||
|
forceUpload("DELIVERED")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun deliver(runnable: Runnable) {
|
||||||
|
deliverExecutor.execute(runnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deliverProperty(name: String, value: String) {
|
||||||
|
deliver {
|
||||||
|
EventInfoStore.setUserProperty(name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeProperties(keys: Set<String>) {
|
||||||
|
deliver {
|
||||||
|
EventInfoStore.removeUserProperties(keys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logFirstOpen() {
|
||||||
|
if (preferencesManager.isFirstOpen == true) {
|
||||||
|
dispatch(
|
||||||
|
EventItem(Constants.Event.FIRST_OPEN),
|
||||||
|
AnalyticsOptions(EventPriority.EMERGENCE),
|
||||||
|
elapsed = AnchorAt.elapsedAt(AnchorAt.SESSION_START)
|
||||||
|
)
|
||||||
|
preferencesManager.isFirstOpen = false
|
||||||
|
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_FIRST_OPEN)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logSessionStart() {
|
||||||
|
val sessionDate = preferencesManager.sessionDate ?: ""
|
||||||
|
val now = Date()
|
||||||
|
val sessionCount = if (sessionDate != dateFormat.format(now)) {
|
||||||
|
preferencesManager.sessionDate = dateFormat.format(now)
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
(preferencesManager.sessionCount ?: 0) + 1
|
||||||
|
}
|
||||||
|
preferencesManager.sessionCount = sessionCount
|
||||||
|
dispatch(
|
||||||
|
EventItem(Constants.Event.SESSION_START, params = mapOf("session_number" to sessionCount)),
|
||||||
|
AnalyticsOptions(EventPriority.EMERGENCE),
|
||||||
|
elapsed = AnchorAt.elapsedAt(AnchorAt.SESSION_START)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private fun logSdkInit(host: String) {
|
||||||
|
deliver {
|
||||||
|
dispatch(
|
||||||
|
EventItem(
|
||||||
|
Constants.Event.SDK_INIT, params = mapOf(
|
||||||
|
"host" to 1,
|
||||||
|
"total_events" to GuruAnalyticsAudit.total,
|
||||||
|
"deleted_events" to GuruAnalyticsAudit.deleted,
|
||||||
|
"uploaded_events" to GuruAnalyticsAudit.uploaded
|
||||||
|
)
|
||||||
|
), AnalyticsOptions(EventPriority.EMERGENCE), elapsed = AnchorAt.elapsedAt(AnchorAt.SDK_INIT)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logSdkInitCompleted() {
|
||||||
|
dispatch(
|
||||||
|
EventItem(
|
||||||
|
Constants.Event.SDK_INIT_COMPLETE, params = mapOf(
|
||||||
|
"duration" to AnchorAt.diffAt(AnchorAt.SDK_INIT, AnchorAt.SDK_INIT_COMPLETED),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
AnalyticsOptions(EventPriority.EMERGENCE),
|
||||||
|
elapsed = AnchorAt.elapsedAt(AnchorAt.SDK_INIT_COMPLETED)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,136 +0,0 @@
|
||||||
//package guru.core.analytics.impl
|
|
||||||
//
|
|
||||||
//import android.content.Context
|
|
||||||
//import android.os.SystemClock
|
|
||||||
//import guru.core.analytics.Constants
|
|
||||||
//import guru.core.analytics.GuruAnalytics
|
|
||||||
//import guru.core.analytics.data.db.model.EventEntity
|
|
||||||
//import guru.core.analytics.data.db.model.EventPriority
|
|
||||||
//import guru.core.analytics.data.local.PreferencesManager
|
|
||||||
//import guru.core.analytics.data.model.EventItem
|
|
||||||
//import guru.core.analytics.data.store.EventInfoStore
|
|
||||||
//import guru.core.analytics.handler.AnalyticsCode
|
|
||||||
//import guru.core.analytics.handler.EventHandler
|
|
||||||
//import io.reactivex.BackpressureStrategy
|
|
||||||
//import io.reactivex.Flowable
|
|
||||||
//import io.reactivex.disposables.CompositeDisposable
|
|
||||||
//import io.reactivex.schedulers.Schedulers
|
|
||||||
//import io.reactivex.subjects.BehaviorSubject
|
|
||||||
//import timber.log.Timber
|
|
||||||
//import java.util.concurrent.Executors
|
|
||||||
//import java.util.concurrent.TimeUnit
|
|
||||||
//import java.util.concurrent.atomic.AtomicLong
|
|
||||||
//
|
|
||||||
//class FgEventHelper private constructor() {
|
|
||||||
//
|
|
||||||
// companion object {
|
|
||||||
// private const val TAG = "FgEventHelper"
|
|
||||||
// private val scheduler = Schedulers.from(Executors.newSingleThreadExecutor())
|
|
||||||
// private const val DEFAULT_FG_UPLOAD_INTERVAL_SECOND = 30L
|
|
||||||
// private const val MINIMUM_FG_UPLOAD_INTERVAL_SECOND = 15L
|
|
||||||
// private const val MAXIMUM_FG_UPLOAD_INTERVAL_SECOND = 60 * 60 * 1000L
|
|
||||||
//
|
|
||||||
// @Volatile
|
|
||||||
// private var INSTANCE: FgEventHelper? = null
|
|
||||||
//
|
|
||||||
// fun getInstance(): FgEventHelper = INSTANCE ?: synchronized(this) {
|
|
||||||
// INSTANCE ?: FgEventHelper().also { INSTANCE = it }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private var compositeDisposable: CompositeDisposable = CompositeDisposable()
|
|
||||||
// private val lifecycleSubject: BehaviorSubject<Boolean> = BehaviorSubject.create()
|
|
||||||
// private val lastTime = AtomicLong(0L)
|
|
||||||
// private var fgDuration = AtomicLong(0L)
|
|
||||||
// private fun updateFgDuration(duration: Long) {
|
|
||||||
// fgDuration.set(duration)
|
|
||||||
// preferencesManager?.setTotalDurationFgEvent(duration)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private var preferencesManager: PreferencesManager? = null
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// fun start(context: Context, fgEventPeriodInSeconds: Long? = null) {
|
|
||||||
// preferencesManager = PreferencesManager.getInstance(context)
|
|
||||||
// AppLifecycleMonitor.getInstance().addLifecycleMonitor {
|
|
||||||
// lifecycleSubject.onNext(it)
|
|
||||||
// Timber.tag(TAG).d("start addLifecycleMonitor isVisible:$it")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// val periodInSeconds = fgEventPeriodInSeconds ?: -1L
|
|
||||||
// val interval =
|
|
||||||
// if (periodInSeconds < 0) DEFAULT_FG_UPLOAD_INTERVAL_SECOND else if (periodInSeconds < MINIMUM_FG_UPLOAD_INTERVAL_SECOND) MINIMUM_FG_UPLOAD_INTERVAL_SECOND else periodInSeconds
|
|
||||||
//
|
|
||||||
// val fgIntervalFlow = Flowable.interval(0, interval, TimeUnit.SECONDS)
|
|
||||||
// compositeDisposable.add(
|
|
||||||
// Flowable.combineLatest(
|
|
||||||
// fgIntervalFlow, lifecycleSubject.toFlowable(BackpressureStrategy.DROP)
|
|
||||||
// ) { _, isVisible -> isVisible }
|
|
||||||
// .doOnSubscribe { logFirstFgEvent() }
|
|
||||||
// .subscribeOn(scheduler)
|
|
||||||
// .observeOn(scheduler)
|
|
||||||
// .map { getFgDuration(MAXIMUM_FG_UPLOAD_INTERVAL_SECOND) }
|
|
||||||
// .filter { it > 0L }
|
|
||||||
// .subscribe({ duration -> logFgEvent(duration) }, Timber::e)
|
|
||||||
// )
|
|
||||||
// Timber.tag(TAG).d("start interval:$interval ${compositeDisposable.size()}")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// private fun logFirstFgEvent() {
|
|
||||||
// if (preferencesManager == null) return
|
|
||||||
// val cachedDuration = 1L.coerceAtLeast(preferencesManager!!.getTotalDurationFgEvent())
|
|
||||||
// logFgEvent(cachedDuration)
|
|
||||||
// updateFgDuration(0L)
|
|
||||||
// Timber.tag(TAG).d("logFirstFgEvent $cachedDuration")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private fun logFgEvent(duration: Long) {
|
|
||||||
// if (duration <= 0L) return
|
|
||||||
// val params = mutableMapOf<String, Any>()
|
|
||||||
// params[Constants.Event.DURATION] = duration
|
|
||||||
// GuruAnalytics.INSTANCE.logEvent(Constants.Event.FG, parameters = params)
|
|
||||||
// val extMap = mapOf("duration" to duration)
|
|
||||||
// EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_FG, extMap)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fun getFgEvent(limit: Long = 0L): EventEntity? {
|
|
||||||
// val duration = getFgDuration(limit)
|
|
||||||
// return if (duration > 0L) {
|
|
||||||
// val extMap = mapOf("duration" to duration)
|
|
||||||
// EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_FG, extMap)
|
|
||||||
// EventInfoStore.deriveEvent(
|
|
||||||
// EventItem(
|
|
||||||
// Constants.Event.FG, params = mapOf(Pair(Constants.Event.DURATION, duration))
|
|
||||||
// ),
|
|
||||||
// priority = EventPriority.EMERGENCE,
|
|
||||||
// )
|
|
||||||
// } else {
|
|
||||||
// null
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Synchronized
|
|
||||||
// fun getFgDuration(limit: Long = 0L): Long {
|
|
||||||
// val lastTimeMillions = lastTime.get()
|
|
||||||
// val currentTimeMillions = SystemClock.elapsedRealtime()
|
|
||||||
// val isVisible = lifecycleSubject.value ?: false
|
|
||||||
// lastTime.set(if (isVisible) currentTimeMillions else 0L)
|
|
||||||
// val totalDuration = if (lastTimeMillions > 0L) {
|
|
||||||
// fgDuration.get() + currentTimeMillions - lastTimeMillions
|
|
||||||
// } else {
|
|
||||||
// fgDuration.get()
|
|
||||||
// }
|
|
||||||
// Timber.tag(TAG).d("getFgDuration limit:$limit isVisible:$isVisible totalDuration:$totalDuration")
|
|
||||||
// return if (totalDuration > 0L.coerceAtLeast(limit)) {
|
|
||||||
// updateFgDuration(0L)
|
|
||||||
// totalDuration
|
|
||||||
// } else {
|
|
||||||
// if (lastTimeMillions > 0) {
|
|
||||||
// updateFgDuration(totalDuration)
|
|
||||||
// }
|
|
||||||
// 0L
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
@ -4,7 +4,9 @@ import android.content.Context
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import guru.core.analytics.Constants
|
import guru.core.analytics.Constants
|
||||||
import guru.core.analytics.GuruAnalytics
|
import guru.core.analytics.GuruAnalytics
|
||||||
|
import guru.core.analytics.data.db.model.EventPriority
|
||||||
import guru.core.analytics.data.local.PreferencesManager
|
import guru.core.analytics.data.local.PreferencesManager
|
||||||
|
import guru.core.analytics.data.model.AnalyticsOptions
|
||||||
import guru.core.analytics.data.model.GuruAnalyticsAudit
|
import guru.core.analytics.data.model.GuruAnalyticsAudit
|
||||||
import guru.core.analytics.handler.AnalyticsCode
|
import guru.core.analytics.handler.AnalyticsCode
|
||||||
import guru.core.analytics.handler.EventHandler
|
import guru.core.analytics.handler.EventHandler
|
||||||
|
|
@ -16,20 +18,17 @@ import java.lang.Math.max
|
||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
import java.util.concurrent.atomic.AtomicLong
|
||||||
|
|
||||||
internal class FgHelper(context: Context) {
|
internal class FgHelper(context: Context) {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val FG_RECORD_INTERVAL_SECOND = 5L
|
private const val FG_RECORD_INTERVAL_SECOND = 5L
|
||||||
private const val FG_REPORT_INTERVAL_MILLIS = 40 * 1000L
|
private const val FG_REPORT_INTERVAL_MILLIS = 60 * 1000L
|
||||||
private val scheduler = Schedulers.from(Executors.newSingleThreadExecutor())
|
private val scheduler = Schedulers.from(Executors.newSingleThreadExecutor())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Volatile
|
private var latestActiveAt = AtomicLong(SystemClock.elapsedRealtime())
|
||||||
private var latestRecordAt = SystemClock.elapsedRealtime()
|
|
||||||
|
|
||||||
@Volatile
|
|
||||||
private var currentDuration = 0L
|
|
||||||
private var disposable: Disposable? = null
|
private var disposable: Disposable? = null
|
||||||
private val initialized = AtomicBoolean(false)
|
private val initialized = AtomicBoolean(false)
|
||||||
|
|
||||||
|
|
@ -37,39 +36,45 @@ internal class FgHelper(context: Context) {
|
||||||
PreferencesManager.getInstance(context)
|
PreferencesManager.getInstance(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val preferenceEditor by lazy {
|
|
||||||
preferencesManager.getSharedPreferencesDirectly().edit()
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun ensureInitialized() {
|
internal fun ensureInitialized() {
|
||||||
if (initialized.compareAndSet(false, true)) {
|
if (initialized.compareAndSet(false, true)) {
|
||||||
currentDuration = preferencesManager.getTotalDurationFgEvent()
|
val elapsedAt = SystemClock.elapsedRealtime()
|
||||||
refresh(force = true)
|
val latestElapsedAt = latestActiveAt.getAndSet(elapsedAt)
|
||||||
|
val duration = kotlin.math.max(1, elapsedAt - latestElapsedAt)
|
||||||
|
val legacyDuration = preferencesManager.getTotalDurationFgEvent() + duration
|
||||||
|
preferencesManager.setTotalDurationFgEvent(0L)
|
||||||
|
if (legacyDuration > 0L) {
|
||||||
|
logUserEngagement(legacyDuration)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun refresh(force: Boolean = false): Long {
|
private fun refresh() {
|
||||||
val now = SystemClock.elapsedRealtime()
|
val elapsedAt = SystemClock.elapsedRealtime()
|
||||||
val duration = now - latestRecordAt
|
val duration = elapsedAt - latestActiveAt.get()
|
||||||
val fgDuration = currentDuration + duration
|
Timber.tag("FgHelper").d("refresh: $duration")
|
||||||
latestRecordAt = now
|
if (duration < FG_REPORT_INTERVAL_MILLIS) {
|
||||||
currentDuration = if (force || fgDuration > FG_REPORT_INTERVAL_MILLIS) {
|
preferencesManager.setTotalDurationFgEvent(duration)
|
||||||
logFgEvent(fgDuration.coerceAtLeast(1))
|
|
||||||
0L
|
|
||||||
} else {
|
} else {
|
||||||
fgDuration
|
userEngagement(elapsedAt)
|
||||||
}
|
}
|
||||||
|
|
||||||
preferenceEditor.putLong(PreferencesManager.KEY_TOTAL_DURATION_FG_EVENT, currentDuration)
|
|
||||||
.commit()
|
|
||||||
return currentDuration
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fun start() {
|
fun start() {
|
||||||
|
userActive()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
userInactive()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun userActive() {
|
||||||
ensureInitialized()
|
ensureInitialized()
|
||||||
latestRecordAt = SystemClock.elapsedRealtime()
|
latestActiveAt.set(SystemClock.elapsedRealtime())
|
||||||
disposable?.dispose()
|
disposable?.dispose()
|
||||||
disposable = Flowable.interval(0, FG_RECORD_INTERVAL_SECOND, TimeUnit.SECONDS)
|
disposable = Flowable.interval(FG_RECORD_INTERVAL_SECOND, FG_RECORD_INTERVAL_SECOND, TimeUnit.SECONDS).onBackpressureDrop()
|
||||||
.subscribeOn(scheduler)
|
.subscribeOn(scheduler)
|
||||||
.subscribe({
|
.subscribe({
|
||||||
refresh()
|
refresh()
|
||||||
|
|
@ -79,17 +84,32 @@ internal class FgHelper(context: Context) {
|
||||||
GuruAnalyticsAudit.fgHelperInitialized = true
|
GuruAnalyticsAudit.fgHelperInitialized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun stop() {
|
private fun userInactive() {
|
||||||
|
userEngagement(SystemClock.elapsedRealtime())
|
||||||
disposable?.dispose()
|
disposable?.dispose()
|
||||||
refresh()
|
disposable = null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun logFgEvent(duration: Long) {
|
private fun isActive() = disposable != null
|
||||||
if (duration <= 0L) return
|
|
||||||
val params = mutableMapOf<String, Any>()
|
private fun userEngagement(elapsedAt: Long) {
|
||||||
params[Constants.Event.DURATION] = duration
|
if (!isActive()) {
|
||||||
GuruAnalytics.INSTANCE.logEvent(Constants.Event.FG, parameters = params)
|
Timber.tag("FgHelper").w("inactive fg helper")
|
||||||
val extMap = mapOf("duration" to duration)
|
return
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_FG, extMap)
|
}
|
||||||
|
val latestElapsedAt = latestActiveAt.getAndSet(elapsedAt)
|
||||||
|
if (elapsedAt > latestElapsedAt) {
|
||||||
|
logUserEngagement(elapsedAt - latestElapsedAt)
|
||||||
|
preferencesManager.setTotalDurationFgEvent(0L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun logUserEngagement(duration: Long) {
|
||||||
|
Timber.tag("FgHelper").w("[GURU ENGAGEMENT] duration: $duration")
|
||||||
|
val params = mapOf(Constants.Event.DURATION to duration)
|
||||||
|
GuruAnalytics.INSTANCE.logEvent(Constants.Event.FG,
|
||||||
|
parameters = params,
|
||||||
|
options = AnalyticsOptions(priority = EventPriority.EMERGENCE))
|
||||||
|
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_FG, params)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,6 @@ package guru.core.analytics.impl
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.transition.Scene
|
|
||||||
import androidx.annotation.RequiresPermission
|
import androidx.annotation.RequiresPermission
|
||||||
import androidx.work.*
|
import androidx.work.*
|
||||||
import guru.core.analytics.Constants
|
import guru.core.analytics.Constants
|
||||||
|
|
@ -52,6 +51,11 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
|
|
||||||
private val initialized = AtomicBoolean(false)
|
private val initialized = AtomicBoolean(false)
|
||||||
|
|
||||||
|
|
||||||
|
private val isMainProcess = AtomicBoolean(true)
|
||||||
|
|
||||||
|
// private val = System.currentTimeMillis()
|
||||||
|
|
||||||
override fun isDebug(): Boolean = debugMode
|
override fun isDebug(): Boolean = debugMode
|
||||||
|
|
||||||
override fun setDebug(debug: Boolean) {
|
override fun setDebug(debug: Boolean) {
|
||||||
|
|
@ -69,6 +73,7 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
Timber.d("setUploadEventBaseUrl:$updateEventBaseUrl")
|
Timber.d("setUploadEventBaseUrl:$updateEventBaseUrl")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@RequiresPermission(allOf = ["android.permission.INTERNET", "android.permission.ACCESS_NETWORK_STATE"])
|
@RequiresPermission(allOf = ["android.permission.INTERNET", "android.permission.ACCESS_NETWORK_STATE"])
|
||||||
override fun initialize(
|
override fun initialize(
|
||||||
context: Context,
|
context: Context,
|
||||||
|
|
@ -87,7 +92,8 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
mainProcess: String?,
|
mainProcess: String?,
|
||||||
isEnableCronet: Boolean?,
|
isEnableCronet: Boolean?,
|
||||||
uploadIpAddress: List<String>?,
|
uploadIpAddress: List<String>?,
|
||||||
dnsMode: Int?
|
dnsMode: Int?,
|
||||||
|
guruSdkVersion: String
|
||||||
) {
|
) {
|
||||||
if (initialized.compareAndSet(false, true)) {
|
if (initialized.compareAndSet(false, true)) {
|
||||||
val debugApp = SystemProperties.read("debug.guru.analytics.app")
|
val debugApp = SystemProperties.read("debug.guru.analytics.app")
|
||||||
|
|
@ -100,19 +106,27 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
} catch (_: Throwable) {
|
} catch (_: Throwable) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_1)
|
val currentProcessName = AndroidUtils.getProcessName(context)
|
||||||
|
if (currentProcessName != context.packageName) {
|
||||||
|
isMainProcess.set(false)
|
||||||
|
Timber.d("initialize $currentProcessName is not main process!")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
AnchorAt.recordAt(AnchorAt.SDK_INIT)
|
||||||
|
isMainProcess.set(true)
|
||||||
|
DeviceInfoStore.GURU_SDK_VERSION = guruSdkVersion
|
||||||
EventInfoStore.initialize(context)
|
EventInfoStore.initialize(context)
|
||||||
delivers.add(EventDispatcher)
|
|
||||||
eventHandlerCallback?.let { EventHandler.INSTANCE.addEventHandler(it) }
|
eventHandlerCallback?.let { EventHandler.INSTANCE.addEventHandler(it) }
|
||||||
|
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_2)
|
// EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_2)
|
||||||
|
|
||||||
val debugUrl = SystemProperties.read("debug.guru.analytics.url")
|
val debugUrl = SystemProperties.read("debug.guru.analytics.url")
|
||||||
|
|
||||||
debugMode = forceDebug || debug
|
debugMode = forceDebug || debug
|
||||||
Timber.d("[$internalVersion]initialize batchLimit:$batchLimit uploadPeriodInSecond:$uploadPeriodInSeconds startUploadDelayInSecond:$startUploadDelayInSecond eventExpiredInDays:$eventExpiredInDays debug:$debugMode debugUrl:$debugUrl uploadIpAddress:$uploadIpAddress dnsMode:$dnsMode")
|
Timber.d("[$internalVersion] $currentProcessName initialize batchLimit:$batchLimit uploadPeriodInSecond:$uploadPeriodInSeconds startUploadDelayInSecond:$startUploadDelayInSecond eventExpiredInDays:$eventExpiredInDays debug:$debugMode debugUrl:$debugUrl uploadIpAddress:$uploadIpAddress dnsMode:$dnsMode guruSdkVersion:$guruSdkVersion")
|
||||||
GuruAnalyticsDatabase.initialize(context.applicationContext)
|
GuruAnalyticsDatabase.initialize(context.applicationContext)
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_3)
|
// EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_3)
|
||||||
|
|
||||||
val baseUrl = if (forceDebug && debugUrl.isNotEmpty()) debugUrl else (uploadEventBaseUrl ?: AnalyticsApiHost.BASE_URL)
|
val baseUrl = if (forceDebug && debugUrl.isNotEmpty()) debugUrl else (uploadEventBaseUrl ?: AnalyticsApiHost.BASE_URL)
|
||||||
|
|
||||||
|
|
@ -123,7 +137,7 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
ServiceLocator.setUploadIpAddress(uploadIpAddress)
|
ServiceLocator.setUploadIpAddress(uploadIpAddress)
|
||||||
ServiceLocator.setDnsMode(dnsMode ?: 0)
|
ServiceLocator.setDnsMode(dnsMode ?: 0)
|
||||||
|
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_4)
|
// EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_4)
|
||||||
if (isEnableCronet == true) {
|
if (isEnableCronet == true) {
|
||||||
GuruAnalyticsAudit.enabledCronet = true
|
GuruAnalyticsAudit.enabledCronet = true
|
||||||
ServiceLocator.setCronet(true)
|
ServiceLocator.setCronet(true)
|
||||||
|
|
@ -135,7 +149,7 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_5)
|
// EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_5)
|
||||||
|
|
||||||
var uploadEventBaseUri: Uri? = null
|
var uploadEventBaseUri: Uri? = null
|
||||||
var hostname: String? = ""
|
var hostname: String? = ""
|
||||||
|
|
@ -147,9 +161,10 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
}
|
}
|
||||||
hostname = uploadEventBaseUri.host
|
hostname = uploadEventBaseUri.host
|
||||||
}
|
}
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_6)
|
// EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_6)
|
||||||
|
|
||||||
ServiceLocator.preloadDns(hostname)
|
ServiceLocator.preloadDns(hostname)
|
||||||
|
EventSink.initialize(context, hostname ?: AnalyticsApiHost.BASE_URL)
|
||||||
|
|
||||||
engine = EventEngine(
|
engine = EventEngine(
|
||||||
context,
|
context,
|
||||||
|
|
@ -161,28 +176,17 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
),
|
),
|
||||||
guruRepository = ServiceLocator.provideGuruRepository(context, baseUri = uploadEventBaseUri) /// 此处需要配合 ServiceLocator的重构进行修改
|
guruRepository = ServiceLocator.provideGuruRepository(context, baseUri = uploadEventBaseUri) /// 此处需要配合 ServiceLocator的重构进行修改
|
||||||
).apply {
|
).apply {
|
||||||
delivers.add(this)
|
|
||||||
start(startUploadDelayInSecond)
|
start(startUploadDelayInSecond)
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_7)
|
// EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_7)
|
||||||
}
|
}
|
||||||
if (isInitPeriodicWork) {
|
if (isInitPeriodicWork) {
|
||||||
val process = AndroidUtils.getProcessName(context)
|
try {
|
||||||
Timber.d("initialize ${mainProcess == process} currentProcess:$process mainProcess:$mainProcess")
|
initAnalyticsPeriodic(context)
|
||||||
if (mainProcess.isNullOrBlank() || mainProcess == process) {
|
} catch (throwable: Throwable) {
|
||||||
try {
|
Timber.d("init worker error!")
|
||||||
initAnalyticsPeriodic(context)
|
|
||||||
} catch (throwable: Throwable) {
|
|
||||||
Timber.d("init worker error!")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (!process.isNullOrBlank()) {
|
|
||||||
val params = mutableMapOf<String, Any>()
|
|
||||||
params[Constants.Event.PROCESS] = process
|
|
||||||
INSTANCE.logEvent(Constants.Event.ERROR_PROCESS, parameters = params)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_8)
|
// EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_8)
|
||||||
|
|
||||||
val extMap = mapOf(
|
val extMap = mapOf(
|
||||||
"version_code" to internalVersion,
|
"version_code" to internalVersion,
|
||||||
|
|
@ -203,8 +207,9 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
if (uploadIpAddress != null) {
|
if (uploadIpAddress != null) {
|
||||||
PreferencesManager.getInstance(context).uploadIpAddressList = uploadIpAddress.joinToString("|")
|
PreferencesManager.getInstance(context).uploadIpAddressList = uploadIpAddress.joinToString("|")
|
||||||
}
|
}
|
||||||
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_9)
|
// EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_9)
|
||||||
GuruAnalyticsAudit.initialized = true
|
GuruAnalyticsAudit.initialized = true
|
||||||
|
AnchorAt.recordAt(AnchorAt.SDK_INIT_COMPLETED)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,19 +334,35 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addEventHandler(listener: (Int, String?) -> Unit) {
|
override fun addEventHandler(listener: (Int, String?) -> Unit) {
|
||||||
EventHandler.INSTANCE.addEventHandler(listener)
|
if (isMainProcess.get()) {
|
||||||
|
EventHandler.INSTANCE.addEventHandler(listener)
|
||||||
|
} else {
|
||||||
|
Timber.w("addEventHandler error! not in main process")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeEventHandler(listener: (Int, String?) -> Unit) {
|
override fun removeEventHandler(listener: (Int, String?) -> Unit) {
|
||||||
EventHandler.INSTANCE.removeEventHandler(listener)
|
if (isMainProcess.get()) {
|
||||||
|
EventHandler.INSTANCE.removeEventHandler(listener)
|
||||||
|
} else {
|
||||||
|
Timber.w("removeEventHandler error! not in main process")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeUserProperty(key: String) {
|
override fun removeUserProperty(key: String) {
|
||||||
removeProperties(setOf(key))
|
if (isMainProcess.get()) {
|
||||||
|
removeProperties(setOf(key))
|
||||||
|
} else {
|
||||||
|
Timber.w("removeUserProperty($key) error! not in main process")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun removeUserProperties(keys: Set<String>) {
|
override fun removeUserProperties(keys: Set<String>) {
|
||||||
removeProperties(keys)
|
if (isMainProcess.get()) {
|
||||||
|
removeProperties(keys)
|
||||||
|
} else {
|
||||||
|
Timber.w("removeUserProperties error! not in main process")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getUserProperties(callback: (Map<String, String>) -> Unit) {
|
override fun getUserProperties(callback: (Map<String, String>) -> Unit) {
|
||||||
|
|
@ -355,7 +376,12 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setEnableUpload(enable: Boolean) {
|
override fun setEnableUpload(enable: Boolean) {
|
||||||
engine?.setEnableUpload(enable)
|
if (isMainProcess.get()) {
|
||||||
|
engine?.setEnableUpload(enable)
|
||||||
|
} else {
|
||||||
|
Timber.w("setEnableUpload($enable) error! not in main process")
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun snapshotAnalyticsAudit(): String {
|
override fun snapshotAnalyticsAudit(): String {
|
||||||
|
|
@ -364,17 +390,27 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearStatistic(context: Context) {
|
override fun clearStatistic(context: Context) {
|
||||||
PreferencesManager.getInstance(context).apply {
|
if (isMainProcess.get()) {
|
||||||
eventCountAll = 0
|
PreferencesManager.getInstance(context).apply {
|
||||||
eventCountDeleted = 0
|
eventCountAll = 0
|
||||||
eventCountUploaded = 0
|
eventCountDeleted = 0
|
||||||
|
eventCountUploaded = 0
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Timber.w("clearStatistic error! not in main process")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun forceUpload(scene: String): Boolean {
|
override fun forceUpload(scene: String): Boolean {
|
||||||
|
if (!isMainProcess.get()) {
|
||||||
|
Timber.w("forceUpload(${scene}) error! not in main process")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
return engine?.let {
|
return engine?.let {
|
||||||
if (it.started.get()) {
|
if (it.started.get()) {
|
||||||
it.forceUpload(scene)
|
EventSink.forceUpload(scene)
|
||||||
return@let true
|
return@let true
|
||||||
}
|
}
|
||||||
return@let false
|
return@let false
|
||||||
|
|
@ -382,30 +418,30 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deliverEvent(item: EventItem, options: AnalyticsOptions) {
|
private fun deliverEvent(item: EventItem, options: AnalyticsOptions) {
|
||||||
Timber.tag("GuruAnalytics").d("deliverEvent ${item.eventName}!")
|
if (isMainProcess.get()) {
|
||||||
deliverExecutor.execute {
|
Timber.tag("GuruAnalytics").d("deliverEvent ${item.eventName}!")
|
||||||
for (deliver in delivers) {
|
EventSink.deliverEvent(item, options)
|
||||||
deliver.deliverEvent(item, options)
|
} else {
|
||||||
}
|
Timber.w("deliverEvent(${item.eventName}) error! not in main process")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun deliverProperty(name: String, value: String) {
|
private fun deliverProperty(name: String, value: String) {
|
||||||
Timber.tag("GuruAnalytics").d("deliverProperty $name = $value")
|
if (isMainProcess.get()) {
|
||||||
deliverExecutor.execute {
|
Timber.tag("GuruAnalytics").d("deliverProperty $name = $value")
|
||||||
for (deliver in delivers) {
|
EventSink.deliverProperty(name, value)
|
||||||
deliver.deliverProperty(name, value)
|
} else {
|
||||||
}
|
Timber.w("deliverProperty($name=$value) error! not in main process")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeProperties(keys: Set<String>) {
|
private fun removeProperties(keys: Set<String>) {
|
||||||
Timber.tag("GuruAnalytics").d("removeProperties $keys")
|
if (isMainProcess.get()) {
|
||||||
deliverExecutor.execute {
|
Timber.tag("GuruAnalytics").d("removeProperties $keys")
|
||||||
for (deliver in delivers) {
|
EventSink.removeProperties(keys)
|
||||||
deliver.removeProperties(keys)
|
} else {
|
||||||
}
|
Timber.w("removeProperties($keys) error! not in main process")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package guru.core.analytics.utils
|
package guru.core.analytics.utils
|
||||||
|
|
||||||
import android.app.ActivityManager
|
import android.app.ActivityManager
|
||||||
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.graphics.Point
|
import android.graphics.Point
|
||||||
|
|
@ -19,6 +20,7 @@ import guru.core.analytics.Constants
|
||||||
import java.io.BufferedReader
|
import java.io.BufferedReader
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
import java.io.FileInputStream
|
||||||
|
import java.io.FileReader
|
||||||
import java.io.InputStreamReader
|
import java.io.InputStreamReader
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
|
@ -119,17 +121,15 @@ object AndroidUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
//try to fix SELinux limit due to unable access /proc file system
|
||||||
//try to fix SELinux limit due to unable access /proc file system
|
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager
|
||||||
val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
|
if (am != null) {
|
||||||
if (null != am) {
|
val appProcessInfoList = am.runningAppProcesses
|
||||||
val appProcessInfoList = am.runningAppProcesses
|
if (null != appProcessInfoList) {
|
||||||
if (null != appProcessInfoList) {
|
for (i in appProcessInfoList) {
|
||||||
for (i in appProcessInfoList) {
|
if (i.pid == Process.myPid()) {
|
||||||
if (i.pid == Process.myPid()) {
|
val result = i.processName.trim { it <= ' ' }
|
||||||
val result = i.processName.trim { it <= ' ' }
|
return result
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -139,5 +139,4 @@ object AndroidUtils {
|
||||||
// the real process name
|
// the real process name
|
||||||
return context.applicationInfo.processName
|
return context.applicationInfo.processName
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package guru.core.analytics.utils
|
package guru.core.analytics.utils
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
import android.content.BroadcastReceiver
|
import android.content.BroadcastReceiver
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
|
@ -11,6 +12,7 @@ import android.net.NetworkCapabilities
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Handler
|
import android.os.Handler
|
||||||
import android.os.Looper
|
import android.os.Looper
|
||||||
|
import guru.core.analytics.data.local.PreferencesManager
|
||||||
import io.reactivex.BackpressureStrategy
|
import io.reactivex.BackpressureStrategy
|
||||||
import io.reactivex.Flowable
|
import io.reactivex.Flowable
|
||||||
import io.reactivex.subjects.BehaviorSubject
|
import io.reactivex.subjects.BehaviorSubject
|
||||||
|
|
@ -18,7 +20,8 @@ import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
class Connectivity(private val context: Context) : BroadcastReceiver() {
|
|
||||||
|
class Connectivity private constructor(private val context: Context) : BroadcastReceiver() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val CONNECTIVITY_NONE: String = "none"
|
const val CONNECTIVITY_NONE: String = "none"
|
||||||
|
|
@ -31,6 +34,14 @@ class Connectivity(private val context: Context) : BroadcastReceiver() {
|
||||||
|
|
||||||
const val CONNECTIVITY_ACTION: String = "android.net.conn.CONNECTIVITY_CHANGE"
|
const val CONNECTIVITY_ACTION: String = "android.net.conn.CONNECTIVITY_CHANGE"
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
@Volatile
|
||||||
|
private var INSTANCE: Connectivity? = null
|
||||||
|
|
||||||
|
fun of(context: Context): Connectivity =
|
||||||
|
INSTANCE ?: synchronized(this) {
|
||||||
|
INSTANCE ?: Connectivity(context.applicationContext).also { INSTANCE = it }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val connectivityManager by lazy {
|
private val connectivityManager by lazy {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue