上一篇文章《Activity的创建》中我们说到了Activity
创建后会调用其onCreate
生命周期,而我们的onCreate
方法一般这么写
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
我一般会使用setContentView
方法设置Activity
的界面布局,今天我们就看下它做了什么
Activity
#setContentView()
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
显然Activity
自己并没有对其做处理,而是交给了getWindow
方法处理,getWindow
方法返回的是Activity
中的全局变量mWindow
,它是Window
窗口类型。Window
是一个抽象类,在Android中它唯一的子类是PhoneWindow
,也就是说Activity的mWindow
全局变量必定是PhoneWindow
类型的。我们看下mWindow
是如何被创建的。
我们在上一篇文章《Activity的创建》讲到了Activity是在ActivityThread
的performLaunchActivity
创建的,而在创建Activity后,会调用activity.attach
方法将Context
和Application
与Activity
绑定,而就在该方法里我们创建了PhoneWindow
对象,我们来看下Activity
#attach
方法
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);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
...
}
我们可以看到mWindow
确实为PhoneWindow
的实例
既然如此,我们接下来就要看一下PhoneWindow
的setContentView
方法
@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.加载Activity设置的界面
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...
}
在上面2处我们可以看到我们再Activity
设置的布局被解析关联到了mContentParent
,而mContentParent
是一个View,他的注释为This is the view in which the window contents are placed. It is either mDecor itself, or a child of mDecor where the contents go.简单翻译为 这是放置窗口内容的视图。它是mDecor本身,或者内容所在的mDecor的child 。
其实mContentParent
是在上面的注释1出installDecor()
方法获得的,下面我们看下installDecor()
方法源码
PhoneWindow
#installDecor()
private void installDecor() {
...
if (mDecor == null) {
//1.初始化decor
mDecor = generateDecor(-1);
...
}
...
if (mContentParent == null) {
//2.初始化ContentParent
mContentParent = generateLayout(mDecor);
...
}
...
}
我们可以看到在2处我们通过generateLayout
得到了ContentParent
我们看下generateLayout
方法
protected ViewGroup generateLayout(DecorView decor) {
int layoutResource;
...
layoutResource = R.layout.screen_simple;
...
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
可以看到我们调用了PhoneWindow
的findViewById
方法,我们继续看它的源码
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
PhoneWindow
类自己并没有处理而是交给了getDecorView()
获取的view对象处理
我们来看下PhoneWindow
的getDecorView()
方法
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
//看这里,是不是很眼熟
installDecor();
}
return mDecor;
}
这里返回了一个mDecor
对象,大家可以看上面installDecor
方法里使用generateDecor
去获取了mDecor
的对象,我们再继续看一下generateDecor
方法;
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
直接简单粗暴的创建了一个DecorView
对象,而DecorView
实际上是一个继承了FrameLayout
的ViewGroup
也就是说我们再generateLayout
方法里调用ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT)
获取到contentParent
实际上就是在DecorView
里找到一个id为ID_ANDROID_CONTENT
(其值为com.android.internal.R.id.content
)的ViewGroup
但是我们翻遍DecorView
也找不到他含有一个id为ID_ANDROID_CONTENT
的child,那么它只能是后期添加到DecorView
里面的。
我们继续回到generateLayout
方法有一样代码mDecor.onResourcesLoaded(mLayoutInflater, layoutResource)
,看名字我们猜测它就是为了加载布局
而layoutResource
的赋值为布局R.layout.screen_simple
(这只是一种情况下的布局)
而我们看R.layout.screen_simple
的内容
<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
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>
在这里我们看到R.id.content
,我们猜测mDecor.onResourcesLoaded
方法将这个布局添加为自己的子布局,后面又用findViewById
获取到了id为R.id.content
的FrameLayout
。
我们看下DecorView
的onResourcesLoaded
方法
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
final View root = inflater.inflate(layoutResource, null);
...
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) root;
...
}
果然像我们猜测的一样,将传入的布局解析后添加为了自己的子View,那么之前我们猜测的contentParent
为id为R.id.content
的FrameLayout
也就属实了。
结合上面PhoneWindow
的setContentView
中我们将Activity的布局文件加载到了contentParent
中我们脑海中可以建立一个View
树结构
DecorVeiw
->布局R.layout.screen_simple
(LinearLayout
)->R.id.content
的FrameLayout
->Activity
定义的布局
到了这里我们好像也还是不清楚Activity
怎么渲染的,其实也对,上面的这些操作都是在onCreate
生命周期里处理的,我们知道onCreate
生命周期时Activity根本不可见,只有在onResume
生命周期里Activity
才可见。
ActivityThread
的 handleResumeActivity
中会执行Activity的onResume
生命周期
ActivityThread
#handleResumeActivity()
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
...
//调用Activity的onResume生命周期
r = performResumeActivity(token, clearHide, reason);
if (r != null) {
final Activity a = r.activity;
...
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
...
//添加View到WM
wm.addView(decor, l);
}
...
}
这里将调用WindowManager
的addView
方法将DecorView
添加,WindowManger
的 addView
后DecorView
被渲染绘制到屏幕上显示.
PhoneWindow
只是负责处理一些应用窗口通用的逻辑(设置标题栏,导航栏等)。但是真正完成把一个 View 作为窗口添加到 WMS
的过程是由 WindowManager
来完成的。
WindowManager
是接口类型,它是在Activity
的attach
方法中调用PhoneWindow
的setWindowManager
方法设置的
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
我们来看下它的源码
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
...
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
可以看到mWindowManager
是WindowManagerImpl
类的实例
那么我们需要看一下WindowManagerImpl
的addView
方法的源码
WindowManagerImpl
#addView
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
WindowManagerImpl
什么都没有做,将其交给了mGlobal
处理
mGlobal
是WindowManagerGlobal
的实例
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
...
}
我们继续看WindowManagerGlobal
的addView
方法
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
//创建一个ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
...
try {
root.setView(view, wparams, panelParentView);
}
...
}
}
可以看到,先是创建了一个ViewRootImpl
,然后调用了其setView
方法设置View
ViewRootImpl
#setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...
mView = view;
...
//开启绘制流程
requestLayout();
...
try{
...
//将 View 添加到 WMS 中
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
...
}
先开启View的绘制流程,确保View在被添加到Window上显示到屏幕之前,已经完成测量和绘制操作,然后调用 mWindowSession
的 addToDisplay
方法将 View 添加到 WMS
中。
mWindowSession
是 WindowManagerGlobal
中的单例对象,实际上是 IWindowSession
类型,真正的实现类是
System 进程中的 Session
,Session
会与WMS进行通信。将View相关的操作转交给WMS。
我们重点看下requestLayout
方法
ViewRootImpl
#requestLayout
@Override
public void requestLayout() {
...
scheduleTraversals();
}
void scheduleTraversals() {
...
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
...
}
mTraversalRunnable
是一个TraversalRunnable
类的实例,它继承自Runnable
它的run方法
public void run() {
doTraversal();
}
void doTraversal() {
...
performTraversals();
...
}
调用performTraversals
private void performTraversals() {
...
//执行测量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//执行布局
performLayout(lp, mWidth, mHeight);
...
//执行绘制
performDraw();
...
}
在performTraversals
方法里开始执行测量、布局、绘制流程
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
...
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
...
}
这里调用mView.measure
方法,而mView
是之前的设置进来的DecorView
,也就是说在这里开启了View
树的测量流程
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
...
final View host = mView;
...
try {
//开启布局
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
...
}
这里跟measure
一样调用DecorView
的layout
方法,开启了View
树的布局流程。
private void performDraw() {
...
try {
draw(fullRedrawNeeded);
}
...
}
private void draw(boolean fullRedrawNeeded) {
...
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty){
return;
}
...
}
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty) {
...
//开始绘制
mView.draw(canvas);
...
}
performDraw
经过一系列方法调用后最终调用DecorView
的draw
方法开始绘制流程,而DecorView
会对其子View进行非法draw
事件