Activity的结构分析

2023-05-16

1. 结构介绍

  大伙儿应该都知道,Activity的结构分为三层,分别是:ActivityWindowView,不同层承担着不同的责任。

  上面的图简单的描述了Activity整个结构的构建流程。这里我再简单的介绍一下这几个部分的作用。

  1. ActivityThread:每个流程调用起始地点,至于ActivityThread的内容不是本文的重点,所以本文不会过多介绍,后续有相关的文章来介绍。
  2. Activity:相当于是一个管理者,负责创建WindowManagerWindow,同时初始化View
  3. Window:承载着View,同时代Activity处理一切View的事务。
  4. WindowManager:从字面意思来理解是Window的管理,其实是管理Window上的View,包括addViewremove

  而这几个角色的对应关系是:ActivityThread全局唯一,整个App进程中只一个实例。ActivityThread可以对应多个Activity,一个Activity对应一个Window(这里不考虑Dialog之类的),一个Window对应一个WindowManager

  本文打算参考上面的流程图,分别分析Activity的attach过程、Activity的onCreateView过程和WindowManager的addView过程。

2. Activity的attach

  Activity的attach过程就是一个初始化的过程,分别对Window进行初始化、WindowManager进行初始化。我们先来看看ActivityThreadhandleLaunchActivity方法:

 public Activity handleLaunchActivity(ActivityClientRecord r,
       // ······

       final Activity a = performLaunchActivity(r, customIntent);
        // ······
   }
复制代码

  handleLaunchActivity方法主要调用performLaunchActivity来启动一个Activity,再来看看performLaunchActivity方法:

 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  // ·······
  activity.attach(appContext, this, getInstrumentation(), r.token,
                      r.ident, app, r.intent, r.activityInfo, title, r.parent,
                      r.embeddedID, r.lastNonConfigurationInstances, config,
                      r.referrer, r.voiceInteractor, window, r.configCallback);
   // ·······
}
复制代码

  在这里,我们看到了Activityattach方法的调用,我们来看看attach方法里面做了哪些事情。

    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.setWindowManager(
               (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
               mToken, mComponent.flattenToString(),
               (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
       // ······
   }
复制代码

  attach方法一共做了两件事情:

  1. 初始化Window,其中Window的构造方法还做了一些事,比如说,我们比较关心的LayoutInflater的初始化。
  2. 初始化WindowManager,并且set到Window里面去。

3. Activity的onCreate

  当初,我们刚入门Android的时候就知道,在ActivityonCreate方法调用setContentView可以给当前页面设置一个布局。当时有没有觉得这个非常的神奇,并且对其充满了好奇,可惜的是当时自己对整个Android的设计还很陌生,不敢去探究。今天我们可以正式进入setContentView的内部了,去一探究竟🤪。

  其实ActivityonCreate过程不仅做setContentView操作,其实还做了其他的事情。从源码中我们也可以看到,比如说,初始化AppCompatDelegate(这里以AppCompatActivity为例),设置Theme等。不过这些不是本文的重点,后面我会专门的文章来分析这些过程,本文的重点是setContentView方法。

  ActivitysetContentView没有做啥事,实际做事的是它的Window。我们来看看WindowsetContentView方法:

    @Override
  public void setContentView(int layoutResID) {
      // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
      // decor, when theme attributes and the like are crystalized. Do not check the feature
      // before this happens.
      if (mContentParent == null) {
          installDecor();
      } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
          mContentParent.removeAllViews();
      }

      if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
          final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                  getContext());
          transitionTo(newScene);
      } else {
          mLayoutInflater.inflate(layoutResID, mContentParent);
      }
      mContentParent.requestApplyInsets();
      final Callback cb = getCallback();
      if (cb != null && !isDestroyed()) {
          cb.onContentChanged();
      }
      mContentParentExplicitlySet = true;
  }
复制代码

  setContentView方法里面主要是做了如下几件事:

  1. 通过installDecor方法创建DecorViewmContentParentDecorView的创建在generateDecor方法;mContentParent的创建在generateLayout方法里面,generateLayout方法里面有一个重要地方根据属性的配置设置了Windowflagsfeatures。这里flags的生效是在DecorViewupdateColorViews方法,会根据flags来计算DecorView的大小;features将那种布局加载到DecorView,这将影响到Activity默认的布局样式。不过我有一点疑惑至今无解,从mContentParent的注释了解到,mContentParent也有可能是DecorView,但是我看了installDecor以及内部调用的generateLayout方法,都没有找到相关可能性。
  2. ContentView加载到mContentParent上去。

  针对上面设置flag生效的解释我还想补充一下,大伙儿应该都知道,在Android中,我们可以通过setFlags或者addFlags来改变一些属性,但是有没有想过是怎么生效的呢?其实过程是非常的简单,我们就不一一的追踪源码了,直接来看一下调用流程:

  Window#setFlags -> PhoneWindow#dispatchWindowAttributesChanged -> DecorView#updateColorViews

  最终,会根据设置好的Flag来计算DecorView的位置和大小,例如说,我们设置了FLAG_FULLSCREEN让界面全屏,最终在updateColorViews方法这么来计算DecorView的高度:

        // If we didn't request fullscreen layout, but we still got it because of the
        // mForceWindowDrawsStatusBarBackground flag, also consume top inset.
        boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
                && (sysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
                && (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
                && (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
                && mForceWindowDrawsStatusBarBackground
                && mLastTopInset != 0;
复制代码

  上面的代码表达的意思非常简单,就是判断是否消费状态栏的高度。至于其他Flag也是如此,有兴趣的同学可以看一看。

4. WindowManager的addView

  我们知道,DecrorViewViewParentViewRootImpl,而View最重要的三大流程就是由ViewRootImpl触发的。在正式介绍这一块的知识之前,我们先来看一个简单的结构图:

  这个图相信大家已经熟悉的不能再熟悉了,不过这里我还是将它贴出来。参考上面的图,我们可以知道,经过上面的两个流程,我们将DecorView部分创建完成,现在还需要两件事需要做:

  1. 初始化ViewRootImpl,并且将ViewRootImplWindow绑定。
  2. 将之前创建好的DecorView添加到ViewRootImpl里面去。

  我们先来看看ActivityThreadhandleResumeActivity方法:

    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        // ······
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
        // ·······
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            // ······
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                }
                // ·······
            }
        }
        // ······

    }
复制代码

  handleResumeActivity方法里面主要做了两件事:

  1. 调用performResumeActivity进而回调ActivityonResume方法。
  2. 调用WindowManageraddView方法,将DecorView添加到ViewRootImpl中去。

  调用的addView方法最终是进入WindowManager的实现类WindowManagerImpl中去,而在WindowManagerImpladdView方法中调用了WindowManagerGlobaladdView方法。   从名字中,我们就可以看出来,WindowManagerGlobal是属于全局的。我们再来看看WindowManagerGlobaladdView方法:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // ······
        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // ·······
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
复制代码

  addView方法主要是做了两件事:

  1. 初始化ViewRootImpl,并且将该Window相关信息保存起来,包括:ViewRootImplDecorViewLayoutParams
  2. 调用ViewRootImplsetView方法,将DecorView添加到ViewRooImpl,并且触发View的三大流程。

  我们都知道,每个Window都对应着一个DecorView,而从这里我们可以发现,每个DecorView都对应着一个ViewRootImpl,从而得知,如果是一个Dialog或者其他新Window的界面,必定有一个新的ViewRootImpl来触发View的三大流程,而不是由宿主WindowViwRootImpl触发的。

(1). 为什么在Activity的onResume方法调用Handler的post不能获取View的宽高呢?

  在回答这个问题之前,我们应该需要注意的是,这里是Handler的post方法,而不是View的post方法。ps:View的post方法能拿到View的宽高。

  我们知道ActivityonResume方法的执行是在ViewRootImpl触发测量过程之前,同时ViewRootImpl是通过如下的方式来触发测量过程的:

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
复制代码

  我们发现这里通过Handler post了一个异步消息来进行测量。可是,尽管post的是异步消息,在onResume方法post的消息也先于它执行,因为它在其后post的。所以,在Activity的onResume方法调用Handler的post不能获取View的宽高。

5. 总结

  到此位置,对Activity的结构分析也差不多,在这里,我们做一个简单的总结。

  1. Activity的结构分为三层,分别是:ActivityWindowView
  2. 结构的创建过程分为三步:1. 创建Activity,并且创建与其相关的WindowManagerWindow,对应着是Activityattach方法的调用;2. 初始化DecorViewContentView,对应着的是ActivityonCreate方法;3. 创建ViewRootImpl,并且将DecorView添加到ViewRootImpl中,同时触发View树的三大流程。
  3. generateLayout方法里面,会根据设置不同的flags来计算DecorView的大小和位置,计算逻辑在updateColorViews方法里面;还是根据设置不同的features方法来选择默认加载到DecorView中,比如说设置了NO_ACTION_BARfeatures,就会加载不带ActionBar的布局。


 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Activity的结构分析 的相关文章

  • TypeScript 终极初学者指南

    大家好 xff0c 我是 ConardLi xff0c 在过去的几年里 TypeScript 变得越来越流行 xff0c 现在许多工作都要求开发人员了解 TypeScript xff0c 各大厂的大型项目基本都要求使用 TypeScript
  • STM32——STM32库结构详解

    STM32库是由ST公司针对STM32提供的函数接口 xff0c 即API xff08 application program interface xff09 xff0c 开发者可以调用这些函数接口来配置STM32的寄存器 xff0c 脱离
  • 监控树莓派Raspberry Pi的CPU/GPU的温度

    监控树莓派Raspberry Pi的CPU GPU的温度 树莓派Raspberry Pi的CPU GPU的温度对于Pi的温度 高效运行非常重要 xff0c 所以我们要实时监控树莓派Raspberry Pi的CPU GPU的温度 1 运行环境
  • 【嵌入式系统】二、初识 Tiva TM4C123G系列开发板

    大二电赛小白 思考 主要偏向于嵌入式的应用 xff0c 请大家多多指教 xff01 TM4C123x系列是TI公司推出的一款32位基于ARM Cortex M4的处理器 1 TM4C123GH6PM的M4内核 超低功耗 耗电量370 A M
  • fdisk用法

    NAME fdisk Partition table manipulator for Linux SYNOPSIS fdisk u b sectorsize C cyls H heads S sects device fdisk l u d
  • 用策略模式优化代码的实例

    实例一 xff1a 利用利用策略模式实际开发中 if else 条件判断过多的问题 xff0c 条件少还好 xff0c 一旦 else if 过多这里的逻辑将会比较混乱 xff0c 并很容易出错 比如 xff1a 刚开始条件较少 xff0c
  • 灰度处理与二值化的关系

    当开始接触图像处理的童鞋可能跟我一样对这两个概念迷惑过 xff0c 在图像处理中 xff0c 用RGB三个分量 xff08 R xff1a Red xff0c G xff1a Green xff0c B xff1a Blue xff09 x
  • ucos2历程——信号量集

    信号量集 信号量集由两部分组成 xff1a 标识组和等待任务列表 xff1b 标识组由三部分组成 xff1a 1 OSFlagType 识别是否为信号量集的标志 2 OSFlagWaitList 指向等待任务列表的指针 3 OSFlagFl
  • 人体姿态估计资源大列表(Human Pose Estimation)

    基础 xff1a Human Pose Estimation人体姿态估计综述调研人体姿态估计数据集整理 xff08 Pose Estimation Keypoint xff09 姿态估计的两个数据集COCO和MPII的认识 Human Po
  • DIY小四轴之电路设计(一)

    DIY小四轴之电路设计 xff08 一 xff09 写在前面 前一阵时间一直在做四轴飞行器 xff0c 略有一点收获吧 xff0c 在这里分享出来 xff0c 一方面算是对自己的总结 xff0c 另一方面希望能给想做小四轴的读者一些思路 本
  • DIY小四轴之电路设计(二)

    DIY小四轴之电路设计 xff08 二 xff09 上次我分析了四轴电源的电路 xff0c 这次我们来看电机驱动与传感器电路 三 空心杯电机驱动电路 一般的小型四轴都选用空心杯电机来驱动旋翼 xff0c 空心杯电机不仅节能而且灵敏 xff0
  • ubuntu 18.04 vnc server开机自启动

    转自 xff1a https blog csdn net lixiaotao 1 article details 90140979 1 首先确定vncserver 以正确安装到linux系统 xff1b 2 设置vncserver随系统自启
  • vnc viewer灰屏的解决方法

    vnc能够连接上 xff0c 但是进入界面灰屏 先关闭当前打开的vnc xff1a vncserver kill 88 然后修改权限 xff1a chmod 43 x vnc xstartup 然后重新打开vnc vncserver geo
  • samba 常用命令

    没怎么修改配置 xff0c 但有时需要修改时 xff0c 又是搜索一番 xff0c 故将常用的在此备份一下 修改samba配置 xff1a span class token function sudo span span class tok
  • .rst 语法+简明教程

    reStructuredText 是扩展名为 rst的纯文本文件 xff0c 含义为 34 重新构建的文本 34 xff0c 也被简称为 xff1a RST或reST xff1b 是Python编程语言的Docutils项目的一部分 xff
  • TG_7100b准备开发环境

    请在 64 位 Ubuntu 下搭建开发环境 Win10 系统可以在应用商店下载安装 Ubuntu18 04 LTS 其他用户可以安装虚拟机软件 以下为基于 Ubuntu 环境开发和编译 SDK 时需要用到的库和依赖包 xff0c 请您按顺
  • C++ STL中各容器内存、优劣的分析

    STL有三大核心部分 xff1a 容器 xff08 Container xff09 算法 xff08 Algorithms xff09 迭代器 xff08 Iterator xff09 以下介绍容器相关内容 xff1a 各种容器的元素在内存
  • 给自己时间沉淀下来

    像很多学长学姐当初一样 xff0c 我也到了繁忙的大四 这个尴尬的时间 xff0c 要选择 xff0c 要放弃 开始实习 xff0c 去窥探一下外面的世界 经过一个月的测试工作 xff0c 开始发现自己与别人的差距还是很大 再继续试水 xf
  • docker安装使用系列二之容器、镜像、仓库使用实例分析

    可能大家对docker了解不深 xff0c 这里再简单赘述一下docker这款利器 1 什么是docker Doker是基于GO语言实现的云开源项目 xff0c 通过对应用组件的封装 分发 部署 运行等生命周期的管理 xff0c 达到应用组
  • 图像处理之opencv库使用小结

    OpenCV是一个基于BSD许可 xff08 开源 xff09 发行的跨平台计算机视觉库 xff0c 可以运行在Linux Windows Android和Mac OS操作系统上 它轻量级而且高效 由一系列 C 函数和少量 C 43 43

