update guru_analytics

Signed-off-by: Haoyi <haoyi.zhang@castbox.fm>
3.0.0
Haoyi 2024-05-09 16:21:50 +08:00
parent 5927987ff5
commit 3803d53afe
23 changed files with 491 additions and 96 deletions

View File

@ -1,3 +1,39 @@
## v1.0.3
##### BugFix
- bugfix snapshotAnalyticsAudit uploaded > total
## v1.0.2
##### Feature
- init step callback
- snapshotAnalyticsAudit()
##### BugFix
- bugfix http exception
## v1.0.1
##### Feature
- init add uploadIpAddress
- CronetHelper init try catch
## v1.0.0
##### Feature
- CustomDns add cache
- init add isEnableCronet
- UserProperty guru_anm cronet/default
- PendingEvent 初始化成功前添加的埋点时间戳校正
- initialize uploadEventBaseUrl fromat check
## v0.3.2
##### Feature
- add fun peakUserProperties
- add fun getUserProperties
- add fun setEnableUpload
## v0.3.1 ## v0.3.1
##### Feature ##### Feature

View File

@ -7,9 +7,11 @@ import android.widget.TextView
import guru.core.analytics.GuruAnalytics import guru.core.analytics.GuruAnalytics
import guru.core.analytics.data.db.model.EventPriority import guru.core.analytics.data.db.model.EventPriority
import guru.core.analytics.data.model.AnalyticsOptions import guru.core.analytics.data.model.AnalyticsOptions
import guru.core.analytics.handler.AnalyticsCode
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private var enableUpload = true
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
@ -36,6 +38,8 @@ class MainActivity : AppCompatActivity() {
.setXAppId("test_x_app_id") .setXAppId("test_x_app_id")
.setXDeviceInfo("test_x_device_info") .setXDeviceInfo("test_x_device_info")
.setMainProcess("com.example.guruanalytics") .setMainProcess("com.example.guruanalytics")
.isEnableCronet(true)
.setUploadIpAddress(listOf("3.210.96.186", "34.196.69.199"))
.build() .build()
findViewById<TextView>(R.id.tvLogEvent).setOnClickListener { findViewById<TextView>(R.id.tvLogEvent).setOnClickListener {
@ -76,9 +80,31 @@ class MainActivity : AppCompatActivity() {
GuruAnalytics.INSTANCE.setUploadEventBaseUrl(this, "https://www.castbox.fm/") GuruAnalytics.INSTANCE.setUploadEventBaseUrl(this, "https://www.castbox.fm/")
} }
val tvEnable = findViewById<TextView>(R.id.tvEnable)
tvEnable?.setOnClickListener {
val enable = !enableUpload
GuruAnalytics.INSTANCE.setEnableUpload(enable)
enableUpload = enable
tvEnable.text = if (enableUpload) "Enable Upload" else "Disable Upload"
}
GuruAnalytics.INSTANCE.setScreen("main") GuruAnalytics.INSTANCE.setScreen("main")
GuruAnalytics.INSTANCE.setAdId("AD_ID_01") GuruAnalytics.INSTANCE.setAdId("AD_ID_01")
GuruAnalytics.INSTANCE.setUserProperty("uid", "110051") GuruAnalytics.INSTANCE.setUserProperty("uid", "110051")
GuruAnalytics.INSTANCE.setUserProperty("age", "12")
GuruAnalytics.INSTANCE.setUserProperty("sex", "male")
val properties = GuruAnalytics.INSTANCE.peakUserProperties()
Log.i("peakUserProperties", "properties:$properties")
GuruAnalytics.INSTANCE.getUserProperties {
Log.i("getUserProperties", "properties:$it")
}
tvEnable?.postDelayed({
val snapshot = GuruAnalytics.INSTANCE.snapshotAnalyticsAudit()
Log.i("snapshotAnalyticsAudit", "snapshot:$snapshot")
}, 10_000)
} }
private val eventHandler: (Int, String?) -> Unit = { code, ext -> private val eventHandler: (Int, String?) -> Unit = { code, ext ->

View File

@ -70,4 +70,13 @@
android:text="Update BaseUrl" android:text="Update BaseUrl"
android:layout_gravity="center_horizontal" /> android:layout_gravity="center_horizontal" />
<TextView
android:id="@+id/tvEnable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:padding="10dp"
android:text="Enable Upload"
android:layout_gravity="center_horizontal" />
</LinearLayout> </LinearLayout>

View File

@ -53,7 +53,9 @@ dependencies {
implementation okhttpDependencies implementation okhttpDependencies
implementation process implementation processDependencies
implementation workerDependencies implementation workerDependencies
implementation cronetDependencies
} }

View File

@ -21,6 +21,8 @@ ext {
preferenceVersion = '1.2.0' preferenceVersion = '1.2.0'
processVersion = '2.4.0' processVersion = '2.4.0'
workVersion = '2.7.1' workVersion = '2.7.1'
cronetOkhttpVersion = '0.1.0'
playServicesCronetVersion = '18.0.1'
kaptDependencies = [ kaptDependencies = [
"androidx.room:room-compiler:$roomVersion", "androidx.room:room-compiler:$roomVersion",
@ -54,7 +56,12 @@ ext {
"androidx.work:work-rxjava2:$workVersion" "androidx.work:work-rxjava2:$workVersion"
] ]
process = [ processDependencies = [
"androidx.lifecycle:lifecycle-process:$processVersion" "androidx.lifecycle:lifecycle-process:$processVersion"
] ]
cronetDependencies = [
"com.google.net.cronet:cronet-okhttp:$cronetOkhttpVersion",
"com.google.android.gms:play-services-cronet:$playServicesCronetVersion"
]
} }

View File

@ -18,7 +18,7 @@ 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 '0.3.1' // Your package version version '1.0.3' // Your package version
// 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")) }

View File

@ -56,6 +56,12 @@ object Constants {
const val COIN = "coin" const val COIN = "coin"
const val EXP = "exp" const val EXP = "exp"
const val HP = "hp" const val HP = "hp"
const val GURU_ANM = "guru_anm"
}
object AnmState {
const val CRONET = "cronet"
const val DEFAULT = "default"
} }
object DeviceType { object DeviceType {

View File

@ -4,7 +4,6 @@ import android.content.Context
import guru.core.analytics.data.db.model.EventStatistic import guru.core.analytics.data.db.model.EventStatistic
import guru.core.analytics.data.model.AnalyticsInfo import guru.core.analytics.data.model.AnalyticsInfo
import guru.core.analytics.data.model.AnalyticsOptions import guru.core.analytics.data.model.AnalyticsOptions
import guru.core.analytics.handler.AnalyticsCode
import guru.core.analytics.impl.GuruAnalyticsImpl import guru.core.analytics.impl.GuruAnalyticsImpl
import java.io.File import java.io.File
@ -21,13 +20,15 @@ abstract class GuruAnalytics {
eventExpiredInDays: Int? = 7, eventExpiredInDays: Int? = 7,
debug: Boolean = false, debug: Boolean = false,
persistableLog: Boolean = true, persistableLog: Boolean = true,
listener: ((Int, String?) -> Unit)? = null, eventHandlerCallback: ((Int, String?) -> Unit)? = null,
isInitPeriodicWork: Boolean = false, isInitPeriodicWork: Boolean = false,
uploadEventBaseUrl: String? = null, uploadEventBaseUrl: String? = null,
fgEventPeriodInSeconds: Long? = null, fgEventPeriodInSeconds: Long? = null,
xAppId: String? = null, xAppId: String? = null,
xDeviceInfo: String? = null, xDeviceInfo: String? = null,
mainProcess: String? = null, mainProcess: String? = null,
isEnableCronet: Boolean? = null,
uploadIpAddress: List<String>? = null,
) )
abstract fun setUploadEventBaseUrl(context: Context, updateEventBaseUrl: String) abstract fun setUploadEventBaseUrl(context: Context, updateEventBaseUrl: String)
@ -71,6 +72,17 @@ abstract class GuruAnalytics {
abstract fun removeUserProperties(keys: Set<String>) abstract fun removeUserProperties(keys: Set<String>)
abstract fun getUserProperties(callback: (Map<String, String>) -> Unit)
/**
* setUserProperty后立即获取, 可能无法获取到最新设置的值
*/
abstract fun peakUserProperties(): Map<String, String>
abstract fun setEnableUpload(enable: Boolean)
abstract fun snapshotAnalyticsAudit(): String
companion object { companion object {
val INSTANCE: GuruAnalytics by lazy() { val INSTANCE: GuruAnalytics by lazy() {
GuruAnalyticsImpl() GuruAnalyticsImpl()
@ -116,6 +128,12 @@ abstract class GuruAnalytics {
fun setMainProcess(process: String) = fun setMainProcess(process: String) =
apply { analyticsInfo.mainProcess = process } apply { analyticsInfo.mainProcess = process }
fun isEnableCronet(isEnableCronet: Boolean) =
apply { analyticsInfo.isEnableCronet = isEnableCronet }
fun setUploadIpAddress(uploadIpAddress: List<String>) =
apply { analyticsInfo.uploadIpAddress = uploadIpAddress }
fun build(): GuruAnalytics { fun build(): GuruAnalytics {
analyticsInfo.run { analyticsInfo.run {
INSTANCE.initialize( INSTANCE.initialize(
@ -133,6 +151,8 @@ abstract class GuruAnalytics {
xAppId, xAppId,
xDeviceInfo, xDeviceInfo,
mainProcess, mainProcess,
isEnableCronet,
uploadIpAddress,
) )
} }
return INSTANCE return INSTANCE

View File

@ -1,20 +1,28 @@
package guru.core.analytics.data.api package guru.core.analytics.data.api
import android.content.Context import android.content.Context
import android.net.Uri
import android.os.SystemClock import android.os.SystemClock
import guru.core.analytics.data.api.logging.LoggingInterceptor import guru.core.analytics.data.api.cronet.CastboxCronetInterceptor
import guru.core.analytics.data.api.dns.CustomDns import guru.core.analytics.data.api.dns.CustomDns
import guru.core.analytics.data.api.dns.GoogleDnsApi import guru.core.analytics.data.api.dns.GoogleDnsApi
import guru.core.analytics.data.api.dns.GoogleDnsApiHost import guru.core.analytics.data.api.dns.GoogleDnsApiHost
import guru.core.analytics.data.api.logging.Level import guru.core.analytics.data.api.logging.Level
import guru.core.analytics.data.api.logging.LoggingInterceptor
import guru.core.analytics.data.local.PreferencesManager import guru.core.analytics.data.local.PreferencesManager
import guru.core.analytics.handler.AnalyticsCode import guru.core.analytics.handler.AnalyticsCode
import guru.core.analytics.handler.EventHandler import guru.core.analytics.handler.EventHandler
import guru.core.analytics.utils.AndroidUtils import guru.core.analytics.utils.AndroidUtils
import guru.core.analytics.utils.DateTimeUtils import guru.core.analytics.utils.DateTimeUtils
import guru.core.analytics.utils.GsonUtil import guru.core.analytics.utils.GsonUtil
import okhttp3.* import okhttp3.Dispatcher
import okhttp3.Headers
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import okhttp3.internal.platform.Platform import okhttp3.internal.platform.Platform
import retrofit2.Converter import retrofit2.Converter
@ -35,6 +43,8 @@ object ServiceLocator {
private val headerParams = mutableMapOf<String, String>() private val headerParams = mutableMapOf<String, String>()
private var uploadIpAddress: List<String>? = null
fun addHeaderParam(key: String, value: String?) { fun addHeaderParam(key: String, value: String?) {
if (value.isNullOrBlank()) return if (value.isNullOrBlank()) return
headerParams[key] = value headerParams[key] = value
@ -44,20 +54,24 @@ object ServiceLocator {
this.debug = debug this.debug = debug
} }
fun provideGuruRepository(context: Context, baseUrl: String? = null): GuruRepository { fun setUploadIpAddress(ipList: List<String>?) {
uploadIpAddress = ipList
}
fun provideGuruRepository(context: Context, baseUri: Uri? = null): GuruRepository {
synchronized(this) { synchronized(this) {
return guruRepository return guruRepository
?: createQuotesRepository(context, baseUrl).apply { guruRepository = this } ?: createQuotesRepository(context, baseUri).apply { guruRepository = this }
} }
} }
private fun createQuotesRepository(context: Context, baseUrl: String? = null): GuruRepository { private fun createQuotesRepository(context: Context, baseUri: Uri? = null): GuruRepository {
return DefaultGuruRepository(provideAnalyticsApi(context, baseUrl)) return DefaultGuruRepository(provideAnalyticsApi(context, baseUri))
} }
private fun provideAnalyticsApi(context: Context, baseUrl: String? = null): AnalyticsApi { private fun provideAnalyticsApi(context: Context, baseUri: Uri? = null): AnalyticsApi {
val finalBaseUrl = if (!baseUrl.isNullOrEmpty()) { val finalBaseUrl = if (baseUri != null) {
baseUrl baseUri.toString()
} else { } else {
val cacheBaseUrl = PreferencesManager.getInstance(context).uploadEventBaseUrl val cacheBaseUrl = PreferencesManager.getInstance(context).uploadEventBaseUrl
if (cacheBaseUrl.isNullOrEmpty()) AnalyticsApiHost.BASE_URL else cacheBaseUrl if (cacheBaseUrl.isNullOrEmpty()) AnalyticsApiHost.BASE_URL else cacheBaseUrl
@ -114,20 +128,22 @@ object ServiceLocator {
maxRequests = 128 maxRequests = 128
maxRequestsPerHost = 10 maxRequestsPerHost = 10
}) })
.dns(CustomDns(context)) .dns(CustomDns(context, uploadIpAddress))
.connectTimeout(20L, TimeUnit.SECONDS) .connectTimeout(20L, TimeUnit.SECONDS)
.readTimeout(readTimeOut, TimeUnit.SECONDS) .readTimeout(readTimeOut, TimeUnit.SECONDS)
.writeTimeout(writeTimeOut, TimeUnit.SECONDS) .writeTimeout(writeTimeOut, TimeUnit.SECONDS)
.addInterceptor(createCacheControlInterceptor(context)) .addInterceptor(createCacheControlInterceptor(context))
.addInterceptor(createAnalyticsApiInterceptor()) .addInterceptor(createAnalyticsApiInterceptor())
.addInterceptor(createLoggingInterceptor()) .addInterceptor(createLoggingInterceptor())
.addInterceptor(createCronetInterceptor())
return builder.build() return builder.build()
} }
private fun createDnsOkHttpClient(context: Context): OkHttpClient { private fun createDnsOkHttpClient(context: Context): OkHttpClient {
val builder = OkHttpClient.Builder() val builder = OkHttpClient.Builder()
.addInterceptor(createCacheControlInterceptor(context)) .addInterceptor(createCacheControlInterceptor(context))
builder.addInterceptor(createLoggingInterceptor()) .addInterceptor(createLoggingInterceptor())
.addInterceptor(createCronetInterceptor())
return builder.build() return builder.build()
} }
@ -140,6 +156,14 @@ object ServiceLocator {
.build() .build()
} }
/**
* Add the Cronet interceptor last, otherwise the subsequent interceptors will be skipped.
* https://github.com/google/cronet-transport-for-okhttp
*/
private fun createCronetInterceptor(): Interceptor {
return CastboxCronetInterceptor()
}
private fun createCacheControlInterceptor(context: Context) = Interceptor { chain -> private fun createCacheControlInterceptor(context: Context) = Interceptor { chain ->
try { try {
val originalResponse = chain.proceed(chain.request()) val originalResponse = chain.proceed(chain.request())
@ -162,10 +186,7 @@ object ServiceLocator {
} }
} catch (e: Exception) { } catch (e: Exception) {
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_CACHE_CONTROL, e.message) EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_CACHE_CONTROL, e.message)
return@Interceptor exceptionResponse( throw e
e,
chain.request()
)
} }
} }
@ -174,7 +195,6 @@ object ServiceLocator {
val request = chain.request() val request = chain.request()
val startTime = SystemClock.elapsedRealtime() val startTime = SystemClock.elapsedRealtime()
val builder = request.newBuilder() val builder = request.newBuilder()
.addHeader(CONTENT_TYPE, "application/json")
.addHeader(CONTENT_ENCODING, "gzip") .addHeader(CONTENT_ENCODING, "gzip")
.addHeader(X_EVENT_TYPE, "event") .addHeader(X_EVENT_TYPE, "event")
headerParams.forEach { headerParams.forEach {
@ -182,17 +202,13 @@ object ServiceLocator {
} }
val newRequest = builder.build() val newRequest = builder.build()
try { try {
val response = chain.proceed(newRequest) chain.proceed(newRequest).also { response ->
val responseTime = (SystemClock.elapsedRealtime() - startTime) / 2 val responseTime = (SystemClock.elapsedRealtime() - startTime) / 2
calibrationTime(responseTime, response.headers) calibrationTime(responseTime, response.headers)
if (response.isSuccessful) {
successResponse(response)
} else {
httpErrorResponse(response)
} }
} catch (e: Exception) { } catch (e: Exception) {
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_API, e.message) EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_API, e.message)
return@Interceptor exceptionResponse(e, newRequest) throw e
} }
} }
} }
@ -205,40 +221,6 @@ object ServiceLocator {
DateTimeUtils.initServerTime(date.time + responseTime) DateTimeUtils.initServerTime(date.time + responseTime)
minResponseTime = responseTime minResponseTime = responseTime
} }
private fun successResponse(response: Response): Response {
val body: ResponseBody?
var bodyString: String?
response.body.let {
bodyString = it?.string()
body = bodyString?.toResponseBody(it!!.contentType())
}
return response.newBuilder()
.body(body)
.build()
}
private fun httpErrorResponse(response: Response): Response {
val body =
"{\"code\": ${response.code}, \"msg\": \"${response.message}\", \"data\": null}".toResponseBody()
response.close()
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_RESPONSE, response.code.toString())
return response.newBuilder()
.code(1)
.body(body)
.build()
}
private fun exceptionResponse(e: Exception, request: Request): Response {
val content = "{\"code\": -1, \"msg\": \"${e.message}\", \"data\": null}"
return Response.Builder()
.code(1)
.request(request)
.protocol(Protocol.HTTP_1_1)
.message("${e.message}")
.body(content.toResponseBody())
.build()
}
} }
const val CONTENT_TYPE = "Content-Type" const val CONTENT_TYPE = "Content-Type"

View File

@ -0,0 +1,27 @@
package guru.core.analytics.data.api.cronet
import guru.core.analytics.handler.AnalyticsCode
import guru.core.analytics.handler.EventHandler
import okhttp3.Interceptor
import okhttp3.Response
class CastboxCronetInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
return if (CronetHelper.cronetInterceptor != null) {
return try {
CronetHelper.cronetInterceptor!!.intercept(chain)
} catch (e: Exception) {
EventHandler.INSTANCE.notifyEventHandler(
AnalyticsCode.ERROR_CRONET_INTERCEPTOR,
e.message
)
throw e
}
} else {
chain.proceed(originalRequest)
}
}
}

View File

@ -0,0 +1,37 @@
package guru.core.analytics.data.api.cronet
import android.content.Context
import com.google.android.gms.net.CronetProviderInstaller
import com.google.net.cronet.okhttptransport.CronetInterceptor
import guru.core.analytics.handler.AnalyticsCode
import guru.core.analytics.handler.EventHandler
import org.chromium.net.CronetEngine
object CronetHelper {
var cronetInterceptor: CronetInterceptor? = null
private set
fun init(context: Context, isEnable: Boolean?, callback:((Boolean) -> Unit)? = null) {
if (isEnable == true) {
try {
CronetProviderInstaller.installProvider(context).addOnSuccessListener {
val cronetEngine = CronetEngine.Builder(context).build()
cronetInterceptor = CronetInterceptor.newBuilder(cronetEngine).build()
callback?.invoke(true)
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.CRONET_INIT_SUCCESS)
}.addOnFailureListener {
cronetInterceptor = null
callback?.invoke(false)
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.CRONET_INIT_FAIL, it.message)
}
} catch (e: Exception) {
callback?.invoke(false)
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.CRONET_INIT_EXCEPTION, e.message)
}
} else {
cronetInterceptor = null
}
}
}

