【Android】源码解析Activity的结构分析

源码解析Activity的结构分析

目录

  • 1、Activity、View、Window有什么关联?
  • 2、Activity的结构构建流程
  • 3 源码解析Activity的构成
    • 3.1 Activity的Attach方法
    • 3.2 Activity的OnCreate
  • 4、WindowManager与View的关系
  • 总结
    • 1、一个Activity对应几个WindowManage,对应几个Window?
    • 2、DecorView在哪被创建?
    • 3、PhoneWindow和Window有什么关系?
    • 4、在Activity的onResume方法调用Handler的post可以获取View的宽高吗?View的post方法能拿到View的宽高?

参考文献:
1、Android进阶之光第二版
2、Android 源码分析 - Activity的结构分析
3、反思|Android LayoutInflater机制的设计与实现

1、Activity、View、Window有什么关联?

用一个简单的例子理解它们,假设现在正在装修一个新房子:

📌Activity相当于一个房子。
Window相当于房子的窗户,可以通过窗户观察到房子。
WindowManage 相当于管家,控制窗户的开关。
View 相当于各种各样的家具。
layoutInflater 相当于室内装修师 将家具(View)正确的摆放在房子(Activity)中。
XML文件 就像是装修图纸,将不同的家具(View)排列组合

通过一个图理解它们之间的层级关系:

关于Activity和Window,DecorView怎么关联起来参考:View事件的分发机制

2、Activity的结构构建流程

首先简单介绍一下各个部分的作用:

ActivityThread:每个流程调用起始地点
Activity:相当于是一个管理者,负责创建WindowManagerWindow
Window:承载着View,同时代Activity处理一切View的事务。
WindowManager:从字面意思来理解是Window的管理,其实是管理Window上的View,包括addViewremove

3 源码解析Activity的构成

3.1 Activity的Attach方法

在Activity的Attach方法中主要做了两件事:

  1. 初始化mWindow,通过new PhoneWindow调用它的构造方法。
  2. 初始化WindowManage,并且将它set到Window中

接下来具体看看源码在干啥:

@UnsupportedAppUsage
private WindowManager mWindowManager;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);  //1// ······//  2mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);// ······mWindowManager = mWindow.getWindowManager();  //3
}

这里的context.getSystemService方法就是用来返回一个WindowManage对象

@Override
public Object getSystemService(@ServiceName @NonNull String name) {if (getBaseContext() == null) {throw new IllegalStateException("System services not available to Activities before onCreate()");}if (WINDOW_SERVICE.equals(name)) {return mWindowManager;} else if (SEARCH_SERVICE.equals(name)) {ensureSearchManager();return mSearchManager;}return super.getSystemService(name);
}

一个小疑问,为什么先setWindowManager接下来又通过getWindowManager获取mWindowManager,但是getSystemService返回的也是这个mWindowManager,这是在做什么?

实际上Android在这里做了一个缓存,在第一次创建时super.getSystemService(name);调用系统级别的管理器WindowManager,再之后的创建每一次都是同一个WindowManager

当我们调用 context.getSystemService(Context.WINDOW_SERVICE) 时,实际上返回的是 WindowManagerGlobal唯一的那个 WindowManagerImpl 实例的一个代理对象。这种设计使得整个系统只存在一个真正的 WindowManagerImpl 实例,所有视图都是由它来管理和调度的。

3.2 Activity的OnCreate

OnCreate主要通过setContentView方法给当前页面设置一个布局,实际上 Activity的setContentView并没有做什么工作,主要是Window的setContentView方法实现了这个功能。

当一个事件点击后首先传递给Activity,在我们写Activity时会调用setContentView方法来加载布局,我们看一下setContenView方法在做什么:

//frameworks/base/core/java/android/app/Activity.javapublic void setContentView(@LayoutRes int layoutResID) {getWindow().setContentView(layoutResID);initWindowDecorActionBar();
}

发现它首先调用了getWindowsetContentview方法,那么getWindow是什么呢?它返回了一个mWindow对象,查看代码后再Activity的attach方法中发现了它。

mWindow = new PhoneWindow(this, window, activityConfigCallback);

它原来是一个PhoneWindow,接下来我们看看它的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();   //1} 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;
}

FEATURE_CONTENT_TRANSITIONS是一个用于启用内容转换特性的标志,作用时提供一种动画效果过渡的切换视图。

我们重点看一下mContentParent为null时installDecor()方法做了什么。这个方法比较长,看一下重点地方:

private void installDecor() {mForceDecorInstall = false;if (mDecor == null) {mDecor = generateDecor(-1);  //1mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);mDecor.setIsRootNamespace(true);if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);}} else {mDecor.setWindow(this);}if (mContentParent == null) {mContentParent = generateLayout(mDecor);  //2

看一下注释1的代码做了什么事情,发现这个generateDecor创建了一个DecorView

protected DecorView generateDecor(int featureId) {// System process doesn't have application context and in that case we need to directly use// the context we have. Otherwise we want the application context, so we don't cling to the// activity.Context context;if (mUseDecorContext) {Context applicationContext = getContext().getApplicationContext();if (applicationContext == null) {context = getContext();} else {context = new DecorContext(applicationContext, this);if (mTheme != -1) {context.setTheme(mTheme);}}} else {context = getContext();}return new DecorView(context, featureId, this, getAttributes());
}

查看DecorView源码发现,它继承了Fragment。

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks 

接下来我们再回到installDecor() 方法,看一下注释2 中的generateLayout(mDecor)做了什么事。

这段代码很长,具体就不展示了。其中最重要的一点就是根据不同的情况给LayoutResource加载不同的布局。我们查看其中的一个布局文件R.layout.screen_title。这个文件在:frameworks/base/core/res/res/layout/screen_title.xml中,代码如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:fitsSystemWindows="true"><!-- Popout bar for action modes --><ViewStub android:id="@+id/action_mode_bar_stub"android:inflatedId="@+id/action_mode_bar"android:layout="@layout/action_mode_bar"android:layout_width="match_parent"android:layout_height="wrap_content"android:theme="?attr/actionBarTheme" /><FrameLayoutandroid:layout_width="match_parent" android:layout_height="?android:attr/windowTitleSize"style="?android:attr/windowTitleBackgroundStyle"><TextView android:id="@android:id/title" style="?android:attr/windowTitleStyle"android:background="@null"android:fadingEdge="horizontal"android:gravity="center_vertical"android:layout_width="match_parent"android:layout_height="match_parent" /></FrameLayout><FrameLayout android:id="@android:id/content"android:layout_width="match_parent" android:layout_height="0dip"android:layout_weight="1"android:foregroundGravity="fill_horizontal|top"android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

上面的ViewStub是用来显示Actionbar的,下面的两个Fragment一个是Title用于显示标题,另一个是Conten,用来显示内容。

刚刚通过这段源码分析可以知道一个Activity包含了一个Window对象,这个对象是PhoneWindow来实现的。PhoneWindow将DecorView作为整个应用窗口的根View,这个DecorView将屏幕分成两个区域,一个区域是TitleView,另一个区域是ContenView。而我们平常写的布局都是展示在ContenView中。如图:

4、WindowManager与View的关系

众所周知,DecrorViewViewParentViewRootImpl,而View最重要的三大流程就是由ViewRootImpl触发的。

结合上面的流程我们知道了DecroView的创建过程,那么它是如何被绑定到Window上的呢?ViewRootImpl又是怎么和WindowDecroView建立联系的?

我们先看一下ActivityThreadhandleResumeActivity方法在干什么:

代码较长这里截取关键地方

@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,boolean isForward, boolean shouldSendCompatFakeFocus, String reason) {//........// TODO 将resumeArgs推送到活动中以供考虑// 对于双恢复和 r.mFinish = true 的情况,跳过以下步骤。// 1if (!performResumeActivity(r, finalStateRequest, reason)) {return;}//........if (r.window == null && !a.mFinished && willBeVisible) {r.window = r.activity.getWindow();View decor = r.window.getDecorView();  //2decor.setVisibility(View.INVISIBLE);ViewManager wm = a.getWindowManager(); //3WindowManager.LayoutParams l = r.window.getAttributes();a.mDecor = decor;l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;l.softInputMode |= forwardBit;if (r.mPreserveWindow) {a.mWindowAdded = true;r.mPreserveWindow = false;// Normally the ViewRoot sets up callbacks with the Activity// in addView->ViewRootImpl#setView. If we are instead reusing// the decor view we have to notify the view root that the// callbacks may have changed.ViewRootImpl impl = decor.getViewRootImpl();if (impl != null) {impl.notifyChildRebuilt();}}if (a.mVisibleFromClient) {if (!a.mWindowAdded) {a.mWindowAdded = true;wm.addView(decor, l); //4} else {// The activity will get a callback for this {@link LayoutParams} change// earlier. However, at that time the decor will not be set (this is set// in this method), so no action will be taken. This call ensures the// callback occurs with the decor set.a.onWindowAttributesChanged(l);}}//..........}

handleResumeActivity主要做了两件事件,第一件事情在注释1处,通过performResumeActivity进而回调ActivityonResume方法。

第二件事是注释2,3,4共同完成,它将一个DecorView添加到了WindowManage中。

我们详细看一下这个addView的过程,通过查找发现这个addView实际上是WindowManageImpladdView

//WindowManageImpl@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {applyTokens(params);mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,mContext.getUserId());
}

在这个方法中调用了mGlobaladdView方法,继续查找源码发现mGlobal居然是一个WindowManagerGlobal。看一下它的addView在干什么,同样的代码过长,我们在这选出重点代码。

//WindowManagerGlobalprivate final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =new ArrayList<WindowManager.LayoutParams>();public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {//.....// 1if (windowlessSession == null) {root = new ViewRootImpl(view.getContext(), display);} else {root = new ViewRootImpl(view.getContext(), display,windowlessSession, new WindowlessWindowLayout());}view.setLayoutParams(wparams);mViews.add(view);mRoots.add(root);mParams.add(wparams);// do this last because it fires off messages to start doing thingstry {root.setView(view, wparams, panelParentView, userId); //2} catch (RuntimeException e) {final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);// BadTokenException or InvalidDisplayException, clean up.if (viewIndex >= 0) {removeViewLocked(viewIndex, true);}throw e;}//.....}

这个方法也主要干了两件事,在注释1处初始化了ViewRootImpl,在注释2处通过这个set方法将DecorView绑定到了ViewRootImpl中,并且触发了View的三大流程1

通过上面的分析我们知道,每个Window都对应着一个DecorView,而从这里我们可以发现,每个DecorView都对应着一个ViewRootImpl

📌从而得知,如果是一个Dialog或者其他新Window的界面,必定有一个新的ViewRootImpl来触发View的三大流程,而不是由宿主WindowViwRootImpl触发的。

总结

1、一个Activity对应几个WindowManage,对应几个Window?

通过3.1源码分析可知,一个Activity对应一个WindowManage,而一个WindowManage对应一个Window。并且一个Window对应一个DecorView,而每个DecorView着对应一个ViewRootImpl

有一些特殊情况下可能会存在多个 DecorView,比如系统弹出对话框或者悬浮窗口等。但是这些额外的 DecorView 通常不是直接与 Activity 关联的,而是由系统创建和管理的。在这些情况下,虽然存在多个 DecorView,但它们不是在同一个 Window 中,并且与主 ActivityDecorView 是独立的。

2、DecorView在哪被创建?

DecorView是在Window被创建的时候同步创建的,具体来说,DecorViewPhoneWindowsetContentView() 方法中被创建。Window会通过LayoutInflater将选定的DecorView布局加载并实例化成View对象。这个View对象就是DecorView

最后,DecorView会被设置为Window的顶级View,所有的UI界面都是附加到这个DecorView的子View上ContentView。

3、PhoneWindow和Window有什么关系?

它们是继承关系,PhoneWindow继承了Window,并针对手机平台的特性进行了具体实现和扩展。

4、在Activity的onResume方法调用Handler的post可以获取View的宽高吗?View的post方法能拿到View的宽高?

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

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

这里使用了一个Handler.post了一个异步消息来进行测量。尽管post的是异步消息,但在onResume方法中无法保证中立即获取到正确的视图宽高,在Activity的onResume方法调用Handler.post不能获取View的宽高。

View.post方法可以获取View的宽高,View.post 方法添加的消息会在主线程空闲时被处理,这时候通常是在视图的测量和布局过程完成之后。


  1. View 的三大流程通常指的是 View 的绘制流程、布局流程和事件分发流程。 ↩︎

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/326444.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL数据库开发设计规范】之字段设计规范

欢迎点开这篇文章&#xff0c;自我介绍一下哈&#xff0c;本人姑苏老陈 &#xff0c;是一名JAVA开发老兵。 本文收录于 《MySQL数据库开发设计规范》专栏中&#xff0c;该专栏主要分享一些关于MySQL数据库开发设计相关的技术规范文章&#xff0c;定期更新&#xff0c;欢迎关注&…

【C++】string类的使用④(字符串操作String operations || 常量成员Member constants)

&#x1f525;个人主页&#xff1a; Forcible Bug Maker &#x1f525;专栏&#xff1a; STL || C 目录 前言&#x1f525;字符串操作&#xff08;String operations&#xff09;c_strdataget_allocatorcopyfindrfindfind_first_offind_last_offind_first_not_offind_last_not…

Dockerfile实践java项目

目的&#xff1a;用java项目测试dockerfil部署&#xff08;前提是安装好了docker&#xff09; 部署准备文件如下 1. java项目 java项目demo地址 https://gitee.com/xiaoqu_12/dockerfileDemo.git 或者百度网盘直接下载打包好的jar包 链接&#xff1a;https://pan.baidu.com/s/…

ES扩缩容

ES扩容 1.1 页面扩容ES1 1.2 拷贝插件及ssl文件 JSON [ec_admin@kde-offline3 ~]$ sudo rsync -avP /usr/kde_ec/2.3.6.6-1/elasticsearch1/plugins/* kde-offline6:/usr/kde_ec/2.3.6.6-1/elasticsearch1/plugins/ ;echo $? [ec_admin@kde-offline3 ~]$ sudo rsync -avP /us…

JavaScript 进阶(一)

一、作用域 1. 局部作用域 &#xff08;1&#xff09;函数作用域 、 &#xff08;2&#xff09;块作用域 2. 全局作用域 3. 作用域链 g 作用域可以访问 f 作用域&#xff08;子访问父&#xff09;&#xff0c;但是 f 作用域&#xff0c;不能访问 g 作用域&#xff08;父…

内容与图像一对多问题解决

场景复现 分析&#xff1a; 其实这是两给表&#xff0c;一个内容表&#xff0c;一个图片表&#xff0c;一对多的关系。 解决思路: 1. 先上传图片拿到图片的List集合ids&#xff0c;返回值是集合的ids&#xff0c;给到前端 2. 再添加内容表的数据生成了id&#xff0c;遍历查…

工程师工具箱系列(3)Arthas

文章目录 工程师工具箱系列&#xff08;3&#xff09;Arthas安装与准备Arthas插件使用场景查看某个变量值ognl方式调用Bean方法tt(TimeTunel)方式调用Bean的方法ognl调用带参数方法 资源总览 工程师工具箱系列&#xff08;3&#xff09;Arthas Java诊断利器 安装与准备 window…

强化学习——马尔可夫过程的理解

目录 一、马尔可夫过程1.随机过程2.马尔可夫性质3.马尔可夫过程4.马尔可夫过程示例 参考文献 一、马尔可夫过程 1.随机过程 随机过程是概率论的“动态”版本。普通概率论研究的是固定不变的随机现象&#xff0c;而随机过程则专注于那些随时间不断变化的情况&#xff0c;比如天…

M 有效算法

M 有效算法 本题考验二分知识&#xff0c;思路是二分k的取值&#xff0c;就按第一组样例来说当我们k取值为1的时候我们遍历数组想让|8-x|<k1的话x的取值范围是7-9&#xff0c;想让|3-x|<k2的话x的取值范围是1-5&#xff0c;两者x的区间不重合&#xff0c;说明肯定没有x能…

测试项目实战--安享理财2(Jmeter接口测试)

说明&#xff1a; 1.访问地址&#xff1a; 本项目实战使用的是传智播客的安享理财项目&#xff08;找了半天这个项目能免费用且能够满足测试实战需求&#xff09; 前台&#xff1a;http://121.43.169.97:8081/ 后台&#xff1a;http://121.43.169.97:8082/ &#xff08;点赞收藏…

运筹系列92:vrp算法包VROOM

1. 介绍 VROOM is an open-source optimization engine written in C20 that aim at providing good solutions to various real-life vehicle routing problems (VRP) within a small computing time. 可以解决如下问题&#xff1a; TSP (travelling salesman problem) CVRP …

数字序列比大小 - 贪心思维

系列文章目录 文章目录 系列文章目录前言一、题目描述二、输入描述三、输出描述四、java代码五、测试用例 前言 本人最近再练习算法&#xff0c;所以会发布自己的解题思路&#xff0c;希望大家多指教 一、题目描述 A&#xff0c;B两个人万一个数字的游戏&#xff0c;在游戏前…

C++学习笔记3

A. 求出那个数 题目描述 喵喵是一个爱睡懒觉的姑娘&#xff0c;所以每天早上喵喵的妈妈都花费很大的力气才能把喵喵叫起来去上学。 在放学的路上&#xff0c;喵喵看到有一家店在打折卖闹钟&#xff0c;她就准备买个闹钟回家叫自己早晨起床&#xff0c;以便不让妈妈这么的辛苦…

Windows2016系统禁止关闭系统自动更新教程

目录 1.输入cmd--适合系统2016版本2.输入sconfig&#xff0c;然后按回车键3.输入5&#xff0c;然后按回车键4.示例需要设置为手动更新&#xff0c;即输入M&#xff0c;然后按回车键 1.输入cmd–适合系统2016版本 2.输入sconfig&#xff0c;然后按回车键 3.输入5&#xff0c;然后…

基于 Spring Boot 博客系统开发(七)

基于 Spring Boot 博客系统开发&#xff08;七&#xff09; 本系统是简易的个人博客系统开发&#xff0c;为了更加熟练地掌握 SprIng Boot 框架及相关技术的使用。&#x1f33f;&#x1f33f;&#x1f33f; 基于 Spring Boot 博客系统开发&#xff08;六&#xff09;&#x1f…

代码随想录第五十一天|最长递增子序列、最长连续递增序列、最长重复子数组

题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题目链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09;

NSSCTF | [第五空间 2021]WebFTP

注意看这里的题目标签&#xff0c;目录扫描&#xff0c;.git泄露。那么这道题虽然打开是一个登录的界面&#xff0c;但是并不是我们熟悉的爆破和SQL注入。 但是可以在题目标签上看到目录扫描&#xff0c;我们就用dirsearch扫一扫看看 python dirsearch.py -u http://node4.ann…

【C++ 】红黑树

1.1 红黑树的概念 红黑树&#xff0c;是一种二叉搜索树&#xff0c;但在每个结点上增加一个存储位表示结点的颜色&#xff0c;可以是Red或 Black。 通过对任何一条从根到叶子的路径上各个结点着色方式的限制&#xff0c;红黑树确保没有一条路 径会比其他路径长出俩倍&#xff…

RabbitMQ的用途

RabbitMQ主要有四个用途&#xff0c;分别是应用解耦、异步提速、削峰填谷、消息分发。详情讲解如下&#xff1a; RabbitMQ介绍、解耦、提速、削峰、分发 详解、RabbitMQ安装 可视化界面讲解 1.应用解耦&#xff1a;提高系统容错性和可维护性 2.异步提速&#xff1a;提升用户体验…

自动驾驶系统中的数据闭环:挑战与前景

目录 自动驾驶概况 1.1自动驾驶分级 1.2自动驾驶国内发展 ​1.3自动驾驶架构模型 数据闭环的意义 2.1 搜集corner case的数据 2.2 提高模型的泛化能力 2.3 驱动算法迭代 数据闭环落地的痛点及对策 3.1 数据采集和使用的合规性问题 3.2 数据确权问题 3.3 数据采集…