Android?無障礙服務(wù)?performAction?調(diào)用過程分析
無障礙服務(wù)可以模擬一些用戶操作,無障礙可以處理的對象,通過類 AccessibilityNodeInfo 表示,通過無障礙服務(wù),可以通過它的 performAction 方法來觸發(fā)一些 action ,包括:
ACTION_FOCUS // 獲取焦點 ACTION_CLEAR_FOCUS // 清除焦點 ACTION_SELECT // 選中 ACTION_CLEAR_SELECTION // 清除選中狀態(tài) ACTION_ACCESSIBILITY_FOCUS // 無障礙焦點 ACTION_CLEAR_ACCESSIBILITY_FOCUS // 清除無障礙焦點 ACTION_CLICK // 點擊 ACTION_LONG_CLICK // 長按 ACTION_NEXT_AT_MOVEMENT_GRANULARITY // 下一步移動 ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY // 上一步移動 ACTION_NEXT_HTML_ELEMENT // 下一個 html 元素 ACTION_PREVIOUS_HTML_ELEMENT // 上一個 html 元素 ACTION_SCROLL_FORWARD // 向前滑動 ACTION_SCROLL_BACKWARD // 向后滑動
他們都可以通過performAction方法進(jìn)行處理:
// in AccessibilityNodeInfo
public boolean performAction(int action) {
enforceSealed();
if (!canPerformRequestOverConnection(mConnectionId, mWindowId, mSourceNodeId)) {
return false;
}
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.performAccessibilityAction(mConnectionId, mWindowId, mSourceNodeId,
action, null);
}在這個方法中,第一步是檢查 perform 是否可以通過 connection 請求,這里 connection 檢查是根據(jù)通過 binder 通信傳遞過來的 id 檢查連接是否正常。 然后通過AccessibilityInteractionClient對象,調(diào)用它的performAccessibilityAction方法去進(jìn)行實際操作的。
AccessibilityInteractionClient
這個類是一個執(zhí)行可訪問性交互的單例,它可以根據(jù) View 的快照查詢遠(yuǎn)程的 View 層次結(jié)構(gòu),以及通過 View 層次結(jié)構(gòu),來請求對 View 執(zhí)行某項操作。
基本原理:內(nèi)容檢索 API 從客戶端的角度來看是同步的,但在內(nèi)部它們是異步的??蛻舳司€程調(diào)用系統(tǒng)請求操作并提供回調(diào)以接收結(jié)果,然后等待該結(jié)果的超時。系統(tǒng)強(qiáng)制執(zhí)行安全性并將請求委托給給定的視圖層次結(jié)構(gòu), 在該視圖層次結(jié)構(gòu)中發(fā)布消息(來自 Binder 線程),描述 UI 線程要執(zhí)行的內(nèi)容,其結(jié)果是通過上述回調(diào)傳遞的。但是,被阻塞的客戶端線程和目標(biāo)視圖層次結(jié)構(gòu)的主 UI 線程可以是同一個線程,例如無障礙服務(wù)和 Activity 在同一個進(jìn)程中運(yùn)行,因此它們在同一個主線程上執(zhí)行。 在這種情況下,檢索將會失敗,因為 UI 線程在等待檢索結(jié)果,會導(dǎo)致阻塞。 為了避免在進(jìn)行調(diào)用時出現(xiàn)這種情況,客戶端還會傳遞其進(jìn)程和線程 ID,以便訪問的視圖層次結(jié)構(gòu)可以檢測發(fā)出請求的客戶端是否正在其主 UI 線程中運(yùn)行。 在這種情況下,視圖層次結(jié)構(gòu),特別是對它執(zhí)行 IPC 的綁定線程,不會發(fā)布要在 UI 線程上運(yùn)行的消息,而是將其傳遞給單例交互客戶端,通過該客戶端發(fā)生所有交互,后者負(fù)責(zé)執(zhí)行開始等待通過回調(diào)傳遞的異步結(jié)果之前的消息。在這種情況下,已經(jīng)收到預(yù)期的結(jié)果,因此不執(zhí)行等待。
上面是官方備注的描述,大概意思最好不要在主線程執(zhí)行檢索操作。
繼續(xù)跟進(jìn)它的performAccessibilityAction方法:
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;
try {
success = connection.performAccessibilityAction(
accessibilityWindowId, accessibilityNodeId, action, arguments,
interactionId, this, Thread.currentThread().getId()); // 【*】
} finally {
Binder.restoreCallingIdentity(identityToken);
}
if (success) {
return getPerformAccessibilityActionResultAndClear(interactionId);
}
}
} catch (RemoteException re) {
Log.w(LOG_TAG, "Error while calling remote performAccessibilityAction", re);
}
return false;
}這里通過 getConnection(connectionId) 獲取了一個 IAccessibilityServiceConnection 。
public static IAccessibilityServiceConnection getConnection(int connectionId) {
synchronized (sConnectionCache) {
return sConnectionCache.get(connectionId);
}
}這里的 sConnectionCache 通過 AccessibilityInteractionClient 的addConnection添加數(shù)據(jù)的,addConnection在 AccessbilityService 創(chuàng)建初始化時調(diào)用的:
case DO_INIT: {
mConnectionId = message.arg1;
SomeArgs args = (SomeArgs) message.obj;
IAccessibilityServiceConnection connection = (IAccessibilityServiceConnection) args.arg1;
IBinder windowToken = (IBinder) args.arg2;
args.recycle();
if (connection != null) {
AccessibilityInteractionClient.getInstance(mContext).addConnection(mConnectionId, connection);
mCallback.init(mConnectionId, windowToken);
mCallback.onServiceConnected();
} else {
AccessibilityInteractionClient.getInstance(mContext).removeConnection(mConnectionId);
mConnectionId = AccessibilityInteractionClient.NO_ID;
AccessibilityInteractionClient.getInstance(mContext).clearCache();
mCallback.init(AccessibilityInteractionClient.NO_ID, null);
}
return;
}也就是說,在 AccessbilityService 創(chuàng)建時,會將一個表示連接的對象存到 AccessibilityInteractionClient 的連接緩存中。
IAccessibilityServiceConnection
它是 AccessibilityManagerService 向 AccessbilityService 暴露的 AIDL 接口,提供給 AccessbilityService 調(diào)用AccessibilityManagerService 的能力。 上面的 performAction 流程中,調(diào)用到了 connection 的performAccessibilityAction方法。 而 IAccessibilityServiceConnection 有兩個實現(xiàn)類,AccessibilityServiceConnectionImpl和AbstractAccessibilityServiceConnection,前者都是空實現(xiàn),顯然不是我們要調(diào)用到的地方,后者的performAccessibilityAction:
@Override
public boolean performAccessibilityAction(int accessibilityWindowId,
long accessibilityNodeId, int action, Bundle arguments, int interactionId,
IAccessibilityInteractionConnectionCallback callback, long interrogatingTid)
throws RemoteException {
final int resolvedWindowId;
synchronized (mLock) {
if (!hasRightsToCurrentUserLocked()) {
return false;
}
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
if (!mSecurityPolicy.canGetAccessibilityNodeInfoLocked(
mSystemSupport.getCurrentUserIdLocked(), this, resolvedWindowId)) {
return false;
}
}
if (!mSecurityPolicy.checkAccessibilityAccess(this)) {
return false;
}
return performAccessibilityActionInternal(
mSystemSupport.getCurrentUserIdLocked(), resolvedWindowId, accessibilityNodeId,
action, arguments, interactionId, callback, mFetchFlags, interrogatingTid);
}最后的一行調(diào)用:
private boolean performAccessibilityActionInternal(int userId, int resolvedWindowId, long accessibilityNodeId, int action, Bundle arguments, int interactionId, IAccessibilityInteractionConnectionCallback callback, int fetchFlags, long interrogatingTid) {
RemoteAccessibilityConnection connection;
IBinder activityToken = null;
// 同步獲取 connection
synchronized (mLock) {
connection = mA11yWindowManager.getConnectionLocked(userId, resolvedWindowId);
if (connection == null) {
return false;
}
final boolean isA11yFocusAction = (action == ACTION_ACCESSIBILITY_FOCUS) || (action == ACTION_CLEAR_ACCESSIBILITY_FOCUS);
if (!isA11yFocusAction) {
final WindowInfo windowInfo = mA11yWindowManager.findWindowInfoByIdLocked(resolvedWindowId);
if (windowInfo != null) activityToken = windowInfo.activityToken;
}
final AccessibilityWindowInfo a11yWindowInfo = mA11yWindowManager.findA11yWindowInfoByIdLocked(resolvedWindowId);
if (a11yWindowInfo != null && a11yWindowInfo.isInPictureInPictureMode() && mA11yWindowManager.getPictureInPictureActionReplacingConnection() != null && !isA11yFocusAction) {
connection = mA11yWindowManager.getPictureInPictureActionReplacingConnection();
}
}
// 通過 connection 調(diào)用到遠(yuǎn)程服務(wù)的performAccessibilityAction
final int interrogatingPid = Binder.getCallingPid();
final long identityToken = Binder.clearCallingIdentity();
try {
// 無論操作是否成功,它都是由用戶操作的無障礙服務(wù)生成的,因此請注意用戶Activity。
mPowerManager.userActivity(SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_ACCESSIBILITY, 0);
if (action == ACTION_CLICK || action == ACTION_LONG_CLICK) {
mA11yWindowManager.notifyOutsideTouch(userId, resolvedWindowId);
}
if (activityToken != null) {
LocalServices.getService(ActivityTaskManagerInternal.class).setFocusedActivity(activityToken);
}
connection.getRemote().performAccessibilityAction(accessibilityNodeId, action, arguments, interactionId, callback, fetchFlags, interrogatingPid, interrogatingTid);
} catch (RemoteException re) {
if (DEBUG) {
Slog.e(LOG_TAG, "Error calling performAccessibilityAction: " + re);
}
return false;
} finally {
Binder.restoreCallingIdentity(identityToken);
}
return true;
}在這個方法中,通過 connection 調(diào)用到了遠(yuǎn)端的 performAccessibilityAction 方法。
關(guān)鍵的一行是:
connection.getRemote().performAccessibilityAction(accessibilityNodeId, action, arguments, interactionId, callback, fetchFlags, interrogatingPid, interrogatingTid);
這里的 connection 類型定義成了RemoteAccessibilityConnection。
RemoteAccessibilityConnection
RemoteAccessibilityConnection 是AccessibilityWindowManager的內(nèi)部類,它的getRemote()返回類型是IAccessibilityInteractionConnection。
AccessibilityWindowManager
此類為 AccessibilityManagerService 提供 API 來管理 AccessibilityWindowInfo 和 WindowInfos。
IAccessibilityInteractionConnection
這是一個 AIDL 中定義的接口,用來進(jìn)行 給定 window 中 AccessibilityManagerService 和 ViewRoot 之間交互的接口。
也就是說getRemote(). performAccessibilityAction(...)最終來到了 ViewRootImpl 中。
AccessibilityInteractionConnection
ViewRootImpl 中存在一個內(nèi)部類AccessibilityInteractionConnection,它是這個 ViewAncestor 提供給 AccessibilityManagerService 的一個接口,后者可以與這個 ViewAncestor 中的視圖層次結(jié)構(gòu)進(jìn)行交互。
它的performAccessibilityAction實現(xiàn)是:
@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 */
}
}
}內(nèi)部又是通過代理調(diào)用 ,ViewRootImpl 的 getAccessibilityInteractionController() 返回了一個 AccessibilityInteractionController 對象。
AccessibilityInteractionController
它的 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, CONSIDER_REQUEST_PREPARERS);
}組裝了一個 message ,并通過 scheduleMessage 方法去執(zhí)行:
private void scheduleMessage(Message message, int interrogatingPid, long interrogatingTid, boolean ignoreRequestPreparers) {
if (ignoreRequestPreparers || !holdOffMessageIfNeeded(message, interrogatingPid, interrogatingTid)) {
if (interrogatingPid == mMyProcessId && interrogatingTid == mMyLooperThreadId
&& mHandler.hasAccessibilityCallback(message)) {
AccessibilityInteractionClient.getInstanceForThread(
interrogatingTid).setSameThreadMessage(message);
} else {
if (!mHandler.hasAccessibilityCallback(message) && Thread.currentThread().getId() == mMyLooperThreadId) {
mHandler.handleMessage(message);
} else {
mHandler.sendMessage(message);
}
}
}
}這里實際上,如果是在主線程,則處理消息,如果不是,則發(fā)送消息到主線程處理。handler 的類型是 PrivateHandler ,在 AccessibilityInteractionController 內(nèi)部定義。
它的處理消息方法的實現(xiàn)是:
@Override
public void handleMessage(Message message) {
final int type = message.what;
switch (type) {
// ...
case MSG_PERFORM_ACCESSIBILITY_ACTION: {
performAccessibilityActionUiThread(message);
} break;
// ...
default:
throw new IllegalArgumentException("Unknown message type: " + type);
}
}執(zhí)行到了 performAccessibilityActionUiThread(message); :
private void performAccessibilityActionUiThread(Message message) {
// ...
boolean succeeded = false;
try {
// ...
final View target = findViewByAccessibilityId(accessibilityViewId);
if (target != null && isShown(target)) {
mA11yManager.notifyPerformingAction(action);
if (action == R.id.accessibilityActionClickOnClickableSpan) {
// 單獨處理這個 hidden action
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);
}
}
mA11yManager.notifyPerformingAction(0);
}
} finally {
try {
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = 0;
callback.setPerformAccessibilityActionResult(succeeded, interactionId);
} catch (RemoteException re) {
/* ignore - the other side will time out */
}
}
}在這個流程中,分為三種情況去真正執(zhí)行 performAction :
1. action == R.id.accessibilityActionClickOnClickableSpan
private boolean handleClickableSpanActionUiThread(
View view, int virtualDescendantId, Bundle arguments) {
Parcelable span = arguments.getParcelable(ACTION_ARGUMENT_ACCESSIBLE_CLICKABLE_SPAN);
if (!(span instanceof AccessibilityClickableSpan)) {
return false;
}
// Find the original ClickableSpan if it's still on the screen
AccessibilityNodeInfo infoWithSpan = null;
AccessibilityNodeProvider provider = view.getAccessibilityNodeProvider();
if (provider != null) {
infoWithSpan = provider.createAccessibilityNodeInfo(virtualDescendantId);
} else if (virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID) {
infoWithSpan = view.createAccessibilityNodeInfo();
}
if (infoWithSpan == null) {
return false;
}
// Click on the corresponding span
ClickableSpan clickableSpan = ((AccessibilityClickableSpan) span).findClickableSpan(
infoWithSpan.getOriginalText());
if (clickableSpan != null) {
clickableSpan.onClick(view);
return true;
}
return false;
}2. View. AccessibilityNodeProvider != null
當(dāng)能夠通過 View 獲取到 AccessibilityNodeProvider 對象是,通過它的 performAction 方法,去執(zhí)行真正的調(diào)用,它的真正調(diào)用在 AccessibilityNodeProviderCompat中,這個 Compat 的實現(xiàn)在ExploreByTouchHelper中的內(nèi)部類MyNodeProvider中:
@Override
public boolean performAction(int virtualViewId, int action, Bundle arguments) {
return ExploreByTouchHelper.this.performAction(virtualViewId, action, arguments);
}在 ExploreByTouchHelper 中繼續(xù)查看:
boolean performAction(int virtualViewId, int action, Bundle arguments) {
switch (virtualViewId) {
case HOST_ID:
return performActionForHost(action, arguments);
default:
return performActionForChild(virtualViewId, action, arguments);
}
}private boolean performActionForHost(int action, Bundle arguments) {
return ViewCompat.performAccessibilityAction(mHost, action, arguments);
}
private boolean performActionForChild(int virtualViewId, int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
return requestAccessibilityFocus(virtualViewId);
case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
return clearAccessibilityFocus(virtualViewId);
case AccessibilityNodeInfoCompat.ACTION_FOCUS:
return requestKeyboardFocusForVirtualView(virtualViewId);
case AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS:
return clearKeyboardFocusForVirtualView(virtualViewId);
default:
return onPerformActionForVirtualView(virtualViewId, action, arguments);
}
}前者調(diào)用到了 ViewCompat :
public static boolean performAccessibilityAction(@NonNull View view, int action,
Bundle arguments) {
if (Build.VERSION.SDK_INT >= 16) {
return view.performAccessibilityAction(action, arguments);
}
return false;
}然后是 View 的 :
public boolean performAccessibilityAction(int action, Bundle arguments) {
if (mAccessibilityDelegate != null) {
return mAccessibilityDelegate.performAccessibilityAction(this, action, arguments);
} else {
return performAccessibilityActionInternal(action, arguments);
}
}mAccessibilityDelegate.performAccessibilityAction的實現(xiàn)是:
public boolean performAccessibilityAction(View host, int action, Bundle args) {
return host.performAccessibilityActionInternal(action, args);
}也是調(diào)用到了 View 的performAccessibilityActionInternal 。 performAccessibilityActionInternal 的實現(xiàn)是:
// in View.java
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()) {
performClickInternal();
return true;
}
} break;
case AccessibilityNodeInfo.ACTION_LONG_CLICK: {
if (isLongClickable()) {
performLongClick();
return true;
}
} break;
// ...
}
return false;
}以 AccessibilityNodeInfo.ACTION_CLICK 為例,內(nèi)部調(diào)用是:
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}這樣就調(diào)用到了 View 的點擊事件。
3. View. AccessibilityNodeProvider == null && virtualDescendantId == AccessibilityNodeProvider.HOST_VIEW_ID
target.performAccessibilityAction(action, arguments);
這里 target 是個 View, 也是走的 View 的 performAccessibilityAction ,和上面流程一樣。
View 的 performClick 方法是同步的還是異步的?
public boolean performClick() {
// We still need to call this method to handle the cases where performClick() was called
// externally, instead of through performClickInternal()
notifyAutofillManagerOnClick();
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;
}同步的。
總結(jié)

到此這篇關(guān)于Android 無障礙服務(wù) performAction 調(diào)用過程分析的文章就介紹到這了,更多相關(guān)Android performAction 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android 開發(fā)音頻組件(Vitamio FAQ)詳細(xì)介紹
本文主要介紹Android開發(fā)音頻播放器,Vitamio是Android播放器組件,支持幾乎所有視頻格式和網(wǎng)絡(luò)視頻流,希望能幫助開發(fā)Android 音頻播放的小伙伴2016-07-07
Android 實現(xiàn)自定義圓形listview功能的實例代碼
這篇文章主要介紹了Android 實現(xiàn)自定義圓形listview功能的實例代碼,本文通過實例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07

