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:5270WindowManagerService 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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值