- 作者:鱼八(网易1902期)
- 公众号:鱼八说
- 日期:2019-06-23
- 类型:Android
- 说明:本文源于 鱼八 原创,如需转载请带上链接或注明出处!
一、View是如何被添加到屏幕窗口上的?
总体上来说:
- 1.创建顶层的布局容器DecorView。
- 2.在顶层布局中加载基础布局的ViewGroup。
- 3.将ContentView添加到基础布局中的FrameLayout中
源码分析:
开发中我们都知道,setContentView方法是将我们编写的xml界面加载到我们的手机屏幕窗口上的。于是,我们就从这里进入一个新的世界。
第一步 找到setContentView的实现
我们从Activity(该Activity继承Activity),进入到
setContentView()
方法中
/** |
我们可以看到这里
getWindow()
调用了setContentView方法,那么这个getWindow()
什么呢?
我们进入到getWindow()
中,可以看到返回的是一个Window
对象。
/** |
我们再进入到Window的源码中查看。
/** |
可以看到Window只是一个抽象类,从注释中了解到,PhoneWindow是他的唯一实现类。我们再进入到PhoneWindow中查看setContentView具体的实现。到这里我们就知道了setContentView的具体实现,实际上是Window类的实现类PhoneWindow所实现的。
我们进入到 setContentView方法中,浏览一遍发现,这个方法主要的过程在
installDecor()
方法和mLayoutInflater.inflate(layoutResID, mContentParent);
。接下来我们主要研究installDecor()
方法。直接理解它是加载Decor的一个方法。通过它,我们可以去了解加载Decor的整个过程。
@Override |
第二步 了解installDecor()的加载过程
进入
installDecor()
方法中
private void installDecor() { |
通过阅读源码,在PhoneWindow的
installDecor()
方法中,我们可以知道这个方法主要做了以下:
- 1 .
generateDecor()
获取到DecorView- 2 .
generateLayout(mDecor)
将layoutResources(基础布局)加载到DecorView
详细流程如下:
1. 通过generateDecor() 方法获取到DecorView(顶层布局)
进入到generateDecor() 方法返回的是一个DecorView的对象,
进入到这个方法中我们可以看到
protected DecorView generateDecor(int featureId) { |
我们在去看看DecorView到底是什么?
/** @hide */ |
通过源码可以看到,它实际上是一个继承自FrameLayout 的一个容器
这就是说明generateDecor()
方法实际上是让我们获取到了一个DecorView的容器。我们接着继续往下看。
2.generateLayout(mDecor)
方法将layoutResources(基础布局)加载到DecorView
我们在得到DecorView容器以后,在进入到
generateLayout()
方法 中,
protected ViewGroup generateLayout(DecorView decor) { |
通过阅读源码 可以看到它主要是做了这三件事。
- 1 . 通过
requestFeature()
和setFlags()
等方法设置系统主题的不同样式和特性。- 2 . 解析窗口view
- 1 . 根据具体的fetures的不同获取到系统相应的 layoutResources。
- 2 . 获取完layoutResource 后,通过mDecor.onResourcesLoaded()方法去解析这个 layoutResources 。将解析获得的view加载到DecorView上。
- 3 . 根据layoutResources (
R.layout.screen_simple
)上的布局ID:ID_ANDROID_CONTENT
获取到相应的 contentParent(实际上是一个R.layout.screen_simple 上的一个FrameLayout布局)并返回。
第一件事
这里是对系统主题特性的一些设置我们这里不做研究了。
第二件事
- 1 . 先通过fetures获取相应的 layoutResources
这里我们假定根据某些特性条件下获取到的是 `layoutResource = R.layout.screen_simple;`
- 2 . 执行onResourcesLoaded()方法,解析 layoutResources,加载到 DecorView上
> 先查看源码 `onResourcesLoaded()源码`
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
final View root = inflater.inflate(layoutResource, null);
...
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
}
在onResourcesLoaded 方法中,他通过解析 layoutResource 创建了一个root对象。 该root对象例如为: R.layout.screen_simple(系统的xml)。通过addView方法将 root 对象加载到DecorView上。
第三件事
在onResourcesLoaded方法执行完毕后,回到 generateLayout()方法中接下来我们会获取到ViewGroup的对象 contentParent。那么这个contentParent是什么?
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); |
首先通过查看ID_ANDROID_CONTENT 的注释, 我们可以看到这是系统主布局(我们这里假定获取的是R.layout.screen_simple)一定所具有的ID 。
com.android.internal.R.id.content
/** |
我们查看
screen_simple.xml
,发现获取到这个contentParent ,实际上是 screen_simple.xml 上的FrameLayout 。
<!--screen_simple.xml--> |
获取contentParent 并返回后,
generateLayout()
方法也就执行完毕。我们回到installDecor()
方法中。
就此installDecor()
的主要方法执行完毕。 以上我们可以理解是在这个过程是获取到DecorView,然后将根据不同特性获取到的系统的layoutResources加载到DecorView上。最后我们再根据ID_ANDROID_CONTENT 从layoutResources获取到contentParent 。它实际上是一个FrameLayout。
View的加载显示如下:

第三步 加载setContentView中设置的布局
1.将setContentView中设置的布局加载到contentParent上
installDecor()
方法执行完毕后,返回到setContentView中,查看源码
@Override |
最后通过:
mLayoutInflater.inflate(layoutResID, mContentParent);
将在activity上设置的布局 layoutResID 加载到 contentParent上 也就是 FrameLayout上。至此,我们的整个View就加载并显示到屏幕上了。
总结
基本三个步骤
- 1.创建顶层的布局容器DecorView。
- 2.在顶层布局中加载基础布局的ViewGroup
- 3.将ContentView添加到基础布局中的FrameLayout中
view是如何被添加到屏幕窗口上的?
1.首先系统会创建一个顶层布局容器DecorView。DecorView 是一个ViewGroup容器, 继承FrameLayout,是PhoneWindow对象持有的一个实例,他是所有应用程序的顶层View,在系统内部进行初始化。当DecorView 初始化完成后,系统会根据应用程序的顶层特性会加载一个基础容器,例如no_actionBar等,不同的主题加载的基础容器也不一样,但是无论什么样的的基础容器一定会有一个android.R.di.content
的FrameLayout。开发者通过setContView设置的xml ,通过解析后就加载到了这个FrameLayout中。
二、View的绘制流程
总体上来说
- 1 . 绘制入口
- 2 . 绘制的类及方法
- 3 . 绘制的三大步骤
源码分析:
第一步 绘制入口
1.找到 wm.addView
绘制的入口是在ActivityThread在这个类中找到
handleMessage()
方法。这个方法是主线程里面有一个处理主线程消息的对象,叫 H 。是 handler的子类。
public void handleMessage(Message msg) { |
我们看到第一个case 是 LAUNCH_ACTIVITY 表示的是启动一个activity,其中有一个
handleLaunchActivity()
方法,点进去
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) { |
可以看到
performLaunchActivity()
,这个方法主要是启动一个Activity。返回到handleLaunchActivity()
方法中接着往下看,我们可以看到handleResumeActivity()
final void handleResumeActivity(IBinder token, |
我们点进去,会发现一个
performResumeActivity()
方法,这个方法被调用时,就会回调Activity生命周期中的OnResume()方法。
接着往下翻 我们看到这样一个判断。
if (r.window == null && !a.mFinished && willBeVisible) { |
通过对源码的阅读,我们发现这个方法 在判空和其他条件满足下,通过方法
WindowManger.LayoutsParams l = r.window.getAttributes()
;获取到窗口的布局属性对象,然后执行了wm.addView()
,到这我们也就找到了入口。
2.wm对象是什么?
我们先看一下 wm 是什么,通过阅读它是一个 ViewManger对象,而ViewManger是一个接口
public interface ViewManager |
那么接下来去找它的实现类,我们点击
getWindowManager()
/** Retrieve the window manager for showing custom windows. */ |
发现她返回的是一个WindowManager对象。我们搜索 mWindowManager
mWindowManager = mWindow.getWindowManager(); |
发现mWindowManager是通过
mWindow.getWindowManager()
获取的。从前面我们知道mWindow的唯一实现类是PhoneWindow。所以我们在PhoneWindow中去找getWindowManager()
方法。
final WindowManager wm = getWindowManager(); |
找到后我们点击
getWindowManager()
方法
/** |
发现它又返回的是一个WindowManager对象,然后我们去搜索 mWindowManager 这个对象。
/** |
我们看到
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
所以我们得出结果。
wm对象是什么? 她是通过createLocalWindowManager()
方法进行进行实例化返回的一个WindowManagerImpl
类型的对象。
3.绘制的入口addView方法
现在我们知道了wm 是什么? 我们可以理解为 他是通过
createLocalWindowManager()
进行实例化的一个对象。createLocalWindowManager()
返回的对象是WindowManagerImpl类型的。
在之前我们看到wm调用了addView()
方法,现在我们进入到WindowManagerImpl中。去寻找wm调用的addView()
方法。
public final class WindowManagerImpl implements WindowManager { |
在这个方法中 我们看到了
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
mGlobal 调用了addView()
方法,点击mGlobal这个对象。
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance(); |
发现mGlobal 是 WindowManagerGlobal 的一个对象,我们再进入到WindowManagerGlobal 中去寻找
addView()
方法
第二步 绘制的类以及方法
1.了解setView方法
接着我们去WindowManagerGlobal中去搜索查看
addView()
方法。
在addView()
方法中 我们看到这里声明了一个ViewRootImpl
public final class WindowManagerGlobal { ... |
往下看,我们可以看到通过ViewRootImpl的构造方法,将root 进行了实例化。
root = new ViewRootImpl(view.getContext(), display);
接着给View 设置了LayoutParams,然后将view ,root 等 添加到了对应的集合中
最后通过 root.setView()将view,wparams,panelParentView 进行关联起来。root.setView(view, wparams, panelParentView);
进入到setView()
中我们主要关心requestLayout()
方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { |
2.requestLayout()后的流程
点击进入到
requestLayout()
方法中
@Override |
我们看到两个方法。
- 1 .
checkThread()
方法主要是检测当前线程是否为主线程。
void checkThread() { |
- 2 .
scheduleTraversals()
点击进入到该方法中
void scheduleTraversals() { |
看到
mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
方法,可以看到执行了mTraversalRunnable
,进入到这个runable中
final class TraversalRunnable implements Runnable { |
这个runable方法里只是执行了一个
doTraversal()
。
void doTraversal() { |
在这个方法中 我们看到他只是执行了一个
performTraversals()
;。
第三步 performTraversals()中的View绘制三大流程
我们进入到
performTraversals()
中,可以发现View的绘制三大流程
- 1 . performMeasure()
- 2 . performLayout()
- 3 . performDraw()
private void performTraversals() |
至此我们找到了类的绘制入口以及绘制的类和方法。
总结
基本三个步骤以及涉及的类和方法
- 绘制入口
ActivityThread.handleResumeActivity()
—> WindowManagerImpl.addView(decorView,layoutParams)
—> WindowManagerGlobal.addView()
- 绘制的类及方法
ViewRootlmpl.setView(decorView,layoutParams,parentView)
—> ViewRootlmpl.requestLayout()
—> scheduleTraversals()
—> doTraversals()
—> performTraversals()
- 绘制的三大步骤
—> ViewRootlmpl.performMesure()
—> ViewRootlmpl.performLayout()
—> ViewRootlmpl.performDraw()
View的绘制流程?
1.当Activity创建之后,在ActivityThread.handleResumeActivity()中 会通过 wm.addView方法,这个wm的实现类是WindowManagerImpl,他调用的就是WindowManagerImpl.addView方法,第一个参数是顶层布局,第二个参数是布局属性。在这个addView中会调用WindowManagerGlobal.addView()方法,在其中会创建出ViewRootlmpl的对象root,并且调用了setView方法,将decorView,布局属性做一个关联。关联成功后,开始绘制。在setView中调用了 requestLayout()方法,然后调用了 scheduleTraversals(),doTraversal(),最后调用了performTraversals,在这个方法中调用了View的三大绘制流程测量performMesure(),布局performLayout(),绘制performDraw()。
