diff --git a/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt b/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt index 1665a488e..b66aaf29d 100644 --- a/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt +++ b/app/src/main/kotlin/li/songe/gkd/data/RawSubscription.kt @@ -570,11 +570,11 @@ data class RawSubscription( } subscription.apps.forEach { a -> a.groups.findDuplicatedItem { v -> v.key }?.let { v -> - error("duplicated app group: key=${v.key}") + error("duplicated app group: key=${v.key}, appId=${a.id}") } a.groups.forEach { g -> g.rules.findDuplicatedItem { v -> v.key }?.let { v -> - error("duplicated app rule: key=${v.key}, groupKey=${g.key}, appId=${a.id} ") + error("duplicated app rule: key=${v.key}, groupKey=${g.key}, appId=${a.id}") } } } diff --git a/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt b/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt index a7dd5bc4c..30ef5091b 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/AbExt.kt @@ -170,6 +170,7 @@ val abTransform = Transform( sequence { val stack = getChildren(node).toMutableList() if (stack.isEmpty()) return@sequence + stack.reverse() val tempNodes = mutableListOf() do { val top = stack.removeLast() diff --git a/app/src/main/kotlin/li/songe/gkd/service/AbState.kt b/app/src/main/kotlin/li/songe/gkd/service/AbState.kt index 9aadd45fd..10eda147f 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/AbState.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/AbState.kt @@ -2,6 +2,8 @@ package li.songe.gkd.service import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import li.songe.gkd.appScope import li.songe.gkd.data.AppRule import li.songe.gkd.data.ClickLog @@ -102,25 +104,28 @@ var lastTriggerRule: ResolvedRule? = null var lastTriggerTime = 0L var appChangeTime = 0L +val clickLogMutex = Mutex() fun insertClickLog(rule: ResolvedRule) { appScope.launchTry(Dispatchers.IO) { - val clickLog = ClickLog( - appId = topActivityFlow.value.appId, - activityId = topActivityFlow.value.activityId, - subsId = rule.subsItem.id, - subsVersion = rule.rawSubs.version, - groupKey = rule.group.key, - groupType = when (rule) { - is AppRule -> SubsConfig.AppGroupType - is GlobalRule -> SubsConfig.GlobalGroupType - }, - ruleIndex = rule.index, - ruleKey = rule.key, - ) - DbSet.clickLogDao.insert(clickLog) - increaseClickCount() - if (recordStoreFlow.value.clickCount % 100 == 0) { - DbSet.clickLogDao.deleteKeepLatest() + clickLogMutex.withLock { + increaseClickCount() + val clickLog = ClickLog( + appId = topActivityFlow.value.appId, + activityId = topActivityFlow.value.activityId, + subsId = rule.subsItem.id, + subsVersion = rule.rawSubs.version, + groupKey = rule.group.key, + groupType = when (rule) { + is AppRule -> SubsConfig.AppGroupType + is GlobalRule -> SubsConfig.GlobalGroupType + }, + ruleIndex = rule.index, + ruleKey = rule.key, + ) + DbSet.clickLogDao.insert(clickLog) + if (recordStoreFlow.value.clickCount % 100 == 0) { + DbSet.clickLogDao.deleteKeepLatest() + } } } } diff --git a/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt b/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt index 5318f761e..a79237aef 100644 --- a/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt +++ b/app/src/main/kotlin/li/songe/gkd/service/GkdAbService.kt @@ -5,10 +5,10 @@ import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.IntentFilter -import android.content.pm.PackageManager import android.graphics.Bitmap import android.graphics.PixelFormat import android.os.Build +import android.util.LruCache import android.view.Display import android.view.View import android.view.WindowManager @@ -56,6 +56,7 @@ import li.songe.gkd.util.subsItemsFlow import li.songe.gkd.util.updateStorage import li.songe.gkd.util.updateSubscription import li.songe.selector.Selector +import java.util.concurrent.Executors import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine @@ -75,7 +76,7 @@ class GkdAbService : CompositionAbService({ val shizukuGrantFlow = MutableStateFlow(false) var lastCheckShizukuTime = 0L onAccessibilityEvent { // 借助无障碍轮询校验 shizuku 权限 - if (storeFlow.value.enableService && it.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {// 筛选降低判断频率 + if (storeFlow.value.enableShizuku && it.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {// 筛选降低判断频率 val t = System.currentTimeMillis() if (t - lastCheckShizukuTime > 5000L) { lastCheckShizukuTime = t @@ -93,32 +94,37 @@ class GkdAbService : CompositionAbService({ // 当锁屏/上拉通知栏时, safeActiveWindow 没有 activityId, 但是此时 shizuku 获取到是前台 app 的 appId 和 activityId fun getShizukuTopActivity(): TopActivity? { + if (!storeFlow.value.enableShizuku) return null // 平均耗时 5 ms val top = safeGetTasksFc()?.lastOrNull()?.topActivity ?: return null return TopActivity(appId = top.packageName, activityId = top.className) } + val activityCache = object : LruCache, Boolean>(128) { + override fun create(key: Pair): Boolean { + return kotlin.runCatching { + packageManager.getActivityInfo( + ComponentName( + key.first, key.second + ), 0 + ) + }.getOrNull() != null + } + } + fun isActivity( appId: String, activityId: String, ): Boolean { if (appId == topActivityFlow.value.appId && activityId == topActivityFlow.value.activityId) return true - val r = (try { - packageManager.getActivityInfo( - ComponentName( - appId, activityId - ), 0 - ) - } catch (e: PackageManager.NameNotFoundException) { - null - } != null) - return r + val cacheKey = Pair(appId, activityId) + return activityCache.get(cacheKey) } var lastTriggerShizukuTime = 0L var lastContentEventTime = 0L val queryThread = Dispatchers.IO.limitedParallelism(1) - val eventThread = Dispatchers.IO.limitedParallelism(1) + val eventExecutor = Executors.newSingleThreadExecutor() onDestroy { queryThread.cancel() } @@ -201,8 +207,12 @@ class GkdAbService : CompositionAbService({ val evActivityId = fixedEvent.className - scope.launch(eventThread) { - val eventNode = event.source ?: return@launch + eventExecutor.execute launch@{ + val eventNode = if (event.className == null) { + null // https://github.com/gkd-kit/gkd/issues/426 event.clear 已被系统调用 + } else { + event.source + } val oldAppId = topActivityFlow.value.appId val rightAppId = if (oldAppId == evAppId) { oldAppId @@ -218,7 +228,7 @@ class GkdAbService : CompositionAbService({ ) } } else { - if (fixedEvent.time - lastTriggerShizukuTime > 300) { + if (storeFlow.value.enableShizuku && fixedEvent.time - lastTriggerShizukuTime > 300) { val shizukuTop = getShizukuTopActivity() if (shizukuTop != null && shizukuTop.appId == rightAppId) { if (shizukuTop.activityId == evActivityId) { @@ -297,12 +307,12 @@ class GkdAbService : CompositionAbService({ } var lastUpdateSubsTime = 0L - onAccessibilityEvent { + onAccessibilityEvent {// 借助 无障碍事件 触发自动检测更新 if (it.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {// 筛选降低判断频率 - // 借助 无障碍事件 触发自动检测更新 val i = storeFlow.value.updateSubsInterval + if (i <= 0) return@onAccessibilityEvent val t = System.currentTimeMillis() - if (i > 0 && t - lastUpdateSubsTime > i.coerceAtLeast(60 * 60_000)) { + if (t - lastUpdateSubsTime > i.coerceAtLeast(60 * 60_000)) { lastUpdateSubsTime = t checkSubsUpdate() }