上一篇文章《Activity的創建》中我們說到了Activity
創建後會調用其onCreate
生命週期,而我們的onCreate
方法一般這麼寫
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
我一般會使用setContentView
方法設置Activity
的界面佈局,今天我們就看下它做了什麼
1.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()
方法源碼
2.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
生命週期
3. 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
4.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
方法
5. 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
事件