前言
此篇博客主要记录如何开启无障碍服务与功能使用。google的设计这个功能是用来帮助残障人士使用设备。 也能帮助我们开发者进行各种各样的全局事件监听(按键、触控手势、UI变化)这样可以免于修改framework插入事件监听。当然启动条件比较苛刻,需要用户手动打开,所以在正常的应用上应该用不上此功能。但是系统级别的应用上我们可以通过反射直接开启。 还有一些人还会使用此服务进行自动抢微信红包的无语行为。个人是测试转开发,我体验后无障碍服务更像是自动化uiautomator2测试的里的翻版。
添加无障碍服务
第一步 创建AccessibilityService服务类
import android.accessibilityservice.AccessibilityService import android.content.Intent import android.content.ServiceConnection import android.util.Log import android.view.KeyEvent import android.view.accessibility.AccessibilityEvent class AccessibilityService : AccessibilityService() { override fun bindService(service: Intent?, conn: ServiceConnection, flags: Int): Boolean { return super.bindService(service, conn, flags) } override fun onCreate() { super.onCreate() } override fun onDestroy() { super.onDestroy() } /** * 无障碍服务的生命周期,表明服务已经连接成功 */ override fun onServiceConnected() { super.onServiceConnected() } /** *当系统想要中断您的服务正在提供的反馈(通常是为了响应将焦点移到其他 *控件等用户操作)时,就会调用此方法。此方法可能会在您的服务的整个生命 *周期内被调用多次。 */ override fun onInterrupt() { } /** * 当用户在触摸屏上执行特定手势时由系统调用。注意:为了接收手势, * 辅助服务必须通过设置AccessibilityServiceInfo请求设备处于触摸探索模式FLAG_REQUEST_TOUCH_EXPLORATION_MOD */ override fun onGesture(gestureId: Int): Boolean { Log.e("zh", "onGesture: ${gestureId}") return super.onGesture(gestureId) } /** *当系统检测到与无障碍服务指定的事件过滤参数匹配的 AccessibilityEvent *时,就会回调此方法。例如,当用户点击按钮,或者聚焦于某个应用(无障碍 *服务正在为该应用提供反馈)中的界面控件时。出现这种情况时,系统会调用 *此方法,并传递关联的 AccessibilityEvent,然后服务会对该类进行解释并 *使用它来向用户提供反馈。此方法可能会在您的服务的整个生命周期内被调用多次。 */ override fun onAccessibilityEvent(event: AccessibilityEvent) { Log.e("zh", "无障碍服务 onAccessibilityEvent:${event}") when(event.eventType){ AccessibilityEvent.TYPE_ANNOUNCEMENT-> Log.e("zh", "应用程序发布公告的事件") AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED -> Log.e("zh", "View的焦点") AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED -> Log.e("zh", "View的焦点清除") AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED -> Log.e("zh", "通知栏状态更新") AccessibilityEvent.TYPE_VIEW_HOVER_ENTER -> Log.e("zh", "View的鼠标悬停选中") AccessibilityEvent.TYPE_VIEW_HOVER_EXIT -> Log.e("zh", "View的鼠标悬停离开") AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START -> Log.e("zh", "开始触摸探索手势的事件") AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END -> Log.e("zh", "结束触摸探索手势的事件") AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED -> Log.e("zh", "窗口内容更新") AccessibilityEvent.TYPE_VIEW_SCROLLED -> Log.e("zh", "滚动类View") AccessibilityEvent.TYPE_VIEW_SELECTED -> Log.e("zh", "表示通常在android.widget.AdapterView的上下文中选择项的事件") AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED -> Log.e("zh", "EditText视图选中内容改变") AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED -> Log.e("zh", "EditText视图内容改变") AccessibilityEvent.TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY -> Log.e("zh", "表示以给定的移动粒度遍历视图文本的事件") AccessibilityEvent.TYPE_VIEW_CLICKED -> Log.e("zh", "点击事件") AccessibilityEvent.TYPE_VIEW_LONG_CLICKED -> Log.e("zh", "长按点击事件") AccessibilityEvent.TYPE_VIEW_CONTEXT_CLICKED -> Log.e("zh", "表示在android.view.View上的上下文单击事件") AccessibilityEvent.TYPE_GESTURE_DETECTION_START -> Log.e("zh", "开始手势检测") AccessibilityEvent.TYPE_GESTURE_DETECTION_END -> Log.e("zh", "结束手势检测") AccessibilityEvent.TYPE_TOUCH_INTERACTION_START -> Log.e("zh", "表示用户开始触摸屏幕的事件") AccessibilityEvent.TYPE_TOUCH_INTERACTION_END -> Log.e("zh", "表示用户结束触摸屏幕的事件") AccessibilityEvent.TYPE_ASSIST_READING_CONTEXT -> Log.e("zh", "表示助手当前正在读取用户屏幕上下文的事件。") } } /** * 按键事件 */ override fun onKeyEvent(event: KeyEvent): Boolean { Log.e("zh", "onKeyEvent: ${event}") return super.onKeyEvent(event) } }
第二步 在xml资源目录下添加配置xml
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android" android:description="@string/accessibility_service_name" android:packageNames="com.zh.XXX,com.android.systemui" android:accessibilityEventTypes="typeAllMask" android:accessibilityFeedbackType="feedbackGeneric" android:accessibilityFlags="flagDefault|flagRetrieveInteractiveWindows|flagIncludeNotImportantViews" android:notificationTimeout="100" android:canPerformGestures="true" android:canRetrieveWindowContent="true" />
android:description 此属性是在无障碍服务启用页面的描述
android:packageNames 此属性代表你需要那些应用支持无障碍服务,如果什么都不填删除此属性则代表你想监听设备的全部应用
android:accessibilityEventTypes 事件类型AccessibilityService服务响应的事件类型,只有声明了的类型,系统才会调用该服务的onAccessibilityEvent,有以下几个事件类型提供选择:
typeViewClicked 点击事件 | typeViewSelected View被选择 | typeViewScrolled 滑动事件 | typeWindowContentChanged 窗口内容该表 | typeAllMask 所有事件
android:accessibilityFeedbackType 反馈类型
feedbackSpoken 语音反馈 | feedbackHaptic 触觉(震动)反馈 | feedbackAudible 音频反馈 | feedbackVisual 视频反馈 | feedbackGeneric 通用反馈 | feedbackAllMask 以上都具有
android:accessibilityFlags 额外声明flagDefault 默认
flagIncludeNotImportantViews
flagRequestTouchExplorationMode 允许获得触控信息,另外你还需要将android:canRequestTouchExplorationMode 属性设置为true。 请注意!此属性有一定的危险,添加此属性后有可能导致触控失效(触发条件可能是需要插入鼠标或者其他外置设备)
flagRequestEnhancedWebAccessibility 允许获取Web地址信息,另外你还需要将 android:canRequestEnhancedWebAccessibility 属性设置为true
flagReportViewIds 允许获得view id,需要获取viewid的时候需要该参数,开始没声明导致nodeInfo. getViewIdResourceName()返回的为null
flagRequestFilterKeyEvents 此事件添加后才能在服务的onKeyEvent方法里输出当前按键键值,另外你还需要将 android:canRequestFilterKeyEvents 属性设置为true
flagRetrieveInteractiveWindows 允许获得windows,使用getWindows时需要该参数,否则会返回空列表
android:canRetrieveWindowContent 设置为“true”表示允许获取屏幕信息,使用getWindows、getRootInActiveWindow等函数时需要为“true”
android:canRequestTouchExplorationMode 设置为“true”表示允许获取触摸信息
android:canRequestEnhancedWebAccessibility 设置为“true”表示允许获取Web地址访问信息
android:canRequestFilterKeyEvents 设置为“true”表示允许获取按键信息
android:canRequestFingerprintGestures 设置为“true”表示允许获取手势信息
android:canControlMagnification 设置为“true”表示允许获取缩放信息android:notificationTimeout 同一种事件类型触发的最短时间间隔(毫秒)
第三步 在AndroidManifest.xml里注册服务
注意在android:resource 属性里添加了上面的配置xml
<application> <service android:name=".ScreenSaverAccessibilityService" android:enabled="true" 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> </application>
第四步 启动服务
如果你不是系统级别应用,你需要手动去设置-无障碍中启动服务,如下图
如果你是系统级别应用,这可以使用下面的工具类,实现自动开启无障碍服务:
import android.accessibilityservice.AccessibilityServiceInfo; import android.content.ComponentName; import android.content.Context; import android.provider.Settings; import android.util.Log; import android.view.accessibility.AccessibilityManager; import java.util.List; public class AccessibilityUtil { /** * 关闭无障碍服务 * @param context */ public static void autoCloseAccessibilityService(Context context){ if (isStartAccessibilityServiceEnable(context)) { String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); ComponentName selfComponentName = new ComponentName(context.getPackageName(), ScreenSaverAccessibilityService.class.getCanonicalName()); String flattenToString = selfComponentName.flattenToString(); enabledServicesSetting=enabledServicesSetting.replace(":"+flattenToString , ""); Settings.Secure.putString(context.getContentResolver(),Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,enabledServicesSetting); //Settings.Secure.putInt(context.getContentResolver(),Settings.Secure.ACCESSIBILITY_ENABLED, 0); Log.d("zh", "autoCloseAccessibilityService: SETTING ACCESSIBILITY SUCCESS!"); } return; } /** * 开启无障碍服务 * @param context */ public static void autoOpenAccessibilityService(Context context){ if (!isStartAccessibilityServiceEnable(context)) { String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); ComponentName selfComponentName = new ComponentName(context.getPackageName(), ScreenSaverAccessibilityService.class.getCanonicalName()); String flattenToString = selfComponentName.flattenToString(); if (enabledServicesSetting==null|| !enabledServicesSetting.contains(flattenToString)) { enabledServicesSetting += ":"+flattenToString; } Settings.Secure.putString(context.getContentResolver(),Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,enabledServicesSetting); Settings.Secure.putInt(context.getContentResolver(),Settings.Secure.ACCESSIBILITY_ENABLED, 1); Log.d("zh", "autoOpenAccessibilityService: SETTING ACCESSIBILITY SUCCESS!"); } return; } /** * 判断无障碍服务是否开启 * * @param context * @return */ public static boolean isStartAccessibilityServiceEnable(Context context) { AccessibilityManager accessibilityManager = (AccessibilityManager)context.getSystemService(Context.ACCESSIBILITY_SERVICE); assert accessibilityManager != null; List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_ALL_MASK); for (AccessibilityServiceInfo info : accessibilityServices) { if (info.getId().contains(context.getPackageName())) { return true; } } return false; } }
第五步 如果无障碍服务无法连接或者创建
这可能是google的一些设计,可能是不允许debug安装或者内置系统应用,直接开启无障碍。 你需要重启一下设备就能恢复正常。
模拟操作
首先在配置xml里一定要添加,否则会出现调用getRootInActiveWindow()始终返回为null的问题
android:canRetrieveWindowContent="true"
单击操作
这里举例一个单击功能,其他操作其实都是一样可以举一反三的。如果你写过uiautomator2自动化简直是信手拈来。
以文本内容查找View
/** * 根据文本查找点击设置 */ fun byTextClickSettings(){ val nodeInfoList = rootInActiveWindow.findAccessibilityNodeInfosByText("设置") //点击 if (nodeInfoList.isNotEmpty()){ nodeInfoList[0].performAction(AccessibilityNodeInfo.ACTION_CLICK) } }
以Id查询View
首先需要知道View的id,路径如下,点击monitor.bat:
代码:
/** * 根据id查找点击设置 */ fun byIdClickSettings(){ val nodeInfoList = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.xxx.xxx:id/settings") //点击 if (nodeInfoList.isNotEmpty()){ nodeInfoList[0].performAction(AccessibilityNodeInfo.ACTION_CLICK) } }
焦点操作
选中焦点
public static final int ACTION_FOCUS = 0x00000001;
清除焦点
public static final int ACTION_CLEAR_FOCUS = 0x00000002;
选中操作
选中
public static final int ACTION_SELECT = 0x00000004;
清除选中
public static final int ACTION_CLEAR_SELECTION = 0x00000008;
多选
public static final int ACTION_SET_SELECTION = 0x00020000;
Bundle arguments = new Bundle(); arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_START_INT, 1); arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_SELECTION_END_INT, 2); info.performAction(AccessibilityNodeInfo.ACTION_SET_SELECTION, arguments);
滚动操作
/** * 操作向前滚动节点内容。 */ public static final int ACTION_SCROLL_FORWARD = 0x00001000; /** * 操作向后滚动节点内容。 */ public static final int ACTION_SCROLL_BACKWARD = 0x00002000;
展开/收起操作
/** * 展开可展开节点的操作。 */ public static final int ACTION_EXPAND = 0x00040000; /** * 折叠可展开节点的操作。 */ public static final int ACTION_COLLAPSE = 0x00080000;
撤销操作
/** * 撤销可撤销节点的操作。 */ public static final int ACTION_DISMISS = 0x00100000;
进度条操作
/** * Argument for specifying the progress value to set. * <p> * <strong>Type:</strong> float<br> * <strong>Actions:</strong> * <ul> * <li>{@link AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_PROGRESS}</li> * </ul> * * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_SET_PROGRESS */ public static final String ACTION_ARGUMENT_PROGRESS_VALUE = "android.view.accessibility.action.ARGUMENT_PROGRESS_VALUE";
移动View操作
/** * Argument for specifying the x coordinate to which to move a window. * <p> * <strong>Type:</strong> int<br> * <strong>Actions:</strong> * <ul> * <li>{@link AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}</li> * </ul> * * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW */ public static final String ACTION_ARGUMENT_MOVE_WINDOW_X = "ACTION_ARGUMENT_MOVE_WINDOW_X"; /** * Argument for specifying the y coordinate to which to move a window. * <p> * <strong>Type:</strong> int<br> * <strong>Actions:</strong> * <ul> * <li>{@link AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW}</li> * </ul> * * @see AccessibilityNodeInfo.AccessibilityAction#ACTION_MOVE_WINDOW */ public static final String ACTION_ARGUMENT_MOVE_WINDOW_Y = "ACTION_ARGUMENT_MOVE_WINDOW_Y";
复制黏贴操作
/** * 操作将当前选定内容复制到剪贴板。 */ public static final int ACTION_COPY = 0x00004000; /** * 操作粘贴当前剪贴板内容。 */ public static final int ACTION_PASTE = 0x00008000; /** * 操作以剪切当前选定内容并将其放置到剪贴板。 */ public static final int ACTION_CUT = 0x00010000;
文本操作
添加文本
/** * 添加文本内容,如果传入是是null这可以视为清空文本。并且光标会跳转到末尾 * <p> * <strong>Arguments:</strong> * {@link #ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE}<br> * <strong>Example:</strong> * <code><pre><p> * Bundle arguments = new Bundle(); * arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, * "android"); * info.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments); * </code></pre></p> */ public static final int ACTION_SET_TEXT = 0x00200000;
向前选中文本位置
/** 请求以给定的移动粒度转到此节点文本中的前一个实体的操作。例如,移动到下一个字符、单词等。 * <p> * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<, * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br> * <strong>Example:</strong> Move to the next character and do not extend selection. * <code><pre><p> * Bundle arguments = new Bundle(); * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER); * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, * false); * info.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, * arguments); * </code></pre></p> * </p> * * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN * * @see #setMovementGranularities(int) * @see #getMovementGranularities() * * @see #MOVEMENT_GRANULARITY_CHARACTER * @see #MOVEMENT_GRANULARITY_WORD * @see #MOVEMENT_GRANULARITY_LINE * @see #MOVEMENT_GRANULARITY_PARAGRAPH * @see #MOVEMENT_GRANULARITY_PAGE */ public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 0x00000200;
向后选中文本位置
/** * 请求以给定的移动粒度转到此节点文本中的下一个实体的操作。例如,移动到下一个字符、单词等。 * <p> * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT}<, * {@link #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN}<br> * <strong>Example:</strong> Move to the previous character and do not extend selection. * <code><pre><p> * Bundle arguments = new Bundle(); * arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT, * AccessibilityNodeInfo.MOVEMENT_GRANULARITY_CHARACTER); * arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN, * false); * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_AT_MOVEMENT_GRANULARITY, arguments); * </code></pre></p> * </p> * * @see #ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT * @see #ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN * * @see #setMovementGranularities(int) * @see #getMovementGranularities() * * @see #MOVEMENT_GRANULARITY_CHARACTER * @see #MOVEMENT_GRANULARITY_WORD * @see #MOVEMENT_GRANULARITY_LINE * @see #MOVEMENT_GRANULARITY_PARAGRAPH * @see #MOVEMENT_GRANULARITY_PAGE */ public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 0x00000100;
HTML操作
移动HTML元素
/** * 动作移动到给定类型的下一个HTML元素。例如,移动到按钮,输入,表等。 * <p> * <strong>Arguments:</strong> {@link #ACTION_ARGUMENT_HTML_ELEMENT_STRING}<br> * <strong>Example:</strong> * <code><pre><p> * Bundle arguments = new Bundle(); * arguments.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_HTML_ELEMENT_STRING, "BUTTON"); * info.performAction(AccessibilityNodeInfo.ACTION_NEXT_HTML_ELEMENT, arguments); * </code></pre></p> * </p> */ public static final int ACTION_NEXT_HTML_ELEMENT = 0x00000400;
End
本文来自博客园,作者:观心静 ,转载请注明原文链接:https://www.cnblogs.com/guanxinjing/p/17031369.html