Skip to content

Commit

Permalink
perf: 性能优化
Browse files Browse the repository at this point in the history
  • Loading branch information
lisonge committed Nov 13, 2023
1 parent d15dac4 commit bb6012c
Show file tree
Hide file tree
Showing 18 changed files with 329 additions and 307 deletions.
31 changes: 24 additions & 7 deletions app/src/main/java/li/songe/gkd/data/Rule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import android.graphics.Path
import android.graphics.Rect
import android.view.ViewConfiguration
import android.view.accessibility.AccessibilityNodeInfo
import com.blankj.utilcode.util.LogUtils
import com.blankj.utilcode.util.ScreenUtils
import kotlinx.serialization.Serializable
import li.songe.gkd.service.lastTriggerRuleFlow
import li.songe.gkd.service.launcherActivityIdFlow
import li.songe.gkd.service.lastTriggerRule
import li.songe.gkd.service.openAdOptimized
import li.songe.gkd.service.querySelector
import li.songe.selector.Selector

Expand Down Expand Up @@ -42,6 +43,12 @@ data class Rule(
val app: SubscriptionRaw.AppRaw,
val subsItem: SubsItem,
) {

/**
* 优化: 切换 APP 后短时间内, 如果存在开屏广告的规则并且没有一次触发, 则不启用其它规则, 避免过多规则阻塞运行
*/
val isOpenAd = group.name.startsWith("开屏广告")

/**
* 任意一个元素是上次点击过的
*/
Expand All @@ -51,6 +58,10 @@ data class Rule(
fun triggerDelay() {
// 触发延迟, 一段时间内此规则不可利用
actionDelayTriggerTime = System.currentTimeMillis()
LogUtils.d(
"触发延迟",
"subsId:${subsItem.id}, gKey=${group.key}, gName:${group.name}, ruleIndex:${index}, rKey:${key}, delay:${actionDelay}"
)
}

var actionTriggerTime = 0L
Expand All @@ -59,7 +70,10 @@ data class Rule(
// 重置延迟点
actionDelayTriggerTime = 0L
actionCount++
lastTriggerRuleFlow.value = this
lastTriggerRule = this
if (isOpenAd && openAdOptimized == true) {
openAdOptimized = false
}
}

var actionCount = 0
Expand Down Expand Up @@ -91,12 +105,15 @@ data class Rule(

fun matchActivityId(activityId: String?): Boolean {
if (matchAnyActivity) return true
if (activityId == null) return false
if (activityId == null) {
if (matchLauncher) {
return true
}
return false
}
if (excludeActivityIds.any { activityId.startsWith(it) }) return false
if (activityIds.isEmpty()) return true
if (matchLauncher && launcherActivityIdFlow.value == activityId) {
return true
}

return activityIds.any { activityId.startsWith(it) }
}

Expand Down
10 changes: 5 additions & 5 deletions app/src/main/java/li/songe/gkd/data/SubscriptionRaw.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.*
import li.songe.gkd.util.Singleton
import li.songe.gkd.util.json
import li.songe.selector.Selector


Expand Down Expand Up @@ -302,12 +302,12 @@ data class SubscriptionRaw(
}

// 订阅文件状态: 文件不存在, 文件正常, 文件损坏(损坏原因)
fun stringify(source: SubscriptionRaw) = Singleton.json.encodeToString(source)
fun stringify(source: SubscriptionRaw) = json.encodeToString(source)

fun parse(source: String, json5: Boolean = true): SubscriptionRaw {
val text = if (json5) Jankson.builder().build().load(source).toJson() else source

val obj = jsonToSubscriptionRaw(Singleton.json.parseToJsonElement(text).jsonObject)
val obj = jsonToSubscriptionRaw(json.parseToJsonElement(text).jsonObject)

val duplicatedApps = obj.apps.groupingBy { it }.eachCount().filter { it.value > 1 }.keys
if (duplicatedApps.isNotEmpty()) {
Expand All @@ -334,12 +334,12 @@ data class SubscriptionRaw(

fun parseAppRaw(source: String, json5: Boolean = true): AppRaw {
val text = if (json5) Jankson.builder().build().load(source).toJson() else source
return jsonToAppRaw(Singleton.json.parseToJsonElement(text).jsonObject, 0)
return jsonToAppRaw(json.parseToJsonElement(text).jsonObject, 0)
}

fun parseGroupRaw(source: String, json5: Boolean = true): GroupRaw {
val text = if (json5) Jankson.builder().build().load(source).toJson() else source
return jsonToGroupRaw(Singleton.json.parseToJsonElement(text).jsonObject, 0)
return jsonToGroupRaw(json.parseToJsonElement(text).jsonObject, 0)
}
}

Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/li/songe/gkd/db/DbSet.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import li.songe.gkd.appScope
import li.songe.gkd.data.SubsItem
import li.songe.gkd.data.SubscriptionRaw
import li.songe.gkd.util.DEFAULT_SUBS_UPDATE_URL
import li.songe.gkd.util.Singleton
import li.songe.gkd.util.client
import li.songe.gkd.util.dbFolder
import li.songe.gkd.util.launchTry
import java.io.File
Expand Down Expand Up @@ -50,7 +50,7 @@ object DbSet {
val newSubsRaw = try {
withTimeout(3000) {
SubscriptionRaw.parse(
Singleton.client.get(defaultSubsItem.updateUrl!!).bodyAsText()
client.get(defaultSubsItem.updateUrl!!).bodyAsText()
)
}
} catch (e: Exception) {
Expand Down
6 changes: 3 additions & 3 deletions app/src/main/java/li/songe/gkd/debug/HttpService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ import kotlinx.serialization.Serializable
import li.songe.gkd.app
import li.songe.gkd.appScope
import li.songe.gkd.composition.CompositionService
import li.songe.gkd.data.GkdAction
import li.songe.gkd.data.DeviceInfo
import li.songe.gkd.data.GkdAction
import li.songe.gkd.data.RpcError
import li.songe.gkd.data.SubsItem
import li.songe.gkd.data.SubscriptionRaw
Expand All @@ -43,7 +43,7 @@ import li.songe.gkd.notif.httpChannel
import li.songe.gkd.notif.httpNotif
import li.songe.gkd.service.GkdAbService
import li.songe.gkd.util.Ext.getIpAddressInLocalNetwork
import li.songe.gkd.util.Singleton
import li.songe.gkd.util.keepNullJson
import li.songe.gkd.util.launchTry
import li.songe.gkd.util.map
import li.songe.gkd.util.storeFlow
Expand All @@ -67,7 +67,7 @@ class HttpService : CompositionService({
return embeddedServer(Netty, port, configure = { tcpKeepAlive = true }) {
install(KtorCorsPlugin)
install(KtorErrorPlugin)
install(ContentNegotiation) { json(Singleton.keepNullJson) }
install(ContentNegotiation) { json(keepNullJson) }

routing {
get("/") { call.respond("hello world") }
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/li/songe/gkd/debug/SnapshotExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import li.songe.gkd.data.createComplexSnapshot
import li.songe.gkd.data.toSnapshot
import li.songe.gkd.db.DbSet
import li.songe.gkd.service.GkdAbService
import li.songe.gkd.util.Singleton
import li.songe.gkd.util.keepNullJson
import li.songe.gkd.util.snapshotZipDir
import li.songe.gkd.util.storeFlow
import java.io.File
Expand Down Expand Up @@ -112,7 +112,7 @@ object SnapshotExt {
File(getScreenshotPath(snapshot.id)).outputStream().use { stream ->
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
}
val text = Singleton.keepNullJson.encodeToString(snapshot)
val text = keepNullJson.encodeToString(snapshot)
File(getSnapshotPath(snapshot.id)).writeText(text)
DbSet.snapshotDao.insert(snapshot.toSnapshot())
}
Expand Down
7 changes: 4 additions & 3 deletions app/src/main/java/li/songe/gkd/debug/SnapshotTileService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import kotlinx.coroutines.delay
import li.songe.gkd.appScope
import li.songe.gkd.debug.SnapshotExt.captureSnapshot
import li.songe.gkd.service.GkdAbService
import li.songe.gkd.service.topActivityFlow
import li.songe.gkd.service.safeActiveWindow
import li.songe.gkd.util.launchTry

class SnapshotTileService : TileService() {
Expand All @@ -18,12 +18,13 @@ class SnapshotTileService : TileService() {
ToastUtils.showShort("无障碍没有开启")
return
}
val oldAppId = topActivityFlow.value?.appId
val oldAppId = service.safeActiveWindow?.packageName
?: return ToastUtils.showShort("获取界面信息根节点失败")
appScope.launchTry {
val interval = 500L
val waitTime = 3000L
var i = 0
while (topActivityFlow.value?.appId == oldAppId) {
while (oldAppId.contentEquals(service.safeActiveWindow?.packageName)) {
service.performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
delay(interval)
i++
Expand Down
130 changes: 92 additions & 38 deletions app/src/main/java/li/songe/gkd/service/AbState.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
package li.songe.gkd.service

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.stateIn
import li.songe.gkd.appScope
import li.songe.gkd.data.ClickLog
import li.songe.gkd.data.Rule
import li.songe.gkd.db.DbSet
import li.songe.gkd.util.appIdToRulesFlow
import li.songe.gkd.util.increaseClickCount
import li.songe.gkd.util.launchTry
import li.songe.gkd.util.map

val launcherActivityIdFlow by lazy {
MutableStateFlow<String?>(null)
}
import li.songe.gkd.util.recordStoreFlow

data class TopActivity(
val appId: String,
Expand All @@ -23,83 +20,122 @@ val topActivityFlow by lazy {
MutableStateFlow<TopActivity?>(null)
}

val currentRulesFlow by lazy {
combine(appIdToRulesFlow, topActivityFlow) { appIdToRules, topActivity ->
(appIdToRules[topActivity?.appId] ?: emptyList()).filter { rule ->
rule.matchActivityId(topActivity?.activityId)
}
}.stateIn(appScope, SharingStarted.Eagerly, emptyList())
}
data class ActivityRule(
val rules: List<Rule> = emptyList(),
val topActivity: TopActivity? = null,
val appIdToRules: Map<String, List<Rule>> = emptyMap(),
)

val activityRuleFlow by lazy { MutableStateFlow(ActivityRule()) }

val lastTriggerRuleFlow by lazy {
MutableStateFlow<Rule?>(null)
private val lastActivityIdCacheMap by lazy { mutableMapOf<String, String>() }

private fun getFixTopActivity(): TopActivity? {
val top = topActivityFlow.value ?: return null
if (top.activityId == null) {
topActivityFlow.value = top.copy(activityId = lastActivityIdCacheMap[top.appId])
} else {
// 当从通知栏上拉返回应用等时, activityId 的无障碍事件不会触发, 此时使用上一次获得的 activityId 填充
lastActivityIdCacheMap[top.appId] = top.activityId
}
return topActivityFlow.value
}

val activityChangeTimeFlow by lazy {
MutableStateFlow(System.currentTimeMillis()).apply {
appScope.launchTry {
topActivityFlow.collect {
this@apply.value = System.currentTimeMillis()
}
fun getCurrentRules(): ActivityRule {
val topActivity = getFixTopActivity()
val activityRule = activityRuleFlow.value
val appIdToRules = appIdToRulesFlow.value
val idChanged = topActivity?.appId != activityRule.topActivity?.appId
val topChanged = activityRule.topActivity != topActivity
if (topChanged || activityRule.appIdToRules !== appIdToRules) {
activityRuleFlow.value =
ActivityRule(rules = (appIdToRules[topActivity?.appId] ?: emptyList()).filter { rule ->
rule.matchActivityId(topActivity?.activityId)
}, topActivity = topActivity, appIdToRules = appIdToRules)
}
if (topChanged) {
val t = System.currentTimeMillis()
if (idChanged) {
appChangeTime = t
openAdOptimized = null
}
activityChangeTime = t
if (openAdOptimized == null && t - appChangeTime < openAdOptimizedTime) {
openAdOptimized = activityRuleFlow.value.rules.any { r -> r.isOpenAd }
}
}
return activityRuleFlow.value
}

val appChangeTimeFlow by lazy {
topActivityFlow.map(appScope) { t -> t?.appId }.map(appScope) { System.currentTimeMillis() }
}
var lastTriggerRule: Rule? = null
var appChangeTime = 0L
var activityChangeTime = 0L

// null: app 切换过
// true: 需要执行优化(此界面组需要存在开屏广告)
// false: 执行优化过了/已经过了优化时间
var openAdOptimized: Boolean? = null
const val openAdOptimizedTime = 5000L

fun isAvailableRule(rule: Rule): Boolean {
val t = System.currentTimeMillis()
if (!rule.isOpenAd && openAdOptimized == true) {
if (t - appChangeTime < openAdOptimizedTime) {
// app 切换一段时间内, 仅开屏广告可使用
return false
} else {
openAdOptimized = false
}
}
if (rule.resetMatchTypeWhenActivity) {
if (activityChangeTimeFlow.value != rule.matchChangeTime) {
if (activityChangeTime != rule.matchChangeTime) {
// 当 界面 更新时, 重置操作延迟点, 重置点击次数
rule.actionDelayTriggerTime = 0
rule.actionCount = 0
rule.matchChangeTime = activityChangeTimeFlow.value
rule.matchChangeTime = activityChangeTime
}
} else {
if (appChangeTimeFlow.value != rule.matchChangeTime) {
if (appChangeTime != rule.matchChangeTime) {
// 当 切换APP 时, 重置点击次数
rule.actionDelayTriggerTime = 0
rule.actionCount = 0
rule.matchChangeTime = appChangeTimeFlow.value
rule.matchChangeTime = appChangeTime
}
}
if (rule.actionMaximum != null) {
if (rule.actionCount >= rule.actionMaximum) {
return false // 达到最大执行次数
}
}
val t = System.currentTimeMillis()
if (rule.matchDelay != null) {
// 处于匹配延迟中
if (rule.resetMatchTypeWhenActivity) {
if (t - activityChangeTimeFlow.value < rule.matchDelay) {
if (t - activityChangeTime < rule.matchDelay) {
return false
}
} else {
if (t - appChangeTimeFlow.value < rule.matchDelay) {
if (t - appChangeTime < rule.matchDelay) {
return false
}
}
}
if (rule.matchTime != null) {
// 超出了匹配时间
if (rule.resetMatchTypeWhenActivity) {
if (t - activityChangeTimeFlow.value > rule.matchAllTime) {
if (t - activityChangeTime > rule.matchAllTime) {
return false
}
} else {
if (t - appChangeTimeFlow.value > rule.matchAllTime) {
if (t - appChangeTime > rule.matchAllTime) {
return false
}
}
}
if (rule.actionTriggerTime + rule.actionCd > t) return false // 处于冷却时间
if (rule.preRules.isNotEmpty()) { // 需要提前点击某个规则
lastTriggerRuleFlow.value ?: return false
lastTriggerRule ?: return false
// 上一个点击的规则不在当前需要点击的列表
return rule.preRules.any { it === lastTriggerRuleFlow.value }
return rule.preRules.any { it === lastTriggerRule }
}
if (rule.actionDelayTriggerTime > 0) {
if (rule.actionDelayTriggerTime + rule.actionDelay > t) {
Expand All @@ -109,4 +145,22 @@ fun isAvailableRule(rule: Rule): Boolean {
return true
}


fun insertClickLog(rule: Rule) {
toastClickTip()
rule.trigger()
appScope.launchTry(Dispatchers.IO) {
val clickLog = ClickLog(
appId = topActivityFlow.value?.appId,
activityId = topActivityFlow.value?.activityId,
subsId = rule.subsItem.id,
groupKey = rule.group.key,
ruleIndex = rule.index,
ruleKey = rule.key
)
DbSet.clickLogDao.insert(clickLog)
increaseClickCount()
if (recordStoreFlow.value.clickCount % 100 == 0) {
DbSet.clickLogDao.deleteKeepLatest()
}
}
}
Loading

0 comments on commit bb6012c

Please sign in to comment.