IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android窗口机制:五、ViewRootImpl的事件分发。(源码版本SDK31) -> 正文阅读

[移动开发]Android窗口机制:五、ViewRootImpl的事件分发。(源码版本SDK31)

Android 窗口机制 SDK31源码分析 总目录

上一章节,我们知道ViewRootImpl的绘制流程。

那么今天就来看一看ViewRootImpl的另一个大的功能点事件分发吧。

事件是如何被接收的呢?

首先需要思考一个问题,事件是如何传递到ViewGroup呢?

我们都知道Android底层是Linux内核,所以事件的传递过程大概如下所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CII4ty5c-1646559514612)(E:\Documentation\My_Learn_Documentation_Git\android\pic_android\event_deliver.jpg)]

在Android系统开机的过程中,会启动许多系统服务,其中包括InputManagerService用来监听事件的输入,而WindowManagerService作为事件输入中转站,管理输入事件,配合InputManagerService将输入事件交由合适的Window,即最终传输到ViewRootImplInputChannel,最终被ViewRootImplWindowInputEventReceiver所接受。

对于上述的流程,这里先简单看一下源码:

ViewRootImpl
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
	...
    //构建inputChannel
	InputChannel inputChannel = null;
    if ((mWindowAttributes.inputFeatures
         & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
        inputChannel = new InputChannel();
    }
    ...
    //在上一章节,就讲的很清楚了,这里会通IPC调用到WMS,添加window。但是注意,此时把inputChannel也传递过去了,被WMS持有用作事件分发。
    res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
             getHostVisibility(), mDisplay.getDisplayId(), userId,
             mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
             mTempControls);
    ...
    //通时inputChannel也被WindowInputEventReceiver所持有,接受WMS分发过来的事件。
    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
                            Looper.myLooper());
	...
}

上一章节我们已经很清楚了,setView会在ActivityThread调用handleResumeActivity时被WindowManager调用addView所调用,用于添加DecorView,以及调用绘制流程。但是这里面也会初始化InputChannel以及WindowInputEventReceiver用于接收事件输入。

ViewRootImpl事件分发源码分析

好了接下来进入正题,看ViewRootImpl接收到事件之后是如何处理的。

ViewRootImpl
final class WindowInputEventReceiver extends InputEventReceiver {
	...
    //用于接收事件输入
	public void onInputEvent(InputEvent event) {
            ...
            //注意:最后一个参数传入了true
            enqueueInputEvent(event, this, 0, true);
           
      }
	...
}

我们直接看WindowInputEventReceiver类,他是ViewRootImpl的内部类。其中onInputEvent用于接收事件输入,它调用了enqueueInputEvent,且最后一个参数传入了true

ViewRootImpl
void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        //获取队列输入事件
        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
		...
        ///输入事件添加进队列后,加入输入事件的默认尾部
        QueuedInputEvent last = mPendingInputEventTail;
        if (last == null) {
            mPendingInputEventHead = q;
            mPendingInputEventTail = q;
        } else {
            last.mNext = q;
            mPendingInputEventTail = q;
        }
        mPendingInputEventCount += 1;
        ...
        //由于调用传进来的是true,这里执行doProcessInputEvents
        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }

enqueueInputEvent中,选获取了队列输入事件,之后将它加入链表末尾,然后调用了doProcessInputEvents

    ViewRootImpl
    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);

            mViewFrameInfo.setInputEvent(mInputEventAssigner.processEvent(q.mEvent));
			//事件处理
            deliverInputEvent(q);
        }
		...
    }

这里循环取出事件,然后交予deliverInputEvent进行处理。

private void deliverInputEvent(QueuedInputEvent q) {
    ...
    InputStage stage;
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;
    } else {
        stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
    }
	...
    if (stage != null) {
        handleWindowFocusChanged();
        //通过deliver最终会调用到,InputStage具体实现类的onProcess方法。
        stage.deliver(q);
    } else {
        finishInputEvent(q);
    }
    ...
}

