AccessibilityService使用入门

AccessibilityService设计初衷在于帮助残障用户使用android设备和应用,在后台运行,可以监听用户界面的一些状态转换,例如页面切换、焦点改变、通知、Toast等,并在触发AccessibilityEvents时由系统接收回调。后来被开发者另辟蹊径,用于一些插件开发,比如微信红包助手,还有一些需要监听第三方应用的插件。

最好的资料:官方文档

生命周期

AccessbilityService的生命周期由系统专门管理,并遵循Service的基本生命周期,它只能由用户自己在设置中手动启动,系统绑定到服务后,会调用它的onServiceConnected()方法。
当用户手动在设置中关闭服务,或者开发者调用disableSelf()方法时,该服务会被关闭销毁。

基本配置

  1. 继承AccessbilityService
class AliAccessibilityService : AccessibilityService() {
    private val TAG = "AliAccessibilityService"

    //服务中断时的回调
    override fun onInterrupt() {
        Log.d(TAG, "onInterrupt")
    }

    //接收到系统发送AccessibilityEvent时的回调
    override fun onAccessibilityEvent(event: AccessibilityEvent) {
        Log.d(TAG, "onAccessibilityEvent:event:$event")
    }
}
  1. AccessbilityService本质上还是一个Service,所以要在AndroidManifest中注册该服务
        
            
                
            
        

android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"是为了确保只有系统可以绑定该服务。

  1. 配置你需要监听的事件类型、要监听哪个程序,最小监听间隔等属性。这里有两种方式可以进行配置,一种是在manifest中通过meta-data配置,一种是在代码中通过setServiceInfo(AccessibilityServiceInfo)设置。
    方式一:通过meta-data设置
        
            
                
            

            
        

accessible_service_config_ali.xml



配置的具体含义我们下面再讲。
方式二:在代码中通过setServiceInfo设置

    override fun onServiceConnected() {
        val serviceInfo = AccessibilityServiceInfo().apply {
            eventTypes = AccessibilityEvent.TYPES_ALL_MASK
            feedbackType = AccessibilityServiceInfo.FEEDBACK_ALL_MASK
            packageNames = arrayOf("com.eg.android.AlipayGphone")//支付宝包名,可以多个
            notificationTimeout = 10
        }
        setServiceInfo(serviceInfo)
    }

这里我建议使用meta-data的方式进行配置,因为我实践过程中发现有的属性不能通过代码配置。

  1. 指引用户去手动打开该服务
    首先判断该服务是否为开启状态:
    public static boolean isAccessibilitySettingsOn(Context mContext, Class clazz) {
        int accessibilityEnabled = 0;
        final String service = mContext.getPackageName() + "/" + clazz.getCanonicalName();
        try {
            accessibilityEnabled = Settings.Secure.getInt(mContext.getApplicationContext().getContentResolver(),
                    Settings.Secure.ACCESSIBILITY_ENABLED);
        } catch (Settings.SettingNotFoundException e) {
            e.printStackTrace();
        }
        TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
        if (accessibilityEnabled == 1) {
            String settingValue = Settings.Secure.getString(mContext.getApplicationContext().getContentResolver(),
                    Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
            if (settingValue != null) {
                mStringColonSplitter.setString(settingValue);
                while (mStringColonSplitter.hasNext()) {
                    String accessibilityService = mStringColonSplitter.next();
                    if (accessibilityService.equalsIgnoreCase(service)) {
                        return true;
                    }
                }
            }
        }
        return false;
    }

没有开启的话则跳转到该服务的开启页面,由用户手动开启

startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))

到这里,我们的准备工作就已经完成了,打开支付宝,我们可以看到这样一条日志:

onAccessibilityEvent:event:EventType: TYPE_WINDOW_STATE_CHANGED; EventTime: 639278268; PackageName: com.eg.android.AlipayGphone; MovementGranularity: 0; Action: 0 [ ClassName: com.eg.android.AlipayGphone.AlipayLogin; Text: [支付宝]; ContentDescription: null; ItemCount: -1; CurrentItemIndex: -1; IsEnabled: true; IsPassword: false; IsChecked: false; IsFullScreen: true; Scrollable: false; BeforeText: null; FromIndex: -1; ToIndex: -1; ScrollX: -1; ScrollY: -1; MaxScrollX: -1; MaxScrollY: -1; AddedCount: -1; RemovedCount: -1; ParcelableData: null ]; recordCount: 0

其中包括了这个event的类别,当前页面的包名、类名、一些文本信息等,具体有哪些属性可以查看AccessibilityEvent的toString方法。

检索窗口内容

除了监听界面变化之外,我们还可以对界面中的内容进行一些操作,前提是我们配置了以下属性,允许服务检索窗口内容:

android:canRetrieveWindowContent="true"

我们可以通过getWindows获取屏幕上的可以看见的窗口,它返回一个列表,按降序的方式,排在第一个的就是最顶层的窗口。
当然了,一般情况下我们关心的只是最新的那个活动窗口,称为当前活动窗口。我们可以通过以下方法来检索其中的控件:

val root = rootInActiveWindow //获取当前活动窗口的根节点
val node = root.findAccessibilityNodeInfosByViewId("com.alipay.mobile.payee:id/payee_NextBtn") //通过控件id来获取某个控件
val node = root.findAccessibilityNodeInfosByText("确定") //通过text来获取某个控件
val node = root.findFoucs(int falg) //寻找拥有特殊焦点的控件(FOCUS_INPUT 或 FOCUS_ACCESSIBILITY)

至于viewId的获取,我们可以通过android Device Monitor工具来查看,对于3.0之后的android studio,可以通过命令行工具进入sdk的tools目录,运行下面命令:

monitor

交互

事件交互

拿到AccessibilityNodeInfo对象后,我们可以进行一些列的操作,包括getChild()、getParent()、getBoundsInScreen()、isClickable等等一些列获取属性的操作,当然也可以进行交互性的操作,比如点击(当然前提是这个控件的clickable为true):

node[0].performAction(AccessibilityNodeInfo.ACTION_CLICK)

除了操作界面内控件之外,我们还可以通过performGlobalAction(int action)执行一些全局操作,比如点击back键、home键等等。

performGlobalAction(GLOBAL_ACTION_BACK)
performGlobalAction(GLOBAL_ACTION_HOME)
performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS)
performGlobalAction(GLOBAL_ACTION_RECENTS)

手势交互

除了Action交互之外,我们还可以模拟人的手势进行操作,这是在Android 24中新加的一个api:

dispatchGesture(gesture, callback, handler)

它接收一个GestureDescription(手势描述)、一个GestureResultCallback(结果回调)和一个Handler。简单封装一下大概是这样的:

/**
 * 通过AccessibilityService在屏幕上模拟手势
 * @param path 手势路径
 */
@RequiresApi(Build.VERSION_CODES.N)
fun AccessibilityService.gestureOnScreen(
        path: Path,
        startTime:Long = 0,
        duration:Long = 100,
        callback:AccessibilityService.GestureResultCallback,
        handler: Handler? = null
){
    val builder = GestureDescription.Builder()
    builder.addStroke(GestureDescription.StrokeDescription(path, startTime, duration))
    val gesture = builder.build()
    dispatchGesture(gesture, callback, handler)
}

/**
 * 通过AccessibilityService在屏幕上某个位置单击
 */
@RequiresApi(Build.VERSION_CODES.N)
fun AccessibilityService.clickOnScreen(
        x:Float,
        y:Float,
        callback:AccessibilityService.GestureResultCallback,
        handler: Handler? = null
){
    val p = Path()
    p.moveTo(x,y)
    gestureOnScreen(p,callback = callback,handler = handler)
}

到这里,AccessibilityService的基本使用方法已经介绍的差不多了,接下来就是根据自己的项目需求进行组装,八仙过海各显神通了。

附录(重要属性介绍)

xml属性 说明 类别
accessibilityEventTypes 指定要监听的事件类型 typeAllMask:接收所有事件。
-------窗口事件相关(常用)---------
typeWindowStateChanged:监听窗口状态变化,比如打开一个popupWindow,dialog,Activity切换等等。
typeWindowContentChanged:监听窗口内容改变,比如根布局子view的变化。
typeWindowsChanged:监听屏幕上显示的系统窗口中的事件更改。 此事件类型只应由系统分派。
typeNotificationStateChanged:监听通知变化,比如notifacation和toast。
-----------View事件相关--------------
typeViewClicked:监听view点击事件。
typeViewLongClicked:监听view长按事件。
typeViewFocused:监听view焦点事件。
typeViewSelected:监听AdapterView中的上下文选择事件。
typeViewTextChanged:监听EditText的文本改变事件。
typeViewHoverEnter、typeViewHoverExit:监听view的视图悬停进入和退出事件。
typeViewScrolled:监听view滚动,此类事件通常不直接发送。
typeViewTextSelectionChanged:监听EditText选择改变事件。
typeViewAccessibilityFocused:监听view获得可访问性焦点事件。
typeViewAccessibilityFocusCleared:监听view清除可访问性焦点事件。
------------手势事件相关---------------
typeGestureDetectionStart、typeGestureDetectionEnd:监听手势开始和结束事件。
typeTouchInteractionStart、typeTouchInteractionEnd:监听用户触摸屏幕事件的开始和结束。
typeTouchExplorationGestureStart、typeTouchExplorationGestureEnd:监听触摸探索手势的开始和结束。
accessibilityFeedbackType 指定反馈方式 feedbackAllMask、feedbackGeneric、feedbackAudible、feedbackSpoken、feedbackHaptic、feedbackVisual
canRetrieveWindowContent 是否希望能够检索活动窗口内容。此设置无法在运行时更改。 true or false
description 该服务的简要说明 @StringRes
notificationTimeout 两个相同类型的可访问性事件之间的最短间隔时间(以毫秒为单位) number
packageNames 要监听的应用的包名 string
canPerformGestures 是否可以执行手势(api 24新增) true or false

你可能感兴趣的:(AccessibilityService使用入门)