如需轉載請評論或簡信,并注明出處,未經允許不得轉載
目錄
前言
對于接觸Android開發不久的同學來說,要寫一個頁面,我們大多數時候都是先創建一個layout.xml
布局文件,在布局文件中進行頁面搭建,然后通過Activity
的sentContentView()
將布局文件設置到Activity中,這樣Android系統就自動幫我們繪制了這個頁面。我們知道,在Android中,一個頁面是由一個個View
組合而成的,那我們有沒有想過,Android中View
的繪制流程是怎么樣的呢?本文將分為上下兩部分,上部分主要講Activity
、Window
、WindowManager
、ViewRoot
等相關概念及互相之間的聯系,了解這些有助于我們對View
的繪制流程有一個更系統的認識,下部分會詳細介紹view的measure
、layout
、draw
的過程
Window和DecorView的創建
我們先從我們比較熟悉的activity.setContentView()
說起,查看這個方法的源碼
private Window mWindow;
public void setContentView(@LayoutRes int layoutResID) {
//調用window對象的setContentView()
getWindow().setContentView(layoutResID);
//創建ActionBar
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
我們發現,Activity
的setContentView()
實際上調用的是Window
的setContentView()
mWindow
什么時候被創建的呢?
Activity.java
//只截取部分主要代碼
final void attach(...) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
//創建PhoneWindow對象
mWindow = new PhoneWindow(this, window, activityConfigCallback);
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);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
...
//通過Context.getSystemService(Context.WINDOW_SERVICE)的方式獲取WindowManager的實例
//給window設置windowManger
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;
mWindow.setColorMode(info.colorMode);
}
可以看出,mWindow
是在Activity
的attach()
中被創建的,它實際上是一個PhoneWindow
對象,PhoneWindow
是Android提供的Window
唯一實現類。且系統會為每個Activity創建一個與之對應的Window
創建Window
后,還會通過Context.getSystemService(Context.WINDOW_SERVICE)
的方式獲取WindowManager
的實例,并設置給Window
。WindowManager
是一個窗口管理類,稍后還會對WindowManager
做更加深入的分析
下面來看PhoneWindow
的setContentView()
@Override
public void setContentView(int layoutResID) {
//先判斷mContentParent是否初始化
if (mContentParent == null) {
//如果沒有初始化調用installDecor
//當前activity第一次調用sentContentView
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//如果Activity沒有過度動畫,多次調用sentContentView會走這里
//移除mContentParent所有內部view
mContentParent.removeAllViews();
}
//判斷是否使用了Activity的過度動畫
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//設置動畫場景
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//將資源文件通過LayoutInflater對象裝換為View樹
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//這個方法在Activity是一個空實現
//說明在Activity的布局改動時 (setContentView或者addContentView 方法執行完畢后會調用改方法) //所以各種View的findViewById方法什么的可以放在這里
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
這里我們重點關注installDecor()
,看看這個方法做了什么
//只截取部分主要代碼
private void installDecor() {
if (mDecor == null) {
//如果mDecor為空則創建一個DecorView實例
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
//如果mContentParent為空則通過generateLayout創建一個
mContentParent = generateLayout(mDecor);
...
}
}
installDecor()
主要干兩件事情
- 如果
mDecor
沒有初始化,則通過generateDecor()
初始化 - 如果
mContentParent
沒有初始化,則通過generateLayout()
初始化
generateDecor()
很簡單,就是new
一個DecorView
實例
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
//創建new DecorView實例
return new DecorView(context, featureId, this, getAttributes());
}
下面來分析,generateLayout()
//只截取部分主要代碼
protected ViewGroup generateLayout(DecorView decor) {
//根據當前style修飾相應樣式
TypedArray a = getWindowStyle();
...
//一堆if判斷,根據設置的主題樣式來設置DecorView的風格
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
} else if(...){
...
}
//加載窗口布局
int layoutResource;
int features = getLocalFeatures();
//根據features選擇不同的layoutResource(布局文件資源)
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if(...){
...
}
//加載layoutResource
View in = mLayoutInflater.inflate(layoutResource, null);
//DecorView是FrameLayout,往DecorView中添加根布局View
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
//這里獲取的就是mContentParent
//ID_ANDROID_CONTENT = com.android.internal.R.id.content;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
return contentParent;
}
根據設置的Window
主題樣式來設置DecorView
的風格,接著為DecorView
添加子View
,而這里的子View
則是上面提到的mContentParent
,如果上面設置了FEATURE_NO_ACTIONBAR
,那么DecorView
就只有mContentParent
一個子View
。可以看我們文章一開始的那張圖,圖中就很好的闡述了DecorView
、TitleView
、mContentParent
三者之間的關系
所以這也就是為什么,在設置Activity屬性的時候,比如
requestWindowFeature(Window.FEATURE_NO_TITLE)
需要在setContentView()
之前調用才會生效
小結
- 一個
Activity
對應一個Window
,Window
是在activity.attach()
的時候被創建的 - 在
Activity
中調用setContentView()
實際上是調用了Window
的setContentView()
- 調用
setContentView()
時,會去初始化DecorView
以及其內部的TitleView
和mContentParent
,可以通過設置Window.FEATURE_NO_TITLE
使得DecorView
內部只存在mContentParent
- 我們在xml中定義的
layout_activity.xml
實際上是mContentParent
的一個子View
通過WindowManager管理Window
上文中我們主要講的是Window
的創建、DecorView
的創建以及DecorView
的子View
是如何被添加到DecorView
中去的。每個Activity
都有一個與之關聯的Window
,那如何管理Window
呢?這時候就需要借助我們的WindowMananger
類。WindowManager
管理Window
實際上就是在管理Window
中的DecorView
下面來看一下DecorView
和WindowManager
是如何關聯起來的
從Activity的啟動開始分析(對Activity的啟動過程感興趣的還可以看Android應用進程的創建 — Activity的啟動流程)
- 使用代理模式啟動到
ActivityManagerService
中執行 - 創建
ActivityRecord
到mHistory
記錄中 - 通過
socket
通信到Zgote
相關類創建process
- 通過
ApplicatonThread
與ActivityManagerService
建立通信 -
ActivityManagerService
通知ActiveThread
啟動Activity
的創建 -
ActivityThread
創建Activity
加入到mActivities
中并開始調度Activity
執行 activityThread.handleLaunchActivity()
上面就是Activity啟動的大致流程,緊接上面第7步,從ActivityThread
開始執行到DecorView
被添加到WindowManager
中的過程如下(代碼這里就省略了,有興趣的自己可以搜索關鍵代碼看一下),這里重點關注handleResumeActivity()
方法
activityThread.handleLaunchActivity()
—> activityThread.performLaunchActivity()
—> activity.attach()
—> activity.onCreate()
—> activityThread.handleResumeActivity()
—>activityThread.performResumeActivity() —> activity.onResume()
—> windowManager.addView()
//只截取部分主要代碼
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
...
// 這里會調用到activity.onResume()方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
if (r != null) {
final Activity a = r.activity;
...
if (r.window == null && !a.mFinished && willBeVisible) {
// 獲得Window對象
r.window = r.activity.getWindow();
// 獲得Window中的DecorView對象
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 獲得WindowManager對象
//這個WindowManager就是在activity.attach()的時候和window一起創建的
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
// 調用windowManager.addView方法
wm.addView(decor, l);
}
...
if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) {
r.activity.mVisibleFromServer = true;
mNumVisibleActivities++;
if (r.activity.mVisibleFromClient) {
//設置decorView可見
r.activity.makeVisible();
}
}
}
}
}
通過調用windowManager.addView(decor, lp)
,這樣一來,DecorView
和WindowManager
就建立了聯系。WindowManager
繼承了ViewManger
接口,但實際上其本身依然是一個接口,實現類是WindowManagerImpl
//WindowManagerImpl implements WindowManager
//WindowManager extends ViewManager
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
//只截取部分主要代碼
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
private final Window mParentWindow;
public WindowManagerImpl(Context context) {
this(context, null);
}
private WindowManagerImpl(Context context, Window parentWindow) {
mContext = context;
mParentWindow = parentWindow;
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
public WindowManagerImpl createPresentationWindowManager(Context displayContext) {
return new WindowManagerImpl(displayContext, mParentWindow);
}
//往Window中添加view
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
//更新布局
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
//將刪除View的消息發送到MessageQueue中,稍后刪除
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
//立刻刪除Window中的view
@Override
public void removeViewImmediate(View view) {
mGlobal.removeView(view, true);
}
}
注意:同樣實現了
ViewManger
接口的還有ViewGroup
,我們知道ViewGroup
也有addView
方法,但是在ViewGroup
中是將普通的view
或者viewGroup
作為Children
加入,而在WindowManagerImpl
是將DecorView
作為根布局加入到PhoneWindow
中去,所以兩個方法的作用是截然不同的
我們發現,WindowManagerImpl
并沒有直接實現操作View的相關方法,而是全部交給WindowManagerGlobal
。WindowManagerGlobal
是一個單例類—即一個進程中最多僅有一個。創建WindowManagerGlobal
對象的方式如下
public static WindowManagerGlobal getInstance() {
synchronized (WindowManagerGlobal.class) {
if (sDefaultWindowManager == null) {
sDefaultWindowManager = new WindowManagerGlobal();
}
return sDefaultWindowManager;
}
}
深入分析WindowManagerGlobal
分析WindowManangerGlobal
實際上就是分析其內部的三個方法
addView(View view, ViewGroup.LayoutParams params)
該方法的主要作用是將decorView
添加到viewRootImp
中,通過viewRootImp
對其內部的view
進行管理
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
//參數檢查
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
...
//判斷是否有父Window,從而調整當前窗口布局參數(layoutParams)
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
//有,調整title和token
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
//無,應用開啟了硬件加速的話,decorview就開啟
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
if (mSystemPropertyUpdater == null) {
//創建一個Runnable,用來遍歷更新所有ViewRootImpl,更新系統參數
mSystemPropertyUpdater = new Runnable() {
@Override public void run() {
synchronized (mLock) {
for (int i = mRoots.size() - 1; i >= 0; --i) {
mRoots.get(i).loadSystemProperties();
}
}
}
};
//添加到執行隊列中
SystemProperties.addChangeCallback(mSystemPropertyUpdater);
}
//看需要add的view是否已經在mViews中
//WindowManager不允許同一個View被添加兩次
int index = findViewLocked(view, false);
if (index >= 0) {
// 如果被添加過,就看是否在死亡隊列里也存在
if (mDyingViews.contains(view)) {
// 如果存在,就將這個已經存在的view對應的window移除
mRoots.get(index).doDie();
} else {
//否則,說明view已經被添加,不需要重新添加了
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
}
// 如果屬于子Window層級
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews.size();
//遍歷viewRootImpl集合,看是否存在一個window的IBinder對象和需要添加的window的token一 //致,之后賦值引用
for (int i = 0; i < count; i++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
//創建一個ViewRootImpl對象并保存在root變量中
root = new ViewRootImpl(view.getContext(), display);
//給需要添加的view設置params,也就是decorView
view.setLayoutParams(wparams);
//將decoView、布局參數以及新建的ViewRootImpl保存在三個集合中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
//將decorView設置給ViewRootImpl
//ViewRootImpl向WMS添加新的窗口、申請Surface以及decorView在Surface上的重繪動作
//這才是真正意義上完成了窗口的添加操作
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
- 父窗口修改新窗口的布局參數。可能修改的只有
LayoutParams.token
和LayoutParams.mTitle
兩個屬性。mTitle
屬性不必贅述,僅用于調試。而token
屬性則值得一提,每一個新窗口必須通過LayoutParams.token
向WMS
出示相應的令牌才可以。在addView()
函數中通過父窗口修改這個token
屬性的目的是為了減少開發者的負擔。開發者不需要關心token
到底應該被設置為什么值,只需將LayoutParams
丟給一個WindowManager
,剩下的事情就不用再關心了。父窗口修改token
屬性的原則是:如果新窗口的類型為子窗口(其類型大于等于LayoutParams.FIRST_SUB_WINDOW
并小于等于LayoutParams.LAST_SUB_WINDOW
),則LayoutParams.token
所持有的令牌為其父窗口的ID(也就是IWindow.asBinder()
的返回值)。否則LayoutParams.token
將被修改為父窗口所屬的Activity的ID(也就是在第4章中所介紹的AppToken
),這對類型為TYPE_APPLICATION
的新窗口來說非常重要。從這點來說,當且僅當新窗的類型為子窗口時addView()
的parentWindow
參數才是真正意義上的父窗口。這類子窗口有上下文菜單、彈出式菜單以及游標等等,在WMS
中,這些窗口對應的WindowState
所保存的mAttachedWindow
既是parentWindow
所對應的WindowState
。然而另外還有一些窗口,如對話框窗口,類型為TYPE_APPLICATION
, 并不屬于子窗口,但需要AppToken
作為其令牌,為此parentWindow
將自己的AppToken賦予了新窗口的的LayoutParams.token
中。此時parentWindow
便并不是嚴格意義上的父窗口了 - 為新窗口創建一個
ViewRootImpl
對象。顧名思義,ViewRootImpl
實現了一個控件樹的根。它負責與WMS
進行直接的通訊,負責管理Surface
,負責觸發控件的測量與布局,負責觸發控件的繪制,同時也是輸入事件的中轉站。總之,ViewRootImpl
是整個控件系統正常運轉的動力所在,無疑是本章最關鍵的一個組件。 - 將控件、布局參數以及新建的
ViewRootImpl
對象以相同的索引值添加到三個對應的集合mViews
、mParams
以及mRoots
中,以供之后的查詢之需。控件、布局參數以及ViewRootImpl
三者共同組成了客戶端的一個窗口。或者說,在控件系統中的窗口就是控件、布局參數與ViewRootImpl
對象的一個三元組
updateViewLayout(View view, ViewGroup.LayoutParams params)
該方法的主要作用是更新decorView
的layoutParams
,如layoutParams.width
從100變為了200,則需要將這個變化通知給WMS
使其調整Surface
的大小,并讓窗口進行重繪
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
//參數檢查
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
//將layoutparams保存到decorView中
view.setLayoutParams(wparams);
synchronized (mLock) {
// 獲取decorView在三個集合中的索引
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
//更新layoutParams到集合中
mParams.add(index, wparams);
//調用viewRootImpl的setLayoutParams()使得新的layoutParams生效
root.setLayoutParams(wparams, false);
}
}
removeView(View view)
該方法的作用是從3個集合中刪除此Window
所對應的元素,包括decorView
、layoutPrams
以及viewRootImpl
,并要求viewRootImpl
從WMS
中刪除對應的Window
,并釋放一切需要回收的資源
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
removeViewLocked(index, immediate);//其內部會調用root.die(immediate)
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
要求viewRootImpl
從WMS
中刪除窗口并釋放資源的方法是調用viewRootImpl.die()
函數。因此可以得出這樣一個結論:viewRootImpl
的生命從setView()
開始,到die()
結束
通過ViewRootImp管理View
ViewRootImpl
實現了ViewParent
接口,它是WindowManagerGlobal
工作的實際實現者
我們先來看setView()
做了什么
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//mView保存了decorView
mView = view;
//mWindowAttributes保存了窗口所對應的LayoutParams
mWindowAttributes.copyFrom(attrs);
attrs = mWindowAttributes;
...
//請求UI開始繪制
requestLayout();
//初始化mInputChannel
//InputChannel是窗口接受來自InputDispatcher的輸入事件的管道
//注意,僅當窗口的屬性inputFeatures不含有INPUT_FEATURE_NO_INPUT_CHANNEL時
//才會創建InputChannel,否則mInputChannel為空,從而導致此窗口無法接受任何輸入事件
mInputChannel = new InputChannel();
try {
//通知WindowManagerService添加一個窗口,注冊一個事件監聽管道
//用來監聽 按鍵(KeyEvent)和觸摸(MotionEvent)事件
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mInputChannel);
}
...
}
}
setView()內部的執行過程viewRootImp.setView()
—> viewRootImp.requestLayout()
—> viewRootImp.scheduleTraversals()
—> viewRootImp.doTraversal()
—> viewRootImp.performTraversals()
—>進入view的繪制流程
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
//線程檢查,這里就是判斷更新UI的操作是否在主線程的方法
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();
}
}
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");
}
//開始view的繪制
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
總結
本文主要幫助我們了解View
的繪制開始之前經過了哪些流程,這有利于我們對整個View
的繪制體系有一個更全面的認識,下面我們來具體分析View的
最全的View繪制流程(下)— Measure、Layout、Draw