Android Accessibility使用及事件流程简介

Accessibility是Android从API 4开始提供的一个功能,它主要目的是帮助一些因为有视觉,听觉,身体障碍而无法完全使用触摸屏或铃声等的用户来使用Android的。而实际上现在很多开发者都用它来实现一些其他功能了,比如说微信抢红包,自动安装APK,强制停止应用等。下面来简单介绍一下它的相关使用及原理。

AccessibilityService

它最主要的接口是类AccessibilityService。AccessibilityService是Service的子类,我们可以继承这个类并实现它的抽象方法来监视一个应用的界面元素状态的变化,比如focus变化,一个按钮被click等等。当有这些变化的时候,系统会将这些信息封装在AccessibilityEvent里面,回调AccessibilityService的onAccessibilityEvent(AccessibilityEvent)方法。我们可以实现onAccessibilityEvent来处理这些AccessibilityEvent。下面看一步一步地使用示例:

实现AccessibilityService

这里使用ApiDemo当中将文字转换为语音的例子来介绍,这段代码在/samples//ApiDemos/src/com/example/android/apis/accessibility/TaskBackService,如何使用ApiDemo可以参考Samples。下面是简单介绍里面的核心代码:





/**

 * This class demonstrates how an accessibility service can query

 * window content to improve the feedback given to the user.

 */

public class TaskBackService extends AccessibilityService implements OnInitListener {



    /** Tag for logging. */

    private static final String LOG_TAG = "TaskBackService/onAccessibilityEvent";



    /** Comma separator. */

    private static final String SEPARATOR = ", ";



    /** The class name of TaskListView - for simplicity we speak only its items. */

    private static final String TASK_LIST_VIEW_CLASS_NAME =

        "com.example.android.apis.accessibility.TaskListView";



    /** Flag whether Text-To-Speech is initialized. */

    private boolean mTextToSpeechInitialized;



    /** Handle to the Text-To-Speech engine. */

    private TextToSpeech mTts;



    @Override

    public void onServiceConnected() {

        // Initializes the Text-To-Speech engine as soon as the service is connected.

        mTts = new TextToSpeech(getApplicationContext(), this);

    }



    /**

     * Processes an AccessibilityEvent, by traversing the View's tree and

     * putting together a message to speak to the user.

     */

    @Override

    public void onAccessibilityEvent(AccessibilityEvent event) {

        if (!mTextToSpeechInitialized) {

            Log.e(LOG_TAG, "Text-To-Speech engine not ready.  Bailing out.");

            return;

        }



        // This AccessibilityNodeInfo represents the view that fired the

        // AccessibilityEvent. The following code will use it to traverse the

        // view hierarchy, using this node as a starting point.

        //

        // NOTE: Every method that returns an AccessibilityNodeInfo may return null,

        // because the explored window is in another process and the

        // corresponding View might be gone by the time your request reaches the

        // view hierarchy.

        AccessibilityNodeInfo source = event.getSource();

        if (source == null) {

            return;

        }



        // Grab the parent of the view that fired the event.

        AccessibilityNodeInfo rowNode = getListItemNodeInfo(source);

        if (rowNode == null) {

            return;

        }



        // Using this parent, get references to both child nodes, the label and the checkbox.

        AccessibilityNodeInfo labelNode = rowNode.getChild(0);

        if (labelNode == null) {

            rowNode.recycle();

            return;

        }



        AccessibilityNodeInfo completeNode = rowNode.getChild(1);

        if (completeNode == null) {

            rowNode.recycle();

            return;

        }



        // Determine what the task is and whether or not it's complete, based on

        // the text inside the label, and the state of the check-box.

        if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) {

            rowNode.recycle();

            return;

        }



        CharSequence taskLabel = labelNode.getText();

        final boolean isComplete = completeNode.isChecked();



        String completeStr = null;

        if (isComplete) {

            completeStr = getString(R.string.task_complete);

        } else {

            completeStr = getString(R.string.task_not_complete);

        }



        String taskStr = getString(R.string.task_complete_template, taskLabel, completeStr);

        StringBuilder utterance = new StringBuilder(taskStr);



        // The custom ListView added extra context to the event by adding an

        // AccessibilityRecord to it. Extract that from the event and read it.

        final int records = event.getRecordCount();

        for (int i = 0; i < records; i++) {

            AccessibilityRecord record = event.getRecord(i);

            CharSequence contentDescription = record.getContentDescription();

            if (!TextUtils.isEmpty(contentDescription )) {

                utterance.append(SEPARATOR);

                utterance.append(contentDescription);

            }

        }



        // Announce the utterance.

        mTts.speak(utterance.toString(), TextToSpeech.QUEUE_FLUSH, null);

