1、View是如何被添加到屏幕窗口上
上代码:
public class MainActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
这是写了一千遍的代码,通过setContentView
将xml布局添加到Activity
中,具体怎么添加的?跟进去:
>>> Activity.java
public void setContentView(@LayoutRes int layoutResID) {
// 调用Window类的setContentView
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
private Window mWindow;
public Window getWindow() {
return mWindow;
}
>>> Window.java
public abstract class Window {
public abstract void setContentView(@LayoutRes int layoutResID);
}
调用Window
类的setContentView
,Window
是个抽象类,它只有唯一一个子类PhoneWindow
,点开之:
>>> PhoneWindow.java
public class PhoneWindow extends Window{
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 1、创建Decor
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 {
// 2、将layoutResID填充在mContentParent里
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
}
主要做了2件事:
- 创建
Decor
- 将
layoutResID
填充在mContentParent
里
我们先看1:
>>> PhoneWindow.java
ViewGroup mContentParent;
private DecorView mDecor;
private void installDecor() {
if (mDecor == null) {
// 如果mDecor为null 则创建它
mDecor = generateDecor(-1);
......
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//
mContentParent = generateLayout(mDecor);
}
.......
}
protected DecorView generateDecor(int featureId) {
Context context;
......
return new DecorView(context, featureId, this, getAttributes());
}
>>> DecorView.java
public class DecorView extends FrameLayout{
}
installDecor
主要也是做了2件事,首先创建mDecor
,mDecor
是DecorView
类型,其实就是一个FramLayout
,接着调用generateLayout
并传入mDecor
,看看具体做了什么操作:
>>> PhoneWindow.java
protected ViewGroup generateLayout(DecorView decor) {
// 前面省略了一些设置主题风格的代码
......
int layoutResource;
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
// 1、根据主题风格,给layoutResource赋值对应的xml文件
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
}
......
// 2、调用 DecorView的 onResourcesLoaded,把layoutResource添加到DecorView上
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 3、从 DecorView 中找到 ID_ANDROID_CONTENT这个id对应的View
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
// 4、检查 contentParent 是否找到
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
return contentParent;
}
// 所有的系统主题 layout 资源都必须包含这个id
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
>>> DecorView.java
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
......
// 把layoutResource添加到DecorView上
final View root = inflater.inflate(layoutResource, null);
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) root;
......
}
generateLayout
这个方法的代码很长,主要干了4件事
- 根据Activity设置的
Theme
主题风格,获取对应的layout
资源id赋值给layoutResource
- 把
layoutResource
解析成View
,添加到DecorView
上- 调用
findViewById
找到R.id.content
对应的ViewGroup
- 验证上一步中的
ViewGroup
是否存在
R.id.content
这个id对应的其实就是我们在 Activity
中setContentView
传入的布局的父容器,对!就是用来放我们自己定义的布局,每一个系统主题风格对应的xml文件中必须包含这个id,我们随便打开一个系统资源布局看看
上面总结下来,就是网上这张图
从上面追溯的过程中也可以看出Activity、Window、View
三者之间的关系
ok,我们回到PhoneWindow
的setContentView
方法的第2件事,就是将我们在Activity
中传入的R.layout.activity_main
填充到mContentParent
,也就是R.id.content
这个容器内,至此,布局添加就结束了。
>>> PhoneWindow.java
public class PhoneWindow extends Window{
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
// 1、创建Decor
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 {
// 2、将layoutResID填充在mContentParent里
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
}
总结
Activity持有一个
Window
对象PhoneWindow
,PhoneWindow
中会初始化所有View容器的顶层布局--DecorView
,它继承自FramLayout
,当DecorView
初始化完成后,系统会根据主题特性去加载一个基础容器,如@style/Theme.AppCompat.NoActionBar
、@style/Theme.AppCompat.Light
等等,不同主题加载不同的基础容器,
但是无论哪种基础容器都会包含一个com.android.internal.R.id.content
子容器,这个容器也是FrameLayout
,而我们从setContentView
中设置的xml布局文件就是被解析后被添加到这个FrameLayout
中,这样,我们定义的布局就被添加到了窗口上。
2、View的绘制流程入口
我们都知道Android应用程序的入口是ActivityThread.java
的main
方法,ActivityThread
有一个内部类H
继承自Handler
,它的handleMessage
方法就是负责分发activity
的生命周期函数
public final class ActivityThread {
......
private class H extends Handler {
......
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
// launch
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
......
}
}
}
case LAUNCH_ACTIVITY
里面调用了handleLaunchActivity
方法,从这里开始就要进入View的绘制流程了,下面是一串调用链
handleLaunchActivity()
--> handleResumeActivity()
// 这里的vm是一个 ViewManager 对象,ViewManager 是一个接口,最终实现类是 WindowManagerImpl
--> wm.addView(decor, layoutParams);
--> mGlobal.addView(view, params)
--> addView(view, params)
看看WindowManagerGlobal
的addView
方法
>>> WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
......
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// 1、创建 ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
// 2、给 DecorView 设置LayoutParams
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 {
// 3、将 ViewRootImpl、LayoutParams、View 三者关联起来
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
这里主要做了三件事:
1、创建
ViewRootImpl
2、给DecorView
设置LayoutParams
3、将ViewRootImpl
、LayoutParams
、View
三者关联起来
再看三者如何关联的
>>> ViewRootImpl.java
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
......
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
// 在添加到window manager之前安排上第一个布局,确保我们在接收系统中的其他事件前完成relayout。
requestLayout();
......
}
代码其实很多,最主要就一件事requestLayout
,跟进去
>>> ViewRootImpl.java
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 1、检查当前线程是否是创建 ViewRootImpl 的线程
checkThread();
mLayoutRequested = true;
// 2、调度遍历,其实就是开始View的绘制流程
scheduleTraversals();
}
}
final Thread mThread;
// 构造函数
public ViewRootImpl(Context context, Display display) {
......
// 调用构造函数的当前线程
mThread = Thread.currentThread();
......
}
void checkThread() {
// mThread 是否是当前线程
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
requestLayout
主要做了2件事
1、检查当前线程是否是创建 ViewRootImpl 的线程,
2、scheduleTraversals
第1点其实很有意思,如果mThread
不是当前线程直接抛出异常 "只有创建视图层次结构的原始线程才能处理他的views",这里的original thread
并不一定是主线程,而是创建ViewRootImpl
的线程,ViewRootImpl
也可以在子线程里面创建,只不过系统一般只会在主线程创建,所以就有了我们常说的只有主线程可以刷新UI,其实子线程也可以刷新UI,只要在子线程创建ViewRootImpl
并且在同一个子线程刷新UI就可以,有兴趣的可以试试。
当然第2点才是重点,跟进去
>>> ViewRootImpl.java
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 1、这里 post一个回调 mTraversalRunnable
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
......
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
// 2、调用 doTraversal
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
......
// 3、调用 performTraversals
performTraversals();
......
}
}
performTraversals
这里就是重点了
private void performTraversals() {
......
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, mWidth, mHeight);
......
performDraw();
......
}
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
try {
// 开始测量
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
......
final View host = mView;
if (host == null) {
return;
}
try {
// 开始布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
......
}
private void performDraw() {
......
try {
// 开始绘制
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
......
}
到这里就走到View绘制的三大步骤了,我们总结一下上面的调用流程图: