parent
							
								
									d5ec200617
								
							
						
					
					
						commit
						d55b525069
					
				|  | @ -179,7 +179,7 @@ class MainActivity : AppCompatActivity() { | |||
|         } | ||||
|         findViewById<TextView>(R.id.tvOpenTestProcessActivity).setOnClickListener { | ||||
| //            TestProcessActivity.startActivity(this) | ||||
|             GuruAnalytics.Builder(this) | ||||
|             GuruAnalytics.Builder(this, "v3.0.0") | ||||
|                 .setBatchLimit(25) | ||||
|                 .setUploadPeriodInSeconds(60) | ||||
|                 .setStartUploadDelayInSecond(3) | ||||
|  |  | |||
|  | @ -21,7 +21,7 @@ class TestProcessActivity : AppCompatActivity() { | |||
|         super.onCreate(savedInstanceState) | ||||
|         setContentView(R.layout.activity_test_process) | ||||
| 
 | ||||
|         GuruAnalytics.Builder(this) | ||||
|         GuruAnalytics.Builder(this, "v3.0.0") | ||||
|             .setBatchLimit(25) | ||||
|             .setUploadPeriodInSeconds(60) | ||||
|             .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 | ||||
| # resources declared in the library itself and none from the library's dependencies, | ||||
| # 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" | ||||
| 
 | ||||
|         buildConfigField "String", "buildTs", buildTs() | ||||
|         buildConfigField "String", "guruAnalyticsSdkVersion", "\"v$guruAnalyticsSdkVersion\"" | ||||
| 
 | ||||
|         testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" | ||||
|     } | ||||
|  |  | |||
|  | @ -18,7 +18,8 @@ publishing {    // Repositories *to* which Gradle can publish artifacts | |||
|         maven(MavenPublication) { | ||||
|             groupId 'guru.core.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 "build/outputs/aar/aar-test-release.aar"//aar包的目录 | ||||
|             afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) } | ||||
|  |  | |||
|  | @ -1,5 +1,6 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     xmlns:tools="http://schemas.android.com/tools" | ||||
|     package="guru.core.analytics"> | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
|     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> | ||||
|  |  | |||
|  | @ -12,14 +12,18 @@ object Constants { | |||
|         const val EVENT = "event" | ||||
|         const val PARAM = "param" | ||||
|         const val SCREEN = "screen_name" | ||||
|         const val SESSION_ID = "session_id" | ||||
|         const val ITEM_CATEGORY = "item_category" | ||||
|         const val ITEM_NAME = "item_name" | ||||
|         const val VALUE = "value" | ||||
|         const val FG = "fg" | ||||
|         const val FG = "guru_engagement" | ||||
|         const val DURATION = "duration" | ||||
|         const val FIRST_OPEN = "first_open" | ||||
|         const val ERROR_PROCESS = "error_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 { | ||||
|  | @ -43,7 +47,9 @@ object Constants { | |||
|         const val SCREEN_W = "screenW" | ||||
|         const val OS_VERSION = "osVersion" | ||||
|         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 { | ||||
|  |  | |||
|  | @ -30,7 +30,8 @@ abstract class GuruAnalytics { | |||
|         mainProcess: String? = null, | ||||
|         isEnableCronet: Boolean? = null, | ||||
|         uploadIpAddress: List<String>? = null, | ||||
|         dnsMode: Int? = null | ||||
|         dnsMode: Int? = null, | ||||
|         guruSdkVersion: 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() | ||||
| 
 | ||||
|         fun setBatchLimit(batchLimit: Int?) = apply { analyticsInfo.batchLimit = batchLimit } | ||||
|  | @ -162,7 +163,8 @@ abstract class GuruAnalytics { | |||
|                     mainProcess, | ||||
|                     isEnableCronet, | ||||
|                     uploadIpAddress, | ||||
|                     dnsMode | ||||
|                     dnsMode, | ||||
|                     guruSdkVersion | ||||
|                 ) | ||||
|             } | ||||
|             return INSTANCE | ||||
|  |  | |||
|  | @ -60,5 +60,7 @@ class PreferencesManager private constructor( | |||
|     var deviceId: String? by bind("device_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 eventDispatcherStarted: Boolean = false | ||||
|     var fgHelperInitialized: Boolean = false | ||||
|     var connectionState: Boolean = false | ||||
|     var networkAvailable: Boolean = false | ||||
| 
 | ||||
|     // 整体的 | ||||
|     var total: Int = 0 | ||||
|  | @ -54,7 +54,7 @@ object GuruAnalyticsAudit { | |||
|         dnsMode = dnsMode, | ||||
|         eventDispatcherStarted = eventDispatcherStarted, | ||||
|         fgHelperInitialized = fgHelperInitialized, | ||||
|         connectionState = connectionState, | ||||
|         connectionState = networkAvailable, | ||||
|         total = total, | ||||
|         deleted = deleted, | ||||
|         uploaded = uploaded, | ||||
|  |  | |||
|  | @ -14,7 +14,7 @@ import java.util.* | |||
| 
 | ||||
| object DeviceInfoStore { | ||||
| 
 | ||||
|     const val SDK_VERSION = "v1.1.0" | ||||
|     var GURU_SDK_VERSION = "" | ||||
| 
 | ||||
|     private val deviceInfoSubject: BehaviorSubject<Map<String, Any>> = | ||||
|         BehaviorSubject.createDefault( | ||||
|  | @ -41,7 +41,9 @@ object DeviceInfoStore { | |||
|         map[Constants.DeviceInfo.SCREEN_W] = AndroidUtils.getWindowWidth(context) ?: 0 | ||||
|         map[Constants.DeviceInfo.OS_VERSION] = Build.VERSION.RELEASE | ||||
|         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) | ||||
|         EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_DEVICE_INFO, map) | ||||
|         Timber.tag("DeviceInfoStore").i("DeviceInfo: $map") | ||||
|  |  | |||
|  | @ -38,6 +38,7 @@ object EventInfoStore { | |||
|                 restoreIds(this) | ||||
|                 GuruAnalyticsAudit.total = this.eventCountAll ?: 0 | ||||
|                 GuruAnalyticsAudit.uploaded = this.eventCountUploaded ?: 0 | ||||
|                 GuruAnalyticsAudit.deleted = this.eventCountDeleted ?: 0 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -163,7 +164,10 @@ object EventInfoStore { | |||
| 
 | ||||
|     private val supplementEventParamsSubject: BehaviorSubject<Map<String, Any>> = | ||||
|         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>> = | ||||
|  | @ -184,7 +188,10 @@ object EventInfoStore { | |||
|         } | ||||
| 
 | ||||
|     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) { | ||||
|             supplementEventParamsSubject.onNext(value) | ||||
|         } | ||||
|  |  | |||
|  | @ -37,11 +37,13 @@ internal class AppLifecycleMonitor internal constructor(context: Context) { | |||
|         when (event) { | ||||
|             Lifecycle.Event.ON_START -> { | ||||
|                 Timber.d("${TAG}_ON_START") | ||||
|                 fgHelper.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 -> { | ||||
|                 Timber.d("${TAG}_ON_PAUSE") | ||||
|                 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 | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.net.Uri | ||||
| import androidx.work.BackoffPolicy | ||||
| import androidx.work.Constraints | ||||
| import androidx.work.ExistingPeriodicWorkPolicy | ||||
| import androidx.work.ExistingWorkPolicy | ||||
| import androidx.work.NetworkType | ||||
| import androidx.work.OneTimeWorkRequestBuilder | ||||
| import androidx.work.PeriodicWorkRequestBuilder | ||||
| import androidx.work.SystemClock | ||||
| import androidx.work.WorkManager | ||||
| import guru.core.analytics.Constants | ||||
| import guru.core.analytics.GuruAnalytics | ||||
| 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.model.EventEntity | ||||
| import guru.core.analytics.data.db.model.EventPriority | ||||
|  | @ -44,6 +39,7 @@ import java.util.concurrent.TimeUnit | |||
| import java.util.concurrent.atomic.AtomicBoolean | ||||
| import kotlin.collections.ArrayList | ||||
| import kotlin.math.ceil | ||||
| import kotlin.math.max | ||||
| 
 | ||||
| internal class EventEngine internal constructor( | ||||
|     private val context: Context, | ||||
|  | @ -51,21 +47,21 @@ internal class EventEngine internal constructor( | |||
|     private val uploadPeriodInSeconds: Long = DEFAULT_UPLOAD_PERIOD_IN_SECONDS, | ||||
|     private val eventExpiredInDays: Int = DEFAULT_EVENT_EXPIRED_IN_DAYS, | ||||
|     private val guruRepository: GuruRepository | ||||
| ) : EventDeliver { | ||||
| ) { | ||||
| 
 | ||||
|     private val preferencesManager by lazy { | ||||
|     private val preferencesManager: PreferencesManager by lazy { | ||||
|         PreferencesManager.getInstance(context) | ||||
|     } | ||||
| 
 | ||||
|     private val connectivity: Connectivity by lazy { | ||||
|         Connectivity(context) | ||||
|         Connectivity.of(context) | ||||
|     } | ||||
| 
 | ||||
|     private val lifecycleMonitor = AppLifecycleMonitor(context) | ||||
| 
 | ||||
|     companion object { | ||||
|         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_EVENT_EXPIRED_IN_DAYS = 7 | ||||
| 
 | ||||
|  | @ -80,7 +76,6 @@ internal class EventEngine internal constructor( | |||
|         @Volatile | ||||
|         var sessionActivated = false | ||||
| 
 | ||||
| 
 | ||||
|         fun logDebug(message: String, vararg args: Any?) { | ||||
|             if (GuruAnalytics.INSTANCE.isDebug()) { | ||||
|                 Timber.tag(TAG).d(message, args) | ||||
|  | @ -101,13 +96,10 @@ internal class EventEngine internal constructor( | |||
|     } | ||||
| 
 | ||||
|     private var compositeDisposable: CompositeDisposable = CompositeDisposable() | ||||
|     private val pendingEventSubject: PublishSubject<Int> = PublishSubject.create() | ||||
|     private val forceTriggerSubject: PublishSubject<String> = PublishSubject.create() | ||||
|     internal val started = AtomicBoolean(false) | ||||
|     private var enableUpload = true | ||||
|     private var latestValidActionTs = 0L | ||||
| 
 | ||||
| 
 | ||||
|     fun setEnableUpload(enable: Boolean) { | ||||
|         enableUpload = enable | ||||
|         val extMap = mapOf("enable" to enable) | ||||
|  | @ -116,6 +108,7 @@ internal class EventEngine internal constructor( | |||
| 
 | ||||
|     fun start(startUploadDelay: Long?) { | ||||
|         if (started.compareAndSet(false, true)) { | ||||
|             AnchorAt.recordAt(AnchorAt.SESSION_START) | ||||
|             prepare() | ||||
|             connectivity.bind() | ||||
|             lifecycleMonitor.initialize() | ||||
|  | @ -131,8 +124,7 @@ internal class EventEngine internal constructor( | |||
|                 logDebug("session started error!") | ||||
|             } | ||||
|             scheduler.scheduleDirect({ | ||||
|                 EventDispatcher.start() | ||||
|                 logFirstOpen() | ||||
|                 EventSink.start() | ||||
|                 startWork() | ||||
|                 val extMap = mapOf("startUploadDelayInSecond" to startUploadDelay) | ||||
|                 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() { | ||||
| 
 | ||||
|  | @ -172,6 +154,7 @@ internal class EventEngine internal constructor( | |||
|             logEvent(event) | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.EVENT_SESSION_ACTIVE) | ||||
|             sessionActivated = true | ||||
| 
 | ||||
|         }.onErrorReturn { | ||||
|             Timber.e("active error! $it") | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_SESSION_START_ERROR, it.message) | ||||
|  | @ -185,7 +168,7 @@ internal class EventEngine internal constructor( | |||
|     private fun dispatchActiveWorker() { | ||||
|         Timber.e("dispatchActiveWorker...") | ||||
|         val constraints = Constraints.Builder() | ||||
|             .setRequiredNetworkType(NetworkType.UNMETERED) | ||||
|             .setRequiredNetworkType(NetworkType.CONNECTED) | ||||
|             .build() | ||||
|         val request = OneTimeWorkRequestBuilder<ActiveWorker>() | ||||
|             .setBackoffCriteria(BackoffPolicy.LINEAR, 1, TimeUnit.MINUTES) | ||||
|  | @ -209,7 +192,6 @@ internal class EventEngine internal constructor( | |||
|     private fun startWork() { | ||||
|         Timber.d("startWork") | ||||
|         pollEvents() | ||||
|         forceUpload("startWork") | ||||
|         Timber.d("UploadEventDaemon started!!") | ||||
|     } | ||||
| 
 | ||||
|  | @ -236,19 +218,25 @@ internal class EventEngine internal constructor( | |||
|     private fun pollEvents() { | ||||
|         logDebug("pollEvents()!! $uploadPeriodInSeconds $batchLimit") | ||||
|         val periodFlowable = Flowable.interval(5, uploadPeriodInSeconds, TimeUnit.SECONDS).onBackpressureDrop() | ||||
|         val forceFlowable = forceTriggerSubject.toFlowable(BackpressureStrategy.DROP).map { scene -> | ||||
|             logDebug("force trigger: $scene") | ||||
|         val forceFlowable = EventSink.forceFlowable.map { scene -> | ||||
|             logDebug("Force Trigger: $scene") | ||||
|         } | ||||
|         val networkFlowable = connectivity.networkAvailableFlowable | ||||
|         compositeDisposable.add(Flowable.combineLatest( | ||||
|             periodFlowable, forceFlowable, networkFlowable, | ||||
|         ) { _, _, available -> | ||||
|             GuruAnalyticsAudit.connectionState = available | ||||
|             val uploadedRate = GuruAnalyticsAudit.uploaded / GuruAnalyticsAudit.total.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 || ignoreAvailable) | ||||
|         }.flatMap { uploadEvents(100) }.subscribe() | ||||
|         compositeDisposable.add( | ||||
|             Flowable.combineLatest( | ||||
|                 periodFlowable, forceFlowable, | ||||
|             ) { _, _ -> | ||||
|                 val available = try { | ||||
|                     connectivity.isNetworkAvailable() | ||||
|                 } catch (throwable: Throwable) { | ||||
|                     logInfo("networkAvailable error: $throwable") | ||||
|                     GuruAnalyticsAudit.networkAvailable | ||||
|                 } | ||||
|                 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() | ||||
|         GuruAnalyticsAudit.uploadReady = true | ||||
|         logDebug("uploadEvents: $count") | ||||
|         return Flowable.just(count).map { eventDao.loadAndMarkUploadEvents(it) }.onErrorReturn { | ||||
|             try { | ||||
|                 eventDao.loadAndMarkUploadEvents(batchLimit) | ||||
|             } catch (throwable: Throwable) { | ||||
|                 EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_LOAD_MARK, it.message) | ||||
|                 emptyList() | ||||
|             } | ||||
|         }.filter { it.isNotEmpty() }.subscribeOn(dbScheduler).observeOn(scheduler).concatMap { splitEntities(it) } | ||||
|         return Flowable.just(count) | ||||
|             .map { eventDao.loadAndMarkUploadEvents(it) } | ||||
|             .onErrorReturn { | ||||
|                 try { | ||||
|                     eventDao.loadAndMarkUploadEvents(batchLimit) | ||||
|                 } catch (throwable: Throwable) { | ||||
|                     EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_LOAD_MARK, it.message) | ||||
|                     emptyList() | ||||
|                 } | ||||
|             }.filter { it.isNotEmpty() }.subscribeOn(dbScheduler).observeOn(scheduler).concatMap { splitEntities(it) } | ||||
|             .flatMapSingle { uploadEventsInternal(it) }.filter { it.isNotEmpty() }.doOnNext { | ||||
|                 eventDao.deleteEvents(it) | ||||
|                 if (GuruAnalytics.INSTANCE.isDebug()) { | ||||
|  | @ -306,76 +296,42 @@ internal class EventEngine internal constructor( | |||
| 
 | ||||
|     private fun uploadEventsInternal(entities: List<EventEntity>): Single<List<EventEntity>> { | ||||
|         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 eventCountUploaded = preferencesManager.eventCountUploaded ?: 0 | ||||
|             val uploaded = eventCountUploaded + entities.size | ||||
|             preferencesManager.eventCountUploaded = uploaded | ||||
|             GuruAnalyticsAudit.uploaded = uploaded | ||||
|             GuruAnalyticsAudit.sessionUploaded += entities.size | ||||
| 
 | ||||
|             val extMap = mapOf( | ||||
|                 "count" to entities.size, | ||||
|                 "eventNames" to entities.joinToString(",") { it.event }, | ||||
|                 "allUploadedCount" to preferencesManager.eventCountUploaded, | ||||
|             ) | ||||
|             logDebug("uploadEvents success: $extMap") | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_SUCCESS, extMap) | ||||
|             latestValidActionTs = android.os.SystemClock.elapsedRealtime() | ||||
|         }.onErrorReturn { | ||||
|             GuruAnalyticsDatabase.getInstance().eventDao().updateEventDefault(entities) | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_FAIL, it.message) | ||||
|             val elapsed = android.os.SystemClock.elapsedRealtime() | ||||
|             val uploadedRate = GuruAnalyticsAudit.sessionUploaded / GuruAnalyticsAudit.sessionTotal | ||||
|             val isActiveNetworkMetered = connectivity.isActiveNetworkMetered | ||||
|             val exceededValidActionGap = elapsed - latestValidActionTs > SESSION_ACTIVE_INTERVAL | ||||
|             logInfo("uploadEvent error $it sessionActivated:$sessionActivated uploadedRate:$uploadedRate isActiveNetworkMetered:$isActiveNetworkMetered gap:${(elapsed - latestValidActionTs) / 1000}s") | ||||
|             /// 如果没有激活,并且当前是计费网络,并且距离上次上传成功时间超过15分钟,激活worker | ||||
|             /// 因为这个 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) | ||||
|                 val extMap = mapOf( | ||||
|                     "count" to entities.size, | ||||
|                     "eventNames" to entities.joinToString(",") { it.event }, | ||||
|                     "allUploadedCount" to preferencesManager.eventCountUploaded, | ||||
|                 ) | ||||
|                 logDebug("uploadEvents success: $extMap") | ||||
|                 EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_SUCCESS, extMap) | ||||
|                 latestValidActionTs = android.os.SystemClock.elapsedRealtime() | ||||
|             }.onErrorReturn { | ||||
|                 GuruAnalyticsDatabase.getInstance().eventDao().updateEventDefault(entities) | ||||
|                 EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.UPLOAD_FAIL, it.message) | ||||
| //                val elapsed = android.os.SystemClock.elapsedRealtime() | ||||
| //                val uploadedRate = GuruAnalyticsAudit.sessionUploaded / max(GuruAnalyticsAudit.sessionTotal, 1) | ||||
| //                val isActiveNetworkMetered = connectivity.isActiveNetworkMetered | ||||
| //                val exceededValidActionGap = elapsed - latestValidActionTs > SESSION_ACTIVE_INTERVAL | ||||
| //                logInfo("uploadEvent error $it sessionActivated:$sessionActivated uploadedRate:$uploadedRate isActiveNetworkMetered:$isActiveNetworkMetered gap:${(elapsed - latestValidActionTs) / 1000}s") | ||||
| //                /// 如果没有激活,并且当前是计费网络,并且距离上次上传成功时间超过15分钟,激活worker | ||||
| //                /// 因为这个 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() } | ||||
|     } | ||||
| 
 | ||||
|     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 guru.core.analytics.Constants | ||||
| 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.model.AnalyticsOptions | ||||
| import guru.core.analytics.data.model.GuruAnalyticsAudit | ||||
| import guru.core.analytics.handler.AnalyticsCode | ||||
| import guru.core.analytics.handler.EventHandler | ||||
|  | @ -16,20 +18,17 @@ import java.lang.Math.max | |||
| import java.util.concurrent.Executors | ||||
| import java.util.concurrent.TimeUnit | ||||
| import java.util.concurrent.atomic.AtomicBoolean | ||||
| import java.util.concurrent.atomic.AtomicLong | ||||
| 
 | ||||
| internal class FgHelper(context: Context) { | ||||
| 
 | ||||
|     companion object { | ||||
|         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()) | ||||
|     } | ||||
| 
 | ||||
|     @Volatile | ||||
|     private var latestRecordAt = SystemClock.elapsedRealtime() | ||||
| 
 | ||||
|     @Volatile | ||||
|     private var currentDuration = 0L | ||||
|     private var latestActiveAt = AtomicLong(SystemClock.elapsedRealtime()) | ||||
|     private var disposable: Disposable? = null | ||||
|     private val initialized = AtomicBoolean(false) | ||||
| 
 | ||||
|  | @ -37,39 +36,45 @@ internal class FgHelper(context: Context) { | |||
|         PreferencesManager.getInstance(context) | ||||
|     } | ||||
| 
 | ||||
|     private val preferenceEditor by lazy { | ||||
|         preferencesManager.getSharedPreferencesDirectly().edit() | ||||
|     } | ||||
| 
 | ||||
|     internal fun ensureInitialized() { | ||||
|         if (initialized.compareAndSet(false, true)) { | ||||
|             currentDuration = preferencesManager.getTotalDurationFgEvent() | ||||
|             refresh(force = true) | ||||
|             val elapsedAt = SystemClock.elapsedRealtime() | ||||
|             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 { | ||||
|         val now = SystemClock.elapsedRealtime() | ||||
|         val duration = now - latestRecordAt | ||||
|         val fgDuration = currentDuration + duration | ||||
|         latestRecordAt = now | ||||
|         currentDuration = if (force || fgDuration > FG_REPORT_INTERVAL_MILLIS) { | ||||
|             logFgEvent(fgDuration.coerceAtLeast(1)) | ||||
|             0L | ||||
|     private fun refresh() { | ||||
|         val elapsedAt = SystemClock.elapsedRealtime() | ||||
|         val duration = elapsedAt - latestActiveAt.get() | ||||
|         Timber.tag("FgHelper").d("refresh: $duration") | ||||
|         if (duration < FG_REPORT_INTERVAL_MILLIS) { | ||||
|             preferencesManager.setTotalDurationFgEvent(duration) | ||||
|         } else { | ||||
|             fgDuration | ||||
|             userEngagement(elapsedAt) | ||||
|         } | ||||
| 
 | ||||
|         preferenceEditor.putLong(PreferencesManager.KEY_TOTAL_DURATION_FG_EVENT, currentDuration) | ||||
|             .commit() | ||||
|         return currentDuration | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     fun start() { | ||||
|         userActive() | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     fun stop() { | ||||
|         userInactive() | ||||
|     } | ||||
| 
 | ||||
|     private fun userActive() { | ||||
|         ensureInitialized() | ||||
|         latestRecordAt = SystemClock.elapsedRealtime() | ||||
|         latestActiveAt.set(SystemClock.elapsedRealtime()) | ||||
|         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) | ||||
|             .subscribe({ | ||||
|                 refresh() | ||||
|  | @ -79,17 +84,32 @@ internal class FgHelper(context: Context) { | |||
|         GuruAnalyticsAudit.fgHelperInitialized = true | ||||
|     } | ||||
| 
 | ||||
|     fun stop() { | ||||
|     private fun userInactive() { | ||||
|         userEngagement(SystemClock.elapsedRealtime()) | ||||
|         disposable?.dispose() | ||||
|         refresh() | ||||
|         disposable = null | ||||
|     } | ||||
| 
 | ||||
|     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) | ||||
|     private fun isActive() = disposable != null | ||||
| 
 | ||||
|     private fun userEngagement(elapsedAt: Long) { | ||||
|         if (!isActive()) { | ||||
|             Timber.tag("FgHelper").w("inactive fg helper") | ||||
|             return | ||||
|         } | ||||
|         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.net.Uri | ||||
| import android.transition.Scene | ||||
| import androidx.annotation.RequiresPermission | ||||
| import androidx.work.* | ||||
| import guru.core.analytics.Constants | ||||
|  | @ -52,6 +51,11 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
| 
 | ||||
|     private val initialized = AtomicBoolean(false) | ||||
| 
 | ||||
| 
 | ||||
|     private val isMainProcess = AtomicBoolean(true) | ||||
| 
 | ||||
| //    private val  = System.currentTimeMillis() | ||||
| 
 | ||||
|     override fun isDebug(): Boolean = debugMode | ||||
| 
 | ||||
|     override fun setDebug(debug: Boolean) { | ||||
|  | @ -69,6 +73,7 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|         Timber.d("setUploadEventBaseUrl:$updateEventBaseUrl") | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
|     @RequiresPermission(allOf = ["android.permission.INTERNET", "android.permission.ACCESS_NETWORK_STATE"]) | ||||
|     override fun initialize( | ||||
|         context: Context, | ||||
|  | @ -87,7 +92,8 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|         mainProcess: String?, | ||||
|         isEnableCronet: Boolean?, | ||||
|         uploadIpAddress: List<String>?, | ||||
|         dnsMode: Int? | ||||
|         dnsMode: Int?, | ||||
|         guruSdkVersion: String | ||||
|     ) { | ||||
|         if (initialized.compareAndSet(false, true)) { | ||||
|             val debugApp = SystemProperties.read("debug.guru.analytics.app") | ||||
|  | @ -100,19 +106,27 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|                 } 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) | ||||
|             delivers.add(EventDispatcher) | ||||
| 
 | ||||
|             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") | ||||
| 
 | ||||
|             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) | ||||
|             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) | ||||
| 
 | ||||
|  | @ -123,7 +137,7 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|             ServiceLocator.setUploadIpAddress(uploadIpAddress) | ||||
|             ServiceLocator.setDnsMode(dnsMode ?: 0) | ||||
| 
 | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_4) | ||||
| //            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_4) | ||||
|             if (isEnableCronet == true) { | ||||
|                 GuruAnalyticsAudit.enabledCronet = 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 hostname: String? = "" | ||||
|  | @ -147,9 +161,10 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|                 } | ||||
|                 hostname = uploadEventBaseUri.host | ||||
|             } | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_6) | ||||
| //            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_6) | ||||
| 
 | ||||
|             ServiceLocator.preloadDns(hostname) | ||||
|             EventSink.initialize(context, hostname ?: AnalyticsApiHost.BASE_URL) | ||||
| 
 | ||||
|             engine = EventEngine( | ||||
|                 context, | ||||
|  | @ -161,28 +176,17 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|                 ), | ||||
|                 guruRepository = ServiceLocator.provideGuruRepository(context, baseUri = uploadEventBaseUri) /// 此处需要配合 ServiceLocator的重构进行修改 | ||||
|             ).apply { | ||||
|                 delivers.add(this) | ||||
|                 start(startUploadDelayInSecond) | ||||
|                 EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_7) | ||||
| //                EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_7) | ||||
|             } | ||||
|             if (isInitPeriodicWork) { | ||||
|                 val process = AndroidUtils.getProcessName(context) | ||||
|                 Timber.d("initialize ${mainProcess == process} currentProcess:$process mainProcess:$mainProcess") | ||||
|                 if (mainProcess.isNullOrBlank() || mainProcess == process) { | ||||
|                     try { | ||||
|                         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) | ||||
|                     } | ||||
|                 try { | ||||
|                     initAnalyticsPeriodic(context) | ||||
|                 } catch (throwable: Throwable) { | ||||
|                     Timber.d("init worker error!") | ||||
|                 } | ||||
|             } | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_8) | ||||
| //            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_8) | ||||
| 
 | ||||
|             val extMap = mapOf( | ||||
|                 "version_code" to internalVersion, | ||||
|  | @ -203,8 +207,9 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|             if (uploadIpAddress != null) { | ||||
|                 PreferencesManager.getInstance(context).uploadIpAddressList = uploadIpAddress.joinToString("|") | ||||
|             } | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_9) | ||||
| //            EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_9) | ||||
|             GuruAnalyticsAudit.initialized = true | ||||
|             AnchorAt.recordAt(AnchorAt.SDK_INIT_COMPLETED) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -329,19 +334,35 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|         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) { | ||||
|         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>) { | ||||
|         removeProperties(keys) | ||||
|         if (isMainProcess.get()) { | ||||
|             removeProperties(keys) | ||||
|         } else { | ||||
|             Timber.w("removeUserProperties error! not in main process") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun getUserProperties(callback: (Map<String, String>) -> Unit) { | ||||
|  | @ -355,7 +376,12 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|     } | ||||
| 
 | ||||
|     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 { | ||||
|  | @ -364,17 +390,27 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|     } | ||||
| 
 | ||||
|     override fun clearStatistic(context: Context) { | ||||
|         PreferencesManager.getInstance(context).apply { | ||||
|             eventCountAll = 0 | ||||
|             eventCountDeleted = 0 | ||||
|             eventCountUploaded = 0 | ||||
|         if (isMainProcess.get()) { | ||||
|             PreferencesManager.getInstance(context).apply { | ||||
|                 eventCountAll = 0 | ||||
|                 eventCountDeleted = 0 | ||||
|                 eventCountUploaded = 0 | ||||
|             } | ||||
|         } else { | ||||
|             Timber.w("clearStatistic error! not in main process") | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     override fun forceUpload(scene: String): Boolean { | ||||
|         if (!isMainProcess.get()) { | ||||
|             Timber.w("forceUpload(${scene}) error! not in main process") | ||||
|             return false | ||||
|         } | ||||
| 
 | ||||
|         return engine?.let { | ||||
|             if (it.started.get()) { | ||||
|                 it.forceUpload(scene) | ||||
|                 EventSink.forceUpload(scene) | ||||
|                 return@let true | ||||
|             } | ||||
|             return@let false | ||||
|  | @ -382,30 +418,30 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|     } | ||||
| 
 | ||||
|     private fun deliverEvent(item: EventItem, options: AnalyticsOptions) { | ||||
|         Timber.tag("GuruAnalytics").d("deliverEvent ${item.eventName}!") | ||||
|         deliverExecutor.execute { | ||||
|             for (deliver in delivers) { | ||||
|                 deliver.deliverEvent(item, options) | ||||
|             } | ||||
|         if (isMainProcess.get()) { | ||||
|             Timber.tag("GuruAnalytics").d("deliverEvent ${item.eventName}!") | ||||
|             EventSink.deliverEvent(item, options) | ||||
|         } else { | ||||
|             Timber.w("deliverEvent(${item.eventName}) error! not in main process") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun deliverProperty(name: String, value: String) { | ||||
|         Timber.tag("GuruAnalytics").d("deliverProperty $name = $value") | ||||
|         deliverExecutor.execute { | ||||
|             for (deliver in delivers) { | ||||
|                 deliver.deliverProperty(name, value) | ||||
|             } | ||||
|         if (isMainProcess.get()) { | ||||
|             Timber.tag("GuruAnalytics").d("deliverProperty $name = $value") | ||||
|             EventSink.deliverProperty(name, value) | ||||
|         } else { | ||||
|             Timber.w("deliverProperty($name=$value) error! not in main process") | ||||
| 
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun removeProperties(keys: Set<String>) { | ||||
|         Timber.tag("GuruAnalytics").d("removeProperties $keys") | ||||
|         deliverExecutor.execute { | ||||
|             for (deliver in delivers) { | ||||
|                 deliver.removeProperties(keys) | ||||
|             } | ||||
|         if (isMainProcess.get()) { | ||||
|             Timber.tag("GuruAnalytics").d("removeProperties $keys") | ||||
|             EventSink.removeProperties(keys) | ||||
|         } else { | ||||
|             Timber.w("removeProperties($keys) error! not in main process") | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,6 +1,7 @@ | |||
| package guru.core.analytics.utils | ||||
| 
 | ||||
| import android.app.ActivityManager | ||||
| import android.app.Application | ||||
| import android.content.Context | ||||
| import android.content.pm.PackageManager | ||||
| import android.graphics.Point | ||||
|  | @ -19,6 +20,7 @@ import guru.core.analytics.Constants | |||
| import java.io.BufferedReader | ||||
| import java.io.File | ||||
| import java.io.FileInputStream | ||||
| import java.io.FileReader | ||||
| import java.io.InputStreamReader | ||||
| 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 | ||||
|             val am = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager | ||||
|             if (null != am) { | ||||
|                 val appProcessInfoList = am.runningAppProcesses | ||||
|                 if (null != appProcessInfoList) { | ||||
|                     for (i in appProcessInfoList) { | ||||
|                         if (i.pid == Process.myPid()) { | ||||
|                             val result = i.processName.trim { it <= ' ' } | ||||
|                             return result | ||||
|                         } | ||||
|         //try to fix SELinux limit due to unable access /proc file system | ||||
|         val am = context.getSystemService(Context.ACTIVITY_SERVICE) as? ActivityManager | ||||
|         if (am != null) { | ||||
|             val appProcessInfoList = am.runningAppProcesses | ||||
|             if (null != appProcessInfoList) { | ||||
|                 for (i in appProcessInfoList) { | ||||
|                     if (i.pid == Process.myPid()) { | ||||
|                         val result = i.processName.trim { it <= ' ' } | ||||
|                         return result | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | @ -139,5 +139,4 @@ object AndroidUtils { | |||
|         // the real process name | ||||
|         return context.applicationInfo.processName | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,5 +1,6 @@ | |||
| package guru.core.analytics.utils | ||||
| 
 | ||||
| import android.annotation.SuppressLint | ||||
| import android.content.BroadcastReceiver | ||||
| import android.content.Context | ||||
| import android.content.Intent | ||||
|  | @ -11,6 +12,7 @@ import android.net.NetworkCapabilities | |||
| import android.os.Build | ||||
| import android.os.Handler | ||||
| import android.os.Looper | ||||
| import guru.core.analytics.data.local.PreferencesManager | ||||
| import io.reactivex.BackpressureStrategy | ||||
| import io.reactivex.Flowable | ||||
| import io.reactivex.subjects.BehaviorSubject | ||||
|  | @ -18,7 +20,8 @@ import timber.log.Timber | |||
| import java.util.concurrent.TimeUnit | ||||
| import java.util.concurrent.atomic.AtomicBoolean | ||||
| 
 | ||||
| class Connectivity(private val context: Context) : BroadcastReceiver() { | ||||
| 
 | ||||
| class Connectivity private constructor(private val context: Context) : BroadcastReceiver() { | ||||
| 
 | ||||
|     companion object { | ||||
|         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" | ||||
| 
 | ||||
|         @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 { | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue