Android AccessibilityService機制源碼解析

一、本文需要解決的問題

之前本人做了一個項目银受,需要用到AccessibilityService這個系統(tǒng)提供的拓展服務(wù)践盼。這個服務(wù)本意是作為Android系統(tǒng)的一個輔助功能,去幫助殘疾人更好地使用手機宾巍。但是由于它的一些特性咕幻,給很多項目的實現(xiàn)提供了一個新的思路顶霞,例如之前大名鼎鼎的微信搶紅包插件,本質(zhì)上就是使用了這個服務(wù)寺惫。我研究AccessibilityService的目的是解決以下幾個我在使用過程中所思考的問題:

  1. AccessibilityService這個Service跟一般的Service有什么區(qū)別?
  2. AccessibilityService是如何做到監(jiān)控并捕捉用戶行為的帮哈?
  3. AccessibilityService是如何做到查找控件,執(zhí)行點擊等操作的古拴?

二轻纪、初步分析

本文基于Android 7.1的源碼對AccessibilityService進行分析航厚。
為了更好地理解和分析代碼,我寫了一個demo轮锥,如果想學(xué)習(xí)具體的使用方法,可以參考Google官方文檔AccessibilityService。本文不做AccessibilityService的具體使用教程骑疆。

創(chuàng)建AccessibilityService
public class MyAccessibilityService extends AccessibilityService {

    private static final String TAG = "MyAccessibilityService";

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "onCreate");
    }

    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        int eventType = event.getEventType();
        switch (eventType) {
            case AccessibilityEvent.TYPE_VIEW_CLICKED:
                // 捕獲到點擊事件
                Log.i(TAG, "capture click event!");
                AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
                if (nodeInfo != null) {
                    // 查找text為Test!的控件
                    List<AccessibilityNodeInfo> button = nodeInfo.findAccessibilityNodeInfosByText("Test!");
                    nodeInfo.recycle();
                    for (AccessibilityNodeInfo item : button) {
                        Log.i(TAG, "long-click button!");
                        // 執(zhí)行長按操作
                        item.performAction(AccessibilityNodeInfo.ACTION_LONG_CLICK);
                    }
                }
                break;
            default:
                break;
        }
    }

    @Override
    public void onInterrupt() {
        Log.i(TAG, "onInterrupt");
    }
}
AccessibilityService配置

res/xml/accessibility_service_config.xml

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackSpoken"
    android:accessibilityFlags="flagRetrieveInteractiveWindows|flagRequestFilterKeyEvents"
    android:canRequestFilterKeyEvents="true"
    android:canRetrieveWindowContent="true"
    android:description="@string/app_name"
    android:notificationTimeout="100"
    android:packageNames="com.xu.accessibilitydemo" />
在manifest中進行注冊
<service
    android:name=".MyAccessibilityService"
    android:enabled="true"
    android:exported="true"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService"/>
    </intent-filter>

     <meta-data
         android:name="android.accessibilityservice"
         android:resource="@xml/accessibility_service_config"/>
</service>
創(chuàng)建一個text為Test!的button控件,設(shè)置監(jiān)聽方法
public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";

    private Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        button = findViewById(R.id.button);

        button.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Log.i(TAG, "onLongClick");
                return false;
            }
        });

    }
}
開啟AccessibilityService

AccessibilityService服務(wù)具體開啟位置在設(shè)置--無障礙中淮菠。

運行應(yīng)用踏拜,點擊text為Test!的按鈕

會出現(xiàn)以下的日志:


log.png

具體解釋:
點擊按鈕即產(chǎn)生TYPE_VIEW_CLICKED事件 --> 被AcceesibilityService捕獲 --> 捕獲后執(zhí)行長按按鈕操作 --> 執(zhí)行長按回調(diào)方法赋荆。

為什么AcceesibilityService能捕獲并執(zhí)行其他操作呢潦蝇,接下來我將對源碼進行解析~

三爽雄、源碼解析

AccessibilityService內(nèi)部邏輯
AccessibilityService.java
public abstract class AccessibilityService extends Service {
      // 省略代碼
      public abstract void onAccessibilityEvent(AccessibilityEvent event);
      
      public abstract void onInterrupt();

      @Override
      public final IBinder onBind(Intent intent) {
          return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
              @Override
              public void onServiceConnected() {
                  AccessibilityService.this.dispatchServiceConnected();
              }

              @Override
              public void onInterrupt() {
                  AccessibilityService.this.onInterrupt();
              }

              @Override
              public void onAccessibilityEvent(AccessibilityEvent event) {
                  AccessibilityService.this.onAccessibilityEvent(event);
              }

              @Override
              public void init(int connectionId, IBinder windowToken) {
                  mConnectionId = connectionId;
                  mWindowToken = windowToken;

                  // The client may have already obtained the window manager, so
                  // update the default token on whatever manager we gave them.
                  final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE);
                  wm.setDefaultToken(windowToken);
              }

              @Override
              public boolean onGesture(int gestureId) {
                  return AccessibilityService.this.onGesture(gestureId);
              }

              @Override
              public boolean onKeyEvent(KeyEvent event) {
                  return AccessibilityService.this.onKeyEvent(event);
              }

              @Override
              public void onMagnificationChanged(@NonNull Region region,
                      float scale, float centerX, float centerY) {
                  AccessibilityService.this.onMagnificationChanged(region, scale, centerX, centerY);
              }

              @Override
              public void onSoftKeyboardShowModeChanged(int showMode) {
                  AccessibilityService.this.onSoftKeyboardShowModeChanged(showMode);
              }

              @Override
              public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
                  AccessibilityService.this.onPerformGestureResult(sequence, completedSuccessfully);
              }
          });
      }
}

分析:

  1. AccessibilityService是一個抽象類布蔗,繼承于Service,提供兩個抽象方法 onAccessibilityEvent() 和 onInterrupt()搓蚪;
  2. 雖然是抽象類空凸,但是實現(xiàn)了最重要的 onBind() 方法兵罢,在其中創(chuàng)建了一個IAccessibilityServiceClientWrapper對象,實現(xiàn)Callbacks接口中的抽象方法滓窍。
IAccessibilityServiceClientWrapper
// 以分析onAccessibilityEvent為例卖词,省略部分代碼
public static class IAccessibilityServiceClientWrapper extends IAccessibilityServiceClient.Stub
            implements HandlerCaller.Callback {
    private final HandlerCaller mCaller;
    private final Callbacks mCallback;
    private int mConnectionId;
    
    public IAccessibilityServiceClientWrapper(Context context, Looper looper,
                Callbacks callback) {
        mCallback = callback;
        mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/);
    }
    
    public void init(IAccessibilityServiceConnection connection, int connectionId,
                IBinder windowToken) {
        Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId,
                    connection, windowToken);
        mCaller.sendMessage(message);
    }
    
    // 省略部分代碼 

    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);
                    mCallback.onAccessibilityEvent(event);
                    // Make sure the event is recycled.
                    try {
                        event.recycle();
                    } catch (IllegalStateException ise) {
                        /* ignore - best effort */
                    }
                }
            } return;
            // ...         
        }
     }
}

分析:

  1. IAccessibilityServiceClientWrapper繼承于IAccessibilityServiceClient類,它是一個aidl接口吏夯,同時注意到它是繼承于IAccessibilityServiceClient.Stub類此蜈,可以大概猜測到,AccessibilityService為一個遠程Service噪生,使用到跨進程通信技術(shù)裆赵,后面我還會繼續(xù)分析這個;
  2. IAccessibilityServiceClientWrapper的類構(gòu)造方法中跺嗽,有兩個比較重要的參數(shù)战授,一個是looper页藻,另一個是Callbacks callback。Looper不用說植兰,而Callbacks接口定義了很多方法份帐,代碼如下:
public interface Callbacks {
    public void onAccessibilityEvent(AccessibilityEvent event);
    public void onInterrupt();
    public void onServiceConnected();
    public void init(int connectionId, IBinder windowToken);
    public boolean onGesture(int gestureId);
    public boolean onKeyEvent(KeyEvent event);
    public void onMagnificationChanged(@NonNull Region region,
                float scale, float centerX, float centerY);
    public void onSoftKeyboardShowModeChanged(int showMode);
    public void onPerformGestureResult(int sequence, boolean completedSuccessfully);
}
  1. IAccessibilityServiceClientWrapper同時也實現(xiàn)了HandlerCaller.Callback接口,HandlerCaller類通過命名也可以知道钉跷,它內(nèi)部含有一個Handler實例弥鹦,所以可以把它當(dāng)做一個Handler,而處理信息的方法就是HandlerCaller.Callback#executeMessage(msg)方法
  2. 代碼有點繞爷辙,故簡單總結(jié)一下流程:
    AccessibilityEvent產(chǎn)生
    ? -> Binder驅(qū)動
    ?? -> IAccessibilityServiceClientWrapper#onAccessibilityEvent(AccessibilityEvent)
    ??? -> HandlerCaller#sendMessage(message); // message中包括AccessibilityEvent
    ???? -> IAccessibilityServiceClientWrapper#executeMessage();
    ????? -> Callbacks#onAccessibilityEvent(event);
    ?????? -> AccessibilityService.this.onAccessibilityEvent(event);

到這里解決了我們的第一個問題:AccessibilityService同樣繼承于Service類彬坏,它屬于遠程服務(wù)類,是Android系統(tǒng)提供的一種服務(wù)膝晾,可以綁定此服務(wù)栓始,用于捕捉界面的一些特定事件。

AccessibilityService外部邏輯

前面分析了接收到AccessibilityEvent之后的代碼邏輯血当,那么幻赚,這些AccessibilityEvent是怎樣產(chǎn)生的呢,而且臊旭,在回調(diào)執(zhí)行之后是怎么做到點擊等操作的(如demo所示)落恼?我們接下來繼續(xù)分析相關(guān)的源碼~

我們從demo作為例子開始入手,首先我們知道离熏,一個點擊事件的產(chǎn)生佳谦,實際代碼邏輯是在View#onTouchEvent() -> View#performClick()中:

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;
    }
    // !W檀痢钻蔑!
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}

這里找到一個重點方法sendAccessibilityEvent(),繼續(xù)跟進去奸鸯,最后走到View#sendAccessibilityEventUncheckedInternal()方法:

public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
    if (!isShown()) {
        return;
    }
    onInitializeAccessibilityEvent(event);
    // Only a subset of accessibility events populates text content.
    if ((event.getEventType() & POPULATING_ACCESSIBILITY_EVENT_TYPES) != 0) {
        dispatchPopulateAccessibilityEvent(event);
    }
    // In the beginning we called #isShown(), so we know that getParent() is not null.
    getParent().requestSendAccessibilityEvent(this, event);
}

這里的getParent()會返回一個實現(xiàn)ViewParent接口的對象咪笑。
我們可以簡單理解為,它會讓View的父類執(zhí)行requestSendAccessibilityEvent()方法娄涩,而View的父類一般為ViewGroup窗怒,我們查看ViewGroup#requestSendAccessibilityEvent()方法

@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
    ViewParent parent = mParent;
    if (parent == null) {
        return false;
    }
    final boolean propagate = onRequestSendAccessibilityEvent(child, event);
    if (!propagate) {
        return false;
    }
    return parent.requestSendAccessibilityEvent(this, event);
}

這里涉及到一個變量mParent,我們要找到這個mParent變量是在哪里被賦值的蓄拣。
首先我們在View類中找到一個相關(guān)的方法View#assignParent():

void assignParent(ViewParent parent) {
    if (mParent == null) {
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but" + " it already has a parent");
    }
}

但是View類中并沒有調(diào)用此方法扬虚,猜測是View的父類進行調(diào)用。
通過對源碼進行搜索弯蚜,發(fā)現(xiàn)最后是在ViewRootImpl#setView()中進行調(diào)用,賦值的是this即ViewRootImpl本身剃法。
直接跳到ViewRootImpl#requestSendAccessibilityEvent()方法:

