163 lines
5.8 KiB
Kotlin
163 lines
5.8 KiB
Kotlin
package com.gh.common.exposure
|
||
|
||
import android.database.sqlite.SQLiteFullException
|
||
import com.aliyun.sls.android.sdk.model.LogGroup
|
||
import com.gh.common.exposure.time.TimeUtil
|
||
import com.gh.common.util.toJson
|
||
import com.gh.common.util.toastInInternalRelease
|
||
import com.gh.common.util.tryWithDefaultCatch
|
||
import com.gh.gamecenter.BuildConfig
|
||
import com.gh.loghub.LgLOG
|
||
import com.gh.loghub.LoghubHelper
|
||
import com.halo.assistant.HaloApp
|
||
import com.lightgame.utils.Utils
|
||
import java.util.concurrent.Executors
|
||
|
||
/**
|
||
* A handful tool for committing logs to aliyun loghub.
|
||
*
|
||
* 如何简单地统计列表中每个 item 的曝光事件?
|
||
*
|
||
* 1. Adapter 实现 IExposable 接口,在 BindView 阶段更新 ExposureEvent,ExposureEvent 供 getEventByPosition(pos) 方法获取用
|
||
* 2. 构建一个 ExposureListener 并作为入参添加至 recyclerview 的 onScroll 回调中
|
||
* 3. 没了
|
||
*/
|
||
object ExposureManager {
|
||
|
||
private const val ENDPOINT = "cn-qingdao.log.aliyuncs.com"
|
||
private const val PROJECT = "ghzs"
|
||
private const val STORE_SIZE = 100
|
||
private const val LOG_STORE = BuildConfig.EXPOSURE_REPO
|
||
|
||
private val loghubHelper = LoghubHelper.getInstance()
|
||
|
||
// exposureCache 用来过滤掉具有相同 id 的曝光事件,避免重复发送事件
|
||
private val exposureSet by lazy { hashSetOf<ExposureEvent>() }
|
||
private val exposureExecutor by lazy { Executors.newSingleThreadExecutor() }
|
||
private val exposureCache by lazy { FixedSizeLinkedHashSet<String>(300) }
|
||
private val exposureDao by lazy { ExposureDatabase.buildDatabase(HaloApp.getInstance().application).logHubEventDao() }
|
||
|
||
@JvmStatic
|
||
fun init() {
|
||
loghubHelper.init(HaloApp.getInstance().application, ENDPOINT, PROJECT, LOG_STORE) { TimeUtil.currentTimeMillis() }
|
||
|
||
exposureExecutor.execute {
|
||
try {
|
||
val eventList = exposureDao.getAll()
|
||
exposureSet.addAll(eventList)
|
||
} catch (e: SQLiteFullException) {
|
||
e.printStackTrace()
|
||
toastInInternalRelease("数据库/磁盘已满,初始化曝光失败")
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Log a single exposure event.
|
||
*/
|
||
fun log(event: ExposureEvent) {
|
||
exposureExecutor.execute {
|
||
try {
|
||
if (!exposureCache.contains(event.id)) {
|
||
// Catch `android.database.sqlite.SQLiteFullException: database or disk is full` exception.
|
||
|
||
exposureSet.add(event)
|
||
exposureDao.insert(event)
|
||
exposureCache.add(event.id)
|
||
|
||
} else {
|
||
Utils.log("Exposure", "遇到重复曝光事件,自动过滤 (${event.id} - ${event.payload.gameName})")
|
||
}
|
||
} catch (e: Exception) {
|
||
e.printStackTrace()
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Log a collection of exposure event.
|
||
*/
|
||
fun log(eventList: List<ExposureEvent>) {
|
||
exposureExecutor.execute {
|
||
for (event in eventList) {
|
||
try {
|
||
if (!exposureCache.contains(event.id)) {
|
||
// Catch `android.database.sqlite.SQLiteFullException: database or disk is full` exception.
|
||
exposureSet.add(event)
|
||
exposureDao.insert(event)
|
||
exposureCache.add(event.id)
|
||
} else {
|
||
Utils.log("Exposure", "遇到重复曝光事件,自动过滤 (${event.id} - ${event.payload.gameName})")
|
||
}
|
||
} catch (e: Exception) {
|
||
e.printStackTrace()
|
||
}
|
||
}
|
||
commitSavedExposureEvents()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param forced Ignore all restrictions.
|
||
*/
|
||
fun commitSavedExposureEvents(forced: Boolean = false) {
|
||
exposureExecutor.execute {
|
||
tryWithDefaultCatch {
|
||
// TODO 初始化 loghubHelper 去掉这个 tryCatch 块
|
||
if (exposureSet.size < STORE_SIZE && !forced || exposureSet.size == 0) return@execute
|
||
|
||
val exposureList = exposureSet.toList()
|
||
// uploadLogGroup 是一个异步方法,LoghubHelper 里面实现了重传功能,所以这里交给它就好了
|
||
loghubHelper.uploadLogGroup(buildLogGroup(exposureList))
|
||
|
||
Utils.log("Exposure", "提交了${exposureList.size}条曝光记录")
|
||
exposureSet.removeAll(exposureList)
|
||
exposureDao.deleteMany(exposureList)
|
||
}
|
||
}
|
||
}
|
||
|
||
private fun eliminateMultipleBrackets(jsonWithMultipleBracket: String): String {
|
||
return jsonWithMultipleBracket.replace("[[", "[").replace("]]", "]")
|
||
}
|
||
|
||
private fun buildLogGroup(eventList: List<ExposureEvent>): LogGroup {
|
||
val logGroup = LogGroup("sls android", "no ip")
|
||
|
||
eventList.forEach { logGroup.PutLog(buildLog(it)) }
|
||
|
||
return logGroup
|
||
}
|
||
|
||
private fun buildLog(event: ExposureEvent): LgLOG {
|
||
val log = LgLOG(TimeUtil.currentTime())
|
||
|
||
log.PutContent("id", event.id)
|
||
log.PutContent("payload", event.payload.toJson())
|
||
log.PutContent("event", event.event.toString())
|
||
log.PutContent("source", eliminateMultipleBrackets(event.source.toJson()))
|
||
log.PutContent("meta", event.meta.toJson())
|
||
log.PutContent("e-traces", if (event.eTrace != null) {
|
||
eliminateMultipleBrackets(event.eTrace?.toJson() ?: "")
|
||
} else "")
|
||
log.PutTime(event.time)
|
||
|
||
return log
|
||
}
|
||
|
||
internal class FixedSizeLinkedHashSet<T>(var maxSize: Int) : LinkedHashSet<T>() {
|
||
override fun add(element: T): Boolean {
|
||
if (size == maxSize) {
|
||
pop()
|
||
}
|
||
return super.add(element);
|
||
}
|
||
|
||
private fun pop() {
|
||
if (size > 0) {
|
||
remove(iterator().next())
|
||
}
|
||
}
|
||
}
|
||
|
||
} |