        Log.d(LOG_TAG, utterance.toString());

    }



    private AccessibilityNodeInfo getListItemNodeInfo(AccessibilityNodeInfo source) {

        AccessibilityNodeInfo current = source;

        while (true) {

            AccessibilityNodeInfo parent = current.getParent();

            if (parent == null) {

                return null;

            }

            if (TASK_LIST_VIEW_CLASS_NAME.equals(parent.getClassName())) { //找到TaskListView

                return current;

            }

            // NOTE: Recycle the infos. 记得回收AccessibilityNodeInfo

            AccessibilityNodeInfo oldCurrent = current;

            current = parent;

            oldCurrent.recycle();

        }

    }



    /**

     * {@inheritDoc}

     */

    @Override

    public void onInterrupt() {

        /* do nothing */

    }





 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272

上面这段代码是对TaskList的辅助。

AccessibilityService的配置声明

首先它跟普通的Service一样,需要在Manifest文件中声明:


<service android:name=".MyAccessibilityService">
     <intent-filter>
         <action android:name="android.accessibilityservice.AccessibilityService" />
     </intent-filter>
     <meta-data android:name="android.accessibilityservice" android:resource="@xml/taskbackconfig" />

 </service>

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

与其他Service不同的是里面有个元数据标志了配置资源—taskbackconfig。taskbackconfig里面的内容如下:


<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
   android:accessibilityEventTypes="typeAllMask"
   android:packageNames="com.example.android.apis"
   android:accessibilityFeedbackType="feedbackSpoken"
   android:notificationTimeout="100"
   android:canRetrieveWindowContent="true"
   android:description="@string/accessibility_query_window_description" />

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

其中packageNames限制了监视的包名,accessibilityEventType表示该AccessibilityService想要收到的Event事件类型。accessibilityFeedbackType表示提供的feedback类型,canRetrieveWindowContent是否可以获取窗口的内容,description表示描述信息,在设置中辅助功能里面允许该应用进行辅助设置时,会显示在那个页面下面。

事件处理流程简述

Accessibility是能够获取到控件的消息,这些信息是从哪传递出来的呢?传递的整个过程是怎样的呢?

从View开始

事件是跟View相关的,它从View开始的,View实现了一个叫AccessibilityEventSource的接口,AccessibilityEventSource接口包含了两个函数:


public void sendAccessibilityEvent(int eventType);

public void sendAccessibilityEventUnchecked(AccessibilityEvent event);

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

这两个函数会用来发送AccessibilityEvent,比如在View收到点击事件时,当View的Focus状态变化时等等,View调用这两个接口来开启发送AccessibilityEvent。

反向递归请求到ViewRootImpl

另外ViewGroup也实现了ViewParent接口,而ViewParent有一个接口函数requestSendAccessibilityEvent,在子View中,sendAccessibilityEvent方法最终会调用getParent().requestSendAccessibilityEvent,一层一层地往上调用,最终在ViewRootImpl里面使用AccessibilityManager的sendAccessibilityEvent方法binder机制将AccessibilityEvent发送给AccessibilityManagerService。

AccessibilityManagerService 管理核心

AccessibilityManagerService是在SystemServer中初始化的,并且添加到ServiceManager中。AccessibilityManagerService在有包变化的时候(安装,卸载)更新AccessibilityService绑定,如下代码所示:


private void updateServicesLocked(UserState userState) {
   if (userState.mIsAccessibilityEnabled) {
       manageServicesLocked(userState);
   } else {
       unbindAllServicesLocked(userState);
   }
}

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
发送到AccessibilityService

在出现新的AccessibilityService时会去绑定AccessibilityService获取绑定的结果IAccessibilityServiceClient(实际上是AccessibilityService中onBinder返回的IAccessibilityServiceClientWrapper的Proxy),跟AccessibilityService通信相关的信息保存在AccessibilityManagerService.Service类当中。当AccessibilityManagerService收到Event后,会遍历所有的Service,并通过IAccessibilityServiceClient将Event发送给AccessibilityService。

从AccessibilityEvent事件产生到发送到AccessibilityService,整个流程就是这样了。整个过程如下图所示:

这里写图片描述

这里就简单介绍一下流程,有机会再好好分析一下源码。

总结

AccessibilityService给我们提供了很多方便,我们可以利用AccessibilityService做很多巧妙的事情。使用AccessibilityService主要步骤就是继承AccessibilityServcie服务,实现onAccessibilityEvent方法,配置好相关的内容,最后在AndroidMainfest声明相关配置。知道AccessibilityService怎么使用,最好也了解整个AccessibilityEvent事件流程,以能够对AccessibilityService有整体把握。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值