• Compose原理-compose中是如何实现事件分法的


    前言:

    安卓原生View的事件分发流程,我们另外一篇文章中有讲到。

    android源码学习-事件分发处理机制_失落夏天的博客-CSDN博客

    在compose学习中,就不禁想到,compose的事件分发应该是怎样的呢?我感觉应该和原生是有区别的,毕竟底层的渲染机制都不一样。安卓原生是View->ViewGroup->ViewGroup层层嵌套的结构,而compose中,只有

    AndroidComposeView->ComposeView这两层结构而已。其余的层级结构都在AndroidComposeView中自行处理的。所以我猜测在compose中的事件分发,应该是在AndroidComposeView中有专门的转发逻辑。

    所以,本文就是一个提出猜想,验证猜想的过程,我们就一步一步的来验证这个的猜测。

    一.找到点击堆栈调用

    为了方便,还是直接以之前文章中讲到的Demo为例了,布局结构如下:

    1. @Composable
    2. fun MainContent() {
    3. Column(Modifier.padding(10.dp)) {
    4. Button(onClick = {
    5. jumpActivity(ComposeDataBindingActivity::class.java)
    6. }) {
    7. Text(text = "Compose_DataBinding")
    8. }
    9. Button(onClick = {
    10. jumpActivity(ComposeListActivity::class.java);
    11. }) {
    12. Text(text = "Compose_List")
    13. }
    14. Button(onClick = {
    15. jumpActivity(ComposeMVIActivity::class.java);
    16. }) {
    17. Text(text = "Compose_MVI")
    18. }
    19. }
    20. }

    点击其中的一个Button后,断点生效,整个堆栈流程如下图所示:

    上面这张图,我们主要分为以下几块吧。

    1.ViewGroup层面的转发。主要流程和原生的View的时间分发是一样的。DecorView一层层向下传递,最终传递给ComposeVIew(PS:ComposeView继承自ViewGroup)。

    2.AndroidComposeView中的分发处理。

    3.Node节点中的分发处理。

    4.事件的执行。

    第一块由于和原生是摸一模一样的,我们就不展开讲了,直接从AndroidAndroidComposeView层开始讲起。

    二.AndroidComposeView中的分发处理

    由于AndroidComposeView是kt写的,我们源码的阅读是不方便的,我们找到对应的类,使用反编译的方式,转换为java代码查看。

    AndroidComposeView中的dispatchTouchEvent方法如下,主要流程在handleMotionEvent这一行,其余的都是一些非主流程的场景处理。

    1. public boolean dispatchTouchEvent(@NotNull MotionEvent motionEvent) {
    2. Intrinsics.checkNotNullParameter(motionEvent, "motionEvent");
    3. if (this.hoverExitReceived) {
    4. this.removeCallbacks(this.sendHoverExitEvent);
    5. MotionEvent var10000 = this.previousMotionEvent;
    6. Intrinsics.checkNotNull(var10000);
    7. MotionEvent lastEvent = var10000;
    8. if (motionEvent.getActionMasked() == 0 && !this.hasChangedDevices(motionEvent, lastEvent)) {
    9. this.hoverExitReceived = false;
    10. } else {
    11. this.sendHoverExitEvent.run();
    12. }
    13. }
    14. if (this.isBadMotionEvent(motionEvent)) {
    15. return false;
    16. } else if (motionEvent.getActionMasked() == 2 && !this.isPositionChanged(motionEvent)) {
    17. return false;
    18. } else {
    19. int processResult = this.handleMotionEvent-8iAsVTc(motionEvent);
    20. if (ProcessResult.getAnyMovementConsumed-impl(processResult)) {
    21. this.getParent().requestDisallowInterceptTouchEvent(true);
    22. }
    23. return ProcessResult.getDispatchedToAPointerInputModifier-impl(processResult);
    24. }
    25. }

    所以我们接着看一下handleMotionEvent方法,直接切换到AndroidComposeView.android.kt中看。

    仍然是一些非主流程判断的处理,我们还是只看核心:

    1. private fun handleMotionEvent(motionEvent: MotionEvent): ProcessResult {
    2. removeCallbacks(resendMotionEventRunnable)
    3. ...
    4. sendMotionEvent(motionEvent)
    5. ...
    6. }

    sendMotionEvent的核心是交给了pointerInputEventProcessor.process进行处理。

    我们看一下pointerInputEventProcessor的构造方法:

    internal class PointerInputEventProcessor(val root: LayoutNode) 

    传入参数是root,对应的是应该是最外层的那个View,所以说明PointerInputEventProcessor是整个Compose中事件分发流程的开始。

    三.PointerInputEventProcessor中的分发流程

    我们仍然看最开始的那张图,接着执行到了下面这个方法。

    1. val dispatchedToSomething =
    2. hitPathTracker.dispatchChanges(internalPointerEvent, isInBounds)

    看一下hitPathTracker的构造方法:

        private val hitPathTracker = HitPathTracker(root.coordinates)

    coordinates从名字就可以推测到是坐标一类的属性对象了。所以接下来,应该会根据坐标进行进一步的分发了。

    而internalPointerEvent是这个点击事件的对象封装,

    isInBounds是否在范围,这个就不用多解释了。

    接下来我们接着看dispatchChanges方法,一样,我们只看核心。核心就是

    root.dispatchMainEventPass方法。此时的root对应的应该是NodeParent。在compose中,所有的视图结构都是以Node的方式来存储的,所以NodeParent是最外层的那个视图结构。

    1. fun dispatchChanges(
    2. internalPointerEvent: InternalPointerEvent,
    3. isInBounds: Boolean = true
    4. ): Boolean {
    5. ...
    6. var dispatchHit = root.dispatchMainEventPass(
    7. internalPointerEvent.changes,
    8. rootCoordinates,
    9. internalPointerEvent,
    10. isInBounds
    11. )
    12. dispatchHit = root.dispatchFinalEventPass(internalPointerEvent) || dispatchHit
    13. return dispatchHit
    14. }

    接着往下看root.dispatchMainEventPass方法:

    1. open fun dispatchMainEventPass(
    2. changes: Map<PointerId, PointerInputChange>,
    3. parentCoordinates: LayoutCoordinates,
    4. internalPointerEvent: InternalPointerEvent,
    5. isInBounds: Boolean
    6. ): Boolean {
    7. var dispatched = false
    8. children.forEach {
    9. dispatched = it.dispatchMainEventPass(
    10. changes,
    11. parentCoordinates,
    12. internalPointerEvent,
    13. isInBounds
    14. ) || dispatched
    15. }
    16. return dispatched
    17. }

    再看一下children对象:

        val children: MutableVector<Node> = mutableVectorOf()

    看到这就感觉豁然开朗了,原来compose中的事件分发,也是由上层向下层一层一层传递的。

    继续往下看,果然不出所料,Node的dispatchMainEventPass方法中,也存在向children传递事件的代码。

    接下来,就是看Node如何处理这个事件了。若干次的dispatchMainEventPass分发后,终于到了Node处理点击的这一层,我们完整的看一下这个方法:

    1. override fun dispatchMainEventPass(
    2. changes: Map<PointerId, PointerInputChange>,
    3. parentCoordinates: LayoutCoordinates,
    4. internalPointerEvent: InternalPointerEvent,
    5. isInBounds: Boolean
    6. ): Boolean {
    7. ...
    8. return dispatchIfNeeded {
    9. val event = pointerEvent!!
    10. val size = coordinates!!.size
    11. // Dispatch on the tunneling pass.
    12. pointerInputFilter.onPointerEvent(event, PointerEventPass.Initial, size)
    13. // Dispatch to children.
    14. if (pointerInputFilter.isAttached) {
    15. children.forEach {
    16. it.dispatchMainEventPass(
    17. // Pass only the already-filtered and position-translated changes down to
    18. // children
    19. relevantChanges,
    20. coordinates!!,
    21. internalPointerEvent,
    22. isInBounds
    23. )
    24. }
    25. }
    26. if (pointerInputFilter.isAttached) {
    27. // Dispatch on the bubbling pass.
    28. pointerInputFilter.onPointerEvent(event, PointerEventPass.Main, size)
    29. }
    30. }
    31. }

    仍然是如果在惦记范围内(pointerInputFilter.isAttached进行的判断),先转发给children。

    然后自身再去进行处理:

    1. if (pointerInputFilter.isAttached) {
    2. // Dispatch on the bubbling pass.
    3. pointerInputFilter.onPointerEvent(event, PointerEventPass.Main, size)
    4. }

    我们注意这里的第二个参数PointerEventPass.Main,这个枚举类型有3种值类型,分别对应的是未处理,该被处理还未处理,以及处理完成三种状态。

    1. enum class PointerEventPass {
    2. Initial, Main, Final
    3. }

    这里传入的是Main,则代表着这个点击事件是该被当前Node处理的。继续往下看,

    onPointerEvent方法如下:

    1. override fun onPointerEvent(
    2. pointerEvent: PointerEvent,
    3. pass: PointerEventPass,
    4. bounds: IntSize
    5. ) {
    6. boundsSize = bounds
    7. if (pass == PointerEventPass.Initial) {
    8. currentEvent = pointerEvent
    9. }
    10. dispatchPointerEvent(pointerEvent, pass)
    11. lastPointerEvent = pointerEvent.takeIf { event ->
    12. !event.changes.fastAll { it.changedToUpIgnoreConsumed() }
    13. }
    14. }

    核心是dispatchPointerEvent方法:

    1. private fun dispatchPointerEvent(
    2. pointerEvent: PointerEvent,
    3. pass: PointerEventPass
    4. ) {
    5. forEachCurrentPointerHandler(pass) {
    6. //下面的内容当作参数传入
    7. it.offerPointerEvent(pointerEvent, pass)
    8. }
    9. }

    这个是kotlin的一种写法,可以理解为把{}中的内容当做参数传入。

    1. private inline fun forEachCurrentPointerHandler(
    2. pass: PointerEventPass,
    3. block: (PointerEventHandlerCoroutine<*>) -> Unit
    4. ) {
    5. // Copy handlers to avoid mutating the collection during dispatch
    6. synchronized(pointerHandlers) {
    7. dispatchingPointerHandlers.addAll(pointerHandlers)
    8. }
    9. try {
    10. when (pass) {
    11. PointerEventPass.Initial, PointerEventPass.Final ->
    12. dispatchingPointerHandlers.forEach(block)
    13. PointerEventPass.Main ->
    14. dispatchingPointerHandlers.forEachReversed(block)
    15. }
    16. } finally {
    17. dispatchingPointerHandlers.clear()
    18. }
    19. }

    此时的pass为Initial状态,所以最终会由前向后执行刚才的那个方法体,进入到offerPointerEvent的方法流程:

    1. fun offerPointerEvent(event: PointerEvent, pass: PointerEventPass) {
    2. if (pass == awaitPass) {
    3. pointerAwaiter?.run {
    4. pointerAwaiter = null
    5. resume(event)
    6. }
    7. }
    8. }

    这里的awaitPass如下:

     private var awaitPass: PointerEventPass = PointerEventPass.Main

    还记得上面讲的传入参数吗?此时这里的pass=Main,恰好和awaitPass相等,所以pointerAwaiter就会被执行。则会执行pointerAwaiter这个协程体。

    而pointerAwaiter这个协程体对应的其实就是点击跳转的方法。

    所以整个流程就串起来了。

    四.总结

    compose中的事件分发流程其实和原生类似,也是由上向下一层一层的传递。

    在attachToWindow的时候,如果Node节点设置了点击监听,则根据监听生成续体对象,加入到一个队列当中。然后遍历这个队列,给每个Node节点都会生成SuspendingPointerInputFilter对象进行观察,其中就包含将要执行的续体PointerEventHandlerCoroutine。而这个续体对象中主要包含两个属性:

    awaitPass,此时会被设置为PointerEventPass.Main。

    pointerAwaiter,这个就是最终执行的点击的方法的续体。

    然后点击的时候,会一层一层的由上而下遍历所有节点,如果发现awaitPass=PointerEventPass.Main,则执行其中的续体方法,也就是最终的点击事件。

    五.备注

    本文的分析仅仅只是基于事件分发的解读,续体如何设置到Node节点上的这块并没有进行说明。这一块作者也正在看,看完后会补充上,也欢迎讨论。

    本文仅基于compose源码的分析,有可能分析结论有误,欢迎指出和讨论

  • 相关阅读:
    k8s中几个基本概念的理解,pod,service,deployment,ingress的使用场景
    [附源码]计算机毕业设计基于Springboot的手机电商网站
    Python语言
    关于@Autowired
    【Leetcode-滑动窗口问题】
    Kubernetes学习篇之对象
    想跟大家说点心里话~(希望大家都看一下谢谢各位 !!)
    【原创】EtherCAT主站IgH解析(二)-- 如何将Igh移植到Linux/Windows/RTOS等操作系统指南
    TypeScript核心
    【操作系统】虚拟内存浅析
  • 原文地址:https://blog.csdn.net/AA5279AA/article/details/126138819