轉自:http://blog.csdn.net/stonecao/article/details/6759189
對於android的窗口window管理,一直感覺很混亂,總想找個時間好好研究,卻不知如何入手,現在寫的Touch事件派發過程詳解,其實跟android的窗口window管理服務WindowManagerService存在緊密聯繫,所以從這裏入手切入到WindowManagerService的研究,本blog主要講述一個touch事件如何從用戶消息的採集,到WindowManagerService對Touch事件的派發,再到一個Activity窗口touch事件的派發,並着重講了Activity窗口touch事件的派發,因爲這個的理解對我們寫應用很好地處理touch事件很重要
一.用戶事件採集到WindowManagerService和派發
--1.WindowManagerService,顧名思義,它是是一個窗口管理系統服務,它的主要功能包含如下:
--窗口管理,繪製
--轉場動畫--Activity切換動畫
--Z-ordered的維護,Activity窗口顯示前後順序
--輸入法管理
--Token管理
--系統消息收集線程
--系統消息分發線程
這裏,我關注的是系統消息的收集和系統消息的分發,其他功能,當我對WindowManagerService有一個完整的研究後在發blog
--2.系統消息收集和分發線程的創建
這個的從WindowManagerService服務的創建說起,與其他系統服務一樣,WindowManagerService在systemServer中創建的:
ServerThread.run
-->WindowManagerService.main
-->WindowManagerService.WMThread.run(構建一個專門線程負責WindowManagerService)
-->WindowManagerService s = new WindowManagerService(mContext, mPM,mHaveInputMethods);
--mQueue = new KeyQ();//消息隊列,在構造KeyQ中會創建一個InputDeviceReader線程去讀取用戶輸入消息
--mInputThread = new InputDispatcherThread();//創建一個消息分發線程,讀取並處理mQueue中消息
整個過程處理原理很簡單,典型的生產者消費者模型,我先畫個圖,後面針對代碼進一步說明
--3.InputDeviceReader線程,KeyQ構建時,會啓動一個線程去讀取用戶消息,具體代碼在KeyInputQueue.mThread,在構造函數中,mThread會start,接下來,接研究一下mThread.run:
//用戶輸入事件消息讀取線程
Thread mThread = new Thread("InputDeviceReader") {
public void run() {
RawInputEvent ev = new RawInputEvent();
while (true) {//開始消息讀取循環
try {
InputDevice di;
//本地方法實現,讀取用戶輸入事件
readEvent(ev);
//根據ev事件進行相關處理
...
synchronized (mFirst) {//mFirst是keyQ隊列頭指針
...
addLocked(di, curTimeNano, ev.flags,RawInputEvent.CLASS_TOUCHSCREEN, me);
...
}
}
}
}
函數我也沒有看大明白:首先調用本地方法readEvent(ev);去讀取用戶消息,這個消息包括按鍵,觸摸,滾輪等所有用戶輸入事件,後面不同的事件類型會有不同的處理,不過最後事件都要添加到keyQ的隊列中,通過addLocked函數
--4隊列添加和讀取函數addLocked,getEvent
addLocked函數比較簡單,就分析一下,有助於對消息隊列KeyQ的數據結構進行理解:
//event加入inputQueue隊列
private void addLocked(InputDevice device, long whenNano, int flags,
int classType, Object event) {
boolean poke = mFirst.next == mLast;//poke爲true表示消息隊列爲空
//從QueuedEvent緩存QueuedEvent獲取一個QueuedEvent對象,並填入用戶事件數據,包裝成一個QueuedEvent
QueuedEvent ev = obtainLocked(device, whenNano, flags, classType, event);
QueuedEvent p = mLast.prev;//隊列尾節點爲mLast,把ev添加到mlast前
while (p != mFirst && ev.whenNano < p.whenNano) {
p = p.prev;
}
ev.next = p.next;
ev.prev = p;
p.next = ev;
ev.next.prev = ev;
ev.inQueue = true;
if (poke) {//poke爲true,意味着在空隊列中添加了一個QueuedEvent,這時系統消息分發線程可能在wait,需要notify一下
long time;
if (MEASURE_LATENCY) {
time = System.nanoTime();
}
mFirst.notify();//喚醒在 mFirst上等待的線程
mWakeLock.acquire();
if (MEASURE_LATENCY) {
lt.sample("1 addLocked-queued event ", System.nanoTime() - time);
}
}
}
很簡單,使用mFirst,mLast實現的指針隊列,addLocked是QueuedEvent對象添加函數,對應在系統消息分發線程中會有一個getEvent函數來讀取inputQueue隊列的消息,我在這裏也先講一下:
QueuedEvent getEvent(long timeoutMS) {
long begin = SystemClock.uptimeMillis();
final long end = begin+timeoutMS;
long now = begin;
synchronized (mFirst) {//獲取mFirst上同步鎖
while (mFirst.next == mLast && end > now) {
try {//mFirst.next == mLast意味隊列爲空,同步等待mFirst鎖對象
mWakeLock.release();
mFirst.wait(end-now);
}
catch (InterruptedException e) {
}
now = SystemClock.uptimeMillis();
if (begin > now) {
begin = now;
}
}
if (mFirst.next == mLast) {
return null;
}
QueuedEvent p = mFirst.next;//返回mFirst的下一個節點爲處理的QueuedEvent
mFirst.next = p.next;
mFirst.next.prev = mFirst;
p.inQueue = false;
return p;
}
}
通過上面兩個函數得知,消息隊列是通過mFirst,mLast實現的生產者消費模型的同步鏈表隊列
--5.InputDispatcherThread線程
InputDispatcherThread處理InputDeviceReader線程存放在KeyInputQueue隊列中的消息,分發到具體的一個客戶端的IWindow
InputDispatcherThread.run
-->windowManagerService.process{
...
while (true) {
// 從mQueue(KeyQ)獲取一個用戶輸入事件,正上調用我上面提到的getEvent方法,若隊列爲空,線程阻塞掛起
QueuedEvent ev = mQueue.getEvent(
(int)((!configChanged && curTime < nextKeyTime)
? (nextKeyTime-curTime) : 0));
...
try {
if (ev != null) {
...
if (ev.classType == RawInputEvent.CLASS_TOUCHSCREEN) {//touch事件
eventType = eventType((MotionEvent)ev.event);
} else if (ev.classType == RawInputEvent.CLASS_KEYBOARD ||
ev.classType == RawInputEvent.CLASS_TRACKBALL) {//鍵盤輸入事件
eventType = LocalPowerManager.BUTTON_EVENT;
} else {
eventType = LocalPowerManager.OTHER_EVENT;//其他事件
}
...
switch (ev.classType) {
case RawInputEvent.CLASS_KEYBOARD:
...
dispatchKey((KeyEvent)ev.event, 0, 0);//鍵盤輸入,派發key事件
mQueue.recycleEvent(ev);
break;
case RawInputEvent.CLASS_TOUCHSCREEN:
dispatchPointer(ev, (MotionEvent)ev.event, 0, 0);//touch事件,派發touch事件
break;
case RawInputEvent.CLASS_TRACKBALL:
dispatchTrackball(ev, (MotionEvent)ev.event, 0, 0);//滾輪事件,派發Trackball事件
break;
case RawInputEvent.CLASS_CONFIGURATION_CHANGED:
configChanged = true;
break;
default:
mQueue.recycleEvent(ev);//銷燬事件
break;
}
}
} catch (Exception e) {
Slog.e(TAG,
"Input thread received uncaught exception: " + e, e);
}
}
}
WindowManagerService.dispatchPointer,一旦判斷QueuedEvent爲屏幕點擊事件,就調用函數WindowManagerService.dispatchPointer進行處理:
WindowManagerService.dispatchPointer
-->WindowManagerService.KeyWaiter.waitForNextEventTarget(獲取touch事件要派發的目標windowSate)
-->WindowManagerService.KeyWaiter.findTargetWindow(從一個一個WindowSate的z-order順序列表mWindow中獲取一個能夠接收當前touch事件的WindowSate)
-->WindowSate target = waitForNextEventTarget返回的WindowSate對象
-->target.mClient.dispatchPointer(ev, eventTime, true);(往目標window派發touch消息)
target.mClient是一個IWindow代理對象IWindow.Proxy,它對應的代理類是ViewRoot.W,通過遠程代理調用,WindowManagerService把touch消息派發到了對應的Activity的PhoneWindow
之後進一步WindowManagerService到Activity消息的派發在下文中說明
二WindowManagerService派發Touch事件到當前top Activity
--1.先我們看一個system_process的touch事件消息調用堆棧,在WindowManagerService中的函數dispatchPointer,通過一個IWindow的客戶端代理對象把消息發送到相應的IWindow服務端,也就是一個IWindow.Stub子類。
Thread [<21> InputDispatcher] (Suspended (breakpoint at line 321 in IWindow$Stub$Proxy))
IWindow$Stub$Proxy.dispatchPointer(MotionEvent, long, boolean) line: 321
WindowManagerService.dispatchPointer(KeyInputQueue$QueuedEvent, MotionEvent, int, int) line: 5270
WindowManagerService$InputDispatcherThread.process() line: 6602
WindowManagerService$InputDispatcherThread.run() line: 6482
--2.通過IWindow.Stub.Proxy代理對象把消息傳遞給IWindow.Stub對象。code=TRANSACTION_dispatchPointer,IWindow.Stub對象被ViewRoot擁有(成員mWindow,它是一個ViewRoot.W類對象)
--3.在case TRANSACTION_dispatchPointer會調用IWindow.Stub子類的實現方法dispatchPointer
--4.IWindow.Stub.dispatchPointer
-->ViewRoot.W.dispatchPointer
-->ViewRoot.dispatchPointer
public void dispatchPointer(MotionEvent event, long eventTime,
boolean callWhenDone) {
Message msg = obtainMessage(DISPATCH_POINTER);
msg.obj = event;
msg.arg1 = callWhenDone ? 1 : 0;
sendMessageAtTime(msg, eventTime);
}
--5.ViewRoot繼承自handle,在handleMessage函數的case-DISPATCH_POINTER會調用mView.dispatchTouchEvent(event),
mView是一個PhoneWindow.DecorView對象,在PhoneWindow.openPanel方法會創建一個ViewRoot對象,並設置ViewRoot對象的mView爲一個PhoneWindow.decorView成員,PhoneWindow.DecorView是真正的root view,它繼承自FrameLayout,這樣調用mView.dispatchTouchEvent(event)
其實就是調用PhoneWindow.decorView的dispatchTouchEvent方法:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super
.dispatchTouchEvent(ev);
}
--6.分析上面一段紅色代碼,可以寫成return (cb != null) && (mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev)).當cb不爲null執行後面,如果mFeatureId<0,執行cb.dispatchTouchEvent(ev),否則執行super.dispatchTouchEvent(ev),也就是FrameLayout.dispatchTouchEvent(ev),那麼callback cb是什麼呢?是Window類的一個成員mCallback,我下面給一個圖你可以看到何時被賦值的:
setCallback(Callback) : void - android.view.Window
-->attach(Context, ActivityThread, Instrumentation, IBinder, int, Application, Intent, ActivityInfo, CharSequence, Activity, String, Object, HashMap<String, Object>, Configuration) : void - android.app.Activity
--> performLaunchActivity(ActivityRecord, Intent) : Activity - android.app.ActivityThread
performLaunchActivity我們很熟識,因爲我前面在講Activity啓動過程詳解時候講過,在啓動一個新的Activity會執行該方法,在該方法裏面會執行attach方法,找到attach方法對應代碼可以看到:
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow就是一個PhoneWindow,它是Activity的一個內部成員,通過調用mWindow的setCallback(this),把新建立的Activity設置爲PhoneWindow一個mCallback成員,這樣我們就清楚了,前面的cb就是擁有這個PhoneWindow的Activity,cb.dispatchTouchEvent(ev)也就是執行:Activity.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//getWindow()返回的就是PhoneWindow對象,執行superDispatchTouchEvent,就是執行PhoneWindow.superDispatchTouchEvent
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//執行Activity.onTouchEvent方法
return onTouchEvent(ev);
}
--7.再看PhoneWindow.superDispatchTouchEvent:
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
--> public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);//FrameLayout.dispatchTouchEvent
}
}
superDispatchTouchEvent調用super.dispatchTouchEvent,我前面講過mDector是一個PhoneWindow.DecorView,它是一個真正Activity的root view,它繼承了FrameLayout,通過super.dispatchTouchEvent他會把touchevent派發給各個activity的子view,也就是我們再Activity.onCreat方法中setContentView時設置的view,touch event時間如何在Activity各個view中進行派發的我後面再作詳細說明,但是從上面我們可以看出一點若Activity下面的子view攔截了touchevent事件(返回true),Activity.onTouchEvent就不會執行。
--8.這部分,我再畫一個靜態類結構圖把前面講到的一些類串起來看一下:
我用紅色箭頭線把整個消息派發過程過程給串起來,然後system_process進程和ap進程分別用虛線橢圓圈起,這樣以後相信你更理解各個類之間關係。
對應的對象空間圖如下,與上面圖是對應的,只是從不同角度去看:
--9.其實上面所講的大部分已經是在客戶端ap中執行了,也就是在ap進程中,只是執行邏輯基本是框架代碼中,還沒有到達我們使用layout.xml佈局的view中來,這裏我先在我們的一個view中onTouchEvent插入一個斷點看一看消息從WindowManagerService到達Activity.PhoneWindow後執行堆棧情況(我插入的斷點在Launcher2的HandleView中),後面繼續講解:
Thread [<1> main] (Suspended (breakpoint at line 4280 in View))
HandleView(View).onTouchEvent(MotionEvent) line: 4280
HandleView.onTouchEvent(MotionEvent) line: 71
HandleView(View).dispatchTouchEvent(MotionEvent) line: 3766
RelativeLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863
DragLayer(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863
FrameLayout(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863
PhoneWindow$DecorView(ViewGroup).dispatchTouchEvent(MotionEvent) line: 863
PhoneWindow$DecorView.superDispatchTouchEvent(MotionEvent) line: 1671
PhoneWindow.superDispatchTouchEvent(MotionEvent) line: 1107
ForyouLauncher(Activity).dispatchTouchEvent(MotionEvent) line: 2086
PhoneWindow$DecorView.dispatchTouchEvent(MotionEvent) line: 1655
ViewRoot.handleMessage(Message) line: 1785
ViewRoot(Handler).dispatchMessage(Message) line: 99
Looper.loop() line: 123
ActivityThread.main(String[]) line: 4634
三.Activity中View中的Touch事件派發
--1.首先我畫一個Activity中的view層次結構圖:
前面我講過,來自windowManagerService的touch消息最終會派發到到Decorview,Decorview繼承子FrameLayout,它只有一個子view就是mContentParent,我們寫ap的view全部添加到到mContentParent。
--2.瞭解了Activity中的view的層次結構,那先從DecorView開始看touch事件是如何被派發的,前面講過最終消息會派發到FrameLayout.dispatchTouchEvent也就是ViewGroup.dispatchTouchEvent(FrameLayout也沒有覆蓋該方法),
同樣mContentParent也是執行ViewGroup.dispatchTouchEvent來派發touch消息,那我們就詳細看一下ViewGroup.dispatchTouchEvent(若要很好掌握應用程序touch事件處理,這部分要重點看):
public boolean dispatchTouchEvent(MotionEvent ev) {
......
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//計算是否禁止touch Intercept
if (action == MotionEvent.ACTION_DOWN) {//按下事件,也就是touch開始
if (mMotionTarget != null) {
mMotionTarget = null;//清除mMotionTarget,也就是說每次touch開始,mMotionTarget要被重新設置
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {//判斷消息是否需要被viewGroup攔截
// 消息不被viewGroup攔截,找到相應的子view進行touch事件派發
ev.setAction(MotionEvent.ACTION_DOWN);//重新設置event 爲action_down
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;//獲取viewgroup所有的子view
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {//若子view可見或者有動畫在執行的,才能夠接收touch事件
child.getHitRect(frame);//獲取子view的佈局座標區域
if (frame.contains(scrolledXInt, scrolledYInt)) {//若子view 區域包含當前touch點擊區域
// offset the event to the view's coordinate system
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
if (child.dispatchTouchEvent(ev)) {//派發TouchEvent給包含這個touch區域的子view
// 若該子view消費了對應的touch事件
mMotionTarget = child;//設置viewgroup消息派發的目標子view
return true;//返回true,該touch事件被消費掉
}
}
}
}
}
//若touch事件被攔截,mMotionTarget = null,後面touch消息不再派發給子view
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||//計算是up或者cancel
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
if (target == null) {
//target爲null,意味着在ACTION_DOWN時沒有找到能消費touch消息的子view或者在ACTION_DOWN時消息被攔截了,這個時候
//調用父類view的dispatchTouchEvent消息進行派發,也就是說,此時viewgroup處理touch消息跟普通view一致。
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev);
}
//target!=null,意味在ACTION_DOWN時touch消息沒有被攔截,而且子view target消費了ACTION_DOWN消息,需要再判斷消息是否被攔截
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
//消息被攔截,而前面ACTION_DOWN時touch消息沒有被攔截,所以需要發送ACTION_CANCEL通知子view target
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {
// 派發消息ACTION_CANCEL給子view target
}
// mMotionTarget=null,後面消息不再派發給子view
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
//isUpOrCancel,設置mMotionTarget=null,後面消息不再派發給子view
mMotionTarget = null;
}
......
//沒有被攔截繼續派發消息給子view target
return target.dispatchTouchEvent(ev);
}
--3.ViewGroup.dispatchTouchEvent我查看了一下所有子類,只有PhoneWindow.DecorView覆蓋了該方法,該方法前面講DecorView消息派發時提過,它會找到對應包含這個PhoneWindow.DecorView對象的Activity把消息交給Activity去處理,其它所有viewGroup的子類均沒有覆蓋dispatchTouchEvent,也就是說所有包含子view的父view對於touch消息派發均採用上面的邏輯,當然,必要的時候我們可以覆蓋該方法實現自己的touch消息派發邏輯,如Launcher2中的workspace類就是重新實現的該dispatchTouchEvent方法,從上面的dispatchTouchEvent函數邏輯其實我們也可以總結幾條touch消息派發邏輯:
(1).onInterceptTouchEvent用來定義是否截取touch消息邏輯,若在groupview中想截取touch消息,必須覆蓋viewgroup中該方法。
(2).消息在整個dispatchTouchEvent過程中,若子view.dispatchTouchEvent返回true,父view中將不再處理該消息,但前提是該消息沒有被父view截取,在整個touch消息處理過程中,若處理函數返回true,我們稱之爲消費了該touch事件,並且後面的父view將不再處理該消息。
(3).在整個touch事件過程中,從action_down到action_up,若父ViewGroup的函數onInterceptTouchEvent一旦返回true,消息將不再派發給子view,細分可爲兩種情況,若是在action_down時onInterceptTouchEvent返回true,不會派發任何消息給子view,並且後面onInterceptTouchEvent函數將不再會被執行,若是action_down時onInterceptTouchEvent返回false ,而後面touch過程中onInterceptTouchEvent==true,父viewGroup會把action_cancel派發給子view,也之後不再派發消息給子view,並且onInterceptTouchEvent函數後面將不再被執行。
--4.爲了更清楚的理解viewGroup消息的派發流程,我畫一個流程圖如下:
--5.上面我只是講了父view與子view之間當有touch事件的消息派發流程,對於view的消息是怎麼派發的(也包裹viewGroup沒有子view或者有子view但是不消費該touch消息情況),因爲從繼承結構上看viewgroup繼承了view,viewgroup覆蓋了view的dispatchTouchEvent方法,不過從上面流程圖也可以看到當mMotionTarget爲Null它會執行父類view.dispatchTouchEvent,其他view的子類都是執行view.dispatchTouchEvent派發touch事件,不過若我們自定義view是可以覆蓋該方法的。下面就仔細研究一下view.dispatchTouchEvent方法的代碼:
public final boolean dispatchTouchEvent(MotionEvent event) {
//mOnTouchListener是被View.setOnTouchListener設置的,(mViewFlags & ENABLED_MASK)計算view是否可被點擊
//當view可被點擊並且mOnTouchListener被設置,執行mOnTouchListener.onTouch
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;//若mOnTouchListener.onTouch返回true,函數返回true
}
return onTouchEvent(event);//若mOnTouchListener.onTouch返回false,調用onToucheEvent
}
函數邏輯很簡單,前面的viewGroup touch事件流程圖中我已經畫出的,爲區別我把它着色成青綠色,總結一句話若mOnTouchListener處理了touch消息,不執行onTouchEvent,否則交給onTouchEvent進行處理。
不知道是否講清楚的,要清楚掌握估計還得寫些例子測試一下是否是我上面所說的流程,不過我想了解事件的派發流程,對寫應用的事件處理相信很有用,比如我以前碰到一個問題是手指點擊屏幕到底是子view執行 touch事件派發流程,該響應點擊的時候響應子view的點擊,該父view移動的時候攔截touch事件交給父view進行處理。