@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
    if (mView == null || mStopped || mPausedForTransition) {
        return false;
    }
    // Intercept accessibility focus events fired by virtual nodes to keep
    // track of accessibility focus position in such nodes.
    final int eventType = event.getEventType();
    switch (eventType) {
        case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
            {
                final long sourceNodeId = event.getSourceNodeId();
                final int accessibilityViewId = AccessibilityNodeInfo.getAccessibilityViewId(sourceNodeId);
                View source = mView.findViewByAccessibilityId(accessibilityViewId);
                if (source != null) {
                    AccessibilityNodeProvider provider = source.getAccessibilityNodeProvider();
                    if (provider != null) {
                        final int virtualNodeId = AccessibilityNodeInfo.getVirtualDescendantId(sourceNodeId);
                        final AccessibilityNodeInfo node;
                        if (virtualNodeId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
                            node = provider.createAccessibilityNodeInfo(AccessibilityNodeProvider.HOST_VIEW_ID);
                        } else {
                            node = provider.createAccessibilityNodeInfo(virtualNodeId);
                        }
                        setAccessibilityFocus(source, node);
                    }
                }
            }
            break;
            // 省略部分代碼
    }
    // K檗唷!!
    mAccessibilityManager.sendAccessibilityEvent(event);
    return true;
}

重點:AccessibilityManager#sendAccessibilityEvent(event)

public void sendAccessibilityEvent(AccessibilityEvent event) {
    final IAccessibilityManager service;
    final int userId;
    synchronized(mLock) {
        service = getServiceLocked();
        if (service == null) {
            return;
        }
        if (!mIsEnabled) {
            Looper myLooper = Looper.myLooper();
            if (myLooper == Looper.getMainLooper()) {
                throw new IllegalStateException("Accessibility off. Did you forget to check that?");
            } else {
                // If we're not running on the thread with the main looper, it's possible for
                // the state of accessibility to change between checking isEnabled and
                // calling this method. So just log the error rather than throwing the
                // exception.
                Log.e(LOG_TAG, "AccessibilityEvent sent with accessibility disabled");
                return;
            }
        }
        userId = mUserId;
    }
    boolean doRecycle = false;
    try {
        event.setEventTime(SystemClock.uptimeMillis());
        // it is possible that this manager is in the same process as the service but
        // client using it is called through Binder from another process. Example: MMS
        // app adds a SMS notification and the NotificationManagerService calls this method
        long identityToken = Binder.clearCallingIdentity();
        // J粘=!
        doRecycle = service.sendAccessibilityEvent(event, userId);
        Binder.restoreCallingIdentity(identityToken);
        if (DEBUG) {
            Log.i(LOG_TAG, event + " sent");
        }
    } catch (RemoteException re) {
        Log.e(LOG_TAG, "Error during sending " + event + " ", re);
    } finally {
        if (doRecycle) {
            event.recycle();
        }
    }
}

private IAccessibilityManager getServiceLocked() {
    if (mService == null) {
        tryConnectToServiceLocked(null);
    }
    return mService;
}

private void tryConnectToServiceLocked(IAccessibilityManager service) {
    if (service == null) {
        IBinder iBinder = ServiceManager.getService(Context.ACCESSIBILITY_SERVICE);
        if (iBinder == null) {
            return;
        }
        service = IAccessibilityManager.Stub.asInterface(iBinder);
    }
    try {
        final int stateFlags = service.addClient(mClient, mUserId);
        setStateLocked(stateFlags);
        mService = service;
    } catch (RemoteException re) {
        Log.e(LOG_TAG, "AccessibilityManagerService is dead", re);
    }
}

這里有使用到Android Binder機制诵叁,重點為IAccessibilityManager#sendAccessibilityEvent()方法雁竞,這里調(diào)用的是代理方法,實際代碼邏輯在AccessibilityManagerService#sendAccessibilityEvent():

@Override
public boolean sendAccessibilityEvent(AccessibilityEvent event, int userId) {
    synchronized(mLock) {
        // We treat calls from a profile as if made by its parent as profiles
        // share the accessibility state of the parent. The call below
        // performs the current profile parent resolution..
        final int resolvedUserId = mSecurityPolicy.resolveCallingUserIdEnforcingPermissionsLocked(userId);
        // This method does nothing for a background user.
        if (resolvedUserId != mCurrentUserId) {
            return true; // yes, recycle the event
        }
        if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) {
            mSecurityPolicy.updateActiveAndAccessibilityFocusedWindowLocked(event.getWindowId(), event.getSourceNodeId(), event.getEventType(), event.getAction());
            mSecurityPolicy.updateEventSourceLocked(event);
            // E《睢1摺!
            notifyAccessibilityServicesDelayedLocked(event, false);
            notifyAccessibilityServicesDelayedLocked(event, true);
        }
        if (mHasInputFilter && mInputFilter != null) {
            mMainHandler.obtainMessage(MainHandler.MSG_SEND_ACCESSIBILITY_EVENT_TO_INPUT_FILTER, AccessibilityEvent.obtain(event)).sendToTarget();
        }
        event.recycle();
    }
    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)) {
                    service.notifyAccessibilityEvent(event);
                }
            }
        }
    } catch (IndexOutOfBoundsException oobe) {
        // An out of bounds exception can happen if services are going away
        // as the for loop is running. If that happens, just bail because
        // there are no more services to notify.
    }
}
  1. 在方法中侥锦,最后會調(diào)用notifyAccessibilityServicesDelayedLocked()方法进栽,然后將event進行回收;
  2. 在notifyAccessibilityServicesDelayedLocked()方法中恭垦,會獲得所有Bound即綁定的Service快毛,執(zhí)行notifyAccessibilityEvent()方法,通過跟蹤代碼邏輯番挺,最后會調(diào)用綁定Service的onAccessibilityEvent()方法唠帝。綁定的Service是指我們自己實現(xiàn)的繼承于AccessibilityService的Service類,當(dāng)你在設(shè)置-無障礙中開啟服務(wù)之后即將服務(wù)綁定到AccessibilityManagerService中玄柏。

這樣我們解決了第二個問題:
AccessibilityService是如何做到監(jiān)控捕捉用戶行為的:(以點擊事件為例)
AccessibilityEvent產(chǎn)生:
View#performClick()
? -> View#sendAccessibilityEventUncheckedInternal()
?? -> ViewGroup#requestSendAccessibilityEvent()
??? -> ViewRootImpl#requestSendAccessibilityEvent()
???? -> AccessibilityManager#sendAccessibilityEvent(event)
????? -> AccessibilityManagerService#sendAccessibilityEvent()
?????? -> AccessibilityManagerService#notifyAccessibilityServicesDelayedLocked()
??????? -> Service#notifyAccessibilityEvent(event)

AccessibilityEvent處理:
AccessibilityEvent
? -> Binder驅(qū)動
?? -> IAccessibilityServiceClientWrapper#onAccessibilityEvent(AccessibilityEvent)
??? -> HandlerCaller#sendMessage(message); // message中包括AccessibilityEvent
???? -> IAccessibilityServiceClientWrapper#executeMessage();
????? -> Callbacks#onAccessibilityEvent(event);
?????? -> AccessibilityService.this.onAccessibilityEvent(event);

AccessibilityService交互之查找控件

在demo中襟衰,我們在MyAccessibilityService中調(diào)用了getRootInActiveWindow()方法獲取被監(jiān)控的View的所有結(jié)點,這些結(jié)點都封裝成一個AccessibilityNodeInfo對象中禁荸。同時也調(diào)用AccessibilityNodeInfo#findAccessibilityNodeInfosByText()方法查找相應(yīng)的控件右蒲。
這些方法的本質(zhì)是調(diào)用了AccessibilityInteractionClient類的對應(yīng)方法。
以AccessibilityInteractionClient#findAccessibilityNodeInfosByText()為例:

public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(int connectionId, int accessibilityWindowId, long accessibilityNodeId, String text) {
    try {
        IAccessibilityServiceConnection connection = getConnection(connectionId);
        if (connection != null) {
            final int interactionId = mInteractionIdCounter.getAndIncrement();
            final long identityToken = Binder.clearCallingIdentity();
            final boolean success = connection.findAccessibilityNodeInfosByText(accessibilityWindowId, accessibilityNodeId, text, interactionId, this, Thread.currentThread().getId());
            Binder.restoreCallingIdentity(identityToken);
            if (success) {
                List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(interactionId);
                if (infos != null) {
                    finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
                    return infos;
                }
            }
        } else {
            if (DEBUG) {
                Log.w(LOG_TAG, "No connection for connection id: " + connectionId);
            }
        }
    } catch (RemoteException re) {
        Log.w(LOG_TAG, "Error while calling remote" + " findAccessibilityNodeInfosByViewText", re);
    }
    return Collections.emptyList();
}

代碼邏輯比較簡單赶熟,就是直接調(diào)用IAccessibilityServiceConnection#findAccessibilityNodeInfosByText()方法瑰妄。
IAccessibilityServiceConnection是一個aidl接口,從注釋看映砖,它是AccessibilitySerivce和AccessibilityManagerService之間溝通的橋梁间坐。
猜想代碼真正的實現(xiàn)在AccessibilityManagerService中。
AccessibilityManagerService.Service#findAccessibilityNodeInfosByText():

@Override
public boolean findAccessibilityNodeInfosByText(int accessibilityWindowId, long accessibilityNodeId, String text, int interactionId, IAccessibilityInteractionConnectionCallback callback, long interrogatingTid) throws RemoteException {
    final int resolvedWindowId;
    IAccessibilityInteractionConnection connection = null;
    Region partialInteractiveRegion = Region.obtain();
    synchronized(mLock) {
        if (!isCalledForCurrentUserLocked()) {
            return false;
        }
        resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
        final boolean permissionGranted = mSecurityPolicy.canGetAccessibilityNodeInfoLocked(this, resolvedWindowId);
        if (!permissionGranted) {
            return false;
        } else {
            connection = getConnectionLocked(resolvedWindowId);
            if (connection == null) {
                return false;
            }
        }
        if (!mSecurityPolicy.computePartialInteractiveRegionForWindowLocked(resolvedWindowId, partialInteractiveRegion)) {
            partialInteractiveRegion.recycle();
            partialInteractiveRegion = null;
        }
    }
    final int interrogatingPid = Binder.getCallingPid();
    final long identityToken = Binder.clearCallingIdentity();
    MagnificationSpec spec = getCompatibleMagnificationSpecLocked(resolvedWindowId);
    try {
        connection.findAccessibilityNodeInfosByText(accessibilityNodeId, text, partialInteractiveRegion, interactionId, callback, mFetchFlags, interrogatingPid, interrogatingTid, spec);
        return true;
    } catch (RemoteException re) {
        if (DEBUG) {
            Slog.e(LOG_TAG, "Error calling findAccessibilityNodeInfosByText()");
        }
    } finally {
        Binder.restoreCallingIdentity(identityToken);
        // Recycle if passed to another process.
        if (partialInteractiveRegion != null && Binder.isProxy(connection)) {
            partialInteractiveRegion.recycle();
        }
    }
    return false;
}
  1. 此方法在AccessibilityManagerService的內(nèi)部類Service中實現(xiàn)邑退,這個Service繼承于IAccessibilityServiceConnection.Stub竹宋,驗證了我上面的猜想是正確的;
  2. 代碼重點是調(diào)用connection.findAccessibilityNodeInfosByText()地技,這里的connection實例與上面不同蜈七,它隸屬于IAccessibilityInteractionConnection類。這個類同樣是一個aidl接口莫矗,從注釋上看飒硅,它又是AccessibilityManagerService與指定窗口的ViewRoot之間溝通的橋梁砂缩。
    再次猜想,真正的代碼邏輯在ViewRootImpl中三娩。
    查看ViewRootImpl.AccessibilityInteractionConnection#findAccessibilityNodeInfosByText():
@Override
public void findAccessibilityNodeInfosByText(long accessibilityNodeId, String text, Region interactiveRegion, int interactionId, IAccessibilityInteractionConnectionCallback callback, int flags, int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
    ViewRootImpl viewRootImpl = mViewRootImpl.get();
    if (viewRootImpl != null && viewRootImpl.mView != null) {
        viewRootImpl.getAccessibilityInteractionController().findAccessibilityNodeInfosByTextClientThread(accessibilityNodeId, text, interactiveRegion, interactionId, callback, flags, interrogatingPid, interrogatingTid, spec);
    } else {
        // We cannot make the call and notify the caller so it does not wait.
        try {
            callback.setFindAccessibilityNodeInfosResult(null, interactionId);
        } catch (RemoteException re) {
            /* best effort - ignore */
        }
    }
}
  1. 同樣的庵芭,此方法在ViewRootImpl的內(nèi)部類AccessibilityInteractionConnection中實現(xiàn),這個內(nèi)部類繼承于IAccessibilityServiceConnection.Stub雀监,驗證了我的猜想双吆;
  2. 查找控件等操作,ViewRootImpl并不是直接處理会前,而是交給AccessibilityInteractionController類去查找好乐,查找到的結(jié)果會保存到一個callback中,這個callback為IAccessibilityInteractionConnectionCallback類型回官,它也是一個aidl接口曹宴,而AccessibilityInteractionClient類繼承了IAccessibilityInteractionConnectionCallback.Stub,即最后查詢后的結(jié)果會回調(diào)到AccessibilityInteractionClient類中歉提,如上面AccessibilityInteractionClient#findAccessibilityNodeInfosByText()方法笛坦,最后會調(diào)用getFindAccessibilityNodeInfosResultAndClear()方法獲取結(jié)果。具體如何尋找指定控件則不再分析代碼苔巨。
AccessibilityService交互之執(zhí)行控件操作

類似的版扩,與上面的流程基本相同,只是回調(diào)的時候侄泽,返回的是執(zhí)行操作的返回值(True or False)礁芦。

到這里,我們解決了最后一個問題:
AccessibilityService是如何做到查找控件悼尾,執(zhí)行點擊等操作的柿扣?
總結(jié):
尋找指定控件/執(zhí)行操作
? -> 交給AccessibilityInteractionClient類處理
? ? -> Binder
? ? ? -> AccessibilityManagerService類進行查找/執(zhí)行操作
? ? ? ? -> Binder
? ? ? ? ? -> 指定窗口的ViewRoot(ViewRootImpl)進行查找/執(zhí)行操作
? ? ? ? <- Binder
? ? <- 結(jié)果回調(diào)到AccessibilityInteractionClient類

四、有用代碼記錄

  1. HandlerCaller類:結(jié)合Handler類和自定義的接口類(Caller.java)闺魏,利用Handler的消息循環(huán)機制來分發(fā)消息未状,將最終的處理函數(shù)交給Caller#executeMessage():
// HandlerCaller.java
public class HandlerCaller {
    final Looper mMainLooper;
    final Handler mH;

    final Callback mCallback;

    class MyHandler extends Handler {
        MyHandler(Looper looper, boolean async) {
            super(looper, null, async);
        }

        @Override
        public void handleMessage(Message msg) {
            mCallback.executeMessage(msg);
        }
    }

    public interface Callback {
        public void executeMessage(Message msg);
    }

    public HandlerCaller(Context context, Looper looper, Callback callback,
            boolean asyncHandler) {
        mMainLooper = looper != null ? looper : context.getMainLooper();
        mH = new MyHandler(mMainLooper, asyncHandler);
        mCallback = callback;
    }

    public Handler getHandler() {
        return mH;
    }

    public void executeOrSendMessage(Message msg) {
        // If we are calling this from the main thread, then we can call
        // right through.  Otherwise, we need to send the message to the
        // main thread.
        if (Looper.myLooper() == mMainLooper) {
            mCallback.executeMessage(msg);
            msg.recycle();
            return;
        }
        
        mH.sendMessage(msg);
    }

    public void sendMessageDelayed(Message msg, long delayMillis) {
        mH.sendMessageDelayed(msg, delayMillis);
    }

    public boolean hasMessages(int what) {
        return mH.hasMessages(what);
    }
    
    public void removeMessages(int what) {
        mH.removeMessages(what);
    }
    
    public void removeMessages(int what, Object obj) {
        mH.removeMessages(what, obj);
    }
    
    public void sendMessage(Message msg) {
        mH.sendMessage(msg);
    }

    public SomeArgs sendMessageAndWait(Message msg) {
        if (Looper.myLooper() == mH.getLooper()) {
            throw new IllegalStateException("Can't wait on same thread as looper");
        }
        SomeArgs args = (SomeArgs)msg.obj;
        args.mWaitState = SomeArgs.WAIT_WAITING;
        mH.sendMessage(msg);
        synchronized (args) {
            while (args.mWaitState == SomeArgs.WAIT_WAITING) {
                try {
                    args.wait();
                } catch (InterruptedException e) {
                    return null;
                }
            }
        }
        args.mWaitState = SomeArgs.WAIT_NONE;
        return args;
    }

    public Message obtainMessage(int what) {
        return mH.obtainMessage(what);
    }
     
    // 省略部分代碼
}
  1. HandlerCaller#sendMessageAndWait():
public SomeArgs sendMessageAndWait(Message msg) {
    if (Looper.myLooper() == mH.getLooper()) {
        throw new IllegalStateException("Can't wait on same thread as looper");
    }
    SomeArgs args = (SomeArgs) msg.obj;
    args.mWaitState = SomeArgs.WAIT_WAITING;
    mH.sendMessage(msg);
    synchronized(args) {
        while (args.mWaitState == SomeArgs.WAIT_WAITING) {
            try {
                args.wait();
            } catch (InterruptedException e) {
                return null;
            }
        }
    }
    args.mWaitState = SomeArgs.WAIT_NONE;
    return args;
}

這篇文章會同步到我的個人日志,如有問題析桥,請大家踴躍提出司草,謝謝大家!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末泡仗,一起剝皮案震驚了整個濱河市埋虹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌娩怎,老刑警劉巖搔课,帶你破解...
    沈念sama閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異截亦,居然都是意外死亡爬泥,警方通過查閱死者的電腦和手機旦事,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來急灭,“玉大人,你說我怎么就攤上這事谷遂≡岵觯” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵肾扰,是天一觀的道長畴嘶。 經(jīng)常有香客問我,道長集晚,這世上最難降的妖魔是什么窗悯? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮偷拔,結(jié)果婚禮上蒋院,老公的妹妹穿的比我還像新娘。我一直安慰自己莲绰,他們只是感情好欺旧,可當(dāng)我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著蛤签,像睡著了一般辞友。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上震肮,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天称龙,我揣著相機與錄音,去河邊找鬼戳晌。 笑死鲫尊,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的躬厌。 我是一名探鬼主播马昨,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼扛施!你這毒婦竟也來了鸿捧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤疙渣,失蹤者是張志新(化名)和其女友劉穎匙奴,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體妄荔,經(jīng)...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡泼菌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年谍肤,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哗伯。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡荒揣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出焊刹,到底是詐尸還是另有隱情系任,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布虐块,位于F島的核電站俩滥,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏贺奠。R本人自食惡果不足惜霜旧,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望儡率。 院中可真熱鬧挂据,春花似錦、人聲如沸儿普。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽箕肃。三九已至婚脱,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間勺像,已是汗流浹背障贸。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留吟宦,地道東北人篮洁。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像殃姓,于是被迫代替她去往敵國和親袁波。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內(nèi)容