简要介绍
AccessibilityService是安卓平台上提供的无障碍服务,用于帮助残障人士使用手机,不过通过此功能可以完成很多事情.可以进行模拟界面操作,如点击界面上某个按钮等.
使用详细步骤
- 增加service定义
在onAccessibilityEvent函数中进行相应操作(安卓界面有变化时,都会调用此函数)
class MyAccessibilityService : AccessibilityService() {
private val TAG = MyAccessibilityService::class.java.simpleName
private var mContext: Context? = null
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate")
mContext = applicationContext
AccessibilityOperator.getInstance().init(this)
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
return Service.START_STICKY
}
override fun onAccessibilityEvent(event: AccessibilityEvent) {
AccessibilityOperator.getInstance().updateEvent(event)
val packageName = AccessibilityOperator.getInstance().rootNodeInfo?.packageName?.toString()
// pasteToEditTextContent(packageName)
var accessibilityService = AccessibilityOperator.getInstance()
//按下返回键
// accessibilityService.performGlobalAction(GLOBAL_ACTION_BACK)
//向下拉出状态栏
// accessibilityService.performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS)
//向下拉出状态栏并显示出所有的快捷操作按钮
// accessibilityService.performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS)
//按下HOME键
// accessibilityService.performGlobalAction(GLOBAL_ACTION_HOME)
//显示最近任务
// accessibilityService.performGlobalAction(GLOBAL_ACTION_RECENTS)
//长按电源键
// accessibilityService.performGlobalAction(GLOBAL_ACTION_POWER_DIALOG)
//分屏
// accessibilityService.performGlobalAction(GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN)
//锁屏(安卓9.0适用)
// accessibilityService.performGlobalAction(GLOBAL_ACTION_LOCK_SCREEN)
//截屏(安卓9.0适用)
// accessibilityService.performGlobalAction(GLOBAL_ACTION_TAKE_SCREENSHOT)
//打开快速设置
accessibilityService.performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS)
}
/**
* 修改EditText输入框内容。
* 下面样例修改了QQ搜索输入框内容。
*/
private fun changeEditTextContent(packageName: String?) {
getNodeToOperate(packageName)?.let {
val arguments = Bundle()
arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "被无障碍服务修改啦")
it.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments)
}
}
/**
* 读取剪贴板内容,粘贴到EditText输入框。
* 下面样例修改了QQ搜索输入框内容。
*/
private fun pasteToEditTextContent(packageName: String?) {
getNodeToOperate(packageName)?.let {
it.performAction(AccessibilityNodeInfo.FOCUS_INPUT)
it.performAction(AccessibilityNodeInfo.ACTION_PASTE)
it.recycle()
}
}
private fun getNodeToOperate(packageName: String?): AccessibilityNodeInfo? {
if (packageName != null && packageName == "com.tencent.mobileqq") {
val nodes = AccessibilityOperator.getInstance().findNodesById("com.tencent.mobileqq:id/et_search_keyword")
if (nodes != null && nodes.isNotEmpty()) {
return nodes[0]
}
}
return null
}
override fun onInterrupt() {
}
}
- 在Manifest文件中增加service定义
<application>
......
<service
android:name=".MyAccessibilityService"
android:enabled="true"
android:exported="true"
android:label="@string/accessibility_service_name"
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_config" />
</service>
</application>
- AccessibilityOperator工具类
class AccessibilityOperator private constructor() {
private var mAccessibilityEvent: AccessibilityEvent? = null
private var accessibilityService: AccessibilityService? = null
val rootNodeInfo: AccessibilityNodeInfo?
get() {
var nodeInfo: AccessibilityNodeInfo? = null
accessibilityService?.let {
nodeInfo = accessibilityService!!.rootInActiveWindow
}
if (nodeInfo == null && mAccessibilityEvent != null) {
nodeInfo = mAccessibilityEvent!!.source
}
return nodeInfo
}
fun init(service: AccessibilityService) {
accessibilityService = service
}
fun updateEvent(event: AccessibilityEvent) {
mAccessibilityEvent = event
}
/**
* 根据Text搜索所有符合条件的节点, 模糊搜索方式
*/
fun findNodesByText(text: String): List<AccessibilityNodeInfo>? {
val nodeInfo = rootNodeInfo
return nodeInfo?.findAccessibilityNodeInfosByText(text)
}
/**
* 根据View的ID搜索符合条件的节点,精确搜索方式;
* 这个只适用于自己写的界面,因为ID可能重复
*
* @param viewId
*/
fun findNodesById(viewId: String): List<AccessibilityNodeInfo>? {
val nodeInfo = rootNodeInfo
return nodeInfo?.findAccessibilityNodeInfosByViewId(viewId)
}
fun clickByText(text: String): Boolean {
return performClick(findNodesByText(text))
}
fun clickParentByText(text: String, depth: Int): Boolean {
return this.performClick(this.findParentNodesByText(text, depth))
}
fun clickParentById(viewId: String, depth: Int): Boolean {
return this.performClick(this.findParentNodesById(viewId, depth))
}
fun findParentNodesByText(text: String, depth: Int): List<AccessibilityNodeInfo> {
val rootNodeInfo = this.rootNodeInfo
val resultNodeList = mutableListOf<AccessibilityNodeInfo>()
if (rootNodeInfo != null) {
val nodeList = findAccessibilityNodeInfosByText(rootNodeInfo, text)
val iterator = nodeList.iterator()
while (iterator.hasNext()) {
val accessibilityNodeInfo = iterator.next() as AccessibilityNodeInfo
resultNodeList.add(getParentNode(accessibilityNodeInfo, depth))
}
}
return resultNodeList
}
fun findParentNodesById(viewId: String, depth: Int): List<AccessibilityNodeInfo> {
val rootNodeInfo = this.rootNodeInfo
val resultNodeList = mutableListOf<AccessibilityNodeInfo>()
if (rootNodeInfo != null) {
val nodeList = rootNodeInfo.findAccessibilityNodeInfosByViewId(viewId)
val iterator = nodeList.iterator()
while (iterator.hasNext()) {
val accessibilityNodeInfo = iterator.next() as AccessibilityNodeInfo
resultNodeList.add(this.getParentNode(accessibilityNodeInfo, depth))
}
}
return resultNodeList
}
private fun findAccessibilityNodeInfosByText(node: AccessibilityNodeInfo?, text: String?): List<AccessibilityNodeInfo> {
val resultNodeList = mutableListOf<AccessibilityNodeInfo>()
if (node != null && text != null) {
val nodeList = node.findAccessibilityNodeInfosByText(text)
if (nodeList != null && !nodeList.isEmpty()) {
val iterator = nodeList.iterator()
while (iterator.hasNext()) {
val nodeInList = iterator.next() as AccessibilityNodeInfo
if (TextUtils.equals(nodeInList.text, text)) {
resultNodeList.add(nodeInList)
}
}
}
return resultNodeList
} else {
return resultNodeList
}
}
private fun getParentNode(nodeInfo: AccessibilityNodeInfo, depth: Int): AccessibilityNodeInfo {
var resultNodeInfo = nodeInfo
for (i in 0 until depth) {
val parentNode = resultNodeInfo.parent
resultNodeInfo = parentNode
}
return resultNodeInfo
}
/**
* 根据View的ID搜索符合条件的节点,精确搜索方式;
* 这个只适用于自己写的界面,因为ID可能重复
*
* @param viewId
* @return 是否点击成功
*/
fun clickById(viewId: String): Boolean {
return performClick(findNodesById(viewId))
}
private fun performClick(nodeInfoList: List<AccessibilityNodeInfo>?): Boolean {
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
var node: AccessibilityNodeInfo
for (i in nodeInfoList.indices) {
node = nodeInfoList[i]
// 进行模拟点击
if (node.isEnabled) {
return node.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
}
}
return false
}
fun clickBackKey(): Boolean {
return performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK)
}
fun performGlobalAction(action: Int): Boolean {
return accessibilityService!!.performGlobalAction(action)
}
private fun getNodeInfo(nodeInfo: AccessibilityNodeInfo?): String {
var result = ""
if (nodeInfo != null) {
result = nodeInfo.className.toString() + ";text:" + nodeInfo.text + ";id:" + nodeInfo.viewIdResourceName + ";"
}
return result
}
fun clickTextParent(text: String): Boolean {
val nodeInfo = rootNodeInfo
return nodeInfo?.let { clickTextParent(it, text) } ?: false
}
private fun clickTextParent(rootInfo: AccessibilityNodeInfo?, text: String): Boolean {
if (rootInfo != null && !TextUtils.isEmpty(rootInfo.className)) {
if ("android.widget.TextView" == rootInfo.className.toString()) {
if (!TextUtils.isEmpty(rootInfo.text) && rootInfo.text.toString().startsWith(text)) {
val result = performClick(rootInfo.parent)
Log.v(TAG, rootInfo.parent.className.toString() + ":result=" + result)
return result
}
}
for (i in 0 until rootInfo.childCount) {
val result = clickTextParent(rootInfo.getChild(i), text)
if (result) {
return result
}
}
return false
}
return false
}
private fun performClick(targetInfo: AccessibilityNodeInfo): Boolean {
return targetInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)
}
companion object {
private val TAG = "AccessibilityOperator"
val instance = AccessibilityOperator()
}
}
Demo源代码
https://gitee.com/cxyzy1/accessibilityDemo
安卓开发技术分享: https://www.jianshu.com/p/442339952f26
更多技术总结好文,请关注:「程序园中猿」
网友评论