ps: Activity 的顯示真是看的我頭暈眼花啊,之前看 app 啟動流程都沒這么費勁,沒辦法,Activity 顯示中有很多沒接觸過的概念,流程,找資料搞明白這些相當(dāng)費事
Activity 的顯示過程中,這哥幾個是邁不過去的:Window、ViewRootImpl、WindowManageService、Surface 其他我們可以暫時忽略,但是這3個是 Activity 顯示的關(guān)鍵,我們必須要明白
概念解釋
1. Window
Window 在 Activity 體系中負(fù)責(zé)曾在 view 視圖,Window 是個接口,PhoneWindow 是 Activity 中 Window 的具體實現(xiàn)類
PhoneWindow 在構(gòu)造函數(shù)時會創(chuàng)建出 Window 窗口的根視圖 DecorView,DecorView 的高度可是包括狀態(tài)爛的,這點要注意
2. WindowManageService
android 系統(tǒng)自己的進(jìn)程,用于屏幕顯示的邏輯控制,Window 只是持有每個 Activity 的 UI 呢容,但是具體在屏幕上如何顯示,和其他類型 Window 窗口的交互控制都是由 WindowManageService 這個服務(wù)來完成的
Window 窗口的類型有很多,PhoneWindow 是頁面的窗口類型,輸入法的輸入框也是一種 Window 窗口,比如我們在點擊 edittext 彈出輸入框把我們所在 Activity 向上頂起,就是由 WindowManageService 操作的。 Window 最終會把 view 傳給 WindowManageService 去操作
3. Window 與 WindowManageService 通訊
妥妥的這又是一對典型的雙向 AIDL ,看圖吧,我相信大家在看完 app 啟動流程 之后都已經(jīng)熟悉這個套路了
ViewRootImpl 持有與 WindowManageService 通訊的 AIDL WindowSession ,WindowManageService 持有與 ViewRootImpl 通訊的 AIDL IWindow,這個 IWindow 也是在某個時候由 ViewRootImpl 注冊到 WindowManageService 里面的
WindowManageService 會接收所有的輸入事件,比如 touch 事件,再通過 IWindow 通知 ViewRootImpl
4. WindowManage
大家不用想啊,有 XX 必有 XXManage,這是我們最熟悉的框架結(jié)構(gòu)了,顯示和控制做功能分離,這是里氏替換原則的跟啊,面向接口編程
WindowManage 依然是個接口,在 Activity 中 WindowManage 的實現(xiàn)類是 WindowManagerImpl ,WindowManagerImpl 內(nèi)部使用的是 WindowManagerGlobal,一看到
Global 這個單詞,大家就能聯(lián)想到 WindowManagerGlobal 這是個靜態(tài)單例了吧,事實上的確是的
WindowManagerGlobal類中主要有3個非常重要的變量
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>();
- mViews 保存的是View對象,DecorView
- mRoots 保存和頂層View關(guān)聯(lián)的ViewRootImpl對象
- mParams 保存的是創(chuàng)建頂層View的layout參數(shù)。
WindowManagerGlobal 在初始化時會聯(lián)通 WindowManageService,和 WindowManageService 通信的 WindowSession 這個 AIDL WindowManagerGlobal 獲取后會傳給 ViewRootImpl
5. ViewRootImpl
ViewRootImpl 負(fù)責(zé)和 WindowManageService 通信,和 WindowManageService 通信的 AIDL WindowSession 雖然不是 ViewRootImpl 自己創(chuàng)建的,但是 WindowManagerGlobal 在和 WindowManageService 聯(lián)通之后,可是把 WindowSession 傳給了 ViewRootImpl 的。WindowManageService 和 window 通信的 AIDL IWindow 也是 ViewRootImpl 創(chuàng)建的,可見 ViewRootImpl 在功能上來說就是負(fù)責(zé)通信的
除了通信外,ViewRootImpl 負(fù)責(zé)整個 window 窗口里所有的 UI 視圖的 Measure,Layout,Draw ,ViewRootImpl.performTraversals() 是整個視圖樹 Measure,Layout,Draw 的起點
6. Surface
官方概念:
一個Surface就是一個對象,該對象持有一群像素(pixels),這些像素是要被組合到一起顯示到屏幕上的。你在手機(jī)屏幕上看到的每一個window(如對話框、全屏的activity、狀態(tài)欄)都有唯一一個自己的surface,window將自己的內(nèi)容(content)繪制到該surface中。Surface Flinger根據(jù)各個surface在Z軸上的順序(Z-order)將它們渲染到最終的顯示屏上。
我們自己理解呢,可以簡單的把 Surface 當(dāng)做顯存來看,Surface 最終也是通過 JNI 方法往內(nèi)存寫入數(shù)據(jù)
我們在自定義 view 時用 canvas 來繪制圖形,其實 canvas 就是操作的 window 所屬的 Surface,把圖形繪制到 Surface 的顯存上,然后再交由系統(tǒng)來完成其他操作
SurfaceFlinger 是一個系統(tǒng)進(jìn)程,專門管理 Surface,Surface 平時我們 new 一個出來也沒用,必須要 SurfaceFlinger 創(chuàng)建給我們才有用。所以這里又涉及到 AIDL 了,沒錯 ISurface 就是需要的和 SurfaceFlinger 通信的 AIDL ,每個窗口都持有一個 ISurface ,具體來說 WindowManageService 持有的 ISurface 用來進(jìn)行窗口大小改變,動畫等操作,ViewRootImpl 持有的 ISurface 用來進(jìn)行 UI 的繪制,所以 canvas 才有用武之地
其他人的解釋,有的我看挺好:
在Android系統(tǒng)中,窗口是獨占一個Surface實例的顯示區(qū)域,每個窗口的Surface由WindowManagerService分配。我們可以把Surface看作一塊畫布,應(yīng)用可以通過Canvas或OpenGL在其上面作畫。畫好之后,通過SurfaceFlinger將多塊Surface按照特定的順序(即Z-order)進(jìn)行混合,而后輸出到FrameBuffer中,這樣用戶界面就得以顯示。
Activity 顯示流程
好了上面一通兒概念介紹完,現(xiàn)在我們開始串串 Activity 顯示流程,先把整體跑下來,之后有疑問的地方再說說,大家記住流程,以后面試筆試會用到。不了解時,看著跟天書一樣,理解了之后也就那樣了,10W 小時理論,我們覺得 30% 時間都耗在理解各種思路,概念上了
在 app 啟動流程 一文中,我們說了 Activity 的起始是 ActivityThread.performLaunchActivity() 這個方法,里面 Instrumentation new 了一個 Activity 對象出來,然后 Activity.attch 進(jìn)行 初始化,最后觸發(fā) Activity.onCreate
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
...
} catch (Exception e) {
...
}
try {
// 返回之前創(chuàng)建過的 application 對象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
...
// attach 到 window 上
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, window, r.configCallback);
...
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
}
} catch (Exception e) {
...
}
return activity;
}
那么 Activity 的其實我們就從 attch 這個方法看起
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow (this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
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();
mCurrentConfig = config;
}
沒用的我們?nèi)サ簦梢钥吹皆?Activity.attach 里,創(chuàng)建了 PhoneWindow ,并給 PhoneWindow 綁定了管理器 WindowManage ,這里 window,WindowManage 就初始化好了
下面就會執(zhí)行 Activity.onCreate 方法了,onCreate 里面有什么呢,就是 setContentView ,這里進(jìn)行 window UI 部分的初始化了
Activity 調(diào)的是 Window 的 setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
PhoneWindow 干了2件事,第一個就是 installDecor 把 window 窗口的根視圖 DecorView new 出來,另一件事就是把我們的內(nèi)容視圖加載出來添加到 DecorView 里面
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
這回視圖 view 都出來了,后面會繼續(xù)走 Activity 的生命周期,一直到 onResume 之后,發(fā)送一個 MSG_RESUME_PENDING 消息
@Override
protected void onResume() {
super.onResume();
mHandler.sendEmptyMessage(MSG_RESUME_PENDING);
mResumed = true;
mFragments.execPendingActions();
}
發(fā)送消息過并不會直接執(zhí)行消息,因為此時 onResume 方法還沒執(zhí)行完呢,只有等 UI 線程空閑了才會執(zhí)行 looper 消息隊列里面的任務(wù)
我們才測試下,點擊按鈕,先用 handle 發(fā)送一個消息,再打印一個標(biāo)記,我們看看先執(zhí)行誰
var handle = object : Handler() {
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
Log.d("AA", "handle 接受消息, handle")
}
}
btn_toast.setOnClickListener({
handle.sendMessage(Message.obtain())
Thread.sleep(1000)
Log.d("AA", "onCreate 方法執(zhí)行任務(wù),BBBBBB")
})
很明顯先執(zhí)行的方法任務(wù),再執(zhí)行的 looper 消息隊列的任務(wù)
這里我想說什么呢,我想說若是我們在 Activity 的 onResume 方法里寫邏輯代碼,一樣是會卡頁面的,此時 Activity 頁面只是把 UI 參數(shù)初始化出來了,但是頁面可是還沒顯示到屏幕上,這點要注意啊,有些新人在這里犯過錯誤,之前也看到過有人說 onResume 時頁面就顯示出來了,真是大錯特錯,誤導(dǎo)新人啊。所以啊,大家自己過一遍 Activity 的顯示流程,好多問題答案自己就出來了,好多錯誤的觀點也能找到證據(jù)了,我們在這方面就不迷糊了,以后就能正確的在這塊編寫代碼了,不會再出現(xiàn)以前那么莫名其妙的問題了
題外話說完了,我們回來,MSG_RESUME_PENDING 消息最終會觸發(fā) ActivityThread.handleResumeActivity
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason){
if (r != null) {
final Activity a = r.activity;
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
}
handleResumeActivity 方法里面最主要的是調(diào)用了 windowManage.addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
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 {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
new 了 ViewRootImpl 對象出來,然后把 DocverView 和 ViewRootImpl 存到 windowManage 里面,最后調(diào)用 ViewRootImpl.setView 正式開始啟動 UI 的顯示邏輯了,我精簡一下,留下主要的
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// 刷新 UI
requestLayout();
// 和 WindowManageService 通訊添加 window
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
}
}
}
ViewRootImpl 的 setView 函數(shù)就干了這2件事
requestLayout 函數(shù)是給 UI 線程添加了一個 callback 任務(wù),這個任務(wù)只有在 UI 線程空閑時才會執(zhí)行,也就是說會在 ViewRootImpl 的 setView 方法執(zhí)行完后才執(zhí)行,這個任務(wù)就是 ViewRootImpl.performTraversals() 這個方法會測量 widnow 窗口的大小,然后請求 WindowManageService 去 SurfaceFlinger 申請顯存,然后遍歷 viewTree,對所有 view 進(jìn)行 measure,layout,draw,這樣頁面就可以在屏幕上顯示出來了
但是 performTraversals() 邏輯太復(fù)雜了,有 800 行代碼,很難懂,下面會結(jié)合 2次 measure的問題說一說
addToDisplay 最終會調(diào)用 WindowManageService 的 addWindow 函數(shù),在 WindowManageService 端生成對應(yīng)的 window 對象,獲取和 SurfaceFlinger 通信的 AIDL 對象 ISurface,并分別返回給 WindowManageService 和 ViewRootImpl ,有了 ISurface 之后,我們才能通過 ISurface 和 SurfaceFlinger 通信,申請顯存,才能繪制圖形出來
requestLayout() 雖說是寫在 addToDisplay() 之前的,但是他是添加了一個 handle 任務(wù),所以是先執(zhí)行 addToDisplay ,然后才是 requestLayout,注意這個順序,這樣后面才能理解,要不然你會覺得很多操作都是倒著的
大體到著就完事了,這樣頁面就可以在屏幕上看到了,但是后面這2個方法,我會再說說的,首先說明非常復(fù)雜啊,我也是跟著前人大體看的,有不清楚的地方大家諒解
addToDisplay
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
注意啊 addToDisplay 是在 WindowManageService 系統(tǒng)進(jìn)程里執(zhí)行的,這時已經(jīng)不在應(yīng)用進(jìn)程了
應(yīng)用進(jìn)程把 IWindow 這個 AIDL 傳進(jìn)來,實現(xiàn) WindowManageService 和 應(yīng)用進(jìn)程的通信,就會在創(chuàng)建一個 WindowState 對象來描述與該 IWindow 所關(guān)聯(lián)的 Window 窗口狀態(tài),并且以后就通過這個 IWindow 對象來控制對應(yīng)的 Window 窗口狀態(tài)
每一個Activity組件在 ActivityManagerService 服務(wù)內(nèi)部,都對應(yīng)有一個 ActivityRecord 對象,這個 ActivityRecord 對象是 Activity 組件啟動的過程中創(chuàng)建的,用來描述 Activity 組件的運行狀態(tài)。這樣,每一個 Activity 組件在應(yīng)用程序進(jìn)程、WindowManagerService 服務(wù)和 ActivityManagerService 服務(wù)三者之間就分別一一地建立了連接
應(yīng)用程序進(jìn)程通過 ViewRootImpl 里面的 sWindowSession 和 WindowManagerService 服務(wù)通信,包括以下內(nèi)容,有助于我們理解頁面的顯示:
在Activity組件的啟動過程中,調(diào)用這個IWindowSession接口的成員函數(shù)add可以將一個關(guān)聯(lián)的W對象傳遞到WindowManagerService服務(wù),以便WindowManagerService服務(wù)可以為該Activity組件創(chuàng)建一個WindowState對象。
在Activity組件的銷毀過程中,調(diào)用這個這個IWindowSession接口的成員函數(shù)remove來請求WindowManagerService服務(wù)之前為該Activity組件所創(chuàng)建的一個WindowState對象。
在Activity組件的運行過程中,調(diào)用這個這個IWindowSession接口的成員函數(shù)relayout來請求WindowManagerService服務(wù)來對該Activity組件的UI進(jìn)行布局,以便該Activity組件的UI可以正確地顯示在屏幕中。
當(dāng)一個Activity組件的窗口的大小發(fā)生改變后,WindowManagerService服務(wù)就會調(diào)用這個IWindow接口的成員函數(shù)resized來通知該Activity組件,它的大小發(fā)生改變了。
當(dāng)一個Activity組件的窗口的可見性之后,WindowManagerService服務(wù)就會調(diào)用這個IWindow接口的成員函數(shù)dispatchAppVisibility來通知該Activity組件,它的可見性發(fā)生改變了。
當(dāng)一個Activity組件的窗口獲得或者失去焦點之后,WindowManagerService服務(wù)就會調(diào)用這個IWindow接口的成員函數(shù)windowFoucusChanged來通知該Activity組件,它的焦點發(fā)生改變了。
WMS 和 AMS 之間也是雙向 AIDL 通信的,具體看下面這篇,我這里就不多說了,說起來又是一大堆
接著說 addToDisplay ,addWindow 中創(chuàng)建 WindowState 對象后,會觸發(fā) WindowState .attach ,該函數(shù)會創(chuàng)建一個關(guān)聯(lián)的 SurfaceSession 對象,以便可以用來和 SurfaceFlinger 服務(wù)通信
void attach () {
if (WindowManagerService.localLOGV) Slog.v(
TAG, "Attaching " + this + " token=" + mToken
+ ", list=" + mToken.windows);
mSession.windowAddedLocked();
}
void windowAddedLocked() {
if (mSurfaceSession == null) {
mSurfaceSession = new SurfaceSession ();
mService.mSessions.add(this);
if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
mService.dispatchNewAnimatorScaleLocked(this);
}
}
mNumWindow++;
}
SurfaceSession 的構(gòu)造方法內(nèi)會創(chuàng)建和 SurfaceFlinger 服務(wù)通信的 AIDL SurfaceComposerClient,通過 SurfaceComposerClient 申請創(chuàng)建繪制表面 Surface 的操作
public SurfaceSession () {
mNativeClient = nativeCreate();
}
static jlong nativeCreate(JNIEnv * env, jclass clazz) {
SurfaceComposerClient * client = new SurfaceComposerClient ();
client->incStrong((void*)nativeCreate);
return reinterpret_cast<jlong>(client);
}
nativeCreate 是 JNI 方法,SurfaceComposerClient 是 C++ 的,到這就差不多了,更多的請看: kc專欄 里的 android 下是部分吧,addToDisplay 方法到這里就 OK 了
requestLayout
requestLayout 還是要看看的,記住他是添加了一個 handle 任務(wù)就行了
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals () {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
看到 postCallback 沒有,mTraversalRunnable 這個 Runnable 就是添加的 handle 任務(wù)了
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal () {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
走到 performTraversals 就行了,到這就開始繪制界面了,這個方法就是繪制界面用的
performTraversals
performTraversals 有 800 行,我精簡一下,留下關(guān)鍵的
private void performTraversals() {
//獲得view寬高的測量規(guī)格,mWidth和mHeight表示窗口的寬高,lp.width和lp.height表示DecorView根布局寬和高
WindowManager.LayoutParams lp = mWindowAttributes;
...
//頂層視圖DecorView所需要窗口的寬度和高度
int desiredWindowWidth;
int desiredWindowHeight;
...
//在構(gòu)造方法中mFirst已經(jīng)設(shè)置為true,表示是否是第一次繪制DecorView
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
//如果窗口的類型是有狀態(tài)欄的,那么頂層視圖DecorView所需要窗口的寬度和高度就是除了狀態(tài)欄
if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
|| lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {//否則頂層視圖DecorView所需要窗口的寬度和高度就是整個屏幕的寬高
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
}
...
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
...
// Ask host how big it wants to be
//執(zhí)行測量操作
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
...
//執(zhí)行布局操作
performLayout(lp, mWidth, mHeight);
...
//執(zhí)行繪制操作
performDraw();
}
performTraversals 計算過程五個工作階段:
- 預(yù)測量階段 - 這是進(jìn)入 performTraversals() 方法后的第一個階段,它會對View樹進(jìn)行第一次測量。在此階段中將會計算出View樹為顯示其內(nèi)容所需的尺寸,即期望的窗口尺寸。(調(diào)用measureHierarchy())
- 窗口布局階段 - 根據(jù)預(yù)測量的結(jié)果,通過IWindowSession.relayout()方法向WMS請求調(diào)整窗口的尺寸等屬性,這將引發(fā)WMS對窗口進(jìn)行重新布局,并將布局結(jié)果返回給ViewRootImpl。(調(diào)用relayoutWindow())
- 測量階段 - 預(yù)測量的結(jié)果是View樹所期望的窗口尺寸。然而由于在WMS中影響窗口布局的因素很多,WMS不一定會將窗口準(zhǔn)確地布局為View樹所要求的尺寸,而迫于WMS作為系統(tǒng)服務(wù)的強勢地位,View樹不得不接受WMS的布局結(jié)果。因此在這一階段,performTraversals()將以窗口的實際尺寸對View樹進(jìn)行最終測量。(調(diào)用performMeasure())
- 布局階段 - 完成最終測量之后便可以對View樹進(jìn)行布局了。(調(diào)用performLayout())
- 繪制階段 - 這是 performTraversals()的最終階段。確定了控件的位置與尺寸后,便可以對View樹進(jìn)行繪制了。(調(diào)用performDraw())
measureHierarchy 方法
measureHierarchy 會判斷跟布局 width 的測量類型,若是 warpContent,那么最多會進(jìn)行 3次測量
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
// 表示測量結(jié)果是否可能導(dǎo)致窗口的尺寸發(fā)生變化
boolean windowSizeMayChange = false;
//goodMeasure表示了測量是否能滿足View樹充分顯示內(nèi)容的要求
boolean goodMeasure = false;
//測量協(xié)商僅發(fā)生在LayoutParams.width被指定為WRAP_CONTENT的情況下
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
//第一次協(xié)商。measureHierarchy()使用它最期望的寬度限制進(jìn)行測量。
//這一寬度限制定義為一個系統(tǒng)資源。
//可以在frameworks/base/core/res/res/values/config.xml找到它的定義
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
// 寬度限制被存放在baseSize中
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
//第一次測量。調(diào)用performMeasure()進(jìn)行測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//View樹的測量結(jié)果可以通過mView的getmeasuredWidthAndState()方法獲取。
//View樹對這個測量結(jié)果不滿意,則會在返回值中添加MEASURED_STATE_TOO_SMALL位
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true; // 控件樹對測量結(jié)果滿意,測量完成
} else {
//第二次協(xié)商。上次的測量結(jié)果表明View樹認(rèn)為measureHierarchy()給予的寬度太小,在此
//在此適當(dāng)?shù)胤艑拰挾鹊南拗疲褂米畲髮挾扰c期望寬度的中間值作為寬度限制
baseSize = (baseSize+desiredWindowWidth)/2;
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
//第二次測量
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// 再次檢查控件樹是否滿足此次測量
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
// 控件樹對測量結(jié)果滿意,測量完成
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
//最終測量。當(dāng)View樹對上述兩次協(xié)商的結(jié)果都不滿意時,measureHierarchy()放棄所有限制
//做最終測量。這一次將不再檢查控件樹是否滿意了,因為即便其不滿意,measurehierarchy()也沒
//有更多的空間供其使用了
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
//如果測量結(jié)果與ViewRootImpl中當(dāng)前的窗口尺寸不一致,則表明隨后可能有必要進(jìn)行窗口尺寸的調(diào)整
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
// 返回窗口尺寸是否可能需要發(fā)生變化
return windowSizeMayChange;
}
relayoutWindow 方法
調(diào)用relayoutWindow()來請求WindowManagerService服務(wù)計算Activity窗口的大小以及過掃描區(qū)域邊襯大小和可見區(qū)域邊襯大小。計算完畢之后,Activity窗口的大小就會保存在成員變量mWinFrame中,而Activity窗口的內(nèi)容區(qū)域邊襯大小和可見區(qū)域邊襯大小分別保存在ViewRoot類的成員變量mPendingOverscanInsets和mPendingVisibleInsets中。
performTraversals 執(zhí)行次數(shù)的問題
這里有一個點,就是 View為什么會至少進(jìn)行2次onMeasure、onLayout,這個問題下面幾個情況會觸發(fā):
- 做為視圖容器的 ViewGroup 寬高不是 match_parent活具體數(shù)值時,都會對子 view 進(jìn)行2次 measure,layout
- ViewRootImpl.performTraversals 會對 viewTree 進(jìn)行2次 measure,layout
- 每個 view 自身的大小位置改變都會觸發(fā)其父容器的 requestLayout,進(jìn)而造成父容器重新 measure,layout 所有子 view
其他不說了,這里說下 ViewRootImpl.performTraversals
看代碼我們知道 performTraversals 方法回執(zhí)行2次,一次 performTraversals 會2次 measure,1次 layout,那么2次 performTraversals 是不是就總共會 4次 measure,2次 layout 呢,答案不是
performMeasure 測量方法有優(yōu)化的,若是 forceLayout = false 或是測量參數(shù)沒有變化,那么就是用上次測量的值,就不會重新測量。forceLayout = true 這表示強制重新布局,可以通過View.requestLayout() 來實現(xiàn)
performLayout 方法會把 forceLayout 置為 false ,這樣在第一次 performTraversals 時,先執(zhí)行2次測量,之后布局,forceLayout 就 = false 了,第二次 performTraversals 進(jìn)來,根據(jù)優(yōu)化原則就不會再進(jìn)行測量了