在《浮窗開發之窗口層級》這片文章中,開篇提出了三個問題:
- 窗口層級關系(浮窗是如何“浮”的) ?
- 浮窗有哪些限制,如何越過用戶授權實現浮窗功能?
- 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 -gt;View.dispatchPointerEvent-gt;PhoneWindow$DecorView.dispatchTouchEvent-gt;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 amp;amp; !isDestroyed() amp;amp; mFeatureId lt; 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 gt;=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事件傳遞流程 :
輸入事件傳遞-服務進程
參考資料:
Tags: Activity
文章來源:http://www.jianshu.com/p/47421ec56795