前言
Android 中 Activity 是作為應(yīng)用程序的載體存在,代表著一個(gè)完整的用戶界面泼各,提供了一個(gè)窗口來繪制各種視圖阶牍,當(dāng) Activity 啟動(dòng)時(shí),我們會(huì)通過 setContentView 方法來設(shè)置一個(gè)內(nèi)容視圖偏灿,這個(gè)內(nèi)容視圖就是用戶看到的界面丹诀。那么 View 和 activity 是如何關(guān)聯(lián)在一起的呢 ?
上圖是 View 和 Activity 之間的關(guān)系翁垂。先解釋圖中一些類的作用以及相關(guān)關(guān)系:
- Window:每個(gè)Activity都會(huì)創(chuàng)建一個(gè)Window用于承載View視圖的顯示铆遭,Window是一個(gè)抽象類,存在一個(gè)唯一實(shí)現(xiàn)類PhoneWindow沿猜。
- PhoneWindow:該類繼承于Window類枚荣,是Window類的具體實(shí)現(xiàn),我們可以通過該類去繪制窗口啼肩。并且橄妆,該類內(nèi)部包含了一個(gè) DecorView 對象,該 DectorView 對象是所有應(yīng)用窗口的根 View祈坠。
- DecorView:最頂層的View呼畸,是一個(gè)FrameLayout子類,最終會(huì)被加載到Window當(dāng)中颁虐,它內(nèi)部只有一個(gè)垂直方向的LinearLayout分為兩部分:一個(gè)是TitleBar(ActionBar 的容器)蛮原,另一個(gè)是ContentView(Activity對應(yīng)的XML布局,通過setContentView設(shè)置到DecorView中)另绩。
- WindowManager : 是一個(gè)接口儒陨,里面常用的方法有:添加View花嘶,更新View和刪除View。主要是用來管理 Window 的蹦漠。WindowManager 具體的實(shí)現(xiàn)類是WindowManagerImpl椭员。最終,WindowManagerImpl 會(huì)將業(yè)務(wù)交給 WindowManagerGlobal 來處理笛园。
- WindowManagerService (WMS) : 負(fù)責(zé)管理各 app 窗口的創(chuàng)建隘击,更新,刪除研铆, 顯示順序埋同。運(yùn)行在 system_server 進(jìn)程。
- ViewRootImpl :擁有 DecorView 的實(shí)例棵红,通過該實(shí)例來控制 DecorView 凶赁,最終通過執(zhí)行ViewRootImpl的performTraversals()開啟整個(gè)View樹的繪制。ViewRootImpl 的一個(gè)內(nèi)部類 W逆甜,實(shí)現(xiàn)了 IWindow 接口虱肄,IWindow 接口是供 WMS 使用的,WSM 通過調(diào)用 IWindow 一些方法交煞,通過 Binder 通信的方式咏窿,最后執(zhí)行到了 W 中對應(yīng)的方法中。同樣的素征,ViewRootImpl 通過 IWindowSession 來調(diào)用 WMS 的 Session 一些方法翰灾。Session 類繼承自 IWindowSession.Stub,每一個(gè)應(yīng)用進(jìn)程都有一個(gè)唯一的 Session 對象與 WMS 通信稚茅。
DecorView 的創(chuàng)建
首先來看一下Activity中setContentView源碼:
public void setContentView(@LayoutRes int layoutResID) {
//將xml布局傳遞到Window當(dāng)中
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
從代碼可以看出纸淮,Activity的setContentView實(shí)質(zhì)是將View傳遞到Window的setContentView()方法中,Window的setContenView會(huì)在內(nèi)部調(diào)用installDecor()方法創(chuàng)建DecorView亚享,看一下它的部分源碼:
public void setContentView(int layoutResID) {
if (mContentParent == null) {
//初始化DecorView以及其內(nèi)部的content
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...............
} else {
//將contentView加載到DecorVoew當(dāng)中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
...............
}
private void installDecor() {
...............
if (mDecor == null) {
//實(shí)例化DecorView
mDecor = generateDecor(-1);
...............
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
//獲取Content
mContentParent = generateLayout(mDecor);
}
...............
}
protected DecorView generateDecor(int featureId) {
...............
return new DecorView(context, featureId, this, getAttributes());
}
通過generateDecor()new一個(gè)DecorView咽块,然后調(diào)用generateLayout()獲取DecorView中content,最終通過inflate將Activity視圖添加到DecorView中的content中欺税,到此侈沪,DecorView 的創(chuàng)建就講完了⊥碓洌可是我們似乎并沒有看到 DecorView 是被添加的亭罪,什么時(shí)候?qū)τ脩艨梢姷摹?/p>
WindowManager
View 創(chuàng)建完以后,那 Decorview 是怎么添加到屏幕中去的呢歼秽?當(dāng)然是 WindowManager 呢应役,那么是如何將 View 傳到 WindowManager 中呢。
public interface WindowManager extends ViewManager {
public static class BadTokenException extends RuntimeException{...}
public static class InvalidDisplayException extends RuntimeException{...}
public Display getDefaultDisplay();
public void removeViewImmediate(View view);
public static class LayoutParams extends ViewGroup.LayoutParams
implements Parcelable
ViewManager接口定義了一組規(guī)則箩祥,也就是add院崇、update、remove的操作View接口袍祖。也就是說ViewManager是用來添加和移除activity中View的接口底瓣,可以通過Context.getSystemService()獲取實(shí)例。
WindowManager是一個(gè)接口蕉陋,而且它繼承與ViewManager捐凭。WindowManager字面理解就是窗口管理器,每一個(gè)窗口管理器都與一個(gè)的窗口顯示綁定凳鬓。獲取實(shí)例可以通過
Context.getSystemService(Context.WINDOW_SERVICE)獲取茁肠。既然繼承了ViewManager,那么它也就可以進(jìn)行添加刪除View的操作了村视,不過它的操作放在它的實(shí)現(xiàn)類WindowManagerImpl里面。
- BadTokenException:則是addView時(shí)它的LayoutParams無效則會(huì)被拋出酒奶,或是添加第二個(gè)View的時(shí)候沒有移除第一個(gè)View則會(huì)被拋出
- InvalidDisplayException:如果一個(gè)窗口是在一個(gè)二級的顯示上而指定的顯示找不到則會(huì)被拋出
- getDefaultDisplay:返回當(dāng)前WindowManager管理的顯示Display
- removeViewImmediate:表示從窗口上移除View蚁孔,一般是當(dāng)View調(diào)用了onDetachedFromWindow也就是從Window上分開后,把它移除惋嚎。
- LayoutParams:靜態(tài)內(nèi)部類杠氢。顯然是Window的布局參數(shù),里面定義了一系列的窗口屬性另伍。
WindowManagerImpl
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Display mDisplay;
private final Window mParentWindow;
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
...
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
}
可以看到WindowManagerImpl里面有一個(gè)成員變量WindowManagerGlobal鼻百,而真正的實(shí)現(xiàn)則是在WindowManagerGlobal了,類似代理摆尝,只不過WindowManagerGlobal是個(gè)沒有實(shí)現(xiàn)WindowManager的類的温艇,自己定義了套實(shí)現(xiàn)。
public final class WindowManagerGlobal {
private static final String TAG = "WindowManager";
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
}
}
ViewRootImpl
實(shí)際上堕汞,View 的繪制是由 ViewRootImpl 來負(fù)責(zé)的勺爱。每個(gè)應(yīng)用程序窗口的 DecorView 都有一個(gè)與之關(guān)聯(lián)的 ViewRootImpl 對象,這種關(guān)聯(lián)關(guān)系是由 WindowManager 來維護(hù)的讯检。
先看 ViewRootImpl 的 setView 方法琐鲁,該方法很長,我們將一些不重要的點(diǎn)注釋掉:
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
......
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
// 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();
......
}
}
}
這里先將 mView 保存了 DecorView 的實(shí)例人灼,然后調(diào)用 requestLayout() 方法围段,以完成應(yīng)用程序用戶界面的初次布局。
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
因?yàn)槭?UI 繪制投放,所以一定要確保是在主線程進(jìn)行的奈泪,checkThread 主要是做一個(gè)校驗(yàn)。接著調(diào)用 scheduleTraversals 開始計(jì)劃繪制了。
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
最終調(diào)用其 run 方法:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
該方法內(nèi)部最終會(huì)調(diào)用 performTraversals 進(jìn)行繪制段磨。
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
到此取逾,DecorView 與 activity 之間的綁定關(guān)系就講完了。后面代碼比較多苹支,總結(jié)一下就是:
ViewRootImpl的作用是用來銜接WindowManager和DecorView砾隅,在Activity被創(chuàng)建后會(huì)通過WindowManager將DecorView添加到PhoneWindow中并且創(chuàng)建ViewRootImpl實(shí)例,隨后將DecorView與ViewRootImpl進(jìn)行關(guān)聯(lián)债蜜,最終通過執(zhí)行ViewRootImpl的performTraversals()開啟整個(gè)View樹的繪制晴埂。
下一章,將會(huì)介紹 performTraversals 所做的事情寻定,也就是 View 繪制流程儒洛。
Android自定義View中篇:View繪制流程