随机推荐

  • react 启动项目遇到的问题

    当启动react 项目时遇到 xff1a 39 react scripts 39 不是内部或外部命令 xff0c 也不是可运行的程序 npm install npm install 下载依赖遇到安装失败 xff0c 则依赖包删除不干净 xf
  • Android LED电子表时钟字体digital font

    字体效果如下图所示 xff1a 这种类型的字体样式会被一些UI设计用于Android APP中时钟显示 xff0c 比如交通灯倒计时 实现这种字体样式 xff0c 先导入一个字体包 xff1a digital ttf 这个digital t
  • Android音视频处理之MediaCodec

    MediaCodec是Android中媒体编解码器 xff0c 可以对媒体进行编 解码 MediaCodec采用同步 异步方式处理数据 xff0c 并且使用了一组输入输出缓存 xff08 ByteBuffer xff09 通过请求一个空的输
  • 计算相机投影矩阵(含代码)(Python)

    计算相机投影矩阵 xff08 含代码 xff09 xff08 Python xff09 前几天处理点云时 xff0c 需要使用到像片与3D点云的对应关系 在这边找了一圈没有发现直接可用的代码 xff0c 于是去GitHub试了一下 xff0
  • H264 SPS中得到宽高的代码(java)

    数据需要去掉头 xff0c SPS测试数据 byte buffer 61 new byte 103 66 64 12 38 5 7 56 7 124 2 得到结果宽320高240 public class H264SpsParser pri
  • git 调换提交顺序

    前两个commit交换顺序 1 查看提交历史 git log oneline 2 把要调整顺序的commit显示在vim中 git rebase i a33d521 a33d521用来确定commit范围 xff0c 表示从此提交开始到当前
  • android hmacSha256 加密

    public class HMACSHA256 public static String hmacSha256 String KEY String VALUE return hmacSha KEY VALUE 34 HmacSHA256 3
  • Java生成固定长度的随机字符串(以大小写字母和数字)

    public class RandomStringUtil public static ArrayList lt String gt strList 61 new ArrayList lt String gt public static R
  • Android reckon 控制项目打包版本

    reckon 用法 github地址 xff1a https github com ajoberstar reckon 根项目 gradle配置 buildscript apply from 39 versions gradle 39 re
  • ArrayList源码解析

    构造函数 Constructs an empty list with an initial capacity of ten 使用10个初始容量构造一个空的集合 public ArrayList super 用一个空的数组进行初始化 this
  • 2023年有效的rtsp,rtmp,hls流媒体测试地址整理汇总

    rtsp rtsp wowzaec2demo streamlock net vod mp4 BigBuckBunny 115k mov 已停用 rtsp wowzaec2demo streamlock net vod mp4 BigBuck
  • http请求

    HTTP请求报文 一个HTTP请求报文由请求行 xff08 request line xff09 请求头部 xff08 header xff09 空行和请求数据4个部分组成 1 请求行 请求行分为三个部分 xff1a 请求方法 请求地址和协
  • http响应报文

    HTTP响应报文主要由状态行 响应头部 空行以及响应数据组成 1 状态行 由3部分组成 xff0c 分别为 xff1a 协议版本 xff0c 状态码 xff0c 状态码描述 其中协议版本与请求报文一致 xff0c 状态码描述是对状态码的简单
  • centos7+jdk8+安装Elasticsearch6.0

    一 xff1a 为Elasticsearch准备用户 1 添加用户 Elasticsearch6 0需要使用非root用户启动 root 64 66 adduser ela root 64 66 passwd ela 2 授权用户 查看文件
  • Retrofit2 源码解析

    0 基本使用 1 Retrofit 将我们的 HTTP API 转换成一个 接口形式 所以我们第一步定义一个 interface public interface GitHubService 64 GET 34 user user repo
  • Android Studio插件的源文件位置——mac端

    有些时候安装插件后 xff0c 整个android studio都卡住了 xff0c 无法通过Android Studio gt preferences gt plugins来卸载 xff0c 这时候就需要找到安装位置 xff0c 进行删除
  • H.264编码基础知识详解

    一 编码基础概念 1 为什么要进行视频编码 xff1f 视频是由一帧帧图像组成 xff0c 就如常见的gif图片 xff0c 如果打开一张gif图片 xff0c 可以发现里面是由很多张图片组成 一般视频为了不让观众感觉到卡顿 xff0c 一
  • Android事件分发

    基本知识 什么是触摸事件 触摸事件 xff0c 是Android用来描述你的手对屏幕做的事情的最小单元 关键词有两个 xff1a 手势 xff08 你的手对屏幕做的事情 xff09 最小单元 所谓手势 xff0c 就是比如按下 移动 抬起
  • HashMap这一篇就够了

    介绍下 HashMap 的底层数据结构 现在用的都是 JDK 1 8 xff0c 底层是由 数组 43 链表 43 红黑树 组成 xff0c 如下图 xff0c 而在 JDK 1 8 之前是由 数组 43 链表 组成 为什么要改成 数组 4
  • Activity的结构分析

    1 结构介绍 大伙儿应该都知道 xff0c Activity的结构分为三层 xff0c 分别是 xff1a Activity Window和View xff0c 不同层承担着不同的责任 上面的图简单的描述了Activity整个结构的构建流程