1.前言
我覺得要弄清楚事件分發前,還是要先大致了解一下幾個概念。
Window,WindowManager,WindowMangerService,ViewRoot,DecorView他們之間是如何協同工作的。
2.Activity和Window的關系
我們知道Activity是支持顯示UI的,那么它是否直接管理view樹或者ViewRoot呢?答案時否定的,Activity并沒有與這兩者產生直接的聯系,因為這中間還有一個被稱為“Window”的對象。大家可以在Activity的源碼中找到如下代碼:
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
...
private Window window;
Window的字面意思是"窗口",這很好地解釋了它存在的意義。Window是基類,根據不同的產品可以衍生出不同的子類——具體則是由系統在Activity.attach中調用PolicyManager.makeNewWindow決定的,目前版本的Android系統默認生成的都是PhoneWindow。
3.Window和WindowManagerImpl的關系
在Android源碼中以“Window”開頭的類有不少,如Window,WindowManager,WindowManagerImpl等,為什么需要這么多相似的類呢?
先來看Window,它是面向Activity的,表示"UI界面的外框";而“框里面”具體的東西包括布局和內容等,是由具體的Window子類,如PhoneWindow來規劃的。
Window的另一層含義是要與WindowManagerService進行通信,但它并沒有直接在自身實現這一功能。原因是:一個應用程序中很可能存在多個Window。如果它們都單獨與WMS通信,那么既浪費資源,又會造成管理的混亂。換句話說,它們需要統一的管理。于是就有了WindowManager,它作為Window的成員變量mWindowManager存在。這個WindowManager是一個接口類,其真正的實現是WindowManagerImpl,后者同時也是整個應用程序中所有Window的管理者。因而WindowManager與WindowManagerImpl的關系有點類似于“地方與中央”:地方為實施中央的“政策”提供了一個接口,然后匯總到中央進行管理。
在Window的源碼中與mWindowMager有關的代碼有如下幾句:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated
|| SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
public WindowManager getWindowManager() {
return mWindowManager;
}
然后我們去WindowManagerImpl的代碼中去查看createLocalWindowManager方法的代碼,代碼如下:
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mDisplay, parentWindow);
}
從這幾處代碼,大家可以看到Window類中的mWindowManger引用的其實是WindowManagerImpl的實例。
4.ViewRoot和WindowManagerImpl的關系
由于每個Activity都有自己的窗口“Window”,每個Window都有一個WindowManagerImpl實例的引用。即每個Activity都對應一個WindowManagerImpl。
在早期的版本中在WindowMangerImpl內部,存在3個全局變量:
public final class WindowManagerImpl implements WindowManager {
private [View] [] mViews;//樹的根節點
private [ViewRoot][] mRoots;//ViewRoot
private [WindowManager][] mParams;//Window的屬性
由此也可以看出,一個進程中不僅有一個ViewRoot;而Activity與ViewRoot則是一對一的關系。
自Android4.3開始對此做了修改,WindowManagerImpl不再直接存儲上述三個數組變量,而是由一個稱為“WindowMangerGlobal”的類統一管理。
public final class WindowManagerImpl implements WindowManager {
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Display mDisplay;
private final Window mParentWindow;
public final class WindowManagerGlobal {
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>();
5.DecorView與ViewRootImpl
從setContentView說起
一般地,我們在Activity中,會在onCreate()方法中寫下這樣一句:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
顯然,這是為activity設置一個我們定義好的main.xml布局,我們跟蹤一下源碼,看看這個方法是怎樣做的,Activity#setContentView:
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
以上代碼我們可以看出,Activity的setContentView,其實是調用的window的setContentView方法。上面我們了解到Window是基類,我們要找到它的實現類。
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對象
mWindow = PolicyManager.makeNewWindow(this);
...
}
我們只看關鍵部分,在Activity里面有一個attach方法,在這個方法中創建了Window對象,下面看PolicyManager里面的makeNewWindow方法。
public final class PolicyManager {
......
private static final IPolicy sPolicy;
public static Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}
}
在這個代碼里面調用的是IPolicy里面的makeNewWindow方法,一看IPolicy就知道它是一個接口,真正的實現是在它的實現類Policy中完成的,直接看代碼。
public class Policy implements IPolicy {
......
public Window makeNewWindow(Context context) {
//看到沒有,是一個PhoneWindow對象
return new PhoneWindow(context);
}
......
}
這就看到了創建的是一個PhoneWindow對象,也就是說Activity里面的Window屬性引用的就是PhoneWindow對象,調用Window的方法實質調用的就是PhoneWindow的方法,都在這里實現了。
總結:
1.在啟動Activity的過程中會調用一個attach函數
2.在attach函數中會執行PolicyManager.makeNewWindow(this)
3.進一步深入,真正執行makeNewWindow的其實是IPolicy,它只是被 PolicyManager進行了包裝。
4.IPolicy是一個接口,所以makeNewWindow的執行是由它的實現類Policy來執行的。
5.可以看到執行創建了一個PhoneWindow對象并且返回,最終也是將這個對象賦值給mWindow。
至此我們知道PhoneWindow是Window的實現類,那么我們在PhoneWindow類里面找到它的setContentView方法,看看它又實現了什么,PhoneWindow#setContentView:
@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) {//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 {
mLayoutInflater.inflate(layoutResID, mContentParent);//2
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
首先判斷了mContentParent是否為null,如果為空則執行installDecor()方法,那么這個mContentParent又是什么呢?我們看一下它的注釋:
// This is the view in which the window contents are placed. It is either
// mDecor itself, or a child of mDecor where the contents go.
private ViewGroup mContentParent;
它是一個ViewGroup類型,結合2號代碼處,可以得知,這個mContentParent是我們設置的布局(即main.xml)的父布局。注釋還提到了,這個mContentParent是mDecor本身或者是mDecor的一個子元素
這里先梳理一下以上的內容:Activity通過PhoneWindow的setContentView方法來設置布局,而設置布局之前,會先判斷是否存在mContentParent,而我們設置的布局文件則是mContentParent的子元素。
DecorView的創建
我們接著看下 installDecor();的源碼 PhoneWindow#installDecor:
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
首先,如果mDecor為空的時候會執行generateDecor()代碼,調用PhoneWindow#generateDecor方法:
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
可以看出,這里實例化了DecorView,在源碼中我們看到DecorView是PhoneWindow的內部類,
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
它繼承的是FrameLayout,由此可知它也是一個ViewGroup。
那么,DecroView到底充當了什么樣的角色呢?
其實,DecorView是整個ViewTree的最頂層View,它是一個FrameLayout布局,代表了整個應用的界面。在該布局下面,有標題view和內容view這兩個子元素,而內容view則是上面提到的mContentParent。我們接著看generateLayout(mDecor)代碼,PhoneWindow#generateLayout方法
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);
}
...
if (...) {
...
}
....
// Inflate the window decor.
// 加載窗口布局
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if (...) {
....
}
mDecor.startChanging();
//加載layoutResource
View in = mLayoutInflater.inflate(layoutResource, null);
//往DecorView中添加子View,即mContentParent
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
// 這里獲取的就是mContentParent
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
ProgressBar progress = getCircularProgressBar(false);
if (progress != null) {
progress.setIndeterminate(true);
}
}
if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
registerSwipeCallbacks();
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
...
mDecor.finishChanging();
return contentParent;
}
由以上代碼可以看出,該方法還是做了相當多的工作的,首先根據設置的主題樣式來設置DecorView的風格,比如說有沒有titlebar之類的,接著為DecorView添加子View,而這里的子View則是上面提到的mContentParent,如果上面設置了FEATURE_NO_ACTIONBAR,那么DecorView就只有mContentParent一個子View,
小結:DecorView是頂級View,內部有titlebar和contentParent兩個子元素,contentParent的id是content,而我們設置的main.xml布局則是contentParent里面的一個子元素。
在DecorView創建完畢后,讓我們回到PhoneWindow#setContentView方法,直接看代碼: mLayoutInflater.inflate(layoutResID, mContentParent);這里加載了我們設置的main.xml布局文件,并且設置mContentParent為main.xml的父布局,至于它怎么加載的,這里就不展開來說了。
到目前為止,通過setContentView方法,創建了DecorView和加載了我們提供的布局,但是這時,我們的View還是不可見的,因為我們僅僅是加載了布局,并沒有對View進行任何的測量、布局、繪制工作。在View進行測量流程之前,還要進行一個步驟,那就是把DecorView添加至window中,然后經過一系列過程觸發ViewRootImpl#performTraversals方法,在該方法內部會正式開始測量、布局、繪制這三大流程。至于該一系列過程是怎樣的,因為涉及到了很多機制,這里簡單說明一下:
將DecorView添加至Window
每一個Activity組件都有一個關聯的Window對象,用來描述一個應用程序窗口。每一個應用程序窗口內部又包含有一個View對象,用來描述應用程序窗口的視圖。上文分析了創建DecorView的過程,現在則要把DecorView添加到Window對象中。而要了解這個過程,我們首先要簡單先了解一下Activity的創建過程:
首先,在ActivityThread#handleLaunchActivity中啟動Activity,在這里面會調用到Activity#onCreate方法,從而完成上面所述的DecorView創建動作,當onCreate()方法執行完畢,在handleLaunchActivity方法會繼續調用到ActivityThread#handleResumeActivity方法,我們看看這個方法的源碼:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
...
// TODO Push resumeArgs into the activity for consideration
// 這里會調用到Activity的onResume()方法
ActivityClientRecord r = performResumeActivity(token, clearHide);
...
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 (a.mVisibleFromClient) {
a.mWindowAdded = true;
wm.addView(decor, l);// 調用addView方法
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
...
}
}
在該方法內部,獲取該activity所關聯的window對象,DecorView對象,以及windowManager對象,而WindowManager是抽象類,它的實現類是WindowManagerImpl,所以后面調用的是WindowManagerImpl#addView方法,我們看看源碼:
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, mDisplay, mParentWindow);
}
接著調用了mGlobal的成員函數,而mGlobal則是WindowManagerGlobal的一個實例,那么我們接著看WindowManagerGlobal#addView方法:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
...
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
...
root = new ViewRootImpl(view.getContext(), display);//1
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);//2
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
synchronized (mLock) {
final int index = findViewLocked(view, false);
if (index >= 0) {
removeViewLocked(index, true);
}
}
throw e;
}
}
先看1號代碼處,實例化了ViewRootImpl類,接著,在2號代碼處,調用ViewRootImpl#setView方法,并把DecorView作為參數傳遞進去,在這個方法內部,會通過跨進程的方式向WMS(WindowManagerService)發起一個調用,從而將DecorView最終添加到Window上,在這個過程中,ViewRootImpl、DecorView和WMS會彼此關聯,最終通過WMS調用ViewRootImpl#performTraverals方法開始View的測量、布局、繪制流程
總結:
Window,它是"UI界面的外框",而里面具體內容是有其子類,如PhoneWindow來規劃的。
WindowManager,是統一管理Window而誕生的。其實現是WindowManagerImpl
WindowManagerImpl,直接或間接的存儲DecorView,ViewRoot,WindowManager;
DecorView,是整個ViewTree的最頂層View,它是一個FrameLayout布局,代表了整個應用的界面,我們setContentView添加的視圖是被mContentParent"包裹"著添加到DecorView 中的。
ViewRoot是GUI管理系統與GUI呈現系統之間的橋梁,根據ViewRoot的定義,我們發現它并不是一個View類型,而是一個Handler。
它的主要作用如下:
- 向DecorView分發收到的用戶發起的event事件,如按鍵,觸屏,軌跡球等事件;
-
與WindowManagerService交互,完成整個Activity的GUI的繪制。
通訊.png