View File

@ -1,28 +1,47 @@
package guru.core.analytics.data.api.dns package guru.core.analytics.data.api.dns
import android.content.Context import android.content.Context
import com.google.gson.reflect.TypeToken
import guru.core.analytics.data.api.ServiceLocator import guru.core.analytics.data.api.ServiceLocator
import guru.core.analytics.data.local.PreferencesManager
import guru.core.analytics.handler.AnalyticsCode import guru.core.analytics.handler.AnalyticsCode
import guru.core.analytics.handler.EventHandler import guru.core.analytics.handler.EventHandler
import guru.core.analytics.utils.GsonUtil
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.Dns import okhttp3.Dns
import java.net.InetAddress import java.net.InetAddress
import java.net.UnknownHostException import java.net.UnknownHostException
class CustomDns(private val context: Context) : Dns { class CustomDns(private val context: Context, private val uploadIpAddress: List<String>? = null) : Dns {
private val cachedHostAddress by lazy {
val hostAddressJson = PreferencesManager.getInstance(context).hostAddressJson
return@lazy runCatching {
if (!hostAddressJson.isNullOrBlank()) {
val mapType = object: TypeToken<Map<String, List<String>>>() {}.type
GsonUtil.gson.fromJson(hostAddressJson, mapType) as? MutableMap<String, List<String>>
} else null
}.getOrNull() ?: mutableMapOf()
}
override fun lookup(hostname: String): List<InetAddress> { override fun lookup(hostname: String): List<InetAddress> {
return try { return try {
Dns.SYSTEM.lookup(hostname) Dns.SYSTEM.lookup(hostname).also { list ->
cacheHostAddress(hostname, list.map { it.hostAddress })
}
} catch (e: UnknownHostException) { } catch (e: UnknownHostException) {
try { try {
lookupByGoogleDns(hostname) lookupByGoogleDns(hostname)
} catch (e: UnknownHostException) {
try {
lookupByCacheAndRemote(hostname)
} catch (e: UnknownHostException) { } catch (e: UnknownHostException) {
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_DNS, e.message) EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_DNS, e.message)
lookupByRemoteConfig(hostname) lookupByRemoteConfig(hostname)
} }
} }
} }
}
private fun lookupByGoogleDns(hostname: String): List<InetAddress> { private fun lookupByGoogleDns(hostname: String): List<InetAddress> {
val dnsApi = ServiceLocator.provideGoogleDnsApi(context) val dnsApi = ServiceLocator.provideGoogleDnsApi(context)
@ -32,7 +51,11 @@ class CustomDns(private val context: Context) : Dns {
?.mapNotNull { it.data } ?.mapNotNull { it.data }
} }
if (!ipList.isNullOrEmpty()) { if (!ipList.isNullOrEmpty()) {
return ipList.map { InetAddress.getByAddress(convert(it)) } cacheHostAddress(hostname, ipList)
val resultIpList = ipList.mapNotNull { convert(it) }
if (resultIpList.isNotEmpty()) {
return resultIpList
}
} }
throw UnknownHostException("Broken Google dns lookup of $hostname") throw UnknownHostException("Broken Google dns lookup of $hostname")
} }
@ -41,13 +64,44 @@ class CustomDns(private val context: Context) : Dns {
// val dnsConfig = RemoteConfig.getDnsConfig() // val dnsConfig = RemoteConfig.getDnsConfig()
// val ipList = dnsConfig?.get(hostname) // val ipList = dnsConfig?.get(hostname)
// if (ipList != null && ipList.isNotEmpty()) { // if (ipList != null && ipList.isNotEmpty()) {
// return ipList.map { InetAddress.getByAddress(convert(it)) } // return ipList.mapNotNull { convert(it) }
// } // }
throw UnknownHostException("Broken remote config lookup of $hostname") throw UnknownHostException("Broken remote config lookup of $hostname")
} }
private fun convert(ip: String): ByteArray { private fun convert(ip: String): InetAddress? {
val ipArray = ip.split(".").map { Integer.parseInt(it).toByte() } return runCatching {
return ipArray.toByteArray() val byteArr = ip.split(".").map { Integer.parseInt(it).toByte() }.toByteArray()
InetAddress.getByAddress(byteArr)
}.getOrNull()
}
private fun cacheHostAddress(hostname: String, hostAddressList: List<String?>) {
val addressList = hostAddressList.filterNotNull()
if (addressList.isEmpty()) return
try {
val cacheHostAddressList = cachedHostAddress[hostname]?.sorted()
if (addressList.sorted() != cacheHostAddressList) {
cachedHostAddress[hostname] = addressList
PreferencesManager.getInstance(context).hostAddressJson = GsonUtil.gson.toJson(cachedHostAddress)
}
} catch (e: Exception) {
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_DNS_CACHE, e.message)
}
}
private fun lookupByCacheAndRemote(hostname: String): List<InetAddress> {
val ipList = uploadIpAddress?.toMutableList() ?: mutableListOf()
val cacheIpList = cachedHostAddress[hostname]
if (!cacheIpList.isNullOrEmpty()) {
ipList.addAll(cacheIpList)
}
if (ipList.isNotEmpty()) {
val resultIpList = ipList.mapNotNull { convert(it) }
if (resultIpList.isNotEmpty()) {
return resultIpList
}
}
throw UnknownHostException("Broken cache dns lookup of $hostname")
} }
} }

View File

@ -48,4 +48,5 @@ class PreferencesManager private constructor(
var eventCountUploaded: Int? by bind("event_count_uploaded", 0) var eventCountUploaded: Int? by bind("event_count_uploaded", 0)
var uploadEventBaseUrl: String? by bind("update_event_base_url", "") var uploadEventBaseUrl: String? by bind("update_event_base_url", "")
var totalDurationFgEvent: Long? by bind("total_duration_fg_event", 0L) var totalDurationFgEvent: Long? by bind("total_duration_fg_event", 0L)
var hostAddressJson: String? by bind("host_address", "")
} }

View File

@ -14,4 +14,6 @@ internal data class AnalyticsInfo(
var xAppId: String? = null, var xAppId: String? = null,
var xDeviceInfo: String? = null, var xDeviceInfo: String? = null,
var mainProcess: String? = null, var mainProcess: String? = null,
var isEnableCronet: Boolean? = null,
var uploadIpAddress: List<String>? = null,
) )

View File

@ -0,0 +1,59 @@
package guru.core.analytics.data.model
import androidx.annotation.Keep
import com.google.gson.annotations.SerializedName
@Keep
data class GuruAnalyticsAuditSnapshot(
@SerializedName("initialized") val initialized: Boolean = false,
@SerializedName("engineInitialized") val engineInitialized: Boolean = false,
@SerializedName("useCronet") val useCronet: Boolean = false,
@SerializedName("eventDispatcherStarted") val eventDispatcherStarted: Boolean = false,
@SerializedName("fgHelperInitialized") val fgHelperInitialized: Boolean = false,
@SerializedName("connectionState") val connectionState: Boolean = false,
@SerializedName("total") val total: Int = 0,
@SerializedName("deleted") val deleted: Int = 0,
@SerializedName("uploaded") val uploaded: Int = 0,
@SerializedName("sessionUploaded") val sessionUploaded: Int = 0,
@SerializedName("sessionDeleted") val sessionDeleted: Int = 0,
@SerializedName("sessionTotal") val sessionTotal: Int = 0,
@SerializedName("uploadReady") val uploadReady: Boolean = false,
)
object GuruAnalyticsAudit {
var initialized: Boolean = false
var engineInitialized: Boolean = false
var useCronet: Boolean = false
var eventDispatcherStarted: Boolean = false
var fgHelperInitialized: Boolean = false
var connectionState: Boolean = false
// 整体的
var total: Int = 0
var deleted: Int = 0
var uploaded: Int = 0
// 当前这一次
var sessionUploaded: Int = 0
var sessionDeleted: Int = 0
var sessionTotal: Int = 0
var uploadReady: Boolean = false
fun snapshot() = GuruAnalyticsAuditSnapshot(
initialized = initialized,
engineInitialized = engineInitialized,
useCronet = useCronet,
eventDispatcherStarted = eventDispatcherStarted,
fgHelperInitialized = fgHelperInitialized,
connectionState = connectionState,
total = total,
deleted = deleted,
uploaded = uploaded,
sessionUploaded = sessionUploaded,
sessionDeleted = sessionDeleted,
sessionTotal = sessionTotal,
uploadReady = uploadReady
)
}

View File

@ -10,6 +10,7 @@ import guru.core.analytics.utils.DateTimeUtils
import guru.core.analytics.utils.GsonUtil import guru.core.analytics.utils.GsonUtil
import io.reactivex.subjects.BehaviorSubject import io.reactivex.subjects.BehaviorSubject
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap
object EventInfoStore { object EventInfoStore {
@ -18,9 +19,11 @@ object EventInfoStore {
UUID.randomUUID().toString() UUID.randomUUID().toString()
} }
private val propertiesSubject: BehaviorSubject<LinkedHashMap<String, String>> = private val propertiesSubject: BehaviorSubject<ConcurrentHashMap<String, String>> =
BehaviorSubject.createDefault( BehaviorSubject.createDefault(
linkedMapOf() ConcurrentHashMap<String, String>().apply {
this[Constants.Properties.GURU_ANM] = Constants.AnmState.DEFAULT
}
) )
private val supplementEventParamsSubject: BehaviorSubject<Map<String, Any>> = private val supplementEventParamsSubject: BehaviorSubject<Map<String, Any>> =
@ -39,8 +42,8 @@ object EventInfoStore {
idsSubject.onNext(value) idsSubject.onNext(value)
} }
private var properties: LinkedHashMap<String, String> var properties: ConcurrentHashMap<String, String>
get() = propertiesSubject.value ?: linkedMapOf() get() = propertiesSubject.value ?: ConcurrentHashMap()
set(value) { set(value) {
propertiesSubject.onNext(value) propertiesSubject.onNext(value)
} }
@ -104,7 +107,12 @@ object EventInfoStore {
} }
} }
fun deriveEvent(event: EventItem, priority: Int = EventPriority.DEFAULT, uploading: Boolean = false): EventEntity { fun deriveEvent(
event: EventItem,
priority: Int = EventPriority.DEFAULT,
uploading: Boolean = false,
elapsed: Long = 0
): EventEntity {
val eventMap = mutableMapOf<String, ParamValue>() val eventMap = mutableMapOf<String, ParamValue>()
if (!event.itemCategory.isNullOrBlank()) { if (!event.itemCategory.isNullOrBlank()) {
eventMap[Constants.Event.ITEM_CATEGORY] = ParamValue(s = event.itemCategory) eventMap[Constants.Event.ITEM_CATEGORY] = ParamValue(s = event.itemCategory)
@ -124,8 +132,9 @@ object EventInfoStore {
eventMap[entry.key] = createParamValue(entry.value) eventMap[entry.key] = createParamValue(entry.value)
} }
val eventId = UUID.randomUUID().toString() val eventId = UUID.randomUUID().toString()
val eventTs = DateTimeUtils.eventAt() - elapsed
val eventData = Event( val eventData = Event(
timestamp = DateTimeUtils.eventAt(), timestamp = eventTs,
info = ids, info = ids,
event = event.eventName, event = event.eventName,
param = eventMap, param = eventMap,
@ -139,7 +148,7 @@ object EventInfoStore {
json = eventJson, json = eventJson,
ext = "", ext = "",
status = if (uploading) 1 else 0, status = if (uploading) 1 else 0,
at = DateTimeUtils.eventAt(), at = eventTs,
version = Constants.VERSION, version = Constants.VERSION,
event = event.eventName, event = event.eventName,
priority = priority priority = priority

View File

@ -9,11 +9,15 @@ enum class AnalyticsCode(val code: Int) {
UPLOAD_SUCCESS(13), // 上报事件成功 UPLOAD_SUCCESS(13), // 上报事件成功
UPLOAD_FAIL(14), // 上报事件失败 UPLOAD_FAIL(14), // 上报事件失败
PERIODIC_WORK_ENQUEUE(15), // 开启PeriodicWork PERIODIC_WORK_ENQUEUE(15), // 开启PeriodicWork
ENABLE_UPLOAD(16), // 修改是否允许上传埋点状态
NETWORK_AVAILABLE(21), // 网络状态可用 NETWORK_AVAILABLE(21), // 网络状态可用
NETWORK_LOST(22), // 网络状态不可用 NETWORK_LOST(22), // 网络状态不可用
LIFECYCLE_START(23), // app可见 LIFECYCLE_START(23), // app可见
LIFECYCLE_PAUSE(24), // app不可见 LIFECYCLE_PAUSE(24), // app不可见
CRONET_INIT_SUCCESS(25), // 开启Cronet成功
CRONET_INIT_FAIL(26), // 开启Cronet失败
CRONET_INIT_EXCEPTION(27), // 开启Cronet报错
ERROR_API(101), // 调用api出错 ERROR_API(101), // 调用api出错
ERROR_RESPONSE(102), // api返回结果错误 ERROR_RESPONSE(102), // api返回结果错误
@ -22,7 +26,20 @@ enum class AnalyticsCode(val code: Int) {
ERROR_LOAD_MARK(105), // 从数据库取事件以及更改事件状态为正在上报出错 ERROR_LOAD_MARK(105), // 从数据库取事件以及更改事件状态为正在上报出错
ERROR_DNS(106), // dns 错误 ERROR_DNS(106), // dns 错误
ERROR_ZIP(107), // zip 错误 ERROR_ZIP(107), // zip 错误
ERROR_DNS_CACHE(108), // zip 错误
ERROR_CRONET_INTERCEPTOR(109),// cronet拦截器
EVENT_FIRST_OPEN(1001), // first_open 事件 EVENT_FIRST_OPEN(1001), // first_open 事件
EVENT_FG(1002), // fg 事件 EVENT_FG(1002), // fg 事件
// 初始化进度
INIT_STEP_1(100001),
INIT_STEP_2(100002),
INIT_STEP_3(100003),
INIT_STEP_4(100004),
INIT_STEP_5(100005),
INIT_STEP_6(100006),
INIT_STEP_7(100007),
INIT_STEP_8(100008),
INIT_STEP_9(100009),
} }

View File

@ -1,16 +1,26 @@
package guru.core.analytics.impl package guru.core.analytics.impl
import android.os.SystemClock
import guru.core.analytics.data.db.GuruAnalyticsDatabase import guru.core.analytics.data.db.GuruAnalyticsDatabase
import guru.core.analytics.data.model.AnalyticsOptions import guru.core.analytics.data.model.AnalyticsOptions
import guru.core.analytics.data.model.EventItem 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.data.store.EventInfoStore
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.ConcurrentLinkedQueue import java.util.concurrent.ConcurrentLinkedQueue
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
class PendingEvent(
val item: EventItem,
val options: AnalyticsOptions
) {
val at = SystemClock.elapsedRealtime()
}
object EventDispatcher : EventDeliver { object EventDispatcher : EventDeliver {
private val pendingEvents = ConcurrentLinkedQueue<Pair<EventItem, AnalyticsOptions>>() private val pendingEvents = ConcurrentLinkedQueue<PendingEvent>()
private val started = AtomicBoolean(false) private val started = AtomicBoolean(false)
@ -18,8 +28,12 @@ object EventDispatcher : EventDeliver {
Timber.d("EventDispatcher dispatchPendingEvent ${pendingEvents.size}!") Timber.d("EventDispatcher dispatchPendingEvent ${pendingEvents.size}!")
if (pendingEvents.isNotEmpty()) { if (pendingEvents.isNotEmpty()) {
while (true) { while (true) {
val pair = pendingEvents.poll() ?: return val pendingEvent = pendingEvents.poll() ?: return
val event = EventInfoStore.deriveEvent(pair.first, priority = pair.second.priority) val event = EventInfoStore.deriveEvent(
pendingEvent.item,
priority = pendingEvent.options.priority,
elapsed = SystemClock.elapsedRealtime() - pendingEvent.at
)
GuruAnalyticsDatabase.getInstance().eventDao().addEvent(event) GuruAnalyticsDatabase.getInstance().eventDao().addEvent(event)
} }
} }
@ -29,6 +43,7 @@ object EventDispatcher : EventDeliver {
if (started.compareAndSet(false, true)) { if (started.compareAndSet(false, true)) {
Timber.d("EventDispatcher started!") Timber.d("EventDispatcher started!")
dispatchPendingEvent() dispatchPendingEvent()
GuruAnalyticsAudit.eventDispatcherStarted = true
} }
} }
@ -40,7 +55,7 @@ object EventDispatcher : EventDeliver {
GuruAnalyticsDatabase.getInstance().eventDao().addEvent(event) GuruAnalyticsDatabase.getInstance().eventDao().addEvent(event)
} else { } else {
Timber.d("EventDispatcher deliverEvent pending!") Timber.d("EventDispatcher deliverEvent pending!")
pendingEvents.offer(item to options) pendingEvents.offer(PendingEvent(item, options))
} }
} }

View File

@ -1,6 +1,7 @@
package guru.core.analytics.impl package guru.core.analytics.impl
import android.content.Context import android.content.Context
import android.net.Uri
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
@ -12,6 +13,7 @@ import guru.core.analytics.data.db.model.EventStatistic
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.AnalyticsOptions
import guru.core.analytics.data.model.EventItem 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.data.store.EventInfoStore
import guru.core.analytics.handler.AnalyticsCode import guru.core.analytics.handler.AnalyticsCode
import guru.core.analytics.handler.EventHandler import guru.core.analytics.handler.EventHandler
@ -39,10 +41,10 @@ internal class EventEngine internal constructor(
private val batchLimit: Int = DEFAULT_BATCH_LIMIT, private val batchLimit: Int = DEFAULT_BATCH_LIMIT,
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 uploadEventBaseUrl: String? = null, private val uploadEventBaseUri: Uri? = null,
) : EventDeliver { ) : EventDeliver {
private val guruRepository: GuruRepository by lazy { private val guruRepository: GuruRepository by lazy {
ServiceLocator.provideGuruRepository(context.applicationContext, baseUrl = uploadEventBaseUrl) ServiceLocator.provideGuruRepository(context.applicationContext, baseUri = uploadEventBaseUri)
} }
private val preferencesManager by lazy { private val preferencesManager by lazy {
@ -83,6 +85,13 @@ internal class EventEngine internal constructor(
private val forceUploadSubject: PublishSubject<Boolean> = PublishSubject.create() private val forceUploadSubject: PublishSubject<Boolean> = PublishSubject.create()
private val started = AtomicBoolean(false) private val started = AtomicBoolean(false)
private var enableUpload = true
fun setEnableUpload(enable: Boolean) {
enableUpload = enable
val extMap = mapOf("enable" to enable)
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ENABLE_UPLOAD, extMap)
}
fun start(startUploadDelay: Long?) { fun start(startUploadDelay: Long?) {
if (started.compareAndSet(false, true)) { if (started.compareAndSet(false, true)) {
@ -96,6 +105,7 @@ internal class EventEngine internal constructor(
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)
}, startUploadDelay ?: 0, TimeUnit.SECONDS) }, startUploadDelay ?: 0, TimeUnit.SECONDS)
GuruAnalyticsAudit.engineInitialized = true
} }
} }
@ -146,6 +156,7 @@ internal class EventEngine internal constructor(
logDebug("pendingEvent ${it.size}") logDebug("pendingEvent ${it.size}")
} }
val networkFlowable = ConnectionStateMonitor.connectStateFlowable.doOnNext { val networkFlowable = ConnectionStateMonitor.connectStateFlowable.doOnNext {
GuruAnalyticsAudit.connectionState = it
logDebug("network $it") logDebug("network $it")
} }
val forceFlowable = forceUploadSubject.toFlowable(BackpressureStrategy.DROP) val forceFlowable = forceUploadSubject.toFlowable(BackpressureStrategy.DROP)
@ -157,6 +168,7 @@ internal class EventEngine internal constructor(
logDebug("pollEvent filter $it") logDebug("pollEvent filter $it")
return@filter it return@filter it
} }
.filter { enableUpload }
.flatMap { uploadEvents(500) } .flatMap { uploadEvents(500) }
.subscribe() .subscribe()
) )
@ -174,7 +186,10 @@ internal class EventEngine internal constructor(
// 记录删除的事件数量 // 记录删除的事件数量
if (pair.first > 0) { if (pair.first > 0) {
val eventCountDeleted = preferencesManager.eventCountDeleted ?: 0 val eventCountDeleted = preferencesManager.eventCountDeleted ?: 0
preferencesManager.eventCountDeleted = eventCountDeleted + pair.first val deleted = eventCountDeleted + pair.first
preferencesManager.eventCountDeleted = deleted
GuruAnalyticsAudit.deleted = deleted
GuruAnalyticsAudit.sessionDeleted += pair.first
val extMap = mapOf( val extMap = mapOf(
"expiredCount" to pair.first, "expiredCount" to pair.first,
@ -192,6 +207,7 @@ internal class EventEngine internal constructor(
internal fun uploadEvents(count: Int): Flowable<List<EventEntity>> { internal fun uploadEvents(count: Int): Flowable<List<EventEntity>> {
val eventDao = GuruAnalyticsDatabase.getInstance().eventDao() val eventDao = GuruAnalyticsDatabase.getInstance().eventDao()
GuruAnalyticsAudit.uploadReady = true
logDebug("uploadEvents: $count") logDebug("uploadEvents: $count")
return Flowable.just(count).map { eventDao.loadAndMarkUploadEvents(it) } return Flowable.just(count).map { eventDao.loadAndMarkUploadEvents(it) }
.onErrorReturn { .onErrorReturn {
@ -232,7 +248,10 @@ internal class EventEngine internal constructor(
.doOnSuccess { .doOnSuccess {
// 记录上传成功的数量 // 记录上传成功的数量
val eventCountUploaded = preferencesManager.eventCountUploaded ?: 0 val eventCountUploaded = preferencesManager.eventCountUploaded ?: 0
preferencesManager.eventCountUploaded = eventCountUploaded + entities.size val uploaded = eventCountUploaded + entities.size
preferencesManager.eventCountUploaded = uploaded
GuruAnalyticsAudit.uploaded = uploaded
GuruAnalyticsAudit.sessionUploaded += entities.size
val extMap = mapOf( val extMap = mapOf(
"count" to entities.size, "count" to entities.size,
@ -288,7 +307,10 @@ internal class EventEngine internal constructor(
private fun increaseEventCount() { private fun increaseEventCount() {
val eventCountAll = preferencesManager.eventCountAll ?: 0 val eventCountAll = preferencesManager.eventCountAll ?: 0
preferencesManager.eventCountAll = eventCountAll + 1 val total = eventCountAll + 1
preferencesManager.eventCountAll = total
GuruAnalyticsAudit.total = total
GuruAnalyticsAudit.sessionTotal ++
} }
override fun deliverProperty(name: String, value: String) { override fun deliverProperty(name: String, value: String) {

View File

@ -5,6 +5,7 @@ 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.local.PreferencesManager import guru.core.analytics.data.local.PreferencesManager
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
import io.reactivex.Flowable import io.reactivex.Flowable
@ -75,6 +76,7 @@ internal class FgHelper(context: Context) {
}, { }, {
Timber.tag("FgHelper").e(it) Timber.tag("FgHelper").e(it)
}) })
GuruAnalyticsAudit.fgHelperInitialized = true
} }
fun stop() { fun stop() {

View File

@ -1,16 +1,19 @@
package guru.core.analytics.impl package guru.core.analytics.impl
import android.content.Context import android.content.Context
import android.net.Uri
import androidx.annotation.RequiresPermission import androidx.annotation.RequiresPermission
import androidx.work.* import androidx.work.*
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.ServiceLocator import guru.core.analytics.data.api.ServiceLocator
import guru.core.analytics.data.api.cronet.CronetHelper
import guru.core.analytics.data.db.GuruAnalyticsDatabase import guru.core.analytics.data.db.GuruAnalyticsDatabase
import guru.core.analytics.data.db.model.EventStatistic import guru.core.analytics.data.db.model.EventStatistic
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.AnalyticsOptions
import guru.core.analytics.data.model.EventItem import guru.core.analytics.data.model.EventItem
import guru.core.analytics.data.model.GuruAnalyticsAudit
import guru.core.analytics.data.store.DeviceInfoStore import guru.core.analytics.data.store.DeviceInfoStore
import guru.core.analytics.data.store.EventInfoStore import guru.core.analytics.data.store.EventInfoStore
import guru.core.analytics.handler.AnalyticsCode import guru.core.analytics.handler.AnalyticsCode
@ -18,6 +21,7 @@ import guru.core.analytics.handler.EventHandler
import guru.core.analytics.log.PersistentTree import guru.core.analytics.log.PersistentTree
import guru.core.analytics.utils.AndroidUtils import guru.core.analytics.utils.AndroidUtils
import guru.core.analytics.utils.EventChecker import guru.core.analytics.utils.EventChecker
import guru.core.analytics.utils.GsonUtil
import guru.core.analytics.utils.SystemProperties import guru.core.analytics.utils.SystemProperties
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
@ -75,24 +79,55 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
fgEventPeriodInSeconds: Long?, fgEventPeriodInSeconds: Long?,
xAppId: String?, xAppId: String?,
xDeviceInfo: String?, xDeviceInfo: String?,
mainProcess: String? mainProcess: String?,
isEnableCronet: Boolean?,
uploadIpAddress: List<String>?,
) { ) {
if (initialized.compareAndSet(false, true)) { if (initialized.compareAndSet(false, true)) {
delivers.add(EventDispatcher) delivers.add(EventDispatcher)
eventHandlerCallback?.let { EventHandler.INSTANCE.addEventHandler(it) } eventHandlerCallback?.let { EventHandler.INSTANCE.addEventHandler(it) }
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_1)
val debugApp = SystemProperties.read("debug.guru.analytics.app") val debugApp = SystemProperties.read("debug.guru.analytics.app")
val forceDebug = debugApp == context.packageName val forceDebug = debugApp == context.packageName
if (forceDebug || persistableLog) { if (forceDebug || persistableLog) {
Timber.plant(PersistentTree(context, debug = debug)) Timber.plant(PersistentTree(context, debug = debug))
} }
debugMode = debug EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_2)
Timber.d("[$internalVersion]initialize batchLimit:$batchLimit uploadPeriodInSecond:$uploadPeriodInSeconds startUploadDelayInSecond:$startUploadDelayInSecond eventExpiredInDays:$eventExpiredInDays debug:$debug")
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")
GuruAnalyticsDatabase.initialize(context.applicationContext) GuruAnalyticsDatabase.initialize(context.applicationContext)
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_3)
val baseUrl = if (forceDebug && debugUrl.isNotEmpty()) debugUrl else uploadEventBaseUrl
DeviceInfoStore.setDeviceInfo(context) DeviceInfoStore.setDeviceInfo(context)
ServiceLocator.setDebug(debug) ServiceLocator.setDebug(debug)
ServiceLocator.addHeaderParam("X-APP-ID", xAppId) ServiceLocator.addHeaderParam("X-APP-ID", xAppId)
ServiceLocator.addHeaderParam("X-DEVICE-INFO", xDeviceInfo) ServiceLocator.addHeaderParam("X-DEVICE-INFO", xDeviceInfo)
ServiceLocator.setUploadIpAddress(uploadIpAddress)
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_4)
CronetHelper.init(context, isEnableCronet) {
if (it) {
setUserProperty(Constants.Properties.GURU_ANM, Constants.AnmState.CRONET)
GuruAnalyticsAudit.useCronet = true
}
}
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_5)
var uploadEventBaseUri: Uri? = null
if (!baseUrl.isNullOrBlank()) {
uploadEventBaseUri =
kotlin.runCatching { Uri.parse(baseUrl) }.getOrNull()
if (uploadEventBaseUri?.scheme?.startsWith("http") != true) {
throw IllegalArgumentException("initialize updateEventBaseUrl:${baseUrl} incorrect format")
}
}
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_6)
engine = EventEngine( engine = EventEngine(
context, context,
@ -102,10 +137,11 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
eventExpiredInDays = EventEngine.DEFAULT_EVENT_EXPIRED_IN_DAYS.coerceAtLeast( eventExpiredInDays = EventEngine.DEFAULT_EVENT_EXPIRED_IN_DAYS.coerceAtLeast(
eventExpiredInDays ?: EventEngine.DEFAULT_EVENT_EXPIRED_IN_DAYS eventExpiredInDays ?: EventEngine.DEFAULT_EVENT_EXPIRED_IN_DAYS
), ),
uploadEventBaseUrl = uploadEventBaseUrl, uploadEventBaseUri = uploadEventBaseUri
).apply { ).apply {
start(startUploadDelayInSecond)
delivers.add(this) delivers.add(this)
start(startUploadDelayInSecond)
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_7)
} }
if (isInitPeriodicWork) { if (isInitPeriodicWork) {
val process = AndroidUtils.getProcessName(context) val process = AndroidUtils.getProcessName(context)
@ -120,6 +156,7 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
} }
} }
} }
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_8)
val extMap = mapOf( val extMap = mapOf(
"version_code" to internalVersion, "version_code" to internalVersion,
@ -127,12 +164,17 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
"uploadPeriodInSecond" to uploadPeriodInSeconds, "uploadPeriodInSecond" to uploadPeriodInSeconds,
"startUploadDelayInSecond" to startUploadDelayInSecond, "startUploadDelayInSecond" to startUploadDelayInSecond,
"eventExpiredInDays" to eventExpiredInDays, "eventExpiredInDays" to eventExpiredInDays,
"uploadEventBaseUri" to uploadEventBaseUri.toString(),
"enabledCronet" to (isEnableCronet ?: false),
"unloadIpAddress" to (uploadIpAddress?.joinToString("|") ?: ""),
"debug" to debug, "debug" to debug,
) )
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.STATE_INITIALIZED, extMap) EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.STATE_INITIALIZED, extMap)
if (!uploadEventBaseUrl.isNullOrEmpty()) { if (!uploadEventBaseUrl.isNullOrEmpty()) {
PreferencesManager.getInstance(context).uploadEventBaseUrl = uploadEventBaseUrl PreferencesManager.getInstance(context).uploadEventBaseUrl = baseUrl
} }
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_9)
GuruAnalyticsAudit.initialized = true
} }
} }
@ -265,6 +307,25 @@ internal class GuruAnalyticsImpl : GuruAnalytics() {
removeProperties(keys) removeProperties(keys)
} }
override fun getUserProperties(callback: (Map<String, String>) -> Unit) {
deliverExecutor.execute {
callback.invoke(EventInfoStore.properties)
}
}
override fun peakUserProperties(): Map<String, String> {
return EventInfoStore.properties
}
override fun setEnableUpload(enable: Boolean) {
engine?.setEnableUpload(enable)
}
override fun snapshotAnalyticsAudit(): String {
val snapshot = GuruAnalyticsAudit.snapshot()
return GsonUtil.gson.toJson(snapshot)
}
private fun deliverEvent(item: EventItem, options: AnalyticsOptions) { private fun deliverEvent(item: EventItem, options: AnalyticsOptions) {
Timber.tag("GuruAnalytics").d("deliverEvent ${item.eventName}!") Timber.tag("GuruAnalytics").d("deliverEvent ${item.eventName}!")
deliverExecutor.execute { deliverExecutor.execute {

View File

@ -11,6 +11,7 @@ object ApiParamUtils {
* 组装接口上传需要的json参数 * 组装接口上传需要的json参数
*/ */
fun generateApiParam(events: List<EventEntity>): String { fun generateApiParam(events: List<EventEntity>): String {
/// todo: 版本需要根据当前的更新
val deviceInfoJson = GsonUtil.gson.toJson(DeviceInfoStore.deviceInfo) val deviceInfoJson = GsonUtil.gson.toJson(DeviceInfoStore.deviceInfo)
return "{\"version\":${Constants.VERSION},\"events\":[${events.joinToString(",") { it.json }}],\"deviceInfo\":$deviceInfoJson}" return "{\"version\":${Constants.VERSION},\"events\":[${events.joinToString(",") { it.json }}],\"deviceInfo\":$deviceInfoJson}"
} }

View File

@ -19,7 +19,7 @@ object GZipUtils {
out.toByteArray() out.toByteArray()
} catch (e: IOException) { } catch (e: IOException) {
EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_ZIP, e.message) EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_ZIP, e.message)
null throw e
} }
} }