码个蛋(codeegg)第 729 次推文作者: 琼珶和予原文: https://juejin.im/post/5d6a2a0cf265da03f66dd7f7写在前面

在很早以前,楼主简单的学习过Activity的结构,但是当时介于各种原因,只是浅尝辄止,并没有进行深入的学习。同时,我发现自己今年在毕业之后有点颓废,不再有去年那股学习劲儿。经过多次的自省,发现自己是因为找不到学习的方向而颓废的。

经过深刻反思自我之后,为了改变现在的状况,也为了弥补弥补当初学习Activity的遗憾,还为了提升自身的技术水平,我决定从Activity开始入手,学习并记录Android framework的知识。

本文打算介绍并分析Activity的结构设计,包含:ActivityWindow 和 View 相关知识。本文参考资料:

  1. Activity、View、Window的理解一篇文章就够了

  2. 反思|Android LayoutInflater机制的设计与实现

  3. 从Activity创建到View呈现中间发生了什么?

1. 结构是怎样的?

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


上面的图简单的描述了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. attach的过程?

Activity的attach过程就是一个初始化的过程,分别对Window进行初始化、WindowManager进行初始化。

我们先来看看ActivityThread的handleLaunchActivity方法:

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);   // ·······}

在这里,我们看到了Activity的 attach 方法的调用,我们来看看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. onCreate到底做了啥?

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

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

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

@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. 谁来addView?

我们知道,DecrorView的 ViewParent 是 ViewRootImpl,而View最重要的三大流程就是由ViewRootImpl触发的。

在正式介绍这一块的知识之前,我们先来看一个简单的结构图:


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

  1. 初始化ViewRootImpl,并且将ViewRootImplWindow绑定。

  2. 将之前创建好的DecorView添加到ViewRootImpl里面去。

我们先来看看ActivityThread 的 handleResumeActivity 方法:

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中去,而在WindowManagerImpl的 addView 方法中调用了WindowManagerGlobal的 addView 方法。 

从名字中,我们就可以看出来,WindowManagerGlobal是属于全局的。我们再来看看WindowManagerGlobal的 addView 方法:

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的三大流程,而不是由宿主Window的ViwRootImpl触发的。

问题:

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

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

ps: View的post方法能拿到View的宽高。

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

void scheduleTraversals() {}

我们发现这里通过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的布局。

正文到这里结束啦~

近期文章:

  • 趣头条大佬带你飞:实现阿里无抖动换肤

  • 解决ANR、JVM、Serializable与Parcelable、红黑树、一道算法题

  • 趣图:如果编程语言是种武器

日问题:

Activity喊你敢答应了吗?

android activity根节点addview_Activity问你4个问题,你敢回答吗?相关推荐

  1. ConstraintLayout 不能作为activity的根节点,否则fragment显示不出来

    2019独角兽企业重金招聘Python工程师标准>>> ConstraintLayout 不能作为activity的根节点,否则fragment显示不出来 转载于:https://m ...

  2. Android Activity和Fragment的转场动画

    Activity转场动画 Activity的转场动画是通过overridePendingTransition(int enterAnim, int exitAnim)实现的. 这个方法是API Lev ...

  3. Android面试:整理了Android面试官最常问的174道面试题,让你秒变offer收割机

    本文是专为 Android 开发工程师准备的高薪面试真题汇总题库,涵盖历年大厂高频面试题总结+核心考点深度解析,可以帮助大家全面梳理知识点,并针对面试中可能遇到的问题进行深入分析,在众多面试者中脱颖而 ...

  4. android 同根动画_Android(java)学习笔记141:Android下的逐帧动画(Drawable Animation)...

    1. 帧动画: 帧动画顾名思义,一帧一帧播放的动画就是帧动画. 帧动画和我们小时候看的动画片的原理是一样的,在相同区域快速切换图片给人们呈现一种视觉的假象感觉像是在播放动画,其实不过是N张图片在一帧一 ...

  5. Android Activity和Intent机制学习笔记

    转自:http://www.cnblogs.com/feisky/archive/2010/01/16/1649081.html Activity Android中,Activity是所有程序的根本, ...

  6. android activity 回调函数,Android Activity的生命周期

    Activity的生命周期 Android系统根据activity的所处不同阶段对应的唤起其特定的回调函数来执行代码.activity的一系列有序的生命周期回调函数.本文将来讨论下activity各阶 ...

  7. Android Activity标签属性

    Android Activity标签属性 Activity 是 Android 系统四大应用组件之一,用户可与 Activity 提供的屏幕进行交互,以执行拨打电话.拍摄照片.发送电子邮件等操作开发者 ...

  8. [转]Android Activity和Intent机制学习笔记

    Activity Android中,Activity是所有程序的根本,所有程序的流程都运行在Activity之中,Activity具有自己的生命周期(见http://www.cnblogs.com/f ...

  9. android 同根动画_android 动画系列 (1) - tween 动画(view动画)

    这是我这个系列的目录,有兴趣的可以看下: android 动画系列 - 目录 tween 动画早些时候我们也叫补间动画(我也不知道为啥),现在也有叫 view 动画的.tween动画是2.X 时代的产 ...

最新文章

  1. python链表的创建_《大话数据结构》配套源码:链表(Python版)
  2. UVA 11401 - Triangle Counting
  3. 应行家算法_一些行家技巧和窍门
  4. 博图买什么样配置的笔记本_3dsmax需要什么样的笔记本配置?
  5. 2018-2019-1 20165309 《信息安全系统设计基础》第一周学习总结
  6. 无限滑动的banner图,中间显示大图两边显示一部分,无限滚动
  7. Python下opencv(图像的阈值处理)
  8. springboot获取Spring容器中的bean(ApplicationContextAware接口的应用)避免过多的或不用if..else,switch
  9. 百度亮相NeurIPS 首届Expo:向世界科普了中国自动机器学习框架
  10. [工程经验] 电气与控制系统设计方案(框架)- 机器人
  11. 局域网、城域网IEEE802(LAN/MAN)标准——【局域网、城域网网络知识基础篇】
  12. 电脑自带热点,去打不开热点(解决方案)
  13. iphone 控制android手机,如何从Apple手机远程控制Android手机
  14. Java面试系列--HashMap
  15. 人员招聘与培训实务【2】
  16. 甲骨文收购mysql,甲骨文提出十大保证 承诺收购Sun后会善待MySQL
  17. 并发编程(五)python实现生产者消费者模式多线程爬虫
  18. 在我方某前沿防守地域 matlab,[matlab]Monte Carlo模拟学习笔记
  19. 骨传导耳机是怎么传声的、骨传导耳机的优点是什么
  20. 网络学习day04_子网划分

热门文章

  1. select的5中子句where,group by, havaing, order by, limit的使用顺序及实例
  2. java转换CSV文件生成xml格式数据
  3. elf文件下载出错问题
  4. 基础SQL面试题(3)
  5. 教你加快Win7 的启动速度
  6. 智能ABC拼音输入法的“秘密”
  7. 安装caffe(CPU版本)的一些参考和问题的解决
  8. 如何解决get和post乱码问题?
  9. 【异常】Container exited with a non-zero exit code 1 Failing this attempt.Stack trace: ExitCodeException
  10. Access Denied for user root @localhost 解决方案