`
923723914
  • 浏览: 634805 次
文章分类
社区版块
存档分类
最新评论

Android FrameWork——Touch事件派发过程详解

 
阅读更多

对于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执行onclick还是执行父view的view移动,这个时候就需要深入了解viewde touch事件派发流程,该响应点击的时候响应子view的点击,该父view移动的时候拦截touch事件交给父view进行处理。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics