Android 事件分发机制详解/ 及Activity启动流程浅谈

目前网上的事件分发机制文章很多,可能大家看了很多,还是很懵逼,这篇文章是我自己总结的事件分发机制,提取了在事件分发机制中,最精华的部分,如果对事件分发还是不太清楚,可以看看这篇文章

更多精华可以在 vx 安卓新视界 免费获取 ,以上是都是免费文章

更多其他文章:
Android------四大组件及动画

一、事件分发机制

用户在使用app时,会经常频繁的触摸屏幕,点击页面交互,打开需要的功能。用户触摸点击的动作,就涉及到app中相关UI的触摸和点击事件。事件在UI中传递和处理的过程,就是事件分发流程。

发生一次点击事件时,事件会按照Activity->ViewGroup->View的顺序,进行事件传递。

  • Activity:控制UI页面的生命周期,是事件分发的入口。
  • ViewGroup:View的特殊子类,是一组View的集合,是Android中所有布局的父类。
  • View:所有UI组件的基类,常见的Button、TextView等控件都继承自View。

1.1 MotionEvent

事件列,即指从手指接触屏幕至手指离开屏幕这个过程产生的一系列事件。一般情况下,事件列都是以DOWN事件开始、UP事件结束,中间有无数的MOVE事件。

1.2 事件分发过程由哪些方法协作完成?

在整个事件分发,并响应事件的过程中,有三个重要的方法:

  • dispatchTouchEvent:分发(传递)点击事件,当点击事件能够传递给当前View时,该方法就会被调用。
  • onInterceptTouchEvent:判断是否拦截某个事件,该方法仅在ViewGroup中存在。一般情况下会在ViewGroup的dispatchTouchEvent方法中调用该方法。
  • onTouchEvent:处理点击事件,在dispatchTouchEvent内部调用。

Activity的事件分发流程

Activity 中包含两个事件分发与处理的方法,分别是:

  • boolean dispatchTouchEvent(MotionEvent ev):事件分发
  • boolean onTouchEvent(MotionEvent event):事件消费
    当一个事件发生时,首先会将点击事件传递到Activity中,执行dispatchTouchEvent进行事件分发。经过window、decorView依次传递后,页面上的 ViewGroup会接收到该事件。ViewGroup如果消费了该事件,则分发结束,未消费则继续调用Activity的onTouchEvent 方法处理事件,简略流程图如下:

源码分析

源码分析:Activity.dispatchTouchEvent()
  public boolean dispatchTouchEvent(MotionEvent ev) {

    // 仅贴出核心代码

    // ->>分析1
    if (getWindow().superDispatchTouchEvent(ev)) {

        return true;
        // 若getWindow().superDispatchTouchEvent(ev)的返回true
        // 则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束
        // 否则:继续往下调用Activity.onTouchEvent

    }
    // ->>分析3
    return onTouchEvent(ev);
  }

/**
  * 分析1:getWindow().superDispatchTouchEvent(ev)
  * 说明:
  *     a. getWindow() = 获取Window类的对象
  *     b. Window类是抽象类,其唯一实现类 = PhoneWindow类
  *     c. Window类的superDispatchTouchEvent() = 1个抽象方法,由子类PhoneWindow类实现
  */
  @Override
  public boolean superDispatchTouchEvent(MotionEvent event) {

      return mDecor.superDispatchTouchEvent(event);
      // mDecor = 顶层View(DecorView)的实例对象
      // ->> 分析2
  }

/**
  * 分析2:mDecor.superDispatchTouchEvent(event)
  * 定义:属于顶层View(DecorView)
  * 说明:
  *     a. DecorView类是PhoneWindow类的一个内部类
  *     b. DecorView继承自FrameLayout,是所有界面的父类
  *     c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
  */
  public boolean superDispatchTouchEvent(MotionEvent event) {

      return super.dispatchTouchEvent(event);
      // 调用父类的方法 = ViewGroup的dispatchTouchEvent()
      // 即将事件传递到ViewGroup去处理,详细请看后续章节分析的ViewGroup的事件分发机制

  }
  // 回到最初的分析2入口处

/**
  * 分析3:Activity.onTouchEvent()
  * 调用场景:当一个点击事件未被Activity下任何一个View接收/处理时,就会调用该方法
  */
  public boolean onTouchEvent(MotionEvent event) {

        // ->> 分析5
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        
        return false;
        // 即 只有在点击事件在Window边界外才会返回true,一般情况都返回false,分析完毕
    }

/**
  * 分析4:mWindow.shouldCloseOnTouch(this, event)
  * 作用:主要是对于处理边界外点击事件的判断:是否是DOWN事件,event的坐标是否在边界内等
  */
  public boolean shouldCloseOnTouch(Context context, MotionEvent event) {

  if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
          && isOutOfBounds(context, event) && peekDecorView() != null) {

        // 返回true:说明事件在边界外,即 消费事件
        return true;
    }

    // 返回false:在边界内,即未消费(默认)
    return false;
  } 

ViewGroup的事件分发流程

ViewGroup 中包含三个事件分发与处理的方法,分别是:

  • dispatchTouchEvent(MotionEvent ev):事件分发
  • onIntercepTouchEvent(MotionEvent ev):事件拦截
  • onTouchEvent(MotionEvent ev):事件消费 ViewGroup的事件分发机制从dispatchTouchEvent开始,接着调用onInterceptTouchEvent方法判断是否需要拦截事件。
    如果需要拦截,则表示当前 ViewGroup 希望处理该事件,或者不希望子 View 处理该事件,此时将直接调用 onTouchEvent方法处理事件。onTouchEvent方法如果消费了该事件,则分发结束,未消费则调用Activity的onTouchEvent方法处理事件。
    如果不需要拦截,则会遍历寻找被点击的子View,将该事件传递给子 View 的 dispatchTouchEvent 方法。如果未找到子View,事件将会继续传递给ViewGroup的onTouchEvent方法,与事件被拦截的效果一致。
    主要的事件分发链路图如下所示:

源码分析

 * 源码分析:ViewGroup.dispatchTouchEvent()
  */ 
  public boolean dispatchTouchEvent(MotionEvent ev) { 

  // 仅贴出关键代码
  ... 

  if (disallowIntercept || !onInterceptTouchEvent(ev)) {  
  // 分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
    // 判断值1-disallowIntercept:是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
    // 判断值2-!onInterceptTouchEvent(ev) :对onInterceptTouchEvent()返回值取反
        // a. 若在onInterceptTouchEvent()中返回false,即不拦截事件,从而进入到条件判断的内部
        // b. 若在onInterceptTouchEvent()中返回true,即拦截事件,从而跳出了该条件判断
        // c. 关于onInterceptTouchEvent() ->>分析1

  // 分析2
    // 1. 通过for循环,遍历当前ViewGroup下的所有子View
    for (int i = count - 1; i >= 0; i--) {  
        final View child = children[i];  
        if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                || child.getAnimation() != null) {  
            child.getHitRect(frame);  

            // 2. 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
            if (frame.contains(scrolledXInt, scrolledYInt)) {  
                final float xc = scrolledXFloat - child.mLeft;  
                final float yc = scrolledYFloat - child.mTop;  
                ev.setLocation(xc, yc);  
                child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

                // 3. 条件判断的内部调用了该View的dispatchTouchEvent()
                // 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面章节介绍的View事件分发机制)
                if (child.dispatchTouchEvent(ev))  { 

                // 调用子View的dispatchTouchEvent后是有返回值的
                // 若该控件可点击,那么点击时dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
                // 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
                // 即该子View把ViewGroup的点击事件消费掉了

                mMotionTarget = child;  
                return true; 
                      }  
                  }  
              }  
          }  
      }  
    }  

  ...

  return super.dispatchTouchEvent(ev);
  // 若无任何View接收事件(如点击空白处)/ViewGroup本身拦截了事件(复写了onInterceptTouchEvent()返回true)
  // 会调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
  // 因此会执行ViewGroup的onTouch() -> onTouchEvent() -> performClick() -> onClick(),即自己处理该事件,事件不会往下传递
  // 具体请参考View事件分发机制中的View.dispatchTouchEvent()

  ... 

}

/**
  * 分析1:ViewGroup.onInterceptTouchEvent()
  * 作用:是否拦截事件
  * 说明:
  *     a. 返回false:不拦截(默认)
  *     b. 返回true:拦截,即事件停止往下传递(需手动复写onInterceptTouchEvent()其返回true)
  */
  public boolean onInterceptTouchEvent(MotionEvent ev) {  
    
    // 默认不拦截
    return false;

  } 

