通過源碼角度看看AccessibilityService
簡介
AccessibilityService的設計初衷是為了輔助有身體缺陷的群體使用Android應用,它的設計貫穿著Android的控件樹View, ViewGroup, ViewRootImpl體系。借助于system_server進程的中轉,能夠注冊Accessibility事件的客戶端可以具備通過system_server提供的Accessibility服務來實現(xiàn)監(jiān)聽、操作其它應用視圖的功能。這個功能十分強大,可以模擬用戶的行為去操作其它APP,常常被用在自動化測試、微信搶紅包、自動回復等功能實現(xiàn)中。
寫這個的初衷有二:
- 之前已經完成了Android View控件樹的繪制、事件分發(fā)的源碼分析,知識儲備足夠
- 最近接觸到了一些自動化方面的項目,并且對使用無障礙服務實現(xiàn)的自動微信搶紅包功能原理十分好奇
整體圖
類圖

- AccessibilityService: APP端直接繼承的類,本質上是Service,通過onBind獲取匿名Binder對象實現(xiàn)通信
- IAccessibilityServiceClientWrapper: 用于和system_server通信的匿名Binder服務
- AccessibilityInteractionClient: 本質上是個binder服務,用于獲取Node信息
- AccessibilityManagerService: 運行在system_server的實名binder服務,是整體的管理類
- Service: AccessibilityManagerService的內部類,用于響應AccessibilityInteractionClient的binder通信請求
- AccessibilityInteractionConnection: 運行在被監(jiān)測的APP端,提供查找、點擊視圖等服務
- AccessibilityManager: 運行在各個APP端,用于發(fā)送視圖變化事件
- AccessibilityInteractionController: 具體視圖查找、點擊服務的中間控制器
- AccessibilityNodeProvider: 由客戶端實現(xiàn)的視圖節(jié)點內容提供者,最終操作的實現(xiàn)者
整體設計圖

實例代碼
public class AutoDismissService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event == null) {
return;
}
// 自動將android系統(tǒng)彈出的其它crash dialog取消
dismissAppErrorDialogIfExists(event);
}
private void dismissAppErrorDialogIfExists(AccessibilityEvent event) {
// WINDOW視圖變化才進行對應操作
if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
&& event.getPackageName().equals("android")) {
// 查找?guī)в?OK"字符的可點擊Node
AccessibilityNodeInfo nodeInfo = findViewByText("OK", true);
if (nodeInfo != null) {
// 查找到后執(zhí)行點擊操作
performViewClick(nodeInfo);
}
}
public AccessibilityNodeInfo findViewByText(String text, boolean clickable) {
// 獲取當前窗口父節(jié)點
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return null;
}
// 獲取到滿足字符要求的節(jié)點
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null && (nodeInfo.isClickable() == clickable)) {
return nodeInfo;
}
}
}
return null;
}
public void performViewClick(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return;
}
// 由下至上進行查詢,直到尋找到可點擊的節(jié)點
while (nodeInfo != null) {
if (nodeInfo.isClickable()) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
nodeInfo = nodeInfo.getParent();
}
}
}
以上是一個典型的實現(xiàn)Accessibility功能的JAVA代碼,主要涉及三點功能:
- 當系統(tǒng)中有應用視圖變化后,onAccessibilityEvent 方法會自動被system_server調用
- 通過AccessibilityService的getRootInActiveWindow與findAccessibilityNodeInfosByText方法,可以獲取到節(jié)點信息
- 通過AccessibilityNodeInfo的performAction方法,最終會在被監(jiān)聽APP中執(zhí)行對應操作
本篇文章將會圍繞著這三點主要功能進行源碼分析
源碼分析
常見 AccessibilityEvent 事件種類
| 序號 | 種類名稱 | 觸發(fā)時機 |
|---|---|---|
| 1 | TYPE_VIEW_CLICKED | 可點擊的組件被點擊 |
| 2 | TYPE_VIEW_LONG_CLICKED | 可點擊的組件被長按 |
| 3 | TYPE_VIEW_SELECTED | 組件被選中 |
| 4 | TYPE_VIEW_FOCUSED | 組件獲取到了焦點 |
| 5 | TYPE_VIEW_TEXT_CHANGED | 組件中的文本發(fā)生變化 |
| 6 | TYPE_VIEW_SCROLLED | 組件被滑動 |
| 7 | TYPE_WINDOW_STATE_CHANGED | dialog等被打開 |
| 8 | TYPE_NOTIFICATION_STATE_CHANGED | 通知彈出 |
| 9 | TYPE_WINDOW_CONTENT_CHANGED | 組件樹發(fā)生了變化 |
onAccessibilityEvent 觸發(fā)流程
這里以TextView.setText觸發(fā)事件變化流程為例進行分析
TextView.setText
應用組件狀態(tài)發(fā)生變化
frameworks/base/core/java/android/widget/TextView.java
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
...
notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
...
}
public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) {
if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
return;
}
if (mSendViewStateChangedAccessibilityEvent == null) {
// 本質上是一個Runnable,意味著這里的流程會進入異步處理
mSendViewStateChangedAccessibilityEvent =
new SendViewStateChangedAccessibilityEvent();
}
mSendViewStateChangedAccessibilityEvent.runOrPost(changeType);
}
private class SendViewStateChangedAccessibilityEvent implements Runnable {
...
@Override
public void run() {
mPosted = false;
mPostedWithDelay = false;
mLastEventTimeMillis = SystemClock.uptimeMillis();
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
final AccessibilityEvent event = AccessibilityEvent.obtain();
event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
event.setContentChangeTypes(mChangeTypes);
// 最終TYPE_WINDOW_CONTENT_CHANGED事件在這里異步發(fā)送
sendAccessibilityEventUnchecked(event);
}
mChangeTypes = 0;
}
...
}
public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);
} else {
sendAccessibilityEventUncheckedInternal(event);
}
}
public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
host.sendAccessibilityEventUncheckedInternal(event);
}
public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
if (!isShown()) {
return;
}
...
// 此處交由TextView所在父View進行處理,為責任鏈模式,事件經過層層向上傳遞,最終交由ViewRootImpl進行處理
ViewParent parent = getParent();
if (parent != null) {
getParent().requestSendAccessibilityEvent(this, event);
}
}
ViewRootImpl.requestSendAccessibilityEvent
ViewRootImpl將事件派發(fā)到system_server
frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
...
// 本地調用到AccessibilityManager進行事件發(fā)送
mAccessibilityManager.sendAccessibilityEvent(event);
return true;
}
frameworks/base/core/java/android/view/accessibility/AccessibilityManager.java
public void sendAccessibilityEvent(AccessibilityEvent event) {
final IAccessibilityManager service;
final int userId;
synchronized (mLock) {
// 獲取system_server的Accessibility實名服務
service = getServiceLocked();
...
}
try {
...
long identityToken = Binder.clearCallingIdentity();
// binder call 到服務端,進行事件分發(fā)中轉
doRecycle = service.sendAccessibilityEvent(event, userId);
Binder.restoreCallingIdentity(identityToken);
...
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error during sending " + event + " ", re);
} finally {
...
}
}
AccessibilityManagerService.sendAccessibilityEvent
system_server將事件分發(fā)到各個監(jiān)聽組件變化的Service
frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
// binder call 到服務端,觸發(fā)事件派發(fā)
@Override
public boolean sendAccessibilityEvent(AccessibilityEvent event, int userId) {
synchronized (mLock) {
...
if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) {
...
notifyAccessibilityServicesDelayedLocked(event, false);
notifyAccessibilityServicesDelayedLocked(event, true);
}
...
}
return (OWN_PROCESS_ID != Binder.getCallingPid());
}
private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event,
boolean isDefault) {
try {
UserState state = getCurrentUserStateLocked();
for (int i = 0, count = state.mBoundServices.size(); i < count; i++) {
Service service = state.mBoundServices.get(i);
if (service.mIsDefault == isDefault) {
if (canDispatchEventToServiceLocked(service, event)) {
// 調用內部服務,以觸發(fā)事件派發(fā)
service.notifyAccessibilityEvent(event);
}
}
}
} catch (IndexOutOfBoundsException oobe) {
...
}
}
class Service extends IAccessibilityServiceConnection.Stub
implements ServiceConnection, DeathRecipient {
public void notifyAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
...
if ((mNotificationTimeout > 0)
&& (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) {
...
// 按照慣例,異步分發(fā)到客戶端進行派發(fā)
message = mEventDispatchHandler.obtainMessage(eventType);
} else {
message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
}
mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
}
}
}
public Handler mEventDispatchHandler = new Handler(mMainHandler.getLooper()) {
@Override
public void handleMessage(Message message) {
final int eventType = message.what;
AccessibilityEvent event = (AccessibilityEvent) message.obj;
notifyAccessibilityEventInternal(eventType, event);
}
};
private void notifyAccessibilityEventInternal(int eventType, AccessibilityEvent event) {
IAccessibilityServiceClient listener;
...
// mServiceInterface是通過bind客戶端的AccessibilityService,在onServiceConnected連接成功后,獲取到binder proxy轉化來的,以這種方式實現(xiàn)了system_server與客戶端的通信
listener = mServiceInterface;
...
try {
listener.onAccessibilityEvent(event);
if (DEBUG) {
Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
}
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
} finally {
event.recycle();
}
}
AccessibilityService.onAccessibilityEvent
APP接收到組件變化的事件,并可以選擇做出相應的處理
frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java
// 抽象方法,模板模式,被系統(tǒng)主動調用
public abstract void onAccessibilityEvent(AccessibilityEvent event);
// 該service是被system_server主動綁定的,獲取到IAccessibilityServiceClientWrapper的proxy來實現(xiàn)系統(tǒng)的主動調用
@Override
public final IBinder onBind(Intent intent) {
return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
...
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityService.this.onAccessibilityEvent(event);
}
...
}
}
// 收到binder調用后,使用handler異步進行事件的處理
public void onAccessibilityEvent(AccessibilityEvent event) {
Message message = mCaller.obtainMessageO(DO_ON_ACCESSIBILITY_EVENT, event);
mCaller.sendMessage(message);
}
@Override
public void executeMessage(Message message) {
switch (message.what) {
case DO_ON_ACCESSIBILITY_EVENT: {
AccessibilityEvent event = (AccessibilityEvent) message.obj;
if (event != null) {
AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
// 通過回調調用以觸發(fā)事件
mCallback.onAccessibilityEvent(event);
...
}
} return;
}
}
getRootInActiveWindow 父節(jié)點獲取流程
在調用findAccessibilityNodeInfosByText之前,需要通過getRootInActiveWindow方法獲取到父節(jié)點,才能通過調用父AccessibilityNodeInfo的方法進行其子節(jié)點信息查詢
AccessibilityService.getRootInActiveWindow
frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java
public AccessibilityNodeInfo getRootInActiveWindow() {
// 查找父節(jié)點的操作沒有在自己的類中實現(xiàn),而是交由了同一進程的Client管理類進行處理
return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId);
}
frameworks/base/core/java/android/view/accessibility/AccessibilityInteractionClient.java
public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {
return findAccessibilityNodeInfoByAccessibilityId(connectionId,
AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
}
public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache,
int prefetchFlags) {
...
// 嘗試binder call到system_server,請求中轉到其它APP進程中查詢父節(jié)點信息,注意的是這里AccessibilityInteractionClient本身是個binder服務端,把this傳到system_server后,其它進程可以通過這個引用拿到binder proxy,以實現(xiàn)通信
final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
accessibilityWindowId, accessibilityNodeId, interactionId, this,
prefetchFlags, Thread.currentThread().getId());
Binder.restoreCallingIdentity(identityToken);
// If the scale is zero the call has failed.
if (success) {
// 調用成功后,這里會嘗試同步獲取結果
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
if (infos != null && !infos.isEmpty()) {
return infos.get(0);
}
}
...
}
Service.findAccessibilityNodeInfoByAccessibilityId
注意一下,這里的Service不是Android中的四大組件的Service,取名叫AccessiblitManagerServiceInternal其實更合適
frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@Override
public boolean findAccessibilityNodeInfoByAccessibilityId(
int accessibilityWindowId, long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
long interrogatingTid) throws RemoteException {
...
// 獲取到其他APP的節(jié)點獲取服務
IAccessibilityInteractionConnection connection = null;
...
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
...
if (!permissionGranted) {
return false;
} else {
connection = getConnectionLocked(resolvedWindowId);
if (connection == null) {
return false;
}
}
...
// 這里的callback為之前應用的服務proxy句柄,將它傳入是為了之后的信息通信不再需要經過system_server中轉,而是直接可以APP對APP的進行通信
connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
partialInteractiveRegion, interactionId, callback, mFetchFlags | flags,
interrogatingPid, interrogatingTid, spec);
...
}
AccessibilityInteractionConnection.findAccessibilityNodeInfoByAccessibilityId
這里調用到了APP端,其實同onAccessibilityEvent調用流程一樣,是APP->SYSTEM->APP的調用順序
frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
// 這里也只是委托給控制類進行細節(jié)操作的處理
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
interactiveRegion, interactionId, callback, flags, interrogatingPid,
interrogatingTid, spec);
} else {
...
}
}
frameworks/base/core/java/android/view/AccessibilityInteractionController.java
private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
...
// 初始化將會返回的節(jié)點
List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
infos.clear();
try {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
root = mViewRootImpl.mView;
} else {
root = findViewByAccessibilityId(accessibilityViewId);
}
...
} finally {
try {
...
adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
// 通過callback binder proxy句柄,將節(jié)點信息binder回應用
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
infos.clear();
} catch (RemoteException re) {
/* ignore - the other side will time out */
}
...
}
}
AccessibilityInteractionClient.setFindAccessibilityNodeInfosResult
frameworks/base/core/java/android/view/accessibility/AccessibilityInteractionClient.java
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mInstanceLock) {
if (interactionId > mInteractionId) {
if (infos != null) {
...
// 設置應用的返回節(jié)點信息
if (!isIpcCall) {
mFindAccessibilityNodeInfosResult = new ArrayList<>(infos);
} else {
mFindAccessibilityNodeInfosResult = infos;
}
} else {
mFindAccessibilityNodeInfosResult = Collections.emptyList();
}
mInteractionId = interactionId;
}
// 釋放鎖,停止等待,節(jié)點信息已經取回
mInstanceLock.notifyAll();
}
}
findAccessibilityNodeInfosByText與performAction 對目標節(jié)點進行操作
AccessibilityNodeInfo.findAccessibilityNodeInfosByText
找到父節(jié)點信息后,就可以通過父節(jié)點獲取對應的子節(jié)點信息了
frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.java
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
...
// 一樣的流程,通過AccessibilityInteractionClient去獲取信息
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
text);
}
```
以下的代碼流程同getRootInActiveWindow大概一致,就不詳細分析了
#### AccessibilityNodeInfo.performAction
獲取到對應子節(jié)點后,通過performAction可以執(zhí)行對應的操作了,如常用的點擊
最終回調用到AccessibilityInteractionController,獲取到AccessibilityProvier后就可以執(zhí)行performAction的最終操作了
frameworks/base/core/java/android/view/AccessibilityInteractionController.java
```java
private void performAccessibilityActionUiThread(Message message) {
View target = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
target = findViewByAccessibilityId(accessibilityViewId);
} else {
target = mViewRootImpl.mView;
}
if (target != null && isShown(target)) {
AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
if (provider != null) {
if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
// 在客戶端執(zhí)行performAction操作
succeeded = provider.performAction(virtualDescendantId, action,
arguments);
} else {
succeeded = provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
action, arguments);
}
} else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
succeeded = target.performAccessibilityAction(action, arguments);
}
}
}
frameworks/base/core/java/android/view/View.java
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
...
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK: {
if (isClickable()) {
// 最終調用到我們熟悉的View.performClick方法
performClick();
return true;
}
} break;
...
}
分析到這里可以看到,Accessibility服務框架類似于hook在Android View組件樹中的一套實現(xiàn),它并不是獨立的一套機制,而是”寄生”在View的顯示、事件分發(fā)的流程中。
總結
功能實現(xiàn)依賴于ViewRootImpl, ViewGroup, View視圖層級管理的基本架構。在視圖變化時發(fā)出事件、當收到視圖操作請求時也能夠作出響應。
system_server在實現(xiàn)該功能的過程中扮演著中間人的角色。當被監(jiān)聽APP視圖變化時,APP首先會發(fā)出事件到system_server,隨后再中轉到監(jiān)聽者APP端。當監(jiān)聽者APP想要執(zhí)行視圖操作時,也是首先在system_server中找到對應的客戶端binder proxy,再調用相應接口調用到被監(jiān)聽APP中。完成相關操作后,通過已經獲取到的監(jiān)聽APP binder proxy句柄,直接binder call到對應的監(jiān)聽客戶端。
無障礙權限十分重要,切記不可濫用,APP自身也需要有足夠的安全意識,防止惡意應用通過該服務獲取用戶隱私信息
好了,以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對腳本之家的支持。
相關文章
Android RecyclerView添加搜索過濾器的示例代碼
本篇文章主要介紹了Android RecyclerView添加搜索過濾器的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01
解決Android Studio突然不顯示logcat日志的問題
這篇文章主要介紹了解決Android Studio突然不顯示logcat日志的問題,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-04-04
Android項目實戰(zhàn)之ListView懸浮頭部展現(xiàn)效果實現(xiàn)
這篇文章主要給大家介紹了Android項目實戰(zhàn)之ListView懸浮頭部展現(xiàn)效果實現(xiàn)的相關資料,文中通過實例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2022-01-01
詳解Android業(yè)務組件化之URL Schema使用
這篇文章主要為大家詳細介紹了Android業(yè)務組件化之URL Schema使用,感興趣的小伙伴們可以參考一下2016-09-09

