Window、Activity、View都經常用到,但三者關系還是沒有系統的理清,今天咱們就開始整理整理這三者的關系:
Window:頂級視窗外觀和行為策略的抽象基類,唯一實作是 PhoneWindow類,
Activity:四大組件之一,它提供一個界面讓用戶點擊和各種滑動操作,
View:代表用戶界面組件的基本構建塊,UI 組件,
原始碼:Android SDK 30
Activity.setContentView
不知道從哪入手,咱們就從setContentView開始,比較剛創建的Android專案Activity和setContentView是必不可少的,記住看Activity的setContentView而不是AppCompatActivity的
來瞅瞅setContentView:
public void setContentView(@LayoutRes int layoutResID) {
//重點來了
getWindow().setContentView(layoutResID);
//創建并設定ActionBar,
initWindowDecorActionBar();
}
getWindow?這么快就遇到了,好簡單,咱繼續看,
Activity.getWindow
public Window getWindow() {
return mWindow;
}
本來以為發現大魚了,顯然 Activity 幾乎什么都沒做,將操作直接交給了一個 Window 來處理,getWindow 回傳的是 Activity 中的全域變數 mWindow,它是 Window 視窗型別,那么它是什么時候賦值的呢?
Activity.attach()
找遍了整個Activity原始碼終于在 attach 方法找到了,咱們先看看代碼,
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*/);
//重點來了對PhoneWindow進行實體化
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
//呼叫setWindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
...
}
接下來呼叫 setWindowManager 方法,將系統 WindowManager 傳給 PhoneWindow,
Window:實際上整個 Android 系統中 Window 只有一個實作類,就是 PhoneWindow
PhoneWindow:Android-specific Window(特定視窗).
WindowManager:對Window進行管理,說到管理那就離不開對Window的添加、更新和洗掉的操作,在這里我們把它們統稱為Window的操作,對于Window的操作,最終都是交由WMS來進行處理,
Window.setWindowManager
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
//通過Binder機制來獲取WMS
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//注釋
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
注釋:這個方法很簡單就是就是又一次創建了 WindowManagerImpl 物件,這時 WindowManager 真正和Window關聯起來,
PhoneWindow.setContentView
Activity 將 setContentView 的操作交給了 PhoneWindow,接下來看下其實作程序:
@Override
public void setContentView(int layoutResID) {
//注意:當主題屬性等結晶化時,可在安裝視窗裝飾的程序中設定功能內容轉換,
//在這種情況發生之前,不要檢查功能,
if (mContentParent == null) {
//注釋1
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 {
//注釋2
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
PhoneWindow.installDecor
private void installDecor() {
mForceDecorInstall = false;
//初始化mDecor
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);
}
//初始化mContentParent
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
注釋1:呼叫 installDecor 初始化 DecorView 和 mContentParent,
注釋2:mLayoutInflater.inflate(layoutResID, mContentParent); 從指定的xml資源展開新的視圖層次結構,呼叫 setContentView 傳入的布局添加到 mContentParent 中,
從中可以看出PhoneWindow 中默認有一個 DecorView(實際上是一個 FrameLayout),在 DecorView 中默認自帶一個 mContentParent(ViewGroup),我們自己實作的布局是被添加到 mContentParent 中的,因此經過 setContentView 之后,PhoneWindow 內部的 View 關系如下所示:

目前為止 PhoneWindow 中只是創建出了一個 DecorView,并在 DecorView 中填充了我們在 Activity 中傳入的 layoutId 布局,可是 DecorView 還沒有跟 Activity 建立任何聯系,也沒有被繪制到界面上顯示,那 DecorView 是何時被繪制到螢屏上的呢?
Activity 執行到 onCreate 時并不可見,只有執行完 onResume 之后 Activity 中的內容才是螢屏可見狀態,造成這種現象的原因就是,onCreate 階段只是初始化了 Activity 需要顯示的內容,而在 onResume 階段(當界面要與用戶進行互動時,會呼叫ActivityThread的handleResumeActivity方法)才會將 PhoneWindow 中的 DecorView 真正的繪制到螢屏上,
ActivityThread.handleResumeActivity
在 ActivityThread 的 handleResumeActivity 中,會呼叫 WindowManager 的 addView 方法將 DecorView 添加到 WMS(WindowManagerService) 上,如下所示:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
...
ViewManager wm = a.getWindowManager();
...
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//注釋
wm.addView(decor, l);
} else {
a.onWindowAttributesChanged(l);
}
}
...
}
注釋:WindowManger 的 addView 結果有兩個: DecorView 被渲染繪制到螢屏上顯示; DecorView 可以接收螢屏觸摸事件,
WindowManager.addView
ViewManager是一個介面,WindowManager是個介面同時又實作了ViewManager介面,而WindowManagerImpl又實作了WindowManager,這是只捋清楚這個方法的層級,清楚后放到一邊繼續查看WindowManagerImpl中的addview方法
WindowManagerImpl.addView
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
WindowManagerImpl.addView也是一個空殼,它呼叫了 WindowManagerGlobal 的 addView 方法,
WindowManagerGlobal.addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
...
root = new ViewRootImpl(view.getContext(), display);
//注釋1
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
//最后執行此操作,因為它會發出訊息開始執行操作
try {
//注釋2
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
...
}
}
}
注釋1:WindowMangerGlobal 是一個單例 在 addView 方法中,創建了一個最關鍵的 ViewRootImpl 物件,
注釋2:然通過 root.setView 方法將 view 添加到 WMS 中,
ViewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
mView = view;
...
int res; /* = WindowManagerImpl.ADD_OKAY; */
//注釋1
requestLayout();
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
...
//注釋2
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
setFrame(mTmpFrame);
} catch (RemoteException e) {
...
} finally {
if (restore) {
attrs.restore();
}
}
}
}
注釋1: requestLayout 是重繪布局的操作,呼叫此方法后 ViewRootImpl 所關聯的 View 也執行 measure -> layout -> draw 操作,確保在 View 被添加到 Window 上顯示到螢屏之前,已經完成測量和繪制操作,
注釋2:呼叫 mWindowSession 的 addToDisplay 方法將 View 添加到 WMS 中,
mWindowSession哪里來的?他是new RootViewlmpl物件是傳入進來的,呼叫WindowManagerGlobal.getWindowSession()生成的,
new ViewRootImpl(context,display);
public ViewRootImpl(Context context, Display display) {
this(context, display, WindowManagerGlobal.getWindowSession(),false);
}
WindowManagerGlobal.getWindowSession()
WindowSession 是 WindowManagerGlobal 中的單例物件,初始化代碼如下:
public static IWindowSession getWindowSession() {
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
IWindowManager windowManager = getWindowManagerService();
//注釋
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
注釋:sWindowSession 實際上是 IWindowSession 型別,是一個 Binder 型別,真正的實作類是 System 行程中的 Session,用 AIDL 獲取 System 行程中 Session 的物件,
Session.addToDisplay
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
Rect outStableInsets,
DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
InsetsState outInsetsState, InsetsSourceControl[] outActiveControls) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
outContentInsets, outStableInsets, outDisplayCutout, outInputChannel,
outInsetsState, outActiveControls, UserHandle.getUserId(mUid));
}
return mService.addWindow(...);
其中的 mService 就是 WMS,至此,Window 已經成功的被傳遞給了 WMS,剩下的作業就全部轉移到系統行程中的 WMS 來完成最終的添加操作,
又回到Activity
addView 成功有一個標志就是能夠接收觸屏事件,通過對 setContentView 流程的分析,可以看出添加 View 的操作實質上是 PhoneWindow 在全盤操作,背后負責人是 WMS,反之 Activity 自始至終沒什么參與感,但是我們也知道當觸屏事件發生之后,Touch 事件首先是被傳入到 Activity,然后才被下發到布局中的 ViewGroup 或者 View(Touch事件分發了解一下),那么 Touch 事件是如何傳遞到 Activity 上的呢?
ViewRootImpl 中的 setView 方法中,除了呼叫 IWindowSession 執行跨行程添加 View 之外,還有一項重要的操作就是設定輸入事件的處理:
ViewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
..
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
// 注釋:設定輸入管道,
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
...
}
注釋:設定了一系列的輸入通道,一個觸屏事件的發生是由螢屏發起,然后經過驅動層一系列的優化計算通過 Socket 跨行程通知 Android Framework 層(實際上就是 WMS),最終螢屏的觸摸事件會被發送到代碼中的輸入管道中,
這些輸入管道實際上是一個鏈表結構,當某一個螢屏觸摸事件到達其中的 ViewPostImeInputState 時,會經過 onProcess 來處理,如下所示:
ViewRootImpl.ViewPostImeInputStage
final class ViewPostImeInputStage extends InputStage {
public ViewPostImeInputStage(InputStage next) {
super(next);
}
@Override
protected int onProcess(QueuedInputEvent q) {
if (q.mEvent instanceof KeyEvent) {
return processKeyEvent(q);
} else {
final int source = q.mEvent.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
return processPointerEvent(q);
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
return processTrackballEvent(q);
} else {
return processGenericMotionEvent(q);
}
}
}
private int processPointerEvent(QueuedInputEvent q) {
final MotionEvent event = (MotionEvent)q.mEvent;
...
boolean handled = mView.dispatchPointerEvent(event);
maybeUpdatePointerIcon(event);
maybeUpdateTooltip(event);
mAttachInfo.mHandlingPointerEvent = false;
...
return handled ? FINISH_HANDLED : FORWARD;
}
}
processPointerEvent 在 ViewPostImeInputStage 中別找錯了,可以看到在 onProcess 中最終呼叫了一個 mView的dispatchPointerEvent 方法,mView 實際上就是 PhoneWindow 中的 DecorView,而 dispatchPointerEvent 是被 View.java 實作的,如下所示:
View.dispatchPointerEvent
public final boolean dispatchPointerEvent(MotionEvent event) {
if (event.isTouchEvent()) {
return dispatchTouchEvent(event);
} else {
return dispatchGenericMotionEvent(event);
}
}
DecorView.dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Window.Callback cb = mWindow.getCallback();
return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
最好呼叫了 Window.Callback 中 cb.dispatchTouchEvent(ev) 方法,那這個 Callback 是不是 Activity 呢?
Activity.attach
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) {
...
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
//this:Activity
mWindow.setCallback(this);
...
}
Activity 將自身傳遞給了 PhoneWindow,再接著看 Activity的dispatchTouchEvent,
Activity.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//該方法是用戶互動,每當向Activity分派按鍵、觸摸或軌跡球事件時呼叫,
//在這里僅用于ACTION_DOWN的判斷
onUserInteraction();
}
//回傳true
if (getWindow().superDispatchTouchEvent(ev)) {
//Activity.dispatchTouchEvent()就回傳true,則方法結束,
//該點擊事件停止往下傳遞&事件傳遞程序結束
return true;
}
return onTouchEvent(ev);
}
PhoneWindow.superDispatchTouchEvent
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
DecorView.superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
Touch 事件在 Activity 中只是繞了一圈最后還是回到了 PhoneWindow 中的 DecorView 來處理, 剩下的就是從 DecorView 開始將事件層層傳遞給內部的子 View 中了
ViewGroup. dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
}
到這里就不做多的重復了,感興趣的可以看看事件分發機制,
小結
通過 setContentView 的流程,分析了 Activity、Window、View 之間的關系,整個程序 Activity 表面上參與度比較低,Activity持有Window的物件,View在Window上的增刪等操作又是通過WindowManager來管理的,而WindowManager又是通過Binder機制獲取到的WMS的映射,WMS把View真正顯示到螢屏上,
三者關系:
- 一個 Activity 中有一個 window,也就是 PhoneWindow 物件,在Activity中呼叫attach,創建了一個PhoneWindow,
- 在 PhoneWindow 中有一個 DecorView,Activity在 呼叫setContentView 中會將 layout 填充到此 DecorView 中,
- 一個應用行程中只有一個 WindowManagerGlobal 物件,因為在 ViewRootImpl 中它是 static 靜態型別,
- 每一個 PhoneWindow 對應一個 ViewRootImple 物件,
- WindowMangerGlobal 通過呼叫 ViewRootImpl 的 setView 方法,完成 window 的添加程序,
- 呼叫ViewGroup的removeAllView(),先將所有的view移除掉
- ViewRootImpl 的 setView 方法中主要完成兩件事情:View 渲染(requestLayout)以及接收觸屏事件,
往期回顧
OkHttp使用和原始碼詳解
RecyclerView 繪制流程及Recycler快取
Glide 快取機制及原始碼(二)
Glide 的簡單使用(一)
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/298685.html
標籤:其他
