關於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方法,裏面的代碼就不詳細分析了。