您现在的位置是:首页 > 正文

怎么看android底层源码,从底层源码分析Android事件传递机制

2024-04-01 00:40:20阅读 0

点击一下告诉你什么是事件分发,知其然知其所以然,所以这边我篇我们就对源码展开分析!

我们知道事件都是由Activity往下面分发的!但是Activity.dispatchTouchEvent()又是从被哪里调用的呢?好奇不好奇?

1.首先从手指触摸到屏幕开始

我们知道Android是基于Linux系统的。当输入设备可用时(这里的输入设备包括很多设备,比如触摸屏和键盘是Android最普遍也是最标准的输入设备,另外它还包括外接的游戏手柄、鼠标等),Linux内核会为输入设置创建对应的设备节点。当输入设备不可用时,就把对应的设备节点删除,这也是如果我们的屏幕意外摔碎了或者其他原因导致触摸屏幕不可用时触摸没有反应的根本原因。当我们的输入设备可用时(我们这里只来讲解触摸屏),我们对触摸屏进行操作时,Linux就会收到相应的硬件中断,然后将中断加工成原始的输入事件并写入相应的设备节点中。而我们的Android 输入系统所做的事情概括起来说就是**监控这些设备节点,当某个设备节点有数据可读时,将数据读出并进行一系列的翻译加工,然后在所有的窗口中找到合适的事件接收者,并派发给它。

手指进行一系列操作(这里指的是手指的移动,这一步可能没有)

手指抬起或者因其他其他原因(突然间来了个电话之类的)导致事件结束

上面我们说到了Android 输入系统所做的事情概括起来说就是监控设备节点,当某个设备节点有数据可读时,将数据读出并进行一系列的翻译加工,然后在所有的窗口中找到合适的事件接收者,并派发给它。那么它是如何做的呢,,我们来具体分析一下。Android 的输入系统InputManagerService(以下简称为IMS)作为系统服务,它像其他系统服务一样在SystemServer进程中创建。

Linux会为所有可用的输入设备在/dev/input目录在建立event0~n或者其他名称的设备节点,Android输入系统会监控这些设备节点,具体是通过INotify和Epoll机制来进行监控。而不是通过一个线程进行轮询查询。

INotify机制

INotify是Linux内核提供的一种文件系统变化通知机制。它可以为应用程序监控文件系统的变化,如文件的新建,删除等。

//创建INotify对象,并用描述符inotifyFd 描述它

int inotifyFd = inotify_init();

/*

添加监听

inotify_add_watch函数参数说明

inotifyFd:上面建立的INotify对象的描述符,当监听的目录或文件发生变化时记录在INotify对象

“/dev/input”:被监听的文件或者目录

IN_CREATE | IN_DELETE:事件类型

综合起来下面的代码表示的意思就是当“/dev/input”下发生IN_CREATE | IN_DELETE(创建或者删除)时即把这个事件写入到INotify对象中

*/

int wd = inotify_add_watch(inotifyFd, "/dev/input", IN_CREATE|IN_DELETE )

Epoll机制

在上述INotify机制中我们知道了我们只需关心inotifyFd这个描述符就行了,可是事件是随机发生的,我们也不会本末倒置的采用轮询的方式轮询这个描述符,因为如果这样做的话会浪费大量系统资源。这时候我们Linux的另一个机制就派上用场了,即Epoll机制。Epoll机制简单的说就是使用一次等待来获取多个描述的可读或者可写状态。这样我们不必对每一个描述符创建独立的线程进行阻塞读取,在避免了资源浪费的同时获得较快的相应速度。

至此原始输入事件已经读取完毕,Android输入系统对原始输入事件进行翻译加工以及派发的详细过程很复杂。我们这里只分析其中一部分——IMS与窗口。

Avtivity,Window,PhoneWindow,以及ViewRootImpl之间的联系

上文中我们也说到了IMS会在所有的窗口中找到合适的事件接收者。IMS是运行在SystemServer进程中,而我们的窗口呢,是在我们的应用进程中。这就引出了我们在上文中留下的悬念

// ② 初始化mInputChanel。InputChannel是窗口接收来自InputDispatcher的输入事件的管道。这部分内容我们将在下一篇介绍。

if ((mWindowAttributes.inputFeatures

& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {

mInputChannel = new InputChannel();

}

...

...

// ③ 如果mInputChannel不为空,则创建mInputEventReceiver用于接收输入事件。

if (mInputChannel != null) {

mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,

Looper.myLooper());

}

InputChannel

InputChannel的本质是一对SocketPair(非网络套接字)。套接字可以用于网络通信,也可以用于本机内的进程通信。进程间通信的一种方式

public final class InputChannel implements Parcelable {

private static final String TAG = "InputChannel";

private static final boolean DEBUG = false;

public static final Parcelable.Creator CREATOR

= new Parcelable.Creator() {

public InputChannel createFromParcel(Parcel source) {

InputChannel result = new InputChannel();

result.readFromParcel(source);

return result;

}

public InputChannel[] newArray(int size) {

return new InputChannel[size];

}

};

@SuppressWarnings("unused")

private long mPtr; // used by native code

private static native InputChannel[] nativeOpenInputChannelPair(String name);

private native void nativeDispose(boolean finalized);

private native void nativeTransferTo(InputChannel other);

private native void nativeReadFromParcel(Parcel parcel);

private native void nativeWriteToParcel(Parcel parcel);

private native void nativeDup(InputChannel target);

private native String nativeGetName();

}

WindowInputEventReceiver

得到InputChannel后,便用它创建WindowInputEventReceiver,WindowInputEventReceiver继承于InputEventReceiver,InputEventReceiver对象可以接收来自InputChannel的输入事件,并触发其onInputEvent方法的回调。我们这里的是WindowInputEventReceiver,所以我们来看一下这个类

final class WindowInputEventReceiver extends InputEventReceiver {

public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {

super(inputChannel, looper);

}

@Override

public void onInputEvent(InputEvent event, int displayId) {

enqueueInputEvent(event, this, 0, true);

}

@Override

public void onBatchedInputEventPending() {

if (mUnbufferedInputDispatch) {

super.onBatchedInputEventPending();

} else {

scheduleConsumeBatchedInput();

}

}

@Override

public void dispose() {

unscheduleConsumeBatchedInput();

super.dispose();

}

}

enqueueInputEvent

void enqueueInputEvent(InputEvent event,

InputEventReceiver receiver, int flags, boolean processImmediately) {

...

//① 将InputEvent对应的InputEventReceiver封装为一个QueuedInputEvent

QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

//② 将新建的QueuedInputEvent 追加到mPendingInputEventTail所表示的一个单向链表中

QueuedInputEvent last = mPendingInputEventTail;

if (last == null) {

mPendingInputEventHead = q;

mPendingInputEventTail = q;

} else {

last.mNext = q;

mPendingInputEventTail = q;

}

mPendingInputEventCount += 1;

if (processImmediately) {

//③ 如果第三个参数为true,则直接在当前线程中开始对输入事件的处理工作

doProcessInputEvents();

} else {

//④ 否则将处理事件的请求发送给主线程的Handler,随后进行处理

scheduleProcessInputEvents();

}

doProcessInputEvents

void doProcessInputEvents() {

//遍历整个输入事件队列,并逐一处理

while (mPendingInputEventHead != null) {

QueuedInputEvent q = mPendingInputEventHead;

mPendingInputEventHead = q.mNext;

if (mPendingInputEventHead == null) {

mPendingInputEventTail = null;

}

q.mNext = null;

mPendingInputEventCount -= 1;

Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,

mPendingInputEventCount);

long eventTime = q.mEvent.getEventTimeNano();

long oldestEventTime = eventTime;

if (q.mEvent instanceof MotionEvent) {

MotionEvent me = (MotionEvent)q.mEvent;

if (me.getHistorySize() > 0) {

oldestEventTime = me.getHistoricalEventTimeNano(0);

}

}

mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);

deliverInputEvent()//方法会将完成单个事件的整个处理流程

deliverInputEvent

private void deliverInputEvent(QueuedInputEvent q) {

Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",

q.mEvent.getSequenceNumber());

if (mInputEventConsistencyVerifier != null) {

mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);

}

InputStage stage;

if (q.shouldSendToSynthesizer()) {

stage = mSyntheticInputStage;

} else {

stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;

}

if (q.mEvent instanceof KeyEvent) {

mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);

}

if (stage != null) {

handleWindowFocusChanged();

stage.deliver(q);

} else {

finishInputEvent(q);

}

}

new ViewPostImeInputStage(mSyntheticInputStage);

从这个方法processPointerEvent将事件传递给了View树的根节点,调用了mView.dispatchPointerEvent(event);

private int processPointerEvent(QueuedInputEvent q) {

final MotionEvent event = (MotionEvent)q.mEvent;

mAttachInfo.mUnbufferedDispatchRequested = false;

mAttachInfo.mHandlingPointerEvent = true;

// 此时ViewRootImpl会将事件的处理权移交给View树的根节点,调用dispatchPointerEvent函数

boolean handled = mView.dispatchPointerEvent(event);

maybeUpdatePointerIcon(event);

maybeUpdateTooltip(event);

mAttachInfo.mHandlingPointerEvent = false;

if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {

mUnbufferedInputDispatch = true;

if (mConsumeBatchedInputScheduled) {

scheduleConsumeBatchedInputImmediately();

}

}

return handled ? FINISH_HANDLED : FORWARD;

}

这里肯定有疑问了,为什么会传到mView.dispatchPointerEvent(event),不应该先到Activity.dispatchPointerEvent(event)?

DecorView.dispatchTouchEvent(MotionEvent ev)

@Override

public boolean dispatchTouchEvent(MotionEvent ev) {

final Window.Callback cb = mWindow.getCallback();

return cb != null && !mWindow.isDestroyed() && mFeatureId < 0

? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);

}

Window.Callback cb = mWindow.getCallback();这个是Activity里面实现的

final void attach(Context context, ActivityThread aThread,

Instrumentation instr, IBinder token, int ident,

Application application, Intent intent, ActivityInfo info,

CharSequence title, Activity parent, String id,

NonConfigurationInstances lastNonConfigurationInstances,

Configuration config, String referrer, IVoiceInteractor voiceInteractor,

Window window, ActivityConfigCallback activityConfigCallback) {

mWindow = new PhoneWindow(this, window, activityConfigCallback);

mWindow.setWindowControllerCallback(this);

mWindow.setCallback(this);

}

Activity在这里实现了Window.Callback

public class Activity extends ContextThemeWrapper

implements LayoutInflater.Factory2,

Window.Callback,

}

public interface Callback {

public boolean dispatchTouchEvent(MotionEvent event);

}

简单暴力查看

de649f1f45cf

image.png

总结下!

1.当我们触摸屏幕时,通过INotify机制&Epoll机制改变和发送给WindowInputEventReceiver。

2.WindowInputEventReceiver是ViewRootImpl的内部类,通过enqueueInputEvent方法,将输入事件加入输入事件队列中,并进行处理和转发。

3.ViewPostImeInputStage收到输入事件,将事件传递给DecorView的dispatchPointerEvent()方法(是View的方法)

4.dispatchPointerEvent()方法通过DecorView中的dispatchTouchEvent()方法,调用了Activity的dispatchTouchEvent()方法。

网站文章

  • 哔咔漫画怎样切换横屏?

    小编建议大家,可以在官网picacr.com下载最新版

    2024-04-01 00:40:11
  • 每周七问(第三十五期):精通区块链之散列函数

    每周七问(第三十五期):精通区块链之散列函数

    每周七问:《每周七问》是由毛球科技集团打造的业内首个讲解区块链行业的知识科普类海报系列。我们将抽象的区块链行业内每一个概念转化为轻松易懂的小图片,每张图都能学懂一个知识点。本期,我们将为您带来主题为“...

    2024-04-01 00:40:04
  • 【JVM系列】5、Jvm垃圾回收器(算法篇)

    【JVM系列】5、Jvm垃圾回收器(算法篇)

    在《【JVM系列】4、Jvm垃圾回收器(基础篇)》中我们主要学习了判断对象是否存活还是死亡?两种基础的垃圾回收算法:引用计数法、可达性分析算法。以及Java引用的4种分类:强引用、软引用、弱引用、虚引用。和方法区的回收介绍。那么接下来我们重点研究下虚拟机的几种常见的垃圾回收算法:标记-清除算法、复制算法、标记-整理算法、分代收集算法。一:标记-清除算法  最基础的收集算法,总共分为‘ ...

    2024-04-01 00:39:40
  • CSS学习(三)—— 浮动与定位

    六、浮动与定位 1、浮动 1)浮动基本概念 浮动最本质的功能:用来实现并排 浮动使用要点:如若要浮动,并排的盒子都要设置浮动;父盒子要有足够宽度,否则盒子会掉下去 浮动的顺序贴靠特性:子盒子会按照顺序...

    2024-04-01 00:39:33
  • Map和Set常用的方法

    Set数据类型:Set和Map类似,也是一组key的集合,但不S存储value。由于key不能重复,所以在Set中,没有重复的key。Map数据类型:Map和Object有点类似,都是键值对来存储数据,和Object不同的是,javaScript支持的所有类型都可以当作Map的key。.........

    2024-04-01 00:39:27
  • SpringBoot @Value获取yml中文自定义配置乱码问题

    SpringBoot @Value获取yml中文自定义配置乱码问题

    SpringBoot项目时,我们读取yam文件的属性时,有时候会出现中文乱码情况,如下所示:解决方案:

    2024-04-01 00:39:04
  • ACME.SH 申请SSL证书 于更新(转) 最新发布

    注意:这里ssl_certificate 配置由原本的abc.com.crt 变成fullchain.crt了,同时配置ssl_trusted_certificate 信任证书。注:以abc.com域...

    2024-04-01 00:38:46
  • js中的函数防抖和函数节流

    js中的函数防抖和函数节流

    1、什么是函数防抖和函数节流 防抖(debounce)和节流(throttle)都是用来控制某个函数在一定时间内执行多少次的技巧,两者相似不相同,基本思想都是某些代码不可以在没有间断的情况下连续重复执...

    2024-04-01 00:38:20
  • 极地求生自定义服务器僵尸模式,绝地求生自定义房间使用手册 教你如何玩僵尸模式...

    极地求生自定义服务器僵尸模式,绝地求生自定义房间使用手册 教你如何玩僵尸模式...

    《绝地求生》正式服即将到来自定义服务器功能,而这次是不限号测试。官方今日发布了自定义服务器的使用手册,作为参考资料教大家如何游玩自定义房间的各种功能。此外官方表示,由于自定义服务器的公开是‘不限号测试...

    2024-04-01 00:38:12
  • java的js拼接字符串的注意事项

    对于JS一直有些模糊,熟悉又陌生的感觉,近期在写项目时需要使用到JS拼接字符串,就是用JS的写页面首先,使用js拼接input是要注意使用引号(次引号不是汉语或英语的引号,而是键盘上的tab键上面的哪...

    2024-04-01 00:38:07