//这里说明一下,在ViewRootImpl的setView方法中,还会初始化,这么几个InputStage,用于处理不同的事件。
//对于点击事件来说,则最终会交由ViewPostImeInputStage进行处理
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                                                            "aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                                        "aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                                                          "aq:native-pre-ime:" + counterSuffix);

这个方法里面出现了InputStage,它是一个基类,有各种不同的实现用来处理不同的事件,比如点击事件,我们可以看ViewPostImeInputStage

ViewPostImeInputStage
protected int onProcess(QueuedInputEvent q) {
	if (q.mEvent instanceof KeyEvent) {
		return processKeyEvent(q);
	} else {
		final int source = q.mEvent.getSource();
		if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
            //判断触摸事件,会走这个方法
			return processPointerEvent(q);
		} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
			return processTrackballEvent(q);
		} else {
			return processGenericMotionEvent(q);
		}
	}
}

这里会判断事件类型,比如触摸还是键盘输入?我们看触摸事件,此时会调用processPointerEvent

ViewPostImeInputStage
private int processPointerEvent(QueuedInputEvent q) {
	final MotionEvent event = (MotionEvent)q.mEvent;

	...
    //调用了dispatchPointerEvent
	boolean handled = mView.dispatchPointerEvent(event);
	...
	return handled ? FINISH_HANDLED : FORWARD;
}

我们看到这里面调用了dispatchPointerEvent,那么这个mView是谁呢?当然是DecorView了,在setView的时候传进来的嘛。

接下来看DecorView的dispatchPointerEvent,但是找了一下竟然没发现!!!

好吧,在其父类View中发现了,我们看一眼

View
public final boolean dispatchPointerEvent(MotionEvent event) {
	if (event.isTouchEvent()) {
        //触摸,执行
		return dispatchTouchEvent(event);
	} else {
		return dispatchGenericMotionEvent(event);
	}
}

这里面依旧判断了是否是触摸,所以我们直接看dispatchTouchEvent,由于我们知道mView就是DecorView,而DecorView会实现dispatchTouchEvent方法,所以我们需要看DecorView的dispatchTouchEvent方法。

DecorView
public boolean dispatchTouchEvent(MotionEvent ev) {
	final Window.Callback cb = mWindow.getCallback();
	return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

而在前面的章节中我们知道,在ActivityThread调用HandleLaunchActivity流程中,会调用到Activity的attach方法,在这里会创建真正的PhoneWindow,同时执行了这样依据代码mWindow.setCallback(this);,所以显而易见,Activity实现了Window.Callback接口,并且被mWindow所持有。所以这里面就调用到了Activity的dispatchTouchEvent

Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
	if (ev.getAction() == MotionEvent.ACTION_DOWN) {
		onUserInteraction();
	}
    //通过PhoneWindow调用到DecorView的superDispatchTouchEvent方法,DecorView调用了ViewGroup的dispatchTouchEvent,开启事件分发。
    if (getWindow().superDispatchTouchEvent(ev)) {
		return true;
	}
    //如果没有拦截处理,则自己消费处理
	return onTouchEvent(ev);
}

说明一下:这里面的getWindow().superDispatchTouchEvent(ev),最终会调用到DecorView的superDispatchTouchEvent方法,而DecorView的superDispatchTouchEvent会调用ViewGroup的dispatchTouchEvent,进而开启我们所熟知的事件分发

看了上面的流程可能大家有点不理解,为什么要从DecorView绕道Activity,之后再给DecorView呢?

因为绕一圈,才可以将Activity作为事件分发的起点呀,当大家都不消费的事件的时候,事件就流回Activity了

总结

直接看流程图就好


下一章节预告: 一定要在主线程才可以更新UI吗?为什么?

创作不易,如有帮助一键三连咯🙆?♀?。欢迎技术探讨噢!

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2022-03-08 22:39:08  更:2022-03-08 22:41:57 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 16:32:35-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码