parent
							
								
									5927987ff5
								
							
						
					
					
						commit
						3803d53afe
					
				|  | @ -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 | ||||
| 
 | ||||
| ##### Feature | ||||
|  |  | |||
|  | @ -7,9 +7,11 @@ import android.widget.TextView | |||
| import guru.core.analytics.GuruAnalytics | ||||
| import guru.core.analytics.data.db.model.EventPriority | ||||
| import guru.core.analytics.data.model.AnalyticsOptions | ||||
| import guru.core.analytics.handler.AnalyticsCode | ||||
| 
 | ||||
| class MainActivity : AppCompatActivity() { | ||||
| 
 | ||||
|     private var enableUpload = true | ||||
| 
 | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         setContentView(R.layout.activity_main) | ||||
|  | @ -36,6 +38,8 @@ class MainActivity : AppCompatActivity() { | |||
|             .setXAppId("test_x_app_id") | ||||
|             .setXDeviceInfo("test_x_device_info") | ||||
|             .setMainProcess("com.example.guruanalytics") | ||||
|             .isEnableCronet(true) | ||||
|             .setUploadIpAddress(listOf("3.210.96.186", "34.196.69.199")) | ||||
|             .build() | ||||
| 
 | ||||
|         findViewById<TextView>(R.id.tvLogEvent).setOnClickListener { | ||||
|  | @ -76,9 +80,31 @@ class MainActivity : AppCompatActivity() { | |||
|             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.setAdId("AD_ID_01") | ||||
|         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 -> | ||||
|  |  | |||
|  | @ -70,4 +70,13 @@ | |||
|         android:text="Update BaseUrl" | ||||
|         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> | ||||
|  | @ -53,7 +53,9 @@ dependencies { | |||
| 
 | ||||
|     implementation okhttpDependencies | ||||
| 
 | ||||
|     implementation process | ||||
|     implementation processDependencies | ||||
| 
 | ||||
|     implementation workerDependencies | ||||
| 
 | ||||
|     implementation cronetDependencies | ||||
| } | ||||
|  | @ -21,6 +21,8 @@ ext { | |||
|     preferenceVersion = '1.2.0' | ||||
|     processVersion = '2.4.0' | ||||
|     workVersion = '2.7.1' | ||||
|     cronetOkhttpVersion = '0.1.0' | ||||
|     playServicesCronetVersion = '18.0.1' | ||||
| 
 | ||||
|     kaptDependencies = [ | ||||
|             "androidx.room:room-compiler:$roomVersion", | ||||
|  | @ -54,7 +56,12 @@ ext { | |||
|             "androidx.work:work-rxjava2:$workVersion" | ||||
|     ] | ||||
| 
 | ||||
|     process = [ | ||||
|     processDependencies = [ | ||||
|             "androidx.lifecycle:lifecycle-process:$processVersion" | ||||
|     ] | ||||
| 
 | ||||
|     cronetDependencies = [ | ||||
|             "com.google.net.cronet:cronet-okhttp:$cronetOkhttpVersion", | ||||
|             "com.google.android.gms:play-services-cronet:$playServicesCronetVersion" | ||||
|     ] | ||||
| } | ||||
|  | @ -18,7 +18,7 @@ publishing {    // Repositories *to* which Gradle can publish artifacts | |||
|         maven(MavenPublication) { | ||||
|             groupId 'guru.core.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 "build/outputs/aar/aar-test-release.aar"//aar包的目录 | ||||
|             afterEvaluate { artifact(tasks.getByName("bundleReleaseAar")) } | ||||
|  |  | |||
|  | @ -56,6 +56,12 @@ object Constants { | |||
|         const val COIN = "coin" | ||||
|         const val EXP = "exp" | ||||
|         const val HP = "hp" | ||||
|         const val GURU_ANM = "guru_anm" | ||||
|     } | ||||
| 
 | ||||
|     object AnmState { | ||||
|         const val CRONET = "cronet" | ||||
|         const val DEFAULT = "default" | ||||
|     } | ||||
| 
 | ||||
|     object DeviceType { | ||||
|  |  | |||
|  | @ -4,7 +4,6 @@ import android.content.Context | |||
| import guru.core.analytics.data.db.model.EventStatistic | ||||
| import guru.core.analytics.data.model.AnalyticsInfo | ||||
| import guru.core.analytics.data.model.AnalyticsOptions | ||||
| import guru.core.analytics.handler.AnalyticsCode | ||||
| import guru.core.analytics.impl.GuruAnalyticsImpl | ||||
| import java.io.File | ||||
| 
 | ||||
|  | @ -21,13 +20,15 @@ abstract class GuruAnalytics { | |||
|         eventExpiredInDays: Int? = 7, | ||||
|         debug: Boolean = false, | ||||
|         persistableLog: Boolean = true, | ||||
|         listener: ((Int, String?) -> Unit)? = null, | ||||
|         eventHandlerCallback: ((Int, String?) -> Unit)? = null, | ||||
|         isInitPeriodicWork: Boolean = false, | ||||
|         uploadEventBaseUrl: String? = null, | ||||
|         fgEventPeriodInSeconds: Long? = null, | ||||
|         xAppId: String? = null, | ||||
|         xDeviceInfo: String? = null, | ||||
|         mainProcess: String? = null, | ||||
|         isEnableCronet: Boolean? = null, | ||||
|         uploadIpAddress: List<String>? = null, | ||||
|     ) | ||||
| 
 | ||||
|     abstract fun setUploadEventBaseUrl(context: Context, updateEventBaseUrl: String) | ||||
|  | @ -71,6 +72,17 @@ abstract class GuruAnalytics { | |||
| 
 | ||||
|     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 { | ||||
|         val INSTANCE: GuruAnalytics by lazy() { | ||||
|             GuruAnalyticsImpl() | ||||
|  | @ -116,6 +128,12 @@ abstract class GuruAnalytics { | |||
|         fun setMainProcess(process: String) = | ||||
|             apply { analyticsInfo.mainProcess = process } | ||||
| 
 | ||||
|         fun isEnableCronet(isEnableCronet: Boolean) = | ||||
|             apply { analyticsInfo.isEnableCronet = isEnableCronet } | ||||
| 
 | ||||
|         fun setUploadIpAddress(uploadIpAddress: List<String>) = | ||||
|             apply { analyticsInfo.uploadIpAddress = uploadIpAddress } | ||||
| 
 | ||||
|         fun build(): GuruAnalytics { | ||||
|             analyticsInfo.run { | ||||
|                 INSTANCE.initialize( | ||||
|  | @ -133,6 +151,8 @@ abstract class GuruAnalytics { | |||
|                     xAppId, | ||||
|                     xDeviceInfo, | ||||
|                     mainProcess, | ||||
|                     isEnableCronet, | ||||
|                     uploadIpAddress, | ||||
|                 ) | ||||
|             } | ||||
|             return INSTANCE | ||||
|  |  | |||
|  | @ -1,20 +1,28 @@ | |||
| package guru.core.analytics.data.api | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.net.Uri | ||||
| 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.GoogleDnsApi | ||||
| import guru.core.analytics.data.api.dns.GoogleDnsApiHost | ||||
| 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.handler.AnalyticsCode | ||||
| import guru.core.analytics.handler.EventHandler | ||||
| import guru.core.analytics.utils.AndroidUtils | ||||
| import guru.core.analytics.utils.DateTimeUtils | ||||
| 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.ResponseBody | ||||
| import okhttp3.ResponseBody.Companion.toResponseBody | ||||
| import okhttp3.internal.platform.Platform | ||||
| import retrofit2.Converter | ||||
|  | @ -35,6 +43,8 @@ object ServiceLocator { | |||
| 
 | ||||
|     private val headerParams = mutableMapOf<String, String>() | ||||
| 
 | ||||
|     private var uploadIpAddress: List<String>? = null | ||||
| 
 | ||||
|     fun addHeaderParam(key: String, value: String?) { | ||||
|         if (value.isNullOrBlank()) return | ||||
|         headerParams[key] = value | ||||
|  | @ -44,20 +54,24 @@ object ServiceLocator { | |||
|         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) { | ||||
|             return guruRepository | ||||
|                 ?: createQuotesRepository(context, baseUrl).apply { guruRepository = this } | ||||
|                 ?: createQuotesRepository(context, baseUri).apply { guruRepository = this } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private fun createQuotesRepository(context: Context, baseUrl: String? = null): GuruRepository { | ||||
|         return DefaultGuruRepository(provideAnalyticsApi(context, baseUrl)) | ||||
|     private fun createQuotesRepository(context: Context, baseUri: Uri? = null): GuruRepository { | ||||
|         return DefaultGuruRepository(provideAnalyticsApi(context, baseUri)) | ||||
|     } | ||||
| 
 | ||||
|     private fun provideAnalyticsApi(context: Context, baseUrl: String? = null): AnalyticsApi { | ||||
|         val finalBaseUrl = if (!baseUrl.isNullOrEmpty()) { | ||||
|             baseUrl | ||||
|     private fun provideAnalyticsApi(context: Context, baseUri: Uri? = null): AnalyticsApi { | ||||
|         val finalBaseUrl = if (baseUri != null) { | ||||
|             baseUri.toString() | ||||
|         } else { | ||||
|             val cacheBaseUrl = PreferencesManager.getInstance(context).uploadEventBaseUrl | ||||
|             if (cacheBaseUrl.isNullOrEmpty()) AnalyticsApiHost.BASE_URL else cacheBaseUrl | ||||
|  | @ -114,20 +128,22 @@ object ServiceLocator { | |||
|                 maxRequests = 128 | ||||
|                 maxRequestsPerHost = 10 | ||||
|             }) | ||||
|             .dns(CustomDns(context)) | ||||
|             .dns(CustomDns(context, uploadIpAddress)) | ||||
|             .connectTimeout(20L, TimeUnit.SECONDS) | ||||
|             .readTimeout(readTimeOut, TimeUnit.SECONDS) | ||||
|             .writeTimeout(writeTimeOut, TimeUnit.SECONDS) | ||||
|             .addInterceptor(createCacheControlInterceptor(context)) | ||||
|             .addInterceptor(createAnalyticsApiInterceptor()) | ||||
|             .addInterceptor(createLoggingInterceptor()) | ||||
|             .addInterceptor(createCronetInterceptor()) | ||||
|         return builder.build() | ||||
|     } | ||||
| 
 | ||||
|     private fun createDnsOkHttpClient(context: Context): OkHttpClient { | ||||
|         val builder = OkHttpClient.Builder() | ||||
|             .addInterceptor(createCacheControlInterceptor(context)) | ||||
|         builder.addInterceptor(createLoggingInterceptor()) | ||||
|             .addInterceptor(createLoggingInterceptor()) | ||||
|             .addInterceptor(createCronetInterceptor()) | ||||
|         return builder.build() | ||||
|     } | ||||
| 
 | ||||
|  | @ -140,6 +156,14 @@ object ServiceLocator { | |||
|             .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 -> | ||||
|         try { | ||||
|             val originalResponse = chain.proceed(chain.request()) | ||||
|  | @ -162,10 +186,7 @@ object ServiceLocator { | |||
|             } | ||||
|         } catch (e: Exception) { | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_CACHE_CONTROL, e.message) | ||||
|             return@Interceptor exceptionResponse( | ||||
|                 e, | ||||
|                 chain.request() | ||||
|             ) | ||||
|             throw e | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -174,7 +195,6 @@ object ServiceLocator { | |||
|             val request = chain.request() | ||||
|             val startTime = SystemClock.elapsedRealtime() | ||||
|             val builder = request.newBuilder() | ||||
|                 .addHeader(CONTENT_TYPE, "application/json") | ||||
|                 .addHeader(CONTENT_ENCODING, "gzip") | ||||
|                 .addHeader(X_EVENT_TYPE, "event") | ||||
|             headerParams.forEach { | ||||
|  | @ -182,17 +202,13 @@ object ServiceLocator { | |||
|             } | ||||
|             val newRequest = builder.build() | ||||
|             try { | ||||
|                 val response = chain.proceed(newRequest) | ||||
|                 val responseTime = (SystemClock.elapsedRealtime() - startTime) / 2 | ||||
|                 calibrationTime(responseTime, response.headers) | ||||
|                 if (response.isSuccessful) { | ||||
|                     successResponse(response) | ||||
|                 } else { | ||||
|                     httpErrorResponse(response) | ||||
|                 chain.proceed(newRequest).also { response -> | ||||
|                     val responseTime = (SystemClock.elapsedRealtime() - startTime) / 2 | ||||
|                     calibrationTime(responseTime, response.headers) | ||||
|                 } | ||||
|             } catch (e: Exception) { | ||||
|                 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) | ||||
|         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" | ||||
|  |  | |||
|  | @ -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) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | @ -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 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | @ -1,25 +1,44 @@ | |||
| package guru.core.analytics.data.api.dns | ||||
| 
 | ||||
| import android.content.Context | ||||
| import com.google.gson.reflect.TypeToken | ||||
| 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.EventHandler | ||||
| import guru.core.analytics.utils.GsonUtil | ||||
| import kotlinx.coroutines.runBlocking | ||||
| import okhttp3.Dns | ||||
| import java.net.InetAddress | ||||
| 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> { | ||||
|         return try { | ||||
|             Dns.SYSTEM.lookup(hostname) | ||||
|             Dns.SYSTEM.lookup(hostname).also { list -> | ||||
|                 cacheHostAddress(hostname, list.map { it.hostAddress }) | ||||
|             } | ||||
|         } catch (e: UnknownHostException) { | ||||
|             try { | ||||
|                 lookupByGoogleDns(hostname) | ||||
|             } catch (e: UnknownHostException) { | ||||
|                 EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_DNS, e.message) | ||||
|                 lookupByRemoteConfig(hostname) | ||||
|                 try { | ||||
|                     lookupByCacheAndRemote(hostname) | ||||
|                 } catch (e: UnknownHostException) { | ||||
|                     EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_DNS, e.message) | ||||
|                     lookupByRemoteConfig(hostname) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | @ -32,7 +51,11 @@ class CustomDns(private val context: Context) : Dns { | |||
|                 ?.mapNotNull { it.data } | ||||
|         } | ||||
|         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") | ||||
|     } | ||||
|  | @ -41,13 +64,44 @@ class CustomDns(private val context: Context) : Dns { | |||
| //        val dnsConfig = RemoteConfig.getDnsConfig() | ||||
| //        val ipList = dnsConfig?.get(hostname) | ||||
| //        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") | ||||
|     } | ||||
| 
 | ||||
|     private fun convert(ip: String): ByteArray { | ||||
|         val ipArray = ip.split(".").map { Integer.parseInt(it).toByte() } | ||||
|         return ipArray.toByteArray() | ||||
|     private fun convert(ip: String): InetAddress? { | ||||
|         return runCatching { | ||||
|             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") | ||||
|     } | ||||
| } | ||||
|  | @ -48,4 +48,5 @@ class PreferencesManager private constructor( | |||
|     var eventCountUploaded: Int? by bind("event_count_uploaded", 0) | ||||
|     var uploadEventBaseUrl: String? by bind("update_event_base_url", "") | ||||
|     var totalDurationFgEvent: Long? by bind("total_duration_fg_event", 0L) | ||||
|     var hostAddressJson: String? by bind("host_address", "") | ||||
| } | ||||
|  |  | |||
|  | @ -14,4 +14,6 @@ internal data class AnalyticsInfo( | |||
|     var xAppId: String? = null, | ||||
|     var xDeviceInfo: String? = null, | ||||
|     var mainProcess: String? = null, | ||||
|     var isEnableCronet: Boolean? = null, | ||||
|     var uploadIpAddress: List<String>? = null, | ||||
| ) | ||||
|  |  | |||
|  | @ -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 | ||||
|     ) | ||||
| 
 | ||||
| } | ||||
|  | @ -10,6 +10,7 @@ import guru.core.analytics.utils.DateTimeUtils | |||
| import guru.core.analytics.utils.GsonUtil | ||||
| import io.reactivex.subjects.BehaviorSubject | ||||
| import java.util.* | ||||
| import java.util.concurrent.ConcurrentHashMap | ||||
| 
 | ||||
| object EventInfoStore { | ||||
| 
 | ||||
|  | @ -18,9 +19,11 @@ object EventInfoStore { | |||
|         UUID.randomUUID().toString() | ||||
|     } | ||||
| 
 | ||||
|     private val propertiesSubject: BehaviorSubject<LinkedHashMap<String, String>> = | ||||
|     private val propertiesSubject: BehaviorSubject<ConcurrentHashMap<String, String>> = | ||||
|         BehaviorSubject.createDefault( | ||||
|             linkedMapOf() | ||||
|             ConcurrentHashMap<String, String>().apply { | ||||
|                 this[Constants.Properties.GURU_ANM] = Constants.AnmState.DEFAULT | ||||
|             } | ||||
|         ) | ||||
| 
 | ||||
|     private val supplementEventParamsSubject: BehaviorSubject<Map<String, Any>> = | ||||
|  | @ -39,8 +42,8 @@ object EventInfoStore { | |||
|             idsSubject.onNext(value) | ||||
|         } | ||||
| 
 | ||||
|     private var properties: LinkedHashMap<String, String> | ||||
|         get() = propertiesSubject.value ?: linkedMapOf() | ||||
|     var properties: ConcurrentHashMap<String, String> | ||||
|         get() = propertiesSubject.value ?: ConcurrentHashMap() | ||||
|         set(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>() | ||||
|         if (!event.itemCategory.isNullOrBlank()) { | ||||
|             eventMap[Constants.Event.ITEM_CATEGORY] = ParamValue(s = event.itemCategory) | ||||
|  | @ -124,8 +132,9 @@ object EventInfoStore { | |||
|             eventMap[entry.key] = createParamValue(entry.value) | ||||
|         } | ||||
|         val eventId = UUID.randomUUID().toString() | ||||
|         val eventTs = DateTimeUtils.eventAt() - elapsed | ||||
|         val eventData = Event( | ||||
|             timestamp = DateTimeUtils.eventAt(), | ||||
|             timestamp = eventTs, | ||||
|             info = ids, | ||||
|             event = event.eventName, | ||||
|             param = eventMap, | ||||
|  | @ -139,7 +148,7 @@ object EventInfoStore { | |||
|             json = eventJson, | ||||
|             ext = "", | ||||
|             status = if (uploading) 1 else 0, | ||||
|             at = DateTimeUtils.eventAt(), | ||||
|             at = eventTs, | ||||
|             version = Constants.VERSION, | ||||
|             event = event.eventName, | ||||
|             priority = priority | ||||
|  |  | |||
|  | @ -9,11 +9,15 @@ enum class AnalyticsCode(val code: Int) { | |||
|     UPLOAD_SUCCESS(13),         // 上报事件成功 | ||||
|     UPLOAD_FAIL(14),            // 上报事件失败 | ||||
|     PERIODIC_WORK_ENQUEUE(15),  // 开启PeriodicWork | ||||
|     ENABLE_UPLOAD(16),          // 修改是否允许上传埋点状态 | ||||
| 
 | ||||
|     NETWORK_AVAILABLE(21),      // 网络状态可用 | ||||
|     NETWORK_LOST(22),           // 网络状态不可用 | ||||
|     LIFECYCLE_START(23),        // 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_RESPONSE(102),         // api返回结果错误 | ||||
|  | @ -22,7 +26,20 @@ enum class AnalyticsCode(val code: Int) { | |||
|     ERROR_LOAD_MARK(105),        // 从数据库取事件以及更改事件状态为正在上报出错 | ||||
|     ERROR_DNS(106),              // dns 错误 | ||||
|     ERROR_ZIP(107),              // zip 错误 | ||||
|     ERROR_DNS_CACHE(108),        // zip 错误 | ||||
|     ERROR_CRONET_INTERCEPTOR(109),// cronet拦截器 | ||||
| 
 | ||||
|     EVENT_FIRST_OPEN(1001),       // first_open 事件 | ||||
|     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), | ||||
| } | ||||
|  | @ -1,16 +1,26 @@ | |||
| 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() | ||||
| } | ||||
| 
 | ||||
| object EventDispatcher : EventDeliver { | ||||
| 
 | ||||
|     private val pendingEvents = ConcurrentLinkedQueue<Pair<EventItem, AnalyticsOptions>>() | ||||
|     private val pendingEvents = ConcurrentLinkedQueue<PendingEvent>() | ||||
| 
 | ||||
|     private val started = AtomicBoolean(false) | ||||
| 
 | ||||
|  | @ -18,8 +28,12 @@ object EventDispatcher : EventDeliver { | |||
|         Timber.d("EventDispatcher dispatchPendingEvent ${pendingEvents.size}!") | ||||
|         if (pendingEvents.isNotEmpty()) { | ||||
|             while (true) { | ||||
|                 val pair = pendingEvents.poll() ?: return | ||||
|                 val event = EventInfoStore.deriveEvent(pair.first, priority = pair.second.priority) | ||||
|                 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) | ||||
|             } | ||||
|         } | ||||
|  | @ -29,6 +43,7 @@ object EventDispatcher : EventDeliver { | |||
|         if (started.compareAndSet(false, true)) { | ||||
|             Timber.d("EventDispatcher started!") | ||||
|             dispatchPendingEvent() | ||||
|             GuruAnalyticsAudit.eventDispatcherStarted = true | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -40,7 +55,7 @@ object EventDispatcher : EventDeliver { | |||
|             GuruAnalyticsDatabase.getInstance().eventDao().addEvent(event) | ||||
|         } else { | ||||
|             Timber.d("EventDispatcher deliverEvent pending!") | ||||
|             pendingEvents.offer(item to options) | ||||
|             pendingEvents.offer(PendingEvent(item, options)) | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
|  | @ -1,6 +1,7 @@ | |||
| package guru.core.analytics.impl | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.net.Uri | ||||
| import guru.core.analytics.Constants | ||||
| import guru.core.analytics.GuruAnalytics | ||||
| 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.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 | ||||
|  | @ -39,10 +41,10 @@ internal class EventEngine internal constructor( | |||
|     private val batchLimit: Int = DEFAULT_BATCH_LIMIT, | ||||
|     private val uploadPeriodInSeconds: Long = DEFAULT_UPLOAD_PERIOD_IN_SECONDS, | ||||
|     private val eventExpiredInDays: Int = DEFAULT_EVENT_EXPIRED_IN_DAYS, | ||||
|     private val uploadEventBaseUrl: String? = null, | ||||
|     private val uploadEventBaseUri: Uri? = null, | ||||
| ) : EventDeliver { | ||||
|     private val guruRepository: GuruRepository by lazy { | ||||
|         ServiceLocator.provideGuruRepository(context.applicationContext, baseUrl = uploadEventBaseUrl) | ||||
|         ServiceLocator.provideGuruRepository(context.applicationContext, baseUri = uploadEventBaseUri) | ||||
|     } | ||||
| 
 | ||||
|     private val preferencesManager by lazy { | ||||
|  | @ -83,6 +85,13 @@ internal class EventEngine internal constructor( | |||
|     private val forceUploadSubject: PublishSubject<Boolean> = PublishSubject.create() | ||||
| 
 | ||||
|     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?) { | ||||
|         if (started.compareAndSet(false, true)) { | ||||
|  | @ -96,6 +105,7 @@ internal class EventEngine internal constructor( | |||
|                 val extMap = mapOf("startUploadDelayInSecond" to startUploadDelay) | ||||
|                 EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.STATE_START_WORK, extMap) | ||||
|             }, startUploadDelay ?: 0, TimeUnit.SECONDS) | ||||
|             GuruAnalyticsAudit.engineInitialized = true | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | @ -146,6 +156,7 @@ internal class EventEngine internal constructor( | |||
|                     logDebug("pendingEvent ${it.size}") | ||||
|                 } | ||||
|         val networkFlowable = ConnectionStateMonitor.connectStateFlowable.doOnNext { | ||||
|             GuruAnalyticsAudit.connectionState = it | ||||
|             logDebug("network $it") | ||||
|         } | ||||
|         val forceFlowable = forceUploadSubject.toFlowable(BackpressureStrategy.DROP) | ||||
|  | @ -157,6 +168,7 @@ internal class EventEngine internal constructor( | |||
|                 logDebug("pollEvent filter $it") | ||||
|                 return@filter it | ||||
|             } | ||||
|             .filter { enableUpload } | ||||
|             .flatMap { uploadEvents(500) } | ||||
|             .subscribe() | ||||
|         ) | ||||
|  | @ -174,7 +186,10 @@ internal class EventEngine internal constructor( | |||
|             // 记录删除的事件数量 | ||||
|             if (pair.first > 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( | ||||
|                     "expiredCount" to pair.first, | ||||
|  | @ -192,6 +207,7 @@ internal class EventEngine internal constructor( | |||
| 
 | ||||
|     internal fun uploadEvents(count: Int): Flowable<List<EventEntity>> { | ||||
|         val eventDao = GuruAnalyticsDatabase.getInstance().eventDao() | ||||
|         GuruAnalyticsAudit.uploadReady = true | ||||
|         logDebug("uploadEvents: $count") | ||||
|         return Flowable.just(count).map { eventDao.loadAndMarkUploadEvents(it) } | ||||
|             .onErrorReturn { | ||||
|  | @ -232,7 +248,10 @@ internal class EventEngine internal constructor( | |||
|             .doOnSuccess { | ||||
|                 // 记录上传成功的数量 | ||||
|                 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( | ||||
|                     "count" to entities.size, | ||||
|  | @ -288,7 +307,10 @@ internal class EventEngine internal constructor( | |||
| 
 | ||||
|     private fun increaseEventCount() { | ||||
|         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) { | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ import android.os.SystemClock | |||
| import guru.core.analytics.Constants | ||||
| import guru.core.analytics.GuruAnalytics | ||||
| 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.EventHandler | ||||
| import io.reactivex.Flowable | ||||
|  | @ -75,6 +76,7 @@ internal class FgHelper(context: Context) { | |||
|             }, { | ||||
|                 Timber.tag("FgHelper").e(it) | ||||
|             }) | ||||
|         GuruAnalyticsAudit.fgHelperInitialized = true | ||||
|     } | ||||
| 
 | ||||
|     fun stop() { | ||||
|  |  | |||
|  | @ -1,16 +1,19 @@ | |||
| package guru.core.analytics.impl | ||||
| 
 | ||||
| import android.content.Context | ||||
| import android.net.Uri | ||||
| import androidx.annotation.RequiresPermission | ||||
| import androidx.work.* | ||||
| import guru.core.analytics.Constants | ||||
| import guru.core.analytics.GuruAnalytics | ||||
| 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.model.EventStatistic | ||||
| 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.DeviceInfoStore | ||||
| import guru.core.analytics.data.store.EventInfoStore | ||||
| 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.utils.AndroidUtils | ||||
| import guru.core.analytics.utils.EventChecker | ||||
| import guru.core.analytics.utils.GsonUtil | ||||
| import guru.core.analytics.utils.SystemProperties | ||||
| import timber.log.Timber | ||||
| import java.io.File | ||||
|  | @ -75,24 +79,55 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|         fgEventPeriodInSeconds: Long?, | ||||
|         xAppId: String?, | ||||
|         xDeviceInfo: String?, | ||||
|         mainProcess: String? | ||||
|         mainProcess: String?, | ||||
|         isEnableCronet: Boolean?, | ||||
|         uploadIpAddress: List<String>?, | ||||
|     ) { | ||||
|         if (initialized.compareAndSet(false, true)) { | ||||
|             delivers.add(EventDispatcher) | ||||
|             eventHandlerCallback?.let { EventHandler.INSTANCE.addEventHandler(it) } | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_1) | ||||
| 
 | ||||
|             val debugApp = SystemProperties.read("debug.guru.analytics.app") | ||||
|             val forceDebug = debugApp == context.packageName | ||||
|             if (forceDebug || persistableLog) { | ||||
|                 Timber.plant(PersistentTree(context, debug = debug)) | ||||
|             } | ||||
|             debugMode = debug | ||||
|             Timber.d("[$internalVersion]initialize batchLimit:$batchLimit uploadPeriodInSecond:$uploadPeriodInSeconds startUploadDelayInSecond:$startUploadDelayInSecond eventExpiredInDays:$eventExpiredInDays debug:$debug") | ||||
|             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") | ||||
|             GuruAnalyticsDatabase.initialize(context.applicationContext) | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_3) | ||||
| 
 | ||||
|             val baseUrl = if (forceDebug && debugUrl.isNotEmpty()) debugUrl else uploadEventBaseUrl | ||||
| 
 | ||||
|             DeviceInfoStore.setDeviceInfo(context) | ||||
|             ServiceLocator.setDebug(debug) | ||||
|             ServiceLocator.addHeaderParam("X-APP-ID", xAppId) | ||||
|             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( | ||||
|                 context, | ||||
|  | @ -102,10 +137,11 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|                 eventExpiredInDays = EventEngine.DEFAULT_EVENT_EXPIRED_IN_DAYS.coerceAtLeast( | ||||
|                     eventExpiredInDays ?: EventEngine.DEFAULT_EVENT_EXPIRED_IN_DAYS | ||||
|                 ), | ||||
|                 uploadEventBaseUrl = uploadEventBaseUrl, | ||||
|                 uploadEventBaseUri = uploadEventBaseUri | ||||
|             ).apply { | ||||
|                 start(startUploadDelayInSecond) | ||||
|                 delivers.add(this) | ||||
|                 start(startUploadDelayInSecond) | ||||
|                 EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_7) | ||||
|             } | ||||
|             if (isInitPeriodicWork) { | ||||
|                 val process = AndroidUtils.getProcessName(context) | ||||
|  | @ -120,6 +156,7 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.INIT_STEP_8) | ||||
| 
 | ||||
|             val extMap = mapOf( | ||||
|                 "version_code" to internalVersion, | ||||
|  | @ -127,12 +164,17 @@ internal class GuruAnalyticsImpl : GuruAnalytics() { | |||
|                 "uploadPeriodInSecond" to uploadPeriodInSeconds, | ||||
|                 "startUploadDelayInSecond" to startUploadDelayInSecond, | ||||
|                 "eventExpiredInDays" to eventExpiredInDays, | ||||
|                 "uploadEventBaseUri" to uploadEventBaseUri.toString(), | ||||
|                 "enabledCronet" to (isEnableCronet ?: false), | ||||
|                 "unloadIpAddress" to (uploadIpAddress?.joinToString("|") ?: ""), | ||||
|                 "debug" to debug, | ||||
|             ) | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.STATE_INITIALIZED, extMap) | ||||
|             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) | ||||
|     } | ||||
| 
 | ||||
|     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) { | ||||
|         Timber.tag("GuruAnalytics").d("deliverEvent ${item.eventName}!") | ||||
|         deliverExecutor.execute { | ||||
|  |  | |||
|  | @ -11,6 +11,7 @@ object ApiParamUtils { | |||
|      * 组装接口上传需要的json参数 | ||||
|      */ | ||||
|     fun generateApiParam(events: List<EventEntity>): String { | ||||
|         /// todo: 版本需要根据当前的更新 | ||||
|         val deviceInfoJson = GsonUtil.gson.toJson(DeviceInfoStore.deviceInfo) | ||||
|         return "{\"version\":${Constants.VERSION},\"events\":[${events.joinToString(",") { it.json }}],\"deviceInfo\":$deviceInfoJson}" | ||||
|     } | ||||
|  |  | |||
|  | @ -19,7 +19,7 @@ object GZipUtils { | |||
|             out.toByteArray() | ||||
|         } catch (e: IOException) { | ||||
|             EventHandler.INSTANCE.notifyEventHandler(AnalyticsCode.ERROR_ZIP, e.message) | ||||
|             null | ||||
|             throw e | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue