UiAutomator原始碼學習(2)-- UiAutomationBridge

wangyy發表於2020-07-08

從上一章對UiDevice的學習,可以看出幾乎所有的操作都離不開 UiAutomationBridge。重新看一下UIDevice的構造方法:

  private UiDevice(Instrumentation instrumentation) {
        mInstrumentation = instrumentation;
        UiAutomation uiAutomation = instrumentation.getUiAutomation();
        mUiAutomationBridge = new InstrumentationUiAutomatorBridge(
                instrumentation.getContext(), uiAutomation);
        // Enable multi-window support for API level 21 and up
        if (UiDevice.API_LEVEL_ACTUAL >= Build.VERSION_CODES.LOLLIPOP) {
            // Subscribe to window information
            AccessibilityServiceInfo info = uiAutomation.getServiceInfo();
            info.flags |= AccessibilityServiceInfo.FLAG_RETRIEVE_INTERACTIVE_WINDOWS;
            uiAutomation.setServiceInfo(info);
        }
    }

 UiAutomationBridge 是一個抽象類。我們先看UiDevice的建構函式中,UiAutomatorBridge的實現類InstrumentationUiAutomatorBridge。這個類比較簡單複寫了getRotation和isScreenOn方法。接下來我們看一下這個抽象類的構造方法:

    UiAutomatorBridge(UiAutomation uiAutomation) {
        mUiAutomation = uiAutomation;
        mInteractionController = new InteractionController(this);
        mQueryController = new QueryController(this);
    }

在這裡初始化了 InteractionController和 QueryController這兩個類的物件。在學習UiDevice的時候應該還記得,幾乎所有的操作都是通過這兩個類來完成的。這裡是UiDevice裡的pressHome方法:

    /**
     * Simulates a short press on the HOME button.
     * @return true if successful, else return false
     * @since API Level 16
     */
    public boolean pressHome() {
        Tracer.trace();
        waitForIdle();
        return getAutomatorBridge().getInteractionController().sendKeyAndWaitForEvent(
                KeyEvent.KEYCODE_HOME, 0, AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
                KEY_PRESS_EVENT_TIMEOUT);
    }

通過這個方法可以看到,這個InteractionController可以向系統注入事件。那接下來就來看看這個InteractionController到底是怎麼向系統注入事件的。還是從構造方法看起:

 public InteractionController(UiAutomatorBridge bridge) {
        mUiAutomatorBridge = bridge;
    }

這個InteractionController持有UiAutomatorBridge的引用。並且在這個類中定義了很多模擬使用者的操作方法如,sendKeyAndWaitForEvent, touchDown,touchUp,swipe等,例如uiDevcie裡用到的sendKeyAndWaitForEvent。

 1     /**
 2      * Send keys and blocks until the first specified accessibility event.
 3      *
 4      * Most key presses will cause some UI change to occur. If the device is busy, this will
 5      * block until the device begins to process the key press at which point the call returns
 6      * and normal wait for idle processing may begin. If no events are detected for the
 7      * timeout period specified, the call will return anyway with false.
 8      *
 9      * @param keyCode
10      * @param metaState
11      * @param eventType
12      * @param timeout
13      * @return true if events is received, otherwise false.
14      */
15     public boolean sendKeyAndWaitForEvent(final int keyCode, final int metaState,
16             final int eventType, long timeout) {
17         Runnable command = new Runnable() {
18             @Override
19             public void run() {
20                 final long eventTime = SystemClock.uptimeMillis();
21                 KeyEvent downEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_DOWN,
22                         keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
23                         InputDevice.SOURCE_KEYBOARD);
24                 if (injectEventSync(downEvent)) {
25                     KeyEvent upEvent = new KeyEvent(eventTime, eventTime, KeyEvent.ACTION_UP,
26                             keyCode, 0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0, 0,
27                             InputDevice.SOURCE_KEYBOARD);
28                     injectEventSync(upEvent);
29                 }
30             }
31         };
32         return runAndWaitForEvents(command, new WaitForAnyEventPredicate(eventType), timeout)
33                 != null;
34     }

 Line17,定義一個Runnable物件,Runnable只是一個介面,它裡面只有一個run()方法,沒有start()方法,所以該物件無法啟動執行緒,必須依託其他類來啟動這個執行緒。

在這個run方法中,定義了一個KeyEvent事件,KeyEnvet物件是android.view.*包下的類,用於報告鍵和按鈕事件。每次按鍵是通過一系列按鍵事件來描述的。按鍵操作以ACTION_DOWN按鍵事件開始。如果金鑰被保持足夠長的時間以至於可以重複,則在初始按下後會出現其他具有ACTION_DOWN和getRepeatCount()非零值的金鑰事件。最後一個按鍵事件是用於按鍵啟動的ACTION_UP。如果取消按鍵,則按鍵事件將設定FLAG_CANCELED標誌。

這個run方法裡還有一個if判斷條件injectEventSync,通過這個方法名就可以看出這是用來判斷同步注入事件是否成功,在injectEventSync方法中,它呼叫了mUiAutomatorBridge.injectInputEvent(event, true);而mUiAutomatorBridge這個類的injectInputEvent方法裡,是呼叫的mUiAutomation.injectInputEvent(event, sync);而mUiAutomation是Android SDK中 android.app.UiAutomation這個類的物件,我們回過頭來看各個函式的建構函式發現,這個UiAutomation來自於UiDevice:

UiAutomation uiAutomation = instrumentation.getUiAutomation();

