1. 程式人生 > >Activity是如何接收到touch事件的(視窗與使用者輸入系統)

Activity是如何接收到touch事件的(視窗與使用者輸入系統)

前兩個問題在前兩篇文章中已經分析,在這篇文章中我們以第三個問題為切入點,簡單分析一下視窗與使用者輸入的關係。

Touch事件是如何分發到Activity上來的?

Act事件傳遞流程?
正常的思路是直接去尋找Activity 的dispatchTouchEvent方法,我們看看Activity的dispatchTouchEvent()方法的呼叫棧,在方法中加入Thread.dumpStack()來檢視呼叫棧。

 @Override
  public boolean dispatchTouchEvent(MotionEvent ev) {
    Thread.dumpStack();
    return super.dispatchTouchEvent(ev);
}

輸出:

05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk       W/System.err: java.lang.Throwable: stack dump
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at java.lang.Thread.dumpStack(Thread.java:496)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at com.demo.liuguangli.suspendbox.MainActivity.dispatchTouchEvent(MainActivity.java:65)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at com.android.internal.policy.impl.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:1901)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.view.View.dispatchPointerEvent(View.java:7426)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:3220)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:3165)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:4292)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:4271)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:4363)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:179)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.os.MessageQueue.nativePollOnce(Native Method)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.os.MessageQueue.next(MessageQueue.java:125)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.os.Looper.loop(Looper.java:124)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5041)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at java.lang.reflect.Method.invokeNative(Native Method)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at java.lang.reflect.Method.invoke(Method.java:511)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)
05-09 09:35:39.775 8911-8911/com.yinhan.hunter.dksdk W/System.err:     at dalvik.system.NativeStart.main(Native Method)

一條粗略的線索:
ViewRootImpl-deliverInputEvent ->View.dispatchPointerEvent->PhoneWindow$DecorView.dispatchTouchEvent->MainActivity.dispatchTouchEvent,讀者可以根據這個線索去跟蹤原始碼。我們這裡先不深入其中細節,先來看看DecorView 到 Activity.dispatchTouchEvent 是如何呼叫的?

《浮窗開發之視窗層級》一文中,我們有講到Activity、PhoneWindow、DecorView的關係,我們先來回顧一下:

Activity和window的關係

window和view的關係
再來看看DecorView的 dispatchTouchEvent方法:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        final Callback cb = getCallback();
        return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                : super.dispatchTouchEvent(ev);
    }

DecorView 是view的子類重寫了dispatchTouchEvent方法,在這個方法中呼叫 Callback,這個Callback是Window的一個靜態內部介面類,Activity實現了這個介面,Activity的dispatchTouchEvent() 方法正是從Callback繼承而來。

Act事件傳遞流程

Touch事件是如何分發到浮窗的根檢視的?

思路同上:dump出根檢視的dispatchTouchEvent()方法呼叫棧:

05-09 11:58:50.623 11447-11447/com.float.mall W/System.err: java.lang.Throwable: stack dump
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at java.lang.Thread.dumpStack(Thread.java:496)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at com.jym.floatwinplugin.view.widget.FloatBallView.dispatchTouchEvent(FloatBallView.java:250)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.view.View.dispatchPointerEvent(View.java:7426)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.view.ViewRootImpl.deliverPointerEvent(ViewRootImpl.java:3220)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:3165)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:4292)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:4271)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:4363)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:179)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.os.MessageQueue.nativePollOnce(Native Method)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.os.MessageQueue.next(MessageQueue.java:125)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.os.Looper.loop(Looper.java:124)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5041)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at    java.lang.reflect.Method.invokeNative(Native Method)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at   java.lang.reflect.Method.invoke(Method.java:511)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:132)
05-09 11:58:50.623 11447-11447/com.float.mall W/System.err:     at dalvik.system.NativeStart.main(Native Method)

對比一下和Activity的關係,是不是發現有些似曾相識。在《浮窗開發之視窗層級》一文中我們講過Activity的顯示和浮窗的顯示本質上是將一個View和對應的LayoutParams
新增到WindowManagerService中管理。所以Activity的dispatchTouchEvent方法其實是View傳遞過來的。

