公衆號:字節數組,但願對你有所幫助 😇😇java
Android 系統中,Window 在代碼層次上是一個抽象類,在概念上表示的是一個窗口。Android 中全部的視圖都是經過 Window 來呈現的,例如 Activity、Dialog 和 Toast 等,它們實際上都是掛載在 Window 上的。大部分狀況下應用層開發者不多須要來和 Window 打交道,Activity 已經隱藏了 Window 的具體實現邏輯了,但我以爲來了解 Window 機制的一個比較大的好處是能夠加深咱們對 View 繪製流程以及事件分發機制的瞭解,這兩個操做就涉及到咱們的平常開發了,實現自定義 View 和解決 View 的滑動衝突時都須要咱們掌握這方面的知識點,而這兩個操做和 Window 機制有很大的關聯。視圖樹只有被掛載到 Window 後纔會觸發視圖樹的繪製流程,以後視圖樹纔有機會接收到用戶的觸摸事件。也就是說,視圖樹被掛載到了 Window 上是 Activity 和 Dialog 可以展現到屏幕上且和用戶作交互的前置條件git
本文就以 Activity 爲例子,展開講解 Activity 是如何掛載到 Window 上的,基於 Android API 30 進行分析,但願對你有所幫助 😇😇github
Window 存在的意義是什麼呢?數組
大部分狀況下,用戶都是在和應用的 Activity 作交互,應用在 Activity 上接收用戶的輸入並在 Activity 上向用戶作出交互反饋。例如,在 Activity 中顯示了一個 Button,當用戶點擊後就會觸發 OnClickListener,這個過程當中用戶就是在和 Activity 中的視圖樹作交互,此時尚未什麼問題。但是,當須要在 Activity 上彈出 Dialog 時,系統須要確保 Dialog 是會覆蓋在 Activity 之上的,有觸摸事件時也須要確保 Dialog 是先於 Activity 接收到的;當啓動一個新的 Activity 時又須要覆蓋住上一個 Activity 以及其顯示的 Dialog;在彈出 Toast 時,又須要確保 Toast 是覆蓋在 Activity 和 Dialog 之上的markdown
這種種要求就涉及到了一個層次管理問題,系統須要對當前屏幕上顯示的多個視圖樹進行統一管理,這樣才能來決定不一樣視圖樹的顯示層次以及在接收觸摸事件時的優先級。系統就經過 Window 這個概念來實現上述目的app
想要在屏幕上顯示一個 Window 並不算多複雜,代碼大體以下所示ide
private val windowManager by lazy {
context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
}
private val floatBallView by lazy {
FloatBallView(context)
}
private val floatBallWindowParams: WindowManager.LayoutParams by lazy {
WindowManager.LayoutParams().apply {
width = FloatBallView.VIEW_WIDTH
height = FloatBallView.VIEW_HEIGHT
gravity = Gravity.START or Gravity.CENTER_VERTICAL
flags =
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
type = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
} else {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
}
}
}
fun showFloatBall() {
windowManager.addView(floatBallView, floatBallWindowParams)
}
複製代碼
顯示一個 Window 最基本的操做流程有:函數
當中,WindowManager.LayoutParams 的 flags 屬性就用於控制 Window 的顯示特性和交互邏輯,常見的有如下幾個:oop
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE。表示當前 Window 不須要獲取焦點,也不須要接收各類按鍵輸入事件,按鍵事件會直接傳遞給下層具備焦點的 Window佈局
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL。表示當前 Window 區域的單擊事件但願本身處理,其它區域的事件則傳遞給其它 Window
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED。表示當前 Window 但願顯示在鎖屏界面
此外,WindowManager.LayoutParams 的 type 屬性就用於表示 Window 的類型。Window 有三種類型:應用 Window、子 Window、系統 Window。應用類Window 對應 Activity。子 Window 具備依賴關係,不能單獨存在,須要附屬在特定的父 Window 之中,好比 Dialog 就是一個子 Window。系統 Window 是須要權限才能建立的 Window,好比 Toast 和 statusBar 都是系統 Window
從這也能夠看出,系統 Window 是處於最頂層的,因此說 type 屬性也用於控制 Window 的顯示層級,顯示層級高的 Window 就會覆蓋在顯示層級低的 Window 之上。應用 Window 的層級範圍是 1~99,子 Window 的層級範圍是 1000~1999,系統 Window 的層級範圍是 2000~2999。若是想要讓咱們建立的 Window 位於其它 Window 之上,那麼就須要使用比較大的層級值了,但想要顯示自定義的系統級 Window 的話就必須向系統申請權限了
WindowManager.LayoutParams 內就聲明瞭這些層級值,咱們能夠擇需選取。例如,系統狀態欄自己也是一個 Window,其 type 值就是 TYPE_STATUS_BAR
public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
public int type;
//應用 Window 的開始值
public static final int FIRST_APPLICATION_WINDOW = 1;
//應用 Window 的結束值
public static final int LAST_APPLICATION_WINDOW = 99;
//子 Window 的開始值
public static final int FIRST_SUB_WINDOW = 1000;
//子 Window 的結束值
public static final int LAST_SUB_WINDOW = 1999;
//系統 Window 的開始值
public static final int FIRST_SYSTEM_WINDOW = 2000;
//系統狀態欄
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
//系統 Window 的結束值
public static final int LAST_SYSTEM_WINDOW = 2999;
}
複製代碼
每一個 Window 都會關聯一個 View,想要顯示 Window 也離不開 WindowManager,WindowManager 就提供了對 View 進行操做的能力。WindowManager 自己是一個接口,其又繼承了另外一個接口 ViewManager,WindowManager 最基本的三種操做行爲就由 ViewManager 來定義,即添加 View、更新 View、移除 View
public interface ViewManager {
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
複製代碼
WindowManager 的實現類是 WindowManagerImpl,其三種基本的操做行爲都交由了 WindowManagerGlobal 去實現,這裏使用到了橋接模式
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
}
複製代碼
這裏主要看下 WindowManagerGlobal 是如何實現 addView
方法的便可
首先,WindowManagerGlobal 會對入參參數進行校驗,並對 LayoutParams 作下參數調整。例如,若是當前要顯示的是子 Window 的話,那麼就須要使其 LayoutParams 遵循父 Window 的要求才行
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
if (display == null) {
throw new IllegalArgumentException("display must not be null");
}
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
···
}
複製代碼
以後就會爲當前的視圖樹(即 view)構建一個關聯的 ViewRootImpl 對象,經過 ViewRootImpl 來繪製視圖樹並完成 Window 的添加過程。ViewRootImpl 的 setView
方法會觸發啓動整個視圖樹的繪製流程,即完成視圖樹的 Measure、Layout、Draw 流程,具體流程能夠看個人另外一篇文章:一文讀懂 View 的 Measure、Layout、Draw 流程
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow, int userId) {
···
ViewRootImpl root;
View panelParentView = null;
···
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 {
//啓動和 view 關聯的整個視圖樹的繪製流程
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
複製代碼
ViewRootImpl 內部最終會經過 WindowSession 來完成 Window 的添加過程,mWindowSession
是一個 Binder 對象,真正的實現類是 Session,也就是說,Window 的添加過程涉及到了 IPC 調用。後面就比較複雜了,能力有限就不繼續看下去了
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
adjustLayoutParamsForCompatibility(mWindowAttributes);
res = mWindowSession.addToDisplayAsUser(
mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls
);
setFrame(mTmpFrame);
複製代碼
須要注意的是,這裏所講的視圖樹表明的是不少種不一樣的視圖形式。在啓動一個 Activity 或者顯示一個 Dialog 的時候,咱們都須要爲它們指定一個佈局文件,佈局文件會經過 LayoutInflater 加載映射爲一個具體的 View 對象,即最終 Activity 和 Dialog 都會被映射爲一個 View 類型的視圖樹,它們都會經過 WindowManager 的 addView
方法來顯示到屏幕上,WindowManager 對於 Activity 和 Dialog 來講具備統一的操做行爲入口
這裏就以 Activity 爲例子來展開講解 Window 相關的知識點,因此也須要先對 Activity 的組成結構作個大體的介紹。Activity 和 Window 之間的關係能夠用如下圖片來表示
每一個 Activity 均包含一個 Window 對象,即 Activity 和 Window 是一對一的關係
Window 是一個抽象類,其惟一的實現類是 PhoneWindow
PhoneWindow 內部包含一個 DecorView,DecorView 是 FrameLayout 的子類,其內部包含一個 LinearLayout,LinearLayout 中又包含兩個自上而下的 childView,即 ActionBar 和 ContentParent。咱們平時在 Activity 中調用的 setContentView
方法實際上就是在向 ContentParent 執行 addView
操做
Window 這個抽象類裏定義了多個和 UI 操做相關的方法,咱們平時在 Activity 中調用的setContentView
和findViewById
方法都會被轉交由 Window 來實現,Window 是 Activity 和視圖樹系統交互的入口。例如,其 getDecorView()
方法就用於獲取內嵌的 DecorView,findViewById()
方法就會將具體邏輯轉交由 DecorView 來實現,由於 DecorView 纔是真正包含 contentView
的容器類
public abstract class Window {
public Window(Context context) {
mContext = context;
mFeatures = mLocalFeatures = getDefaultFeatures(context);
}
public abstract void setContentView(@LayoutRes int layoutResID);
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
public abstract void setTitle(CharSequence title);
public abstract @NonNull View getDecorView();
···
}
複製代碼
每一個 Activity 內部都包含一個 Window 對象 mWindow
,在 attach
方法中完成初始化,這說明 Activity 和 Window 是一對一的關係。mWindow
對象對應的是 PhoneWindow 類,這也是 Window 的惟一實現類
public class Activity extends ContextThemeWrapper implements LayoutInflater.Factory2, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener, ComponentCallbacks2, Window.OnWindowDismissedCallback, AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {
@UnsupportedAppUsage
private Window mWindow;
@UnsupportedAppUsage
private WindowManager mWindowManager;
@UnsupportedAppUsage
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, IBinder assistToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
//初始化 mWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
···
}
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
}
複製代碼
Activity 的attach
方法又是在 ActivityThread 的 performLaunchActivity
方法中被調用的,在經過反射生成 Activity 實例後就會調用attach
方法,且能夠看到該方法的調用時機是早於 Activity 的 onCreate
方法的。因此說,在生成 Activity 實例後不久其 Window 對象就已經被初始化了,並且早於各個生命週期回調函數
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);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
···
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,
r.assistToken);
···
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
return activity;
}
複製代碼
此外,從 Activity 的setContentView
的方法簽名來看,具體邏輯都交由了 Window 的同名方法來實現,傳入的 layoutResID
就是咱們但願在屏幕上呈現的佈局,那麼 PhoneWindow 天然就須要去加載該佈局文件生成對應的 View。而爲了可以有一個對 View 進行統一管理的入口,View 應該要包含在一個指定的 ViewGroup 中才行,該 ViewGroup 指的就是 DecorView
PhoneWindow 的 setContentView
方法的邏輯能夠總結爲:
mDecor
。DecorView 是 FrameLayout 的子類,其內部包含兩個咱們常常會接觸到的 childView:actionBar 和 contentParent,actionBar 即 Activity 的標題欄,contentParent 即 Activity 的視圖內容容器mContentParent
爲 null 的話則調用 installDecor()
方法來初始化 DecorView,從而同時初始化 mContentParent
;不爲 null 的話則移除 mContentParent
的全部 childView
,爲 layoutResID
騰出位置(不考慮轉場動畫,實際上最終的操做都同樣)LayoutInflater.inflate
生成 layoutResID
對應的 View,並將其添加到 mContentParent
中,從而將咱們的目標視圖掛載到一個統一的容器中(不考慮轉場動畫,實際上最終的操做都同樣)Callback.onContentChanged
方法,咱們能夠經過重寫 Activity 的該方法從而獲得佈局內容改變的通知因此說,Activity 的 setContentView
方法實際上就是在向 DecorView 的 mContentParent
執行 addView
操做,因此該方法才叫setContentView
而非setView
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private DecorView mDecor;
ViewGroup mContentParent;
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
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 {
//將 layoutResID 對應的 View 添加到 mContentParent 中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//回調通知 contentView 發生變化了
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeFrameworkOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
···
} else {
···
}
···
}
}
}
複製代碼
mContentParent
經過 generateLayout
方法來完成初始化,該方法的邏輯能夠分爲三步:
<item name="windowNoTitle">true</item>
的話,那麼就會執行 requestFeature(FEATURE_NO_TITLE)
來隱藏標題欄layoutResource
。之因此會有多種佈局文件,是由於不一樣的 Activity 會有不一樣的顯示要求,有的要求顯示 title,有的要求顯示 leftIcon,而有的可能全都不須要,爲了不控件冗餘就須要來選擇合適的佈局文件。而雖然每種佈局文件結構上略有不一樣,但均會包含一個 ID 名爲content
的 FrameLayout,mContentParent
就對應該 FrameLayoutlayoutResource
生成對應的 View 對象並添加爲本身的 childView,對應 DecorView 中的 mContentRoot
,後續執行的 findViewById(ID_ANDROID_CONTENT)
操做就都是交由 DecorView 來實現的了,而正常來講每種 layoutResource
都會包含一個 ID 爲 content
的 FrameLayout,若是發現找不到的話就直接拋出異常,不然就成功返回拿到 mContentParent
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
···
//第一步
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);
}
···
//第二步
int layoutResource;
···
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
//第三步
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
···
return contentParent;
}
複製代碼
DecorView 是 FrameLayout 的子類,其 onResourcesLoaded
方法在拿到 PhoneWindow 傳遞過來的 layoutResource
後,就會生成對應的 View 並添加爲本身的 childView,就像普通的 ViewGroup 執行 addView
方法同樣,該 childView 就對應 mContentRoot
,咱們能夠在 Activity 中經過(window.decorView as ViewGroup).getChildAt(0)
來獲取到 mContentRoot
因此 DecorView 能夠看作是 Activity 中整個視圖樹的根佈局
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
@UnsupportedAppUsage
private PhoneWindow mWindow;
ViewGroup mContentRoot;
DecorView(Context context, int featureId, PhoneWindow window,
WindowManager.LayoutParams params) {
···
}
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
if (mBackdropFrameRenderer != null) {
loadBackgroundDrawablesIfNeeded();
mBackdropFrameRenderer.onResourcesLoaded(
this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
getCurrentColor(mNavigationColorViewState));
}
mDecorCaptionView = createDecorCaptionView(inflater);
final View root = inflater.inflate(layoutResource, null);
if (mDecorCaptionView != null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
}
複製代碼
完成以上步驟後,此時其實還只是完成了 Activity 整個視圖樹的加載工做,雖然 Activity 的 attach
方法已經建立了 Window 對象,但還須要將 DecorView 提交給 WindowManager 後才能正式將視圖樹展現到屏幕上
DecorView 具體的提交時機還須要看 ActivityThread 的 handleResumeActivity
方法,該方法用於回調 Activity 的 onResume
方法,裏面還會回調到 Activity 的makeVisible
方法,從方法名能夠猜出來makeVisible
方法就用於令 Activity 變爲可見狀態
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
···
r.activity.makeVisible();
···
}
複製代碼
makeVisible
方法會判斷當前 Activity 是否已經將 DecorView 提交給 WindowManager 了,若是還沒的話就進行提交,最後將 DecorView 的可見狀態設爲 VISIBLE,至此才創建起 Activity 和 WindowManager 之間的關聯關係,以後 Activity 才正式對用戶可見
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
複製代碼
對以上流程作下總結
setContentView
、findViewById
等操做都會交由 Window 來實現,Window 是 Activity 和整個 View 系統交互的入口layoutResource
,每種 layoutResource
雖然在佈局結構上略有不一樣,可是均會包含一個 ID 名爲content
的 FrameLayout,contentParent
即該 FrameLayout。咱們能夠經過 Window.ID_ANDROID_CONTENT
來拿到該 ID,也能夠在 Activity 中經過 findViewById<View>(Window.ID_ANDROID_CONTENT)
來獲取到contentParent
layoutResource
來生成對應的 rootView 並將開發者指定的 contentView 添加爲contentParent
的 childView,因此能夠將 DecorView 看作是視圖樹的根佈局。正由於如此,Activity 的 findViewById
操做實際上會先交由 Window,Window 再交由 DecorView 去完成,由於 DecorView 纔是實際持有 contentView 的容器類makeVisible
方法裏提交給 WindowManager 的,以後 WindowManagerImpl 會經過 ViewRootImpl 來完成整個視圖樹的繪製流程,以後 Activity 才正式對用戶可見這裏我也提供一個自定義 Window 的 Demo,實現了基本的拖拽移動和點擊事件,代碼點擊這裏:AndroidOpenSourceDemo
最近比較傾向於只用一篇文章來寫一個知識點,也懶得老是想文章標題,就一直沿用一開始用的一文讀懂XXX,寫着寫着也攢了蠻多篇文章了,以前也已經寫了幾篇關於 View 系統的文章,但願對你有所幫助 😇😇