惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 司徒正美
Google Online Security Blog
Google Online Security Blog
博客园_首页
量子位
大猫的无限游戏
大猫的无限游戏
酷 壳 – CoolShell
酷 壳 – CoolShell
The Cloudflare Blog
Vercel News
Vercel News
GbyAI
GbyAI
Y
Y Combinator Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
C
Cisco Blogs
IT之家
IT之家
A
Arctic Wolf
P
Privacy International News Feed
G
GRAHAM CLULEY
腾讯CDC
Microsoft Security Blog
Microsoft Security Blog
Microsoft Azure Blog
Microsoft Azure Blog
美团技术团队
T
The Blog of Author Tim Ferriss
AWS News Blog
AWS News Blog
A
About on SuperTechFans
P
Proofpoint News Feed
I
Intezer
月光博客
月光博客
Cloudbric
Cloudbric
Google DeepMind News
Google DeepMind News
T
Tor Project blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
S
Securelist
Engineering at Meta
Engineering at Meta
爱范儿
爱范儿
F
Full Disclosure
V2EX - 技术
V2EX - 技术
Last Week in AI
Last Week in AI
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
人人都是产品经理
人人都是产品经理
C
Check Point Blog
I
InfoQ
S
Security Affairs
Simon Willison's Weblog
Simon Willison's Weblog
P
Palo Alto Networks Blog
F
Fortinet All Blogs
S
Security @ Cisco Blogs
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
K
Kaspersky official blog
D
Darknet – Hacking Tools, Hacker News & Cyber Security

音视频开发进阶

音视频教程-第三节 音视频教程-第二节 真的,AI 可能就是新时代的信息差 充值 Cursor 之后,工作有了哪些变化?🤔 个人'蒸馏'大模型能做哪些有意思的事情 DeepSeek 大模型在 Mac 上的部署和运行 音视频教程-第一节 【WebRTC 专栏】-- Android 开发集成 WebRTC 库的几种方式 【WebRTC 专栏】-- 在 Mac M1 等系列芯片编译和开发 WebRTC-Android 库 Meta Llama3 大模型在 Mac 上的部署和运行 iOS VideoToolBox 解码 HEVC Open-GOP 视频的问题排查 Flutter 状态管理之 InheritedWidget 使用和分析 用 ChatGPT 回答技术问题怎么样 ? 音视频开发系统入门大致路线 UE 4.27 添加自定义 ShadingModel 用 UE4 虚幻引擎做个捏脸小功能~~ UE4 材质练习 之 凹凸贴图偏移的使用 UE4 材质练习系列基础 OpenGL上下文创建以及共享机制 007 | 播放器系列专栏-解析 MP4 文件读取信息 006 | 播放器系列专栏-在 Mac 上查看 MP4 格式信息 干货 | 快速抽取缩略图是怎么练成的? 关于直播、WebRTC、FFmpeg 的那些事 005 | 播放器系列专栏-在 Windows 上查看 MP4 格式信息 将音视频中的花屏、绿屏、黑屏问题一网打尽 关于音视频里面的解码帧率和渲染帧率 004 | 播放器系列专栏-认识MP4视频(下) 003 | 播放器系列专栏-认识MP4视频(上) 入门或者转行音视频,应该要怎么做? H264视频文件如何缩放分辨率 002 | 播放器系列专栏-FFmpeg依赖库的配置 001 | 播放器系列专栏-关于播放器项目的一个小实践 Seek策略以及在有B帧情况下的处理 目前流媒体开发工程师工作内容主要是什么? 一个音视频领域专业问答的小圈子 干货收藏 || Vulkan Game Engine 视频教程 音视频春节假期内卷指南(实操) Vulkan 在 FFmpeg 中的支持 Windows 下 FFmpeg 和 LibX264 的编译和配置 Metal 开发 | 使用 C++ 进行接口调用 音视频开发工作经验分享 || 视频版 FFmpeg 调用 MediaCodec 硬解码到 Surface 上 代码吸猫 | 用 OpenGL 图像渲染的养猫计划 百倍变速--解码到底能不能丢 非参考帧 ?FFmpeg 有话说!!! 老生常谈-FFmpeg 的编译问题轻松搞定 FFmpeg 调用 Android MediaCodec 进行硬解码(附源码) 【WebRTC 专栏】--创建相机预览 Unity Shader 光照基础之 Half Lambert 光照模型 Unity Shader 光照基础之Lambert光照模型 Unity Shader 光照基础内容 Unity Shader 显示一张图片纹理 UnityShader 的基本概念 Unity 物体的基本操作 C++ 模板系列小结07-尾置返回类型 C++ 模板系列小结06-可变参数模板特性 C++ 中的多线程的使用和线程池建设 C++ 模板系列小结05-模板类型作为模板参数 C++ 模板系列小结04-类模板中的成员模板 C++ 模板系列小结03-在模板中指定变量类型 C++ 模板系列小结02-非类型模板参数 C++ 模板系列小结01-函数模板和类模板 从零打造渲染引擎系列01-什么是渲染引擎 iOS开发 - 在 Swift 中去调用 C/C++ 代码 2021 技术新番 - 从零打造渲染引擎系列 iOS 音视频开发的一些基础准备工作 音视频交流群又来啦~~~ 【WebRTC 专栏】WebRTC & Android 开发学习环境搭建~ 【喜大普奔】域名终于备案通过啦 Shader 优化 | OpenGL 绘制网格效果 【音视频连载-011】第二季 FFmpeg 一层一层获取文件信息 KodeLife | Shader 实时编辑预览的强大工具使用实践 推荐几个堪称教科书级别的 Android 音视频入门项目 【音视频连载-010】第二季 FFmpeg 日志打印 【音视频连载-008】基础学习篇-SDL 播放 PCM 音频文件(下) 【音视频连载-007】基础学习篇-SDL 播放 PCM 音频文件(上) 【音视频连载-006】基础学习篇-SDL 播放 YUV 视频文件 【音视频连载-005】基础学习篇-SDL 加载 YUV 文件并显示 【音视频连载-004】基础学习篇-SDL 加载图片并显示 【音视频连载-003】基础学习篇-SDL 消息循环和事件响应 【音视频连载-002】基础学习篇-SDL 创建窗口并显示颜色 【音视频连载-001】基础学习篇- SDL 介绍以及工程配置 LearnOpenGL 源码在 MAC 上的编译与调试 2019 年终总结与回顾 Android NDK 开发的免费技术视频来啦~~ OpenGL 实现视频编辑中的转场效果 OpenGL 实践之贝塞尔曲线绘制 图像库 libjpeg-turbo 编译与实践 图像库 libpng 编译与实践 rust 开发编译 Android 动态库实践 Android NDK 开发 —— 从 Assets 文件夹加载图片并上传纹理 简单易用的图像解码库介绍 —— stb_image 博客图床迁移记 进击的 Vulkan 移动开发之 SwapChain 进击的 Vulkan 移动开发之 Command Buffer 进击的 Vulkan 移动开发之 Instance & Device & Queue 进击的 Vulkan 移动开发(一)之今生前世 Java 显式锁 Lock 与条件队列 C++ 标准容器库小结 一文读懂 YUV 的采样与格式 《OpenGL ES 3.x 游戏开发》碰撞检测之 AABB 包围盒
Android Activity 创建 Window 及添加 View 流程分析
音视频开发进阶 · 2017-12-22 · via 音视频开发进阶

一个专注音视频领域的小圈子

在之前有分析过 Android 6.0 Launcher 启动 Activity 过程,文章的链接如下:

  1. Android 6.0 Launcher 启动 Activity 过程源码分析(一)
  2. Android 6.0 Launcher 启动 Activity 过程源码分析(二)
  3. Android 6.0 Launcher 启动 Activity 过程源码分析(三)
  4. Android 6.0 Launcher 启动 Activity 过程源码分析(四) 大致的总结一下:

ContextImplstartActivity方法开始,通过ActivityManagerProxy代理对象向ActivityManagerService发起 IPC 调用。

ActivityManagerService会对startActivity方法进行响应,处理传递过来的Intent。这其中包括,解析Intent信息,创建或分配待启动的 Activity 的 Task任务,将启动者 Activity 运行至Paused状态,创建待启动 Activity 的进程。

完成一系列准备工作之后,就该启动 Activity 了。启动的入口是ActivityThread类的main方法。在main方法中会开启主线程的消息循环,并调用attch方法,之后就是创建 Application,然后再创建 Activity。

创建 Activity 是 ActivityManagerService 进程向应用程序进程发送一个进程间通信,应用程序进程通过handleLaunchActivity方法进行响应,在其方法内部执行了performLaunchActivity方法和handleResumeActivity来完成启动。

performLaunchActivity方法内部,通过反射创建了 Activity 类,并且调用了 Activity 类的 attach 方法,为其关联运行过程中所依赖的一系列上下文环境变量,比如 Activity 的 Context 就是在 attach 方法内部调用attachBaseContext方法添加上的,同时,还调度了待启动的 Activity 的生命周期,从 OnCreate 到 OnStart 状态 。

performLaunchActivity方法后,就执行了handleResumeActivity方法,将 Activity 的状态运行至 onResume 状态,此时 Activity 就处于可见的状态了。Activity 也处于能够正常工作的状态了。

而 Activity 类的 attach 方法也就是本文要追踪的方法了,在此方法内如何为 Activity 创建 Window 的,之后才是向 Window 中添加 View。

attach 方法内创建 Window 的代码示例如下:

		mWindow = new PhoneWindow(this); // 创建 Window,实例是 PhonwWindow
        mWindow.setCallback(this);    // 设置回调
        mWindow.setOnWindowDismissedCallback(this);  // 设置回调
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);    // 调整键盘
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }

        mWindow.setWindowManager(    // 设置 WindowManager
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();

在 Android 6.0 的源码中,创建 Window 不再采用策略模式了,直接创建了 PhoneWindow。Window 是一个抽象类,它的唯一实现就是 PhoneWindow。

接下来就是为 Window 设置回调,回调是实现都是由 Activity 来实现的,回调方法有很多,就不一一列举了,常见的有如下一些:

  • onAttachedToWindow
  • onDetachedFromWindow
  • dispatchTouchEvent
  • dispatchKetEvent

从 dispatchTouchEvent 回调可以看出,Activity 响应触摸事件,实际上的响应了 Window 的回调方法,说明触摸事件是传递给我们的 Window,然后再又 Activity 进行分发的。

然后就是为 Window 设置 WindowManager 。

至此,Activity 的 Window 就算是创建完成的,剩下的就是往 Window 中添加 View 了。

Window 中 View 的添加

Android 中的所有视图都是通过 Window 来呈现的,不管是 Activity、Dialog 还是 Toast,它们的视图实际上都是附加在 Window 上的。

因此,我们的 Activity 中的所有 View 也是添加在 Window 上的,而这一切入点就是setContentView方法。

    public void setContentView(int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

getWindow 方法返回的就是上面创建的 PhoneWindow 对象,而重点就在于setContentView方法内。

是时候展现一张经典的图了:

activity-window

在上图中,DecorView 就是一个 FrameLayout 的 ViewGroup 容器,也是 Activity 中的顶级 View,一般来说它内部包含标题栏和内部栏,但还是会随着主题发生变化的,但不管怎么样,内容栏是肯定有的。

当创建了一个 DecorView 对象时,还得把内容栏和标题栏给添加进去才行。

同时,DecorView 是依附于 Window 之上的,没有了 Window,DecorView 也无从谈起了。当我们创建好了 DecorView 时,还得把 DecorView 添加到 Window 上。

setContentView方法就是对上述图片创建 DecorView 的解释了。

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {    // 代表图中 ContentView 的内容
            installDecor();    // 创建 DecorView
        } 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 中去,注意 mContentParent 就是上图中的内容栏
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();    // 在 Activity 内部回调 onContentChanged 方法,表示视图已经发生改变
        }
    }

首先要明确的是mContentParent指的就是图中的ContentView内容栏,当内容栏为 null 时,则去为 Window 创建 DecorView 。

当 DecorView 创建完成后,则直接调用 LayoutInflater 的 inflate 方法将我们自己的布局文件添加到mContentParent中,由此完成了 View 的添加。关于 LayoutInflater 如何将布局添加到父布局中,可以参考之前写的文章:Android 布局加载之LayoutInflater

DecorView 的创建和初始 View 的添加

下面就是分析 DecorView 是如何创建并添加初始的布局内容的。

    private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();    // 直接 new 一个 DecorView 对象
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);    // 创建内容栏
        }
	}
	

在 installDecor 方法中直接 new 了一个 DecorView 对象,此时它还是只是一个 FrameLayout 容器,内部还没有东西。接着就是去创建内容栏。

		int layoutResource ;
		// 省略掉根据各自 Feature 参数来初始化不同的 layoutResource 过程
		mDecor.startChanging();    // 标识 DecorView 中的内容开始改变
        View in = mLayoutInflater.inflate(layoutResource, null);    // 加载 XML 布局文件,可能没有标题栏,但一定有内容栏
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));    // 为 DecorView 添加内容
        mContentRoot = (ViewGroup) in;
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        mDecor.finishChanging(); // 标识 DecorView 中的内容改变完成

在上面曾提到了,在 DecorView 中,标题栏可能没有,则内容栏是一定要有的,这是因为用户可能在 Actiivty 设置了相应的参数,比如设置 Window 类型为 FEATURE_NO_TITLE 的类型。

而 DecorView 添加内容 View 时,这个 View 是从 XML 布局文件中加载的,针对不同的 FEATURE, XML 布局文件中的内容也不相同,有的可能就没有标题栏了,只有一个内容栏,而且不管怎么样,所有的内容栏的 ID 都是固定的,为 android.R.id.content

至此,DecorView 就完成了创建过程,并且还向其中添加了标题栏和内容栏。

DecorView 添加至 Window 上

众所周知,在 Activity 的 onCreate 和 onStart 阶段,View 还是处于不可见的状态,只有在 onResume 状态时,View 才是可见的。

而我们现在只是分析完了 DecorView 的添加,还并没将它依附到 Window 上去。只有将 View 添加到 Window 上之后,它才能够拥有具体的功能,处理外界的输入信息等等。

handleResumeActivity方法内的处理代码如下:

	// 变量 r 指的是 ActivityClientRecord 类,变量 a 指的是 Activity,也就是 r.activity
		 if (r.window == null && !a.mFinished && willBeVisible) {    // if 判断成立
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();    // 设置 DecorView 可见
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                l.softInputMode |= forwardBit;
                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);    // WindowManager 添加 DecorView
                }

截取的代码片段,其中r指的就是ActivityClientRecord类型,存储了 Activity 的状态,此时rwindow变量还是为 null 的。于是为ActivityClientRecord相应的变量设置值,以记录当前 Activity 的状态,然后在设置 DecorView 为可见,并把 DecorView 添加到 WindowManager 中去,以便 DecorView 能够响应各种事件,进行事件分发等等操作。

至此,就分析完了 Activity 的 Window 的创建以及 View 的添加过程。

总结

最后再来一发小小的总结,Activity 创建 Window 并添加 View 的流程大致如下:

  • ActivityThread是应用程序的入口,它的attach方法会开启主线程消息循环,然后创建ApplicationActivity等。
  • 创建Activity时,又有调用Activityattach方法,在此方法内将为 Activity 创建一个 Window,此时 Window 内还是空白的。
  • Instrumentation类开始回调 Activity 的一系列生命周期方法,当回调onCreate方法时,主要做的就是创建 DecorView,并将我们传递过来的 View 添加到 DecorView 的内容栏中,而此时,DecorView 和 Window 两者还是处于分离的状态。
  • 在 Activity 生命周期的onResume方法内,将 DecorView 添加进了 Window 中,并设置 DecorView 可见,此时,就完成了 Activity Window 的创建和 View 的添加过程。

原创文章,转载请注明来源:    Android Activity 创建 Window 及添加 View 流程分析