我們可以猜測粗略線索是:touch事件-》硬體裝置-》某個服務-》 ViewRootImpl --》View。

輸入事件傳遞-某個服務

ViewRootImpl是個啥?

我們先來看看ViewRootImpl和View到底有啥關係?首先,看看WindowManager的addView方法,WindowManager是個介面,我們看其實現類WindowMangerImpl的原始碼:

…..
private final WindowManagerGlobal mGlobal =     WindowManagerGlobal.getInstance();
…..

@Override
public void addView(View view, ViewGroup.LayoutParams params) {
    mGlobal.addView(view, params, mDisplay, mParentWindow);
}

在來看看WindowManagerGlobal的原始碼:

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { 
    ViewRootImpl root; 
    View panelParentView = null;
    ...這裡省略了一堆程式碼
    root =new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams); mViews.add(view);
    mRoots.add(root); mParams.add(wparams);
  // do this last because it fires off messages to start doing things
try{
    root.setView(view, wparams, panelParentView);
 }catch(RuntimeException e) {
    // BadTokenException or InvalidDisplayException, clean up.
synchronized(mLock) {
    final int index = findViewLocked(view,false);
    if(index >=0) {
       removeViewLocked(index,true);
    }
   }
   throw  e;
  }
}

在這裡建立了ViewRootImpl物件,並且把傳單下來的view通過setView方法設定到其中的變數,來看看setView的原始碼:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
     synchronized (this) {
         if (mView == null) {
             mView = view;
            ...省略一堆程式碼
         }
     }


由此得到關係圖:

ViewRootImpl關係.png
通過WindowManagerImpl.addView,最終把View新增賦值到了ViewRootImpl的變數mView。ViewRootImpl是View(視窗)和WindowManagerService協議的紐帶
從MVC的角度來看的話:可以認為View是V,ViewRootImpl是Controller,WindowManagerService是Model。View的繪製、重新整理都需要通過ViewRootImpl與WindowManagerService互動,另外View的輸入事件(鍵盤、觸控)也是由ViewRootImpl傳遞給View的,那麼ViewRootImpl是如何監聽到使用者輸入事件的呢?

使用者輸入與視窗

回憶下上文點到的WindowInputEventReceiver,這是ViewRootImpl的一個內部類,我們dump出來的dispatchTouchEvent最初的地方就是源於這個類,再往下就是MessageQueue、Looper的資訊。由此可以推斷WindowInputEventReceiver是ViewRootImpl和底層某個服務進行IPC互動的關鍵,這個服務是什麼服務呢?

這部分涉及到Anddroid系統的兩個重要的模組:圖形視窗和使用者輸入,分別對應的服務是WindowManagerService和InputManagerService。WindowManagerService負責圖形視窗(View)的繪製、重新整理等事物、InputManagerService管理使用者輸入事件處理。

1、InputManagerService 管理者兩個角色InputReader和InputDispatcher 。
2、InputReader負責從硬體(EventHub)讀取輸入訊號,轉化成為事件,傳遞給InputDispatcher。
3、InputDispatcher將InputReader傳遞過來的事件分發到對應的場景,例如將touch事件分發到ViewRootImpl。

那麼InputManagerService(InputDispatcher)是如何將touch事件傳遞到ViewRootImpl(WindowInputEventReceiver)的呢?

使用者輸入事件處理模型是“生產者-消費者“模型,生產者發生在系統程序中,消費者發生在使用者程序中。傳遞過程由IPC互動,這裡的通訊是採用的Socket通訊,消費者需要向生產者”註冊“通訊管道,RegisterInputChannel建立連線。在ViewRootImpl的setView()方法中建立了WindowInputEventReceiver,並通過WindowManagerService向InputManagerService註冊InputChannel監聽輸入事件。

事件管道連線過程.png

TouchEvent事件傳遞流程 :

輸入事件傳遞-服務程序

參考資料: