前言
在Activity
的启动过程中,通常我们在onCreate
生命周期中调用setContentView
方法设置布局文件(即xml文件),似乎这样就完成了布局文件对应的View
的绘制及显示,那么背后具体的原理是什么呢?
👉 Activity onCreate生命周期
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
👇 布局文件
setContentView(R.layout.activity_main);
}
很明显布局文件对应的View
的绘制和显示是伴随着Activity
的生命周期而进行的。在Activity
的启动过程中,ActivityThread
类中的handleLaunchActivity
,performLaunchActivity
,handleResumeActivity
这3个主要的方法完成了Activity
的创建到启动工作,完成了Activity
的onCreate、onStart、onResume
这三个生命周期的执行。
具体的一个Activity启动过程可以参考Android Activity生命周期,启动模式,启动过程详解
让我们伴随着Activity
的生命周期执行过程,来具体进行分析和相关源码解读。先来张总结本文的图:
Activity启动过程
我们按照Activity
的启动过程中涉及到的ActivityThread
类三个方法来逐步说明,首先来看handleLaunchActivity
方法。
1. ActivityThread#handleLaunchActivity()方法:调用performLaunchActivity()和handleResumeActivity()方法
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
...
// 👉 此方法中完成 Activity 生命周期的 onCreate 和 onStart 方法
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
...
// 👉 此方法中完成 Activity 生命周期的 onResume 方法
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
...
}
...
}
handleLaunchActivity()
方法中主要调用了performLaunchActivity()
方法和handleResumeActivity()
方法,performLaunchActivity()
中完成onCreate
和onStart
回调;handleResumeActivity()
中完成onResume
回调。
2. ActivityThread#performLaunchActivity()方法:执行onCreate、onStart生命周期
performLaunchActivity()
方法中主要做的工作包括:
- 1️⃣ 创建
Activity
实例:调用mInstrumentation.newActivity
方法来创建Activity
对象; - 2️⃣ 创建
Application
对象,如果已经创建就不再创建,一个进程只有一个Application
对象; - 3️⃣ 调用
Activity
的attach
方法,在其中创建了PhoneWindow
对象(有关PhoneWindow
对象下文再慢慢解释); - 4️⃣ 调用了
Instrumentation
的callActivityOnCreate
方法,从而间接执行了onCreate
生命周期; - 5️⃣ 调用了
Activity
的performStart
方法,从而间接执行了onStart
生命周期。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
// 👉 通过反射创建 Activity 实例
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
// 👉 通过反射构建 Application,如果已经构建则不会重复构建,一个进程只能有一个 Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
...
// 👉 在这里实例化了 PhoneWindow,并将该 Activity 设置为 PhoneWindow 的 Callback 回调;
// 初始化 WindowManager
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config);
...
// 👉 调用了 Activity 的 performCreate 方法,间接调用了 Activity 的 onCreate 方法
mInstrumentation.callActivityOnCreate(activity, r.state);
// 👉 调用 Activity 的 onStart 方法
if (!r.activity.mFinished) {
activity.performStart();
r.stopped = false;
}
...
}
}
performLaunchActivity()
方法的前两步工作:Activity
对象和Application
对象的创建过程,这里不再具体描述,可以参考Android Activity生命周期,启动模式,启动过程详解。
下面重点看下attach()
方法,其中创建了PhoneWindow
对象,而PhoneWindow
是Window
的一个子类(其实也是唯一子类)。
要想理解PhoneWindow
的作用,肯定要先知道Window
这个概念。
Window:视图容器
Window代表一个窗口,是视图的容器。Android中的视图是以View树的形式组织的,而View树必须依附在Window上才能工作,一个Window对应着一个View树。Activity并不负责视图控制,它只是控制生命周期和处理事件,真正控制视图的是Window。
启动Activity时会创建一个Window,显示Dialog时也会创建一个Window,而显示Toast时也会创建Window,因此Activity内部可以有多个Window。由于View的测量、布局、绘制只是在View树内进行的,因此一个Window内View的改动不会影响到另一个Window。Window是一个抽象类,它只有一个实现类PhoneWindow
,也就是说在PhoneWindow中完成了Window的真正工作。
- 作者:青霉素
- 链接:https://juejin.im/post/5d00cc9fe51d4510624f97b4
- 来源:掘金
Activity#attach()方法:实例化PhoneWindow
介绍完Window
和PhoneWindow
后,再回到attach
方法。其中主要做了以下两步工作:
- 1️⃣ 实例化PhoneWindow:直接new一个
PhoneWindow
对象; - 2️⃣ 为Activity的Window设置WindowManager:Android 中对 Window 的管理都是通过
WindowManager
来完成的,创建PhoneWindow
之后还会为该 Window 对象设置WindowManager
,WindowManager
是一个接口,继承ViewManager
接口,从这里也能看出对 Window 的操作其实就是对 View 的操作。WindowManager
的实现类是WindowMangerImpl
。具体关于 Window和WindowManager
的关系下文再说。
- 作者:任教主来也
- 链接:https://www.jianshu.com/p/a7e9797094eb
- 来源:简书
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) {
...
// 👉 实例化 PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
...
// 👉 为 Activity 的 Window 设置 WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
mWindowManager = mWindow.getWindowManager();
...
}
Activity#onCreate生命周期:调用setContentView方法(设置Activity对应的布局)
在performLaunchActivity()
方法的第4️⃣步中,调用了Instrumentation
的callActivityOnCreate
方法,源码较为简单,如下:
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
// 👉 然后调用 Activity 的 performCreate 方法
final void performCreate(Bundle icicle) {
performCreate(icicle, null);
}
// 👉 执行 onCreate 生命周期
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
....
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
onCreate(icicle);
}
......
}
Activity#setContentView方法
在onCreate
方法中,我们通过setContentView
来加载我们定义的布局文件。Activity的源码中提供了三个重载的setContentView方法,如下:
// 👉 最常用的 setContentView 方法
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public void setContentView(View view) {
getWindow().setContentView(view);
initWindowDecorActionBar();
}
public void setContentView(View view, ViewGroup.LayoutParams params) {
getWindow().setContentView(view, params);
initWindowDecorActionBar();
}
这三种方法都先调用了getWindow()
的setContentView
方法,而getWindow()
方法返回的正是之前创建的PhoneWindow
对象;然后调用Activity的initWindowDecorActionBar
方法。
// 👉 Activity 中 Window 成员变量
private Window mWindow;
public Window getWindow() {
// 👉 这个 mWindow 是在 attach 方法中设置的;
// 👉 mWindow 是一个 PhoneWindow 对象
return mWindow;
}
所以最终调用了PhoneWindow
中的setContentView
方法。
PhoneWindow#setContentView方法
PhoneWindow
中也有三个对应的重载setContentView方法,下面以参数为布局文件
id(layoutResID)的方法进行说明,其中大概做了以下几步工作:
- 1️⃣ 初始化:首先判断
mContentParent
是否为null,如果为 null,则调用installDecor()
方法初始化mContentParent
,此方法后续会说明;
mContentParent 用来装xml布局文件解析出来的view树,是一个FrameLayout,后文还会继续说明其作用。
- 2️⃣ 填充布局:默认情况下会将设置的布局文件解析为View树,并添加到
mContentParent
中; - 3️⃣ 通知Activity布局改变:通过相关回调通知Activity布局已发改变。
public void setContentView(int layoutResID) {
// 👉 首先判断 mContentParent 是否为 null,如果是第一次调用,则调用 installDecor() 方法
if (mContentParent == null) {
👇
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
// 否则判断是否设置 FEATURE_CONTENT_TRANSITIONS Window属性(默认false),
// 如果没有就移除该 mContentParent 内所有的所有子View;
mContentParent.removeAllViews();
}
// 👉 填充布局
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext()););
transitionTo(newScene);
} else {
// 将xml资源文件通过 LayoutInflater 转换为 View 树
// 并且添加至 mContentParent 视图中
👇
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
// 👉 通知 Activity 布局改变
// 获取 Callback,在 Activity attach 方法中通过 setCallback 设置
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
// onContentChanged 是个空方法,当 Activity 的布局改动时,
// 即 setContentView() 或者 addContentView() 方法执行完毕时就会调用该方法
👇
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
PhoneWindow
的setContentView
方法将我们想要显示的View添加到mContentParent
中,而mContentParent
又是由installDecor
方法得来,下面我们关注此方法。
DecorView:窗口顶层视图
再介绍installDecor()
方法之前,我们需要介绍一个重要的概念:DecorView。
installDecor()
方法中重点完成了两个对象的初始化,一个是mDecor
对象,另一个就是之前说的mContentParent
对象,这两个对象的定义如下:
// 👉 是一个 DecorView 对象,而 DecorView 是窗口顶层视图
DecorView mDecor;
// 👉 是一个 ViewGroup ,本质上是一个 FrameLayout
ViewGroup mContentParent;
这里让我们先回顾下已经提到的概念,Activity(调用setContentView
设置布局),PhoneWindow
(setContentView中完成 mContentParent 初始化,填充布局等),这两个与视图有关的概念并未真正直接承载视图,而承载视图的便是 DecorView 对象,即此处的 mDecor
,DecorView
继承自 FrameLayout
,如下所示:
public class DecorView extends FrameLayout
DecorView
既然是一个FrameLayout,那么肯定也有其对应的布局文件,只不过是系统默认设置的布局文件,由于在不同的Activity主题情况下,系统默认的 DecorView 对应的布局不一样,我们这里以其中一种举例说明:
// 👉 screen_simple.xml 为例
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<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" />
<FrameLayout
👇 对应 mContentParent
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
那么mDecor
和 mContentParent
之间是什么关系呢? DecorView
分为两部分,一部分是 ActionBar
(有的Activity Theme情况下没有ActionBar),另一部分就是 mContentParent
(即上图中id为content的FrameLayout)。
那么Activity、PhoneWindow、DecorView三者之间有什么关系呢?见下图所示:
PhoneWindow#installDecor方法
介绍完DecorView
后,我们再回头介绍installDecor
方法的具体工作,其中主要做了以下几步工作:
- 1️⃣ 初始化
mDecor
:调用generateDecor
方法来创建mDecor
对象; - 2️⃣ 初始化
mContentParent
:调用generateLayout
方法创建mContentParent
对象;
private void installDecor() {
...
// 👉 初始化 mDecor
if (mDecor == null) {
// 如果 mDecor 为空,则生成一个 DecorView 对象
mDecor = generateDecor(-1);
...
} else {
// 将 PhoneWindow 设置给 mDecor
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 👉 初始化 mContentParent
mContentParent = generateLayout(mDecor);
...
}
}
// 👉 generateDecor:初始化 DecorView
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
// 👉 generateLayout:根据窗口的风格,为 DecorView 选择对应的布局文件
protected ViewGroup generateLayout(DecorView decor) {
// 获取窗口属性
TypedArray a = getWindowStyle();
...
int layoutResource;
int features = getLocalFeatures();
// 👉 根据设定好的 features 值选择不同的窗口布局文件,得到 layoutResource 值,前文中曾以 screen_simple 举例
// 注意:此处还有很多分支判断代码省略了
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
}
...
// 👉 把选中的窗口布局文件解析成 View 树,并添加到 DecorView 中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 👉 指定 contentParent 值,对应的是布局文件中 id 为 content 的 View
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
// 👉 返回 contentParent,赋值给 mContentParent,所以 mContentParent 对应的是 R.id.content
return contentParent;
}
介绍完installDecor
方法,我们再来看看PhoneWindow.setContentView
方法中的第2️⃣步工作:填充布局,即默认情况下会将我们设置的布局文件解析为 View 树,并添加到 mContentParent
中,现在我们应该理解的更加清楚了。
3. ActivityThread#handleResumeActivity()方法:执行onResume生命周期
执行完onCreate、onStart
生命周期后,来到了handleResumeActivity()
方法中执行onResume
生命周期,有一点需要注意的是,到目前为止,我们也仅是生成了一个Activity
,一个PhoneWindow
,一个DecorView
,而并未真正的将我们需要显示的内容和Android系统进行交互,以进行View绘制,而onResume
是我们启动Activity过程中的生命周期的最后一步,那我们有理由猜想有关绘制的执行应该在此生命周期中执行。此方法主要工作包括以下几步:
- 1️⃣ 执onResume生命周期:调用
performResumeActivity
来间接执行 Activity 的onResume
生命周期; - 2️⃣ 获取Activity的Window和DecorView:获取这两个变量赋值给相关变量,同时暂时使
DecorView
不可见; - 3️⃣ 获取 WindowManager:在
Activity.attach()
方法中,我们为Activity设置了WindowManager
; - 4️⃣ WindowManager添加DecorView:调用
WindowManager.addView
方法为Window添加DecorView
; - 5️⃣ 使得 DecorView 可见:调用
Activity.makeVisible
方法使得DecorView
重新可见。
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
// 👉 在其内部间接执行 Activity 的 onResume 方法,此时界面还不可见
ActivityClientRecord r = performResumeActivity(token, clearHide, reason);
...
final Activity a = r.activity;
...
// 👉 获取 Window,DecorView 等对象
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
// 👉 使 DecorView 不可见
decor.setVisibility(View.INVISIBLE);
// 👉 获取 WindowManager
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
if (a.mVisibleFromClient && !a.mWindowAdded) {
...
// 👉 WindowManager 添加 DecorView,此时依然不可见
wm.addView(decor, l);
...
}
...
if (r.activity.mVisibleFromClient) {
// 👉 使得 DecorView 可见
r.activity.makeVisible();
}
...
}
// 👉 Activity.makeVisible方法:使得 DecorView 可见
void makeVisible() {
...
mDecor.setVisibility(View.VISIBLE);
}
可见到目前为止,还是没有相关直接绘制View的操作,但对DecorView
的操作有好几处,重点是第4️⃣步,其中调用WindowManager
的addView
方法添加DecorView。我们之前解释过View必须依附于Window才能显示,而Android 中对 Window 的管理都是通过 WindowManager 来完成的,那相关的绘制操作应该就是在此方法中了。
在理解addView
方法之前,我们先来看看WindowManager
的初始化过程,即Activity.attach
方法中调用的setWindowManager
方法:
// 👉 Window.setWindowManager 方法
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
// 👉 WindowManagerImpl.createLocalWindowManager 方法
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
可见此处WindowManager
它的真正实现是WindowManagerImpl
,而WindowManagerImpl
也并没有真正实现ViewManager
接口的三大操作(addView,updateViewLayout,removeView),而是交给了WindowManagerGlobal
。
// 👉 WindowManagerImpl.addView 方法
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
👇 WindowManagerGlobal对象
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
mGlobal
对象的初始化如下,就是 WindowManagerGlobal
的单例模式。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
WindowManagerGlobal#addView方法
下面再来看看 WindowManagerGlobal.addView
方法,主要做了两步工作:
- 1️⃣ 初始化ViewRootImpl:
ViewRootImpl
是视图层次的最顶层,连接WindowManagerService和DecorView的纽带, 同时 ViewRootImpl 的内容大部分都是 WindowManagerGlobal 类的内部实现细节; - 2️⃣ 调用 ViewRootImpl.setView 方法:调用此方法来完成真正的添加 View 操作。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
// 👉 ViewRootImpl 对象
ViewRootImpl root;
synchronized (mLock) {
...
// 👉 初始化 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
...
// 👉 调用 ViewRootImpl.setView 方法
root.setView(view, wparams, panelParentView);
}
}
ViewRootImpl#setView方法
真正完成添加 View 的操作是此方法:
// 👉 ViewRootImpl.setView 方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
👇
requestLayout();
...
👇
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
...
👇 将 DecorView 的 parent 设置为 ViewRootImpl
view.assignParent(this);
...
}
此方法中做的工作主要包括以下两个方面:
- 1️⃣ requestLayout()方法:调用View的测量,布局,绘制三个流程;
- 2️⃣ mWindowSession.addToDisplay()方法:mWindowSession 是一个aidl,ViewRootImpl 利用它来和 WindowManagerService 进行跨进程交互,这里先不过多介绍有关
WindowManagerService
内容,只简单介绍下其作用,如下。
WindowManagerService(WMS)的作用有很多:
- 窗口的管理者:负责窗口的启动、添加和删除,另外窗口的大小和层级也是由WMS进行管理;
- 事件的管理和派发工作:通过对窗口的触摸从而产生触摸事件,InputManagerService(IMS)会对触摸事件进行处理,它会寻找一个最合适的窗口来处理触摸反馈信息,WMS是窗口的管理者,因此,WMS“理所应当”的成为了事件的中转站。
- Surface管理:窗口并不具备有绘制的功能,因此每个窗口都需要有一块Surface来供自己绘制。为每个窗口分配Surface是由WMS来完成的。
我们重点看看 requestLayout()
方法,源码如下:
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 👉 检查是不是主线程
checkThread();
mLayoutRequested = true;
// 👉 view绘制三大流程入口
scheduleTraversals();
}
}
熟悉View绘制流程的同学应该知道,当View的大小、形状发生了变化的时候,可以调用此方法来进行重新绘制,此方法会从View树重新进行一次测量、布局、绘制这三个流程。此方法主要做了两步工作:
- 1️⃣ 检查当前线程:如果调用此方法的现场不是主线程,那么就在
checkThread()
方法中抛出异常,这一点与我们"通常意义"上所说的:只能在主线程更新UI相吻合,具体checkThread()方法稍后写明; - 2️⃣ 调用 scheduleTraversals 方法:此方法后续完成了View的三大流程(测量(measure),布局(layout),绘制(draw)),具体分析见后续 。
再看下 checkThread()
方法:
void checkThread() {
👇
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
mThread
的初始化是在 ViewRootImpl
的构造函数中完成的:
// ViewRootImpl 构造函数
public ViewRootImpl(Context context, Display display) {
...
👇
mThread = Thread.currentThread();
...
}
回忆下前文, ViewRootImpl 的初始化是在 WindowManagerGlobal.addView 方法中完成的,因此mThread 肯定对应的是主线程,因为 ActivityThread.handleLaunchActivity 方法就是在主线程中执行的,而其中并未切换过线程。因此如果我们在子线程中更新UI,那么最终会走到 requestLayout 方法进行重绘制,但此时会发现 mThread(主线程) 和 Thread.currentThread()(子线程)不是同一个线程,那么便会抛出异常。
再来说说 ViewRootImpl ,其作用非常重大。所有View的绘制以及事件分发等交互都是通过它来执行或传递的。View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRootImpl来完成。Android的所有触屏事件、按键事件、界面刷新等事件都是通过ViewRootImpl进行分发的。
- 作者:Ruheng
- 链接:https://www.jianshu.com/p/8766babc40e0
- 来源:简书
从源码实现上来看,ViewRooImpl 既非View的子类,也非View的父类,但是,它实现了ViewParent接口,这让它可以作为View的名义上的父视图。
在 ViewRootImpl.setView
方法中调用了 View.assignParent
方法:
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
}
...
}
View是以View树组织的,每个View都有其Parent,DecorView作为最顶层的View,其Parent被设置为ViewRootImpl对象。这样它可以作为View的名义上的父视图,实质上完成了View的更新,绘制等工作。
View类中也有requestLayout
方法:用于 View 的位置,大小、形状发生了变化的时候进行调用。当一个子 View 调用此方法时,便会令 View 树重新进行一次测量、布局、绘制这三个流程,具体源码如下:
// 👉 View#requestLayout:
public void requestLayout() {
...
if (mParent != null && !mParent.isLayoutRequested()) {
// 👉 调用父容器的 `requestLayout` 方法
mParent.requestLayout();
}
...
}
此方法中最重要的是调用mParent.requestLayout
方法,是向父容器请求布局,即调用父容器的 requestLayout
方法,此方法沿着View树向上传递,最终来到了 DecorView#requestLayout中,而DecorView是顶层View,其mParent便是ViewRootImpl,所以子View的requestLayout方法,经过层层传递,最终会被ViewRootImpl 接收并处理。
那具体的 scheduleTraversals
方法中主要完成了 View 的三大流程,本文暂且不进行分析。
总结
通过以上可以知道,Activity就像个控制器,不负责视图部分。Window像个承载器,装着内部视图。DecorView是个顶层视图,是所有View的最外层布局。ViewRootImpl像个连接器,负责沟通,通过硬件的感知(分发事件,绘制View内容)来通知视图,进行用户之间的交互。
- 作者:Ruheng
- 链接:https://www.jianshu.com/p/8766babc40e0
- 来源:简书
其实本文还存在两个问题没有说明:
- 1️⃣ WindowManagerService具体原理:ViewRootImpl 和 WindowManagerService 进行跨进程交互的背后原理是什么,怎么做到View的真正显示?
- 2️⃣ View绘制的三大流程:View 测量,布局,绘制的详细流程是什么?
这些分析后续陆续进行说明。