Android事件分发传递到Acitivity后,总是先传递到ViewGroup、再传递到View。流程总结如下:(假设已经经过了Acitivity事件分发传递并传递到ViewGroup)

View的事件分发流程

View 中包含如下两个事件分发与处理的方法,分别是:

  • dispatchTouchEvent(MotionEvent event)
  • onTouchEvent(MotionEvent event) View通过dispatchTouchEvent方法接收到从ViewGroup传递过来的事件后,直接调用 onTouchEvent方法处理事件。如果消费了该事件,则分发结束,未消费则调用ViewGroup的onTouchEvent方法处理事件。所以 View 中的事件处理流程很简单,如下图:

    此处说明一个细节,当View把事件消费后,如果View的onTouch方法返回true,View的dispatchTouchEvent方法会直接返回true,不会再调用View的onClick方法。只有当onTouch方法返回false时,才会有onClick事件处理。
    场景:拦截DOWN的后续事件
    结论
  • 若 ViewGroup 拦截了一个半路的事件(如MOVE),该事件将会被系统变成一个CANCEL事件 & 传递给之前处理该事件的子View;
  • 该事件不会再传递给ViewGroup 的onTouchEvent()
  • 只有再到来的事件才会传递到ViewGroup的onTouchEvent()
    场景描述
    ViewGroup B 无拦截DOWN事件(还是View C来处理DOWN事件),但它拦截了接下来的MOVE事件

即 DOWN事件传递到View C的onTouchEvent(),返回了true

实例

  • 在后续到来的MOVE事件,ViewGroup B 的onInterceptTouchEvent()返回true拦截该MOVE事件,但该事件并没有传递给ViewGroup B ;这个MOVE事件将会被系统变成一个CANCEL事件传递给View C的onTouchEvent()
  • 后续又来了一个MOVE事件,该MOVE事件才会直接传递给ViewGroup B 的onTouchEvent()
    后续事件将直接传递给ViewGroup B 的onTouchEvent()处理,而不会再传递给ViewGroup B 的onInterceptTouchEvent(),因该方法一旦返回一次true,就再也不会被调用了。
  • View C再也不会收到该事件列产生的后续事件

1.3 特殊说明

  • 若给控件注册了Touch事件,每次点击都会触发一系列action事件(ACTION_DOWN,ACTION_MOVE,ACTION_UP等)
  • 当dispatchTouchEvent()事件分发时,只有前一个事件(如ACTION_DOWN)返回true,才会收到后一个事件(ACTION_MOVE和ACTION_UP)
    即如果在执行ACTION_DOWN时返回false,后面一系列的ACTION_MOVE、ACTION_UP事件都不会执行

二、Activity解析

2.1 主要的几种Activity

在 Android 应用程序开发中,Activity 是一个关键组件,用于处理用户界面和用户交互。当开发 Android

应用程序时,通常会使用 Activity 类作为应用程序中每个屏幕的基础。 ComponentActivity 是一个继承自

FrgmentActivity 的类,它提供了与 AndroidX 组件兼容的 Activity 基础功能,比如支持

Fragment,支持生命周期,支持在旋转设备时保存 Activity 状态等等。 AppCompatActivity 是一个

Android 支持库中的类,用于在旧版 Android 平台上实现 Material Design 风格的应用程序。它扩展自

FragmentActivity,是 Activity 的子类,为 Android 应用程序提供了兼容性支持,使得应用程序可以在不同版本的

Android 平台上运行。 因此,ComponentActivity 和 AppCompatActivity 都是 Activity

的子类,但 ComponentActivity 是 AndroidX 组件兼容的 Activity 基础类,而

AppCompatActivity 则是提供兼容性支持的 Activity 类。 一般来说,如果应用程序需要支持 Material

Design,并且需要在旧版 Android 平台上运行,那么就应该使用 AppCompatActivity。而如果你的应用程序已经使用了

AndroidX 组件,并且不需要在旧版 Android 平台上运行,那么就应该使用 ComponentActivity。

2.2 Activity启动流程

我们手机的桌面是一个叫做Launcher的Activity,它罗列了手机中的应用图标,图标中包含安装apk时解析的应用默认启动页等信息。在点击应用图标时,即将要启动的App和Launcher、AMS、Zygote所属进程不同所以涉及到Launcher与AMS,AMS与Zygote,AMS与新App这四者多次通信,才会启动一个App,然后再启动Activity

Activity启动 的 整体交互图:

Launcher向AMS发送启动Activity

在用户点击应用图标时,Launcher这个Activity会调用startActivitySafely方法,最后调用到Activity.startActivity方法。

Launcher.java
   public boolean startActivitySafely(View v, Intent intent, ItemInfo item) {
       ...
       //标记在新的栈启动
       intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
       ...
       startActivity(intent, optsBundle);
       ...
   }

Activity.java
   @Override
   public void startActivity(Intent intent, @Nullable Bundle options) {
       ...
       if (options != null) {
           //-1为requestCode表明不需要知道是否启动成功
           startActivityForResult(intent, -1, options);
       } else {
           startActivityForResult(intent, -1);
       }
   }

   public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
           @Nullable Bundle options) {
           ...
           Instrumentation.ActivityResult ar =
               mInstrumentation.execStartActivity(
                   this, mMainThread.getApplicationThread(), mToken,this,intent, requestCode, options);
           ...
    }

里面调用了mInstrumentation.execStartActivity方法,其中一个参数mMainThread.getApplicationThread(),它的类型是ApplicationThread,ApplicationThread是ActivityThread的内部类,继承IApplicationThread.Stub,也是个Binder对象,在Activity工作流程中有重要作用。而Instrumentation具有跟踪application及activity生命周期的功能

Instrumentation.java
public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
    ...
        int result = ActivityTaskManager.getService().startActivity(whoThread,who.getBasePackageName(),
        who.getAttributionTag(),intent,intent.resolveTypeIfNeeded(who.getContentResolver()),
        token,target != null ? target.mEmbeddedID : null, requestCode, 0, null, options); 
    ...
}

ActivityTaskManager.java
public static IActivityTaskManager getService() {
       return IActivityTaskManagerSingleton.get();
}
    
private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
            new Singleton<IActivityTaskManager>() {
                @Override
                protected IActivityTaskManager create() {
                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                    return IActivityTaskManager.Stub.asInterface(b);
                }
             }
};

这一步Launcher开始向AMS通信,由于在不同的进程所以需要通过Binder来通信,IActivityTaskManager是一个代理AMS端Binder的对象,之后AMS开始startActivity。

ATMS是在Android10中新增的,分担了之前ActivityManagerService(AMS)的一部分功能(activity task相关)。 在Android10 之前 ,这个地方获取的是服务是AMS。查看Android10的AMS,你会发现startActivity方法内也是调用了ATMS的startActivity方法。所以在理解上,ATMS就隶属于AMS。

PS:Activity的启动过程详解(基于Android10.0)

整体类图

关联知识点:

1、Activity的显示原理(Window、DecorView、ViewRoot)

2、Activity的UI刷新机制(Vsync、Choreographer)

3、UI的绘制原理(Measure、Layout、Draw)

4、Surface原理(Surface、SurfaceFlinger服务):

Activity相关重要类含义

相关推荐
胡八一3 分钟前
Maven插件打包发布远程Docker镜像
java·docker·maven
Abelard_14 分钟前
JVM--内存结构
java·开发语言·jvm
JWASX17 分钟前
定时/延时任务-Netty时间轮源码分析
java·netty·定时任务·时间轮
oioihoii25 分钟前
深入解析C++中的函数指针与`typedef`的妙用
java·开发语言·c++
程序猿进阶41 分钟前
Dead Code Clean
java·spring boot·后端·kafka·代码规范·质量管理·代码清理
枫叶落雨2221 小时前
jdk1.8安装及环境配置(最新最详细教学!!!)
java·jdk1.8
吴冰_hogan2 小时前
MySQL索引优化
android·mysql
不爱运动的跑者2 小时前
全面解析JWT技术
java·web安全
weisian1513 小时前
Redis篇-7--原理篇6--过期机制(定时删除,惰性删除,Redis过期事件监听和Java实现)
java·数据库·redis