关于Accessibility的使用网上已经有不少的文章,但是很少有从源码角度去分析如何去实现的,本文基于源码android-26 由于代码量不少,又不想一篇文章过度的长影响阅读,所以本篇以performAction视角来分析如何通过去操作界面。
AccessibilityNodeInfo.performAction(int action)
在操作目标APP时通过AccessibilityService#getRootInActiveWindow()获取当前界面的布局AccessibilityNodeInfo,然后根据findAccessibilityNodeInfosByText 或 findAccessibilityNodeInfosByViewId进行过滤,最后调用performAction进行操作。
performAction传入比较常见的值有
ACTION_CLICK 点击
ACTION_LONG_CLICK 长按
ACTION_SCROLL_FORWARD 向前滑动
ACTION_SCROLL_BACKWARD 向后滑动
ACTION_SET_TEXT 设置文本
...
源码如下
public boolean performAction(int action) {
enforceSealed();
if (!canPerformRequestOverConnection(mSourceNodeId)) {
return false;
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
action, null);
}
调用了performAccessibilityAction方法,这个方法传的参数比较多,除了action是外面传进来的,Bundle null,其他的参数都是Accessibility的全局变量,其中mConnectionId是连接系统的id,mWindowId是在AccessibilityManagerService的静态自增变量sNextWindowId,对唯一windowm的标识;SourceNodeId是View经过转换的静态自增变量sNextAccessibilityViewId,对view唯一性的标识,这里我们不用去管他
AccessibilityInteractionClient. performAccessibilityAction(int connectionId, int accessibilityWindowId,long accessibilityNodeId, int action, Bundle arguments)
public boolean performAccessibilityAction(int connectionId, int accessibilityWindowId,
long accessibilityNodeId, int action, Bundle arguments) {
try {
IAccessibilityServiceConnection connection = getConnection(connectionId);
if (connection != null) {
final int interactionId = mInteractionIdCounter.getAndIncrement();
final long identityToken = Binder.clearCallingIdentity();
final boolean success = connection.performAccessibilityAction(
accessibilityWindowId, accessibilityNodeId, action, arguments,
interactionId, this, Thread.currentThread().getId());
Binder.restoreCallingIdentity(identityToken);
if (success) {
return getPerformAccessibilityActionResultAndClear(interactionId);
}
} else {
if (DEBUG) {
Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
}
}
} catch (RemoteException re) {
Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
}
return false;
}
根据connectionId获取到 IAccessibilityServiceConnection,然后通过该类调用 performAccessibilityAction ,方法传递的参数在原基础的参数增加一个interrogatingTid 线程id,还有一个interactionId
,final int interactionId = mInteractionIdCounter.getAndIncrement()
该变量也是一个自增变量,用来标识此次操作。IAccessibilityServiceConnection 是一个aidl接口,充当AccessibilityService 和 AccessibilityManagerService沟通的桥梁,猜测AccessibilityManagerService实现了IAccessibilityServiceConnection,接下来我们跳转到 AccessibilityManagerService
AccessibilityManagerService.performAccessibilityAction
public boolean performAccessibilityAction(int accessibilityWindowId,
long accessibilityNodeId, int action, Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
final int resolvedWindowId;
IAccessibilityInteractionConnection connection = null;
......
......
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
// Regardless of whether or not the action succeeds, it was generated by an
// accessibility service that is driven by user actions, so note user activity.
mPowerManager.userActivity(SystemClock.uptimeMillis(),
PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY, 0);
connection.performAccessibilityAction(accessibilityNodeId, action, arguments,
interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling performAccessibilityAction()");
}
} finally {
Binder.restoreCallingIdentity(identityToken);
}
return true;
}
此处省略了部分代码 ,经过校验是否可以获取当前界面,通知powerManager活动发送点亮屏幕等一些列操作,IAccessibilityInteractionConnection 也是一个aidl,官方的介绍
/**
* Interface for interaction between the AccessibilityManagerService
* and the ViewRoot in a given window.
*
* @hide
*/
大概意思就是AccessibilityManagerService和指定window的ViewRoot的交互,我们看到传入的参数多一个pid以及flag,我们查看ViewRoot代码没有看到IAccessibilityInteractionConnection的实现,全局搜索代码IAccessibilityInteractionConnection.Stub 看到ViewRootImpl,查看ViewRootImpl.AccessibilityInteractionConnection#performAccessibilityAction
ViewRootImpl.AccessibilityInteractionConnection#performAccessibilityAction
@Override
public void performAccessibilityAction(long accessibilityNodeId, int action,
Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
viewRootImpl.getAccessibilityInteractionController()
.performAccessibilityActionClientThread(accessibilityNodeId, action, arguments,
interactionId, callback, flags, interrogatingPid, interrogatingTid);
} else {
// We cannot make the call and notify the caller so it does not wait.
try {
callback.setPerformAccessibilityActionResult(false, interactionId);
} catch (RemoteException re) {
/* best effort - ignore */
}
}
}
核心看到
viewRootImpl.getAccessibilityInteractionController()
.performAccessibilityActionClientThread(accessibilityNodeId, action, arguments,
interactionId, callback, flags, interrogatingPid, interrogatingTid);
这里我们跳转到AccessibilityInteractionCollector#performAccessibilityActionClientThread
AccessibilityInteractionCollector#performAccessibilityActionClientThread
public void performAccessibilityActionClientThread(long accessibilityNodeId, int action,
Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid,
long interrogatingTid) {
Message message = mHandler.obtainMessage();
message.what = PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION;
message.arg1 = flags;
message.arg2 = AccessibilityNodeInfo.getAccessibilityViewId(accessibilityNodeId);
SomeArgs args = SomeArgs.obtain();
args.argi1 = AccessibilityNodeInfo.getVirtualDescendantId(accessibilityNodeId);
args.argi2 = action;
args.argi3 = interactionId;
args.arg1 = callback;
args.arg2 = arguments;
message.obj = args;
scheduleMessage(message, interrogatingPid, interrogatingTid);
}
这块代码我们比较熟悉,把传进来的参数组成Message(SomeArgs 是为了Message传递更多参数的封装),在mHandler处理
延伸:源码在com.android.internal或者或者被标识被@hidden没有打包到android.jar,所以开发者不能够调用,SomeArgs所在的包就在com.android.internal.os(当然github已经有不少把@hidden标识去掉,com.android.internal代码加进去打包成android.jar,然后用户就可以调用了)
进入sceduleMessage
private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid) {
// If the interrogation is performed by the same thread as the main UI
// thread in this process, set the message as a static reference so
// after this call completes the same thread but in the interrogating
// client can handle the message to generate the result.
if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId) {
AccessibilityInteractionClient.getInstanceForThread(
interrogatingTid).setSameThreadMessage(message);
} else {
mHandler.sendMessage(message);
}
}
如果操作是UI线程并且在同一线程则被client处理,这里我们看否的情况mHandler.sendMessage(message)
,这里找到mHandler的初始化mHandler = new PrivateHandler(looper);
,必定会复写handMessage方法
private class PrivateHandler extends Handler {
private static final int MSG_PERFORM_ACCESSIBILITY_ACTION = 1;
private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID = 2;
private static final int MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID = 3;
private static final int MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT = 4;
private static final int MSG_FIND_FOCUS = 5;
private static final int MSG_FOCUS_SEARCH = 6;
public PrivateHandler(Looper looper) {
super(looper);
}
@Override
public String getMessageName(Message message) {
final int type = message.what;
switch (type) {
case MSG_PERFORM_ACCESSIBILITY_ACTION:
return "MSG_PERFORM_ACCESSIBILITY_ACTION";
case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID:
return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID";
case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID:
return "MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID";
case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT:
return "MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT";
case MSG_FIND_FOCUS:
return "MSG_FIND_FOCUS";
case MSG_FOCUS_SEARCH:
return "MSG_FOCUS_SEARCH";
default:
throw new IllegalArgumentException("Unknown message type: " + type);
}
}
@Override
public void handleMessage(Message message) {
final int type = message.what;
switch (type) {
case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_ACCESSIBILITY_ID: {
findAccessibilityNodeInfoByAccessibilityIdUiThread(message);
} break;
case MSG_PERFORM_ACCESSIBILITY_ACTION: {
performAccessibilityActionUiThread(message);
} break;
case MSG_FIND_ACCESSIBILITY_NODE_INFOS_BY_VIEW_ID: {
findAccessibilityNodeInfosByViewIdUiThread(message);
} break;
case MSG_FIND_ACCESSIBILITY_NODE_INFO_BY_TEXT: {
findAccessibilityNodeInfosByTextUiThread(message);
} break;
case MSG_FIND_FOCUS: {
findFocusUiThread(message);
} break;
case MSG_FOCUS_SEARCH: {
focusSearchUiThread(message);
} break;
default:
throw new IllegalArgumentException("Unknown message type: " + type);
}
}
}
前面我们传递的msg.what为PrivateHandler.MSG_PERFORM_ACCESSIBILITY_ACTION
对应的handleMessage处理的方法为performAccessibilityActionUiThread(message)
private void performAccessibilityActionUiThread(Message message) {
.......
if (target != null && isShown(target)) {
if (action == R.id.accessibilityActionClickOnClickableSpan) {
// Handle this hidden action separately
succeeded = handleClickableSpanActionUiThread(
target, virtualDescendantId, arguments);
} else {
AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
if (provider != null) {
succeeded = provider.performAction(virtualDescendantId, action,
arguments);
} else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
succeeded = target.performAccessibilityAction(action, arguments);
}
}
}
.......
}
View.performAccessibilityAction
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (mAccessibilityDelegate != null) {
return mAccessibilityDelegate.performAccessibilityAction(this, action, arguments);
} else {
return performAccessibilityActionInternal(action, arguments);
}
}
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
if (isNestedScrollingEnabled()
&& (action == AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD
|| action == AccessibilityNodeInfo.ACTION_SCROLL_FORWARD
|| action == R.id.accessibilityActionScrollUp
|| action == R.id.accessibilityActionScrollLeft
|| action == R.id.accessibilityActionScrollDown
|| action == R.id.accessibilityActionScrollRight)) {
if (dispatchNestedPrePerformAccessibilityAction(action, arguments)) {
return true;
}
}
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK: {
if (isClickable()) {
performClick();
return true;
}
} break;
case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
if (isLongClickable()) {
performLongClick();
return true;
}
} break;
case AccessibilityNodeInfo.ACTION_FOCUS: {
if (!hasFocus()) {
// Get out of touch mode since accessibility
// wants to move focus around.
getViewRootImpl().ensureTouchMode(false);
return requestFocus();
}
} break;
......
}
终于找到了我们最熟悉的View类了,点进去相应的方法就可以看到view事件的操作详情,比如我们以AccessibilityNodeInfo.ACTION_CLICK
为例。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
notifyEnterOrExitForAutoFillIfNeeded(true);
return result;
}
代码li.mOnClickListener.onClick(this)
,最终调用了view的onClick事件
细心的童鞋可能注意到没有AccessibilityNodeInfo#ACTION_SCROLL_BACKWARD和AccessibilityNodeInfo#ACTION_SCROLL_FORWARD,我们查找AccessibilityNodeInfo#ACTION_SCROLL_FORWARD的引用会发现RecyclerView里有
public boolean performAccessibilityAction(Recycler recycler, State state, int action,
Bundle args) {
if (mRecyclerView == null) {
return false;
}
int vScroll = 0, hScroll = 0;
switch (action) {
case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD:
if (mRecyclerView.canScrollVertically(-1)) {
vScroll = -(getHeight() - getPaddingTop() - getPaddingBottom());
}
if (mRecyclerView.canScrollHorizontally(-1)) {
hScroll = -(getWidth() - getPaddingLeft() - getPaddingRight());
}
break;
case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD:
if (mRecyclerView.canScrollVertically(1)) {
vScroll = getHeight() - getPaddingTop() - getPaddingBottom();
}
if (mRecyclerView.canScrollHorizontally(1)) {
hScroll = getWidth() - getPaddingLeft() - getPaddingRight();
}
break;
}
if (vScroll == 0 && hScroll == 0) {
return false;
}
mRecyclerView.scrollBy(hScroll, vScroll);
return true;
}
而该方法又被如下方法调用
boolean performAccessibilityAction(int action, Bundle args) {
return performAccessibilityAction(mRecyclerView.mRecycler, mRecyclerView.mState,
action, args);
}
看到这里就知道RecyclerView复写了View的 performAccessibilityAction方法,里面的代码就不详细分析了。