前段時間公司招人,做爲面試官,我常常讓面試者簡述View的繪製流程。他們基本都能講明白View的測量(measure)、佈局(layout)、繪製(draw)等過程。還有少數人會提到DecorView和ViewRootImp的做用。可是,當我繼續追問關於Window的內容時,幾乎沒有人回答上來。而本章將會帶你深刻理解Window、DecorView、ViewRootImp。除此以外,你還能在本章找到如下問題的答案:java
上圖出現了五個對象:Activity、Window、WindowManager、DecorView、ViewRootImpl,我分別解釋一下它們的做用。面試
Activity:Activity像是一個指揮官,它不處理具體的事務,只在適當的時候指揮Window/WindowManager工做。例如:在attach時建立Window對象、onResume後通知WindowManager添加view。app
Window:Window是一個窗口,它是View的容器。Android中的視圖以View樹的形式組織在一塊兒,而View樹必須依附在Window上才能工做。一個Window對應着一個View樹。啓動Activity時會建立一個Window,顯示Dialog時也會建立一Window。所以Activity內部能夠有多個Window。因爲View的測量、佈局、繪製只是在View樹內進行的,所以一個Window內View的改動不會影響到另外一個Window。Window是一個抽象類,它只有一個實現類PhoneWindow。ide
WindowManager:WindowManager是Window的管理類。它不直接操做Window,而是操做Window內的DecorView。WindowManager是一個接口。它的具體實現類是WindowManagerImpl。oop
public interface WindowManager{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
複製代碼
DecorView是View樹的頂級View,它是FrameLayout的子類。根據Activity設置的Theme,DecorView會有不一樣佈局。但不管佈局怎麼變,DecorView都有一個Id爲R.id.content的FrameLayout。Activity.setContentView()方法就是在這個FrameLayout中添加子View。佈局
ViewRootImpl是鏈接WindowManager和DecorView的紐帶,View的三大流程均是經過ViewRootImpl來完成的。post
在Android插件化之啓動Activity中,我介紹過Activity的啓動流程。其中handleLaunchActivity()方法是啓動Activity的核心方法。本節就以它爲切入點開始分析。ui
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
//省略代碼...
//performLaunchActivity
Activity a = performLaunchActivity(r, customIntent);
if (a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
//handleResumeActivity
handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed);
//省略代碼...
}
}
複製代碼
handleLaunchActivity()主要調用了兩個方法:performLaunchActivity()和handleResumeActivity()this
performLaunchActivity:完成Activity的建立,以及調用Activity的 onCreate()和onStart()方法。spa
handleResumeActivity:調用Activity的onResume()方法,處理View的呈現。
咱們進入performLaunchActivity()方法,核心代碼以下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
ComponentName component = r.intent.getComponent();
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
//建立Activity
Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
//建立Context
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
//調用Activity.attach。
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
//省略代碼...
//調用Activity.onCreate()方法。
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
r.activity = activity;
if (!r.activity.mFinished) {
//調用Activity.onStart()方法。
activity.performStart();
}
}
r.paused = true;
mActivities.put(r.token, r);
return activity;
}
複製代碼
performLaunchActivity()主要作了如下幾件事:
上面說了,在Activity.attach()方法執行時會建立Window併爲Window關聯WindowManager。咱們看一下僞代碼。
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) {
attachBaseContext(context);
//建立Window
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
//設置UI線程
mUiThread = Thread.currentThread();
mMainThread = aThread;
//關聯WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
}
複製代碼
順着流程圖,Activity.setContentView()方法會被調用。Activity.setContentView()內又直接調用了PhoneWindow.setContentView()。咱們直接看PhoneWindow.setContentView()的源碼。
private DecorView mDecor;
//setContentView傳過來的View會被add到mContentParent中。mContentParent的Id是R.id.content。
private ViewGroup mContentParent;
@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
if (mContentParent == null) {
installDecor();
}else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
//省略代碼。。。
}
private void installDecor() {
if (mDecor == null) {
//初始化DecorView
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
//省略代碼。。。
}
}
複製代碼
若是mContentParent爲null,會執行installDecor()方法。generateDecor()源代碼很長,可是它的邏輯很簡單主要是根據Activity Theme的不一樣,初始化不一樣的佈局。DecorView的佈局雖然不一樣,但它們都一個Id爲R.id.content的FrameLayout。Activity.setContentView()就是在這個FrameLayout中添加子View。
performLaunchActivity()執行完後,緊接着執行handleResumeActivity()。
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
//處理Activity的onRestart onResume生命週期。
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
if (r.window == null && !a.mFinished) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
//設置DecorView不可見
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//利用WindowManager添加DecorView。
wm.addView(decor, l);
}
}
if (!r.activity.mFinished && r.activity.mDecor != null) {
if (r.activity.mVisibleFromClient) {
//設置DecorView可見。
r.activity.makeVisible();
}
}
//IPC調用,通知AMS Activity啓動完成。
ActivityManagerNative.getDefault().activityResumed(token);
} else {
try {
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null, false);
} catch (RemoteException ex) {
}
}
}
複製代碼
handleResumeActivity()處理的事情比較多。我總結爲如下幾個過程:
前面說過,WindowManger是一個抽象類,它的實現類是WindowManagerImpl。WindowManager.addView()封裝了View繪製的細節。咱們着重看一下。
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Display mDisplay;
private final Window mParentWindow;
private IBinder mDefaultToken;
public WindowManagerImpl(Display display) {
this(display, null);
}
private WindowManagerImpl(Display display, Window parentWindow) {
mDisplay = display;
mParentWindow = parentWindow;
}
public void setDefaultToken(IBinder token) {
mDefaultToken = token;
}
@Override
//這裏的View是PhoneWindow內建立的DecorView。
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
if (mDefaultToken != null && mParentWindow == null) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (wparams.token == null) {
wparams.token = mDefaultToken;
}
}
}
}
複製代碼
WindowManagerImpl.addView會調用WindowManagerGlobal.addView()。在WindowManagerGlobal.addView()方法執行以前,會先執行applyDefaultToken()方法。這個方法實際上是給傳進來的DecorView加一個身份標識,表示這個DecorView屬於哪一個Activity。這樣系統(WindowManagerService)纔會知道要把DecorView繪製到哪一個Activity。
咱們繼續追蹤WindowManagerGlobal.addView(),僞代碼以下:
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
//這裏的View是PhoneWindow內建立的DecorView。
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
//省略代碼....
root = new ViewRootImpl(view.getContext(), display);
//省略代碼...
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
}
複製代碼
首先會建立ViewRootImpl,隨後把View、ViewRootImpl、LayoutParams都保存在List中,以供未來更新UI使用。它們的index值相同,這樣就三者就對應起來了。最後,調用ViewRootImpl.setView()方法。
public class ViewRootImpl{
View mView;
//這裏的View是PhoneWindow內建立的DecorView。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
//省略代碼。。。
// 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.
requestLayout();
//省略代碼。。。
//IPC通訊,通知WMS渲染。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
//省略代碼。。。
}
}
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//檢查當前執行的線程是否是UI線程
checkThread();
mLayoutRequested = true;
//處理DecorView的measure、layout、draw。
scheduleTraversals();
}
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
}
複製代碼
ViewRootImpl.setView()的僞代碼有兩句,但咱們只關心requestLayout。由於mWindowSession.addToDisplay()就是經過IPC通知WMS去渲染,咱們再去分析WMS意義已經不大了。requestLayout()方法首先會檢查當前執行的線程是否是UI線程,隨後調用scheduleTraversals()。scheduleTraversals會把本次請求封裝成一個TraversalRunnable對象,這個對象最後會交給Handler去處理。最後ViewRootImpl.performTraversals()被調用。調用鏈以下:
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
//省略代碼。。。
mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
//省略代碼。。。
}
}
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
//處理DecorView的measure、layout、draw。
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
複製代碼
performTraversals()主要是處理View樹的measure、layout、draw等流程。不清楚的同窗能夠去看《Android開發藝術探索》第四章,我在這裏就不繼續深刻了。
下面我回答文章前言部分提出的幾個問題。
爲何要有設計Window?
要理解Window須要從面向對象的角度出發。
子線程真的不能更新UI嗎?
更新視圖時,線程檢查是在ViewRootImpl的checkThread()中。ViewRootImpl的初始化是在Activity的onResume()方法以後。所以,若是有子線程在onResume以前更新UI是能夠成功的。固然還有一種Hook ViewRootImpl的mThread的方法也能夠更新UI。這裏不作介紹了。
Activity的onCreate方法爲何沒法獲取View的寬和高?
這個問題和子線程不能更新UI的問題很像,也是方法執行時機的一個問題。View的measure、layout、draw。發生在Activity.onResume()以後,所以在onResume()以前都是沒法獲取View的寬、高等信息的。