來看一下這個類中定義的injectInputEvent事件:

 

/**
     * A method for injecting an arbitrary input event.
     * <p>
     * <strong>Note:</strong> It is caller's responsibility to recycle the event.
     * </p>
     * @param event The event to inject.
     * @param sync Whether to inject the event synchronously.
     * @return Whether event injection succeeded.
     */
    public boolean injectInputEvent(InputEvent event, boolean sync) {
        synchronized (mLock) {
            throwIfNotConnectedLocked();
        }
        try {
            if (DEBUG) {
                Log.i(LOG_TAG, "Injecting: " + event + " sync: " + sync);
            }
            // Calling out without a lock held.
            return mUiAutomationConnection.injectInputEvent(event, sync);
        } catch (RemoteException re) {
            Log.e(LOG_TAG, "Error while injecting input event!", re);
        }
        return false;
    }

 

看來這裡也不是真正做事件注入的地方,mUiAutomationConnection是一個介面物件,這個物件是在UiAutomaton建構函式裡初始化的。看他的實現類UiAutomationConnection中的injectInputEvent方法。

 @Override
    public boolean injectInputEvent(InputEvent event, boolean sync) {
        synchronized (mLock) {
            throwIfCalledByNotTrustedUidLocked();
            throwIfShutdownLocked();
            throwIfNotConnectedLocked();
        }
        final int mode = (sync) ? InputManager.INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
                : InputManager.INJECT_INPUT_EVENT_MODE_ASYNC;
        final long identity = Binder.clearCallingIdentity();
        try {
            return mWindowManager.injectInputAfterTransactionsApplied(event, mode);
        } catch (RemoteException e) {
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
        return false;
    }
private final IWindowManager mWindowManager = IWindowManager.Stub.asInterface(
            ServiceManager.getService(Service.WINDOW_SERVICE));
package android.os;
public final class ServiceManager {
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }
}

從這裡可以看出mWindowManager是一個IBinder物件,通過這個物件呼叫openSession開啟一個Session,實現IPC通訊。看一下WindowManagerService裡的

injectInputAfterTransactionsApplied方法:
 1  @Override
 2     public boolean injectInputAfterTransactionsApplied(InputEvent ev, int mode) {
 3         boolean isDown;
 4         boolean isUp;
 5 
 6         if (ev instanceof KeyEvent) {
 7             KeyEvent keyEvent = (KeyEvent) ev;
 8             isDown = keyEvent.getAction() == KeyEvent.ACTION_DOWN;
 9             isUp = keyEvent.getAction() == KeyEvent.ACTION_UP;
10         } else {
11             MotionEvent motionEvent = (MotionEvent) ev;
12             isDown = motionEvent.getAction() == MotionEvent.ACTION_DOWN;
13             isUp = motionEvent.getAction() == MotionEvent.ACTION_UP;
14         }
15         final boolean isMouseEvent = ev.getSource() == InputDevice.SOURCE_MOUSE;
16 
17         // For ACTION_DOWN, syncInputTransactions before injecting input.
18         // For all mouse events, also sync before injecting.
19         // For ACTION_UP, sync after injecting.
20         if (isDown || isMouseEvent) {
21             syncInputTransactions();
22         }
23         final boolean result =
24                 LocalServices.getService(InputManagerInternal.class).injectInputEvent(ev, mode);
25         if (isUp) {
26             syncInputTransactions();
27         }
28         return result;
29     }
syncInputTransactions()這個方法是同步系統注入事件的事物,對於action up事件是在注入之後同步,其他的事件是在事件注入之前同步。 我們主要看一下事件注入.
LocalServices 的getService方法,返回一個實現了InputManagerInternal型別的Service, InputManagerInternal是一個抽象類,而injectInputEvent也是一個抽象方法。
那接下來我們就看一下這個InputManger型別的service。這是一個系統的服務 SystemService。
 1 /**
 2      * Injects an input event into the event system on behalf of an application.
 3      * The synchronization mode determines whether the method blocks while waiting for
 4      * input injection to proceed.
 5      * <p>
 6      * Requires {@link android.Manifest.permission.INJECT_EVENTS} to inject into
 7      * windows that are owned by other applications.
 8      * </p><p>
 9      * Make sure you correctly set the event time and input source of the event
10      * before calling this method.
11      * </p>
12      *
13      * @param event The event to inject.
14      * @param mode The synchronization mode.  One of:
15      * {@link #INJECT_INPUT_EVENT_MODE_ASYNC},
16      * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT}, or
17      * {@link #INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH}.
18      * @return True if input event injection succeeded.
19      *
20      * @hide
21      */
22     @UnsupportedAppUsage
23     public boolean injectInputEvent(InputEvent event, int mode) {
24         if (event == null) {
25             throw new IllegalArgumentException("event must not be null");
26         }
27         if (mode != INJECT_INPUT_EVENT_MODE_ASYNC
28                 && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_FINISH
29                 && mode != INJECT_INPUT_EVENT_MODE_WAIT_FOR_RESULT) {
30             throw new IllegalArgumentException("mode is invalid");
31         }
32         try {
33             return mIm.injectInputEvent(event, mode);
34         } catch (RemoteException ex) {
35             throw ex.rethrowFromSystemServer();
36         }
37     }

Line33,呼叫的是IInputManager.aidl裡的injectInputEvent,通過程式之間的通訊,實現了系統的事件注入。到此事件注入的流程分析完畢,先到此為止。再想深入研究就是Native層的邏輯了。

相關文章