背景
在接手一个新项目时,都会先把 App 相关的页面都点一点,如果对某个页面感兴趣就会到工程中找到对应的 Activity 类看看具体的实现方式,这个时候就会有个问题,这个页面叫啥了,如果之前的开发者在命名时比较规范,还可以通过名称来判断,那如果不规范,就得花点时间去慢慢找了。
基于这个问题,目前主要有以下方式来解决:
- Logcat;
- adb 命令;
- 基于 AccessibilityService 实现;
- 基于 UsageStatsManager 实现;
一、Logcat
在每个 Activity 或者 BaseActivity 中添加日志,输出当前 Activity 的类名,然后在跳转页面时,在 Logcat 中查看对应的日志。
优点:
缺点:
- 如果没有统一的 BaseActivity,则需要到每个 Activity 中都添加日志,比较繁琐;
- 查看日志需要先连接设备再过滤日志,不是特别方便和直观;
- 只能查看当前应用的页面;
二、adb 命令
在 Terminal 中输入如下命令:
adb -d shell dumpsys activity activities | grep mResumedActivity
|
然后会输出如下的信息:
优点:
缺点:
- 需要连接设备;
- 每次切换页面都需重新执行 adb 命令,不方便;
三、基于 AccessibilityService 实现
AccessibilityService 即无障碍服务,是一种应用,可提供界面增强功能,来协助残障用户或可能暂时无法与设备进行全面互动的用户完成操作。这里就主要介绍下通过 AccessibilityService 来获取当前栈顶 Activity 的名称和包名。
实现步骤:
- 创建一个 Service 继承自 AccessibilityService(WindowStateChangeService),并实现 onServiceConnected() 和 onAccessibilityEvent(event: AccessibilityEvent) 两个方法;
- 在 onAccessibilityEvent 方法中监听 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED 事件,获取对应的页面信息;
- 在 AndroidManifest 文件中配置无障碍服务;
核心代码:
private fun isAccessibilityEnabled(): Boolean { val am = this.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager return am.isEnabled.also { Log.d("MainActivity", "AccessibilityService服务是否已经启动:$it") } }
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
|
override fun onAccessibilityEvent(event: AccessibilityEvent) { Log.d("AccessibilityService", "onAccessibilityEvent, ${event.eventType}") if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { if (event.packageName != null && event.className != null) { val packageName: String = event.packageName.toString() val clsName: String = event.className.toString() Log.d("AccessibilityService", "packageName: $packageName, clsName: $clsName") } } }
|
// AndroidManifest 中无障碍服务配置 <service android:name=".WindowStateChangeService" android:exported="true" android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"> <intent-filter> <action android:name="android.accessibilityservice.AccessibilityService" /> </intent-filter> <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibility" /> </service>
|
// @xml/accessibility <accessibility-service xmlns:tools="http://schemas.android.com/tools" android:accessibilityEventTypes="typeWindowStateChanged" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagIncludeNotImportantViews" android:canRetrieveWindowContent="true" android:description="@string/app_name" xmlns:android="http://schemas.android.com/apk/res/android" tools:ignore="UnusedAttribute"/>
|
优点:
- 安装 APP 后,无需连接设备也可实时查看页面信息;
- 可以查看设备所有应用的页面信息;
缺点:
- 实现方式复杂;
- 每次重启 APP 后,都需要重新开启无障碍服务;
- 如果是从最近打开应用里面恢复应用后,此时显示的当前页面名称有错
从最近打开应用恢复之前的应用时,AccessibilityService 会触发三次,前两次是当前的页面,是正确的,但最后还回调了一次最近打开应用页面,就导致显示错误了。
四、基于 UsageStatsManager 实现
UsageStatsManager 是用来统计 APP 使用情况的类,用于获取包含特定时间范围的应用包的使用情况统计信息。本文主要就介绍下如何通过 UsageStatsManager 来获取当前的栈顶 Activity 名称和包名。
实现步骤:
- 申请应用数据权限;
- 创建一个 Service(WatchingService),待 service 启动后开启一个计时器,通过 UsageStatsManager 去轮询最近一段的使用情况(UsageEvents);
- 过滤 UsageEvents 的 eventType 等于 UsageEvents.Event.ACTIVITY_RESUMED 的事件信息,获取到当前页面的包名和类名;
核心代码:
startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS)
private fun isUsageStatsPermissionEnabled(): Boolean { getSystemService(Context.APP_OPS_SERVICE)?.let { val appOps = it as AppOpsManager val mode = appOps.checkOpNoThrow("android:get_usage_stats", myUid(), packageName) return mode == AppOpsManager.MODE_ALLOWED } return false }
|
// AndroidManifest 申请应用数据使用权限 <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" tools:ignore="ProtectedPermissions" />
|
internal inner class RefreshTask : TimerTask() { override fun run() { val name = getCurrentActivityName() Log.d("WatchingService", "top running app is : $name") } }
private fun getCurrentActivityName(): String { var topActivityName = "" if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { val usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager val now = System.currentTimeMillis() val events = usageStatsManager.queryEvents(now - 600000, now) while (events.hasNextEvent()) { val event = UsageEvents.Event() events.getNextEvent(event) if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED) { topActivityName = "${event.packageName}\n${event.className}" } } } else { val forGroundActivity = mActivityManager.getRunningTasks(1) topActivityName = forGroundActivity[0].topActivity!!.packageName + "\n" + forGroundActivity[0].topActivity!!.className } return topActivityName }
|
优点:
- 安装 APP 后,无需连接设备也可实时查看页面信息;
- 可以查看设备所有应用的页面信息;
- 开启应用数据权限后,后续打开无需再重新申请权限;
- 从最近使用应用页面重新恢复 APP 能正确显示页面名称和包名,准确性比 AccessibilityService 高;
缺点:
- 实现方式复杂;
- 因为是轮询,所以在切换页面时,在更新当前页面的名称时会有一点延迟,实效性没有 AccessibilityService 好;
五、总结
这里对四种获取当前页面名称的方法进行个总结,如下表格:
方式 |
实现难度 |
查看范围 |
准确性 |
实效性 |
是否需要一直连接设备 |
Logcat |
易 |
当前 APP |
高 |
高 |
是 |
adb 命令 |
易 |
设备所有 APP |
高 |
高 |
是 |
AccessibilityService |
难 |
设备所有 APP |
较高 |
高 |
安装 APP 的时候需要,后续不需要 |
UsageStatsManager |
难 |
设备所有 APP |
高 |
较高 |
安装 APP 的时候需要,后续不需要 |
针对 AccessibilityService 和 UsageStatsManager 的完整使用,可以参考 demo 工程:CurrentActivity
SSH:@github.com:TowerYe/CurrentActivity.git HTTPS:https://github.com/TowerYe/CurrentActivity.git
|