本文主題
關於WindowManager這個複雜的系統,本文會基於Android9.0源碼,把其中的關鍵代碼截取出來進行分析,並通過問答的形式來進行敘述,最終回答以下幾個問題:
- WindowManager是什麼?它的作用是什麼?
- Window和WindowManager如何關聯?
- Window、WindowManager和WindowManagerService三者有什麼關係?
- Window有哪些類型?
- Window在Activity啓動過程中的作用?
- Window如何處理View的添加、移除和更新?
WindowManager是什麼
The interface that apps use to talk to the window manager.
Each window manager instance is bound to a particular Display. To obtain a WindowManager for a different display, use Context#createDisplayContext to obtain a Context for that display, then use Context.getSystemService(Context.WINDOW_SERVICE) to get the WindowManager.
The simplest way to show a window on another display is to create a Presentation. The presentation will automatically obtain a WindowManager and Context for that display.
用通俗一點的話來講就是:
WindowManager是一個實現了ViewManager的接口,至於它是幹嘛用的,官方文檔並沒有詳細說明,從名字上我們可以知道它和顯示有關,用於管理Window,具體作用還是直接看源碼吧。
如何獲取WindowManager
正如官方文檔所說,我們可以直接通過 Context.getSystemService(Context.WINDOW_SERVICE) 來獲取WindowManager
繼承ViewManager接口
WindowManager繼承ViewManager接口,而ViewManager接口很簡單,只有三個方法
public interface ViewManager
{
/**
* Assign the passed LayoutParams to the passed View and add the view to the window.
* <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
* errors, such as adding a second view to a window without removing the first view.
* <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
* secondary {@link Display} and the specified display can't be found
* (see {@link android.app.Presentation}).
* @param view The view to be added to this window.
* @param params The LayoutParams to assign to view.
*/
// 添加View
public void addView(View view, ViewGroup.LayoutParams params);
// 更新View
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
// 移除View
public void removeView(View view);
}
定義了許多Flags
在WindowManager.LayoutParams裏面有許多Flags,這些Flag的作用就是在創建Window的時候用於區分這個Window到底是什麼類型的,關於Window類型的問題我們會在後面再詳細說明。
下面列舉了一小部分Flags
public static final int TYPE_BASE_APPLICATION = 1;
public static final int TYPE_APPLICATION = 2;
public static final int TYPE_APPLICATION_STARTING = 3;
public static final int TYPE_DRAWN_APPLICATION = 4;
public static final int LAST_APPLICATION_WINDOW = 99;
public static final int FIRST_SUB_WINDOW = 1000;
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
// 後面省略N個Flag
通過int類型的Type,我們可以區分不同的Window類型。
大家也可以直接跳到第四個問題查看:Window有哪些類型?
回答:WindowManager是什麼
WindowManager負責的事情其實並不多,主要完成一些配置工作(定義Window類型的Flags,以及LayoutParams靜態內部類),具體的跨進程通信還是要看WindowManagerService,而對View的操作則是通過WindowManagerGlobal來進行。
Window和WindowManager如何關聯
說了這麼多,分析了一通WindowManager這個類的源碼,我們只知道它是一個接口,但是還不知道它具體是怎麼使用的。別急,接下來就輪到Window登場了。
關聯的關鍵:Window.java
我們在Window.java找到以下代碼:
/**
* Set the window manager for use by this Window to, for example,
* display panels. This is <em>not</em> used for displaying the
* Window itself -- that must be done by the client.
*
* @param wm The window manager for adding new windows.
*/
public void setWindowManager(WindowManager wm, IBinder appToken, String appName) {
setWindowManager(wm, appToken, appName, false);
}
/**
* Set the window manager for use by this Window to, for example,
* display panels. This is <em>not</em> used for displaying the
* Window itself -- that must be done by the client.
*
* @param wm The window manager for adding new windows.
*/
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {
mAppToken = appToken;
mAppName = appName;
mHardwareAccelerated = hardwareAccelerated;
if (wm == null) {
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
// 通過WindowManagerImpl創建WindowManager對象,並賦值給mWindowManager
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
這兩個setWindowManager()方法分別在Activity和Dialog中被調用了。說明在Activity創建,Toast顯示的過程中都需要用到WindowManager。
注意:mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
這一行代碼就是Window和WindowManager產生化學反應的關鍵!
首先需要看看WindowManagerImpl是什麼
WM的實現類:WindowManagerImpl.java
WindowManagerImpl.java
public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
return new WindowManagerImpl(mContext, parentWindow);
}
其實Window.java裏面調用的這個方法,就是創建一個Impl對象。
在WindowManagerImpl中,我們會看到有個成員變量
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
這個WindowManagerGlobal 正是用於對View進行操作的實際對象。
無論是addView(), updateView(), removeView(),都是通過這個對象進行操作的。
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// 調用WindowManagerGlobal 的addView()方法
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@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);
}
@Override
public void removeViewImmediate(View view) {
// 同理
mGlobal.removeView(view, true);
}
關於WindowManagerGlobal 怎麼處理View的邏輯,我們在後面會繼續解析。這裏還是先回到Window和WindowManager這兩者是如何關聯這個問題上來。
如果大家有細心留意的話就能看到,WindowManagerImpl的構造方法裏面有一個parentWindow 的參數,這個參數的類型是Window,也就是說,當我們在Activity或者Dialog調用setWindowManager()的時候,就會把當前Window作爲參數傳遞過來,在創建WindowManager的同時把這兩者給關聯起來。
回答:Window和WindowManager如何關聯
在創建Activity或者Dialog的時候會調用Window.setWindowManager()方法,然後把當前Window作爲參數傳遞到WindowManagerImpl的createLocalWindowManager()方法中,在創建WindowManager對象的時候把Window關聯起來。
Window、WindowManager和WindowManagerService三者有什麼關係
通過前面的分析,我們知道Window和WindowManager是如何關聯起來的,然鵝到目前爲止,我們還是不知道在Framework層的Window是如何同Native層的WindowManagerService進行通信的,讓我們繼續看源碼。
上面說到,具體對View的操作實際是WindowManagerGlobal這個類來做的,那我們看看addView()方法裏做了什麼:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省略代碼
synchronized (mLock) {
// 繼續省略代碼
// 構建ViewRootImpl對象
root = new ViewRootImpl(view.getContext(), display);
// 設置參數
view.setLayoutParams(wparams);
// 把view,root, param添加到對應的list中
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
// 調用setView方法顯示View
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
這裏主要做了幾件事:
- 構建ViewRootImpl對象
- 設置參數
- 添加到list中
- 把View顯示出來
我們一個個來看,首先是構建ViewRootImpl對象
public ViewRootImpl(Context context, Display display) {
mContext = context;
mWindowSession = WindowManagerGlobal.getWindowSession();
mDisplay = display;
mBasePackageName = context.getBasePackageName();
mThread = Thread.currentThread();
mLocation = new WindowLeaked(null);
mLocation.fillInStackTrace();
// 省略代碼
}
這裏初始化了許多的成員變量,其中有一個是mWindowSession
,我們進去看看
@UnsupportedAppUsage
public static IWindowSession getWindowSession() {
// 同步代碼塊,保證線程安全
synchronized (WindowManagerGlobal.class) {
if (sWindowSession == null) {
try {
// Emulate the legacy behavior. The global instance of InputMethodManager
// was instantiated here.
// TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsage
InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();
// 獲取WindowManagerService對象
IWindowManager windowManager = getWindowManagerService();
// 建立一個Session
sWindowSession = windowManager.openSession(
new IWindowSessionCallback.Stub() {
@Override
public void onAnimatorScaleChanged(float scale) {
ValueAnimator.setDurationScale(scale);
}
});
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowSession;
}
}
看一下WindowManager是如何獲取WMS對象的:
@UnsupportedAppUsage
public static IWindowManager getWindowManagerService() {
synchronized (WindowManagerGlobal.class) {
if (sWindowManagerService == null) {
sWindowManagerService = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"));
try {
if (sWindowManagerService != null) {
ValueAnimator.setDurationScale(
sWindowManagerService.getCurrentAnimatorScale());
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return sWindowManagerService;
}
}
通過ServiceManager.getService(“window”)獲取到WMS,然後再轉爲IWindowManager,那麼在getService()方法中做了什麼呢?
public static IBinder getService(String name) {
try {
IBinder service = sCache.get(name);
if (service != null) {
return service;
} else {
return Binder.allowBlocking(rawGetService(name));
}
} catch (RemoteException e) {
Log.e(TAG, "error in getService", e);
}
return null;
}
可以看到getService()方法返回的是IBinder對象。到這裏我們就真正拿到了WMS了,也知道了Framework層與Native底層其實都是通過Binder機制進行通信。
這裏比較繞,我們按照流程來捋一遍:首先在ViewRootImpl構建過程中,我們需要初始化IWindowSession對象,因此在getWindowSession() -> getWindowManagerService() -> getService()中從緩存列表(HashMap)獲取WMS,然後通過asInterface函數轉爲WindowManager對象,最後通過openSession()與WMS建立會話,也就是在Framework層和Native層之間建立了連接。
回答:Window,WM和WMS有什麼關係
經過前面三個問題的分析,我們應該有比較清晰的脈絡了,對於這三者,Window和WM有關聯(通過Activity和Dialog,忘記了可以回頭看),WM和WMS有關聯(通過WM的實現類WindowManagerImp的小弟WindowManagerGlobal)
因此,在Activity或者Dialog創建的時候,其實這三者就已經創建並且相互關聯起來了。
我們還沒說View到底是怎麼顯示出來的,這個問題留到最後一步再來解決。
Window有哪些類型
我們現在來填第一個問題時候埋下的坑,關於Window有哪些類型,其實就三種
- System Window
- Sub Window
- Application Window
System Window(系統窗口)
常見的例如Toast,輸入法,系統彈出框等等,這部分窗口我們是沒有權限創建的。
還記得上面我們列舉了一小部分的Flags嗎?System Window的type範圍是2000以上,下面列舉一部分
WindowManager.java:
//系統窗口
public static final int FIRST_SYSTEM_WINDOW = 2000;
//狀態欄
public static final int TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW;
// 搜索欄
public static final int TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1;
// 來電窗口
@Deprecated
public static final int TYPE_PHONE = FIRST_SYSTEM_WINDOW+2;
// 系統提示
@Deprecated
public static final int TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3;
Sub Window(子窗口)
所謂子窗口,則是指這個窗口還要有一個父窗口,例如PopupWindow
Sub Window的範圍是是1000~1999,由於Sub Window比較少,我就全部列出來了
WindowManager.java
// 子窗口
public static final int FIRST_SUB_WINDOW = 1000;
// 面板窗口
public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
// 媒體窗口
public static final int TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1;
// 應用程序窗口子面板
public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2;
child of its container.
// 對話框
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
// 媒體信息
@UnsupportedAppUsage
public static final int TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4;
// 應用程序窗口頂層子面板
public static final int TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5;
// 結束子窗口
public static final int LAST_SUB_WINDOW = 1999;
Application Window(應用程序窗口)
常見的例如Activity,由於比較少,我也全部列舉出來了:
WindowManager.java
// 開始應用程序窗口
public static final int FIRST_APPLICATION_WINDOW = 1;
// 程序窗口的base窗口,其他窗口都在它之上
public static final int TYPE_BASE_APPLICATION = 1;
// 普通應用程序窗口
public static final int TYPE_APPLICATION = 2;
// 程序啓動窗口
public static final int TYPE_APPLICATION_STARTING = 3;
// 普通應用程序窗口的一種變體,顯示應用程序之前等待時的窗口
public static final int TYPE_DRAWN_APPLICATION = 4;
// 結束程序窗口
public static final int LAST_APPLICATION_WINDOW = 99;
回答:Window有哪些類型
三種,分別是系統窗口,子窗口,應用程序窗口,根據Type大小,系統窗口>子窗口>應用窗口,因此係統窗口在最上層,優先級最高。
WindowManager在Activity啓動過程中的作用
Activity的attach()方法
關於Activity啓動過程我們先忽略,只瞭解與Window/WindowManager相關的源碼
在Activity.attach() 方法中,我們找到了WindowManager的身影
@UnsupportedAppUsage
final void attach(Context context, ActivityThread aThread,
// 忽略代碼...
// 創建Window
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
// 創建WindowManager
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);
setAutofillOptions(application.getAutofillOptions());
setContentCaptureOptions(application.getContentCaptureOptions());
}
在attach()方法中,Activity會創建一個Window,然後setWindowManager() 我們在上面已經分析過了,其實就是創建了一個WindowManagerImpl類,並把Window和WM關聯了起來。
接下來在Activity的onCreate()方法中,我們會調用setContentView()方法:
AppCompatActivity.java
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
繼續進入AppCompatDelegate.java,這是一個抽象類,沒有實現具體方法,我們直接看他的子類AppCompatDelegateImpl.java
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
// 主要關注這個方法
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
@Override
public void setContentView(View v, ViewGroup.LayoutParams lp) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v, lp);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
這裏提供了三個setContentView的重載方法,我們只看第二個,也就是傳入layoutId的這個方法:
ensureSubDecor();
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor(); // 創建Decor
// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
if (mDecorContentParent != null) {
mDecorContentParent.setWindowTitle(title);
} else if (peekSupportActionBar() != null) {
peekSupportActionBar().setWindowTitle(title);
} else if (mTitleView != null) {
mTitleView.setText(title);
}
}
applyFixedSizeWindow();
onSubDecorInstalled(mSubDecor);
mSubDecorInstalled = true;
// Invalidate if the panel menu hasn't been created before this.
// Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
// being called in the middle of onCreate or similar.
// A pending invalidation will typically be resolved before the posted message
// would run normally in order to satisfy instance state restoration.
PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
if (!mIsDestroyed && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
createSubDecor()
private ViewGroup createSubDecor() {
// 忽略代碼...
// 獲取Window
ensureWindow();
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
// 忽略代碼...
// 調用PhoneWindow的setContentView方法
mWindow.setContentView(subDecor);
contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});
return subDecor;
這個方法主要做了兩個事情
- 獲取Window,保證mWindow對象不爲空
- 獲取到Window對象後再調用setContentView()方法
我們先看第一步:
private void ensureWindow() {
// We lazily fetch the Window for Activities, to allow DayNight to apply in
// attachBaseContext
if (mWindow == null && mHost instanceof Activity) {
// 調用Activity的getWindow()方法
attachToWindow(((Activity) mHost).getWindow());
}
if (mWindow == null) {
throw new IllegalStateException("We have not been given a Window");
}
}
// 把上面獲取到的window對象賦值給mWindow
private void attachToWindow(@NonNull Window window) {
if (mWindow != null) {
throw new IllegalStateException(
"AppCompat has already installed itself into the Window");
}
final Window.Callback callback = window.getCallback();
if (callback instanceof AppCompatWindowCallback) {
throw new IllegalStateException(
"AppCompat has already installed itself into the Window");
}
mAppCompatWindowCallback = new AppCompatWindowCallback(callback);
// Now install the new callback
window.setCallback(mAppCompatWindowCallback);
final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
mContext, null, sWindowBackgroundStyleable);
final Drawable winBg = a.getDrawableIfKnown(0);
if (winBg != null) {
// Now set the background drawable
window.setBackgroundDrawable(winBg);
}
a.recycle();
mWindow = window;
}
還記得在這個問題的開頭,Activity的attach()方法中做了什麼嗎?
沒錯,我們初始化了一個PhoneWindow對象,並賦值爲了mWindow!因此在這裏拿到的Window對象自然也是PhoneWindow了!
因此第二步調用的setContentView()方法,也就是:PhoneWindow.setContentView()了。我們繼續往下看
PhoneWindow的setContentView()方法
PhoneWindow.java
@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.創建DecorView,以及DecorView中的mContentParent 佈局
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,將layoutResID佈局加載到mContentParent和上
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
//3通知視圖改變回調
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
我們看一下installDecor()
做了什麼
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 創建Decor
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) {
// 生成一個Layout
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
R.id.decor_content_parent);
if (decorContentParent != null) {
mDecorContentParent = decorContentParent;
mDecorContentParent.setWindowCallback(getCallback());
if (mDecorContentParent.getTitle() == null) {
mDecorContentParent.setWindowTitle(mTitle);
}
final int localFeatures = getLocalFeatures();
for (int i = 0; i < FEATURE_MAX; i++) {
if ((localFeatures & (1 << i)) != 0) {
mDecorContentParent.initFeature(i);
}
}
}
具體的源碼就不再仔細分析了,在這裏系統主要做了:創建Decor,然後根據Window的Flag, Theme等配置創建一個佈局,並且添加到Decor中。
至此,Activity已經成功的創建了WindowManager, 創建了Decor,創建了相應的佈局,還差最後一步:把這個佈局添加到Window中並顯示出來。
讓我們回到最初的起點,也就是ActivityThread,在這裏我們能看到有個方法:handleResumeActivity()並找到其中一段
// 設置activity爲可見狀態
if (r.activity.mVisibleFromClient) {
r.activity.makeVisible();
}
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
// 注意這個調用,正是因爲它用戶才能看到activity的界面內容
mDecor.setVisibility(View.VISIBLE);
}
在這裏系統又做了三件事情:
- 獲取WindowManager
- 把剛剛創建的Decor對象添加到WM中
- 設置Decor爲可見狀態
最終Activity才能正常顯示在用戶的面前。
回答:WindowManager在Activity啓動過程中的作用
首先ActivityThread.attach()方法會創建Window和WindowManager
然後在onCreate()方法中會調用創建的PhoneWindow的setContentView()方法,接着在裏面創建Decor以及一個根佈局,創建完畢後把layoutResId加載到佈局中,然後通知回調
最後調用ActivityThead.handleResumeActivity()方法,在這裏把WindowManager和Decor關聯起來,並且調用setVisibility讓Decor可見,最終把UI呈現在用戶面前。
WindowManager如何處理View的添加、移除和更新
這一個問題其實是上一個問題的延伸和擴展,雖然在上面我們說到了ActivityThread()調用handleResumeActivity()方法,然後把Decor通過addView()的方式加入到WindowManager中,但是我們有沒有想過,這個addView()的過程有涉及到哪些模塊呢?
正是因爲這一過程比較複雜,因此也值得單獨提出來探究。
addView() 過程
還記得WindowManager.addView() 實際調用的是哪個類的方法嗎?忘了的請回去重新看一遍第二問~
WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 省略N行代碼...
try {
// 調用ViewRootImpl.setView方法
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
RootViewImpl.setView()
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// 1
requestLayout();
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
mInputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// 2
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
mTempInsets);
setFrame(mTmpFrame);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
} finally {
if (restore) {
attrs.restore();
}
}
}
}
RootViewImpl.setView()方法很複雜,其中我們需要關注的是兩點:
- requestLayout()
- mWindowSession.addToDisplay()
我們先看一下requestLayout()方法:
// 定義 TraversalRunnable
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
// 初始化 mTraversalRunnable
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread(); // 檢查鎖
mLayoutRequested = true;
scheduleTraversals(); // 發送 CALLBACK_TRAVERSAL 消息
}
}
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 向 mTraversalRunnable 發送一條 CALLBACK_TRAVERSAL 消息
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
往Handler裏面發送一條CALLBACK_TRAVERSAL消息,這條消息的意思就是刷新界面。
最終會調用 doTraversal() 方法
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals(); //1
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
然後到 performTraversals() 方法, 這個方法非常複雜,整個方法加起來大概有800多行,主要工作就是
- 測量各個View的大小(performMeasure)
- 佈局(performLayout)
- 繪製(performDraw)
最終把整個視圖樹展示出來
updateViewLayout() 過程
update過程比較簡單,直接上源碼:
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;
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index); // 1
mParams.remove(index); // 2
mParams.add(index, wparams); // 3
root.setLayoutParams(wparams, false); // 4
}
}
- 首先獲取要update的View的ViewRootImpl
- 把這個View的LayoutParam移除掉
- 重新添加LayoutParam
- 刷新根佈局
removeView() 過程
@UnsupportedAppUsage
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); // 1
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
// 移除對應的View
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = view.getContext().getSystemService(InputMethodManager.class);
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate); // 2
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
boolean die(boolean immediate) {
if (immediate && !mIsInTraversal) {
doDie(); // 3
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE); // 5
return true;
}
void doDie() {
checkThread();
if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
synchronized (this) {
if (mRemoved) {
return;
}
// 省略代碼...
WindowManagerGlobal.getInstance().doRemoveView(this); //4
- 首先通過removeView()方法來移除View,在這個方法裏面調用了removeViewLocked()
- 在removeViewLocked()又調用了 root.die(immediate)
- 在die() 又調用了 doDie()
- 在doDie()中調用了 doRemoveView()
- 如果需要延遲,則再發送一條MSG_DIE,重新調用doDie()方法
doRemoveView()
void doRemoveView(ViewRootImpl root) {
synchronized (mLock) {
final int index = mRoots.indexOf(root);
if (index >= 0) {
mRoots.remove(index);
mParams.remove(index);
final View view = mViews.remove(index);
mDyingViews.remove(view);
}
}
if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
doTrimForeground();
}
這個方法和上面的update方法類似,從緩存列表中找到對應的view,然後移除掉。
總結
在這篇文章中,我們從Acitivty的創建過程說起,涉及到ActivityThread, Window, WindowManager, WindowManagerService, RootViewImpl, PhoneWindow, WindowManagerImpl, WindowManagerGlobal 這麼多類的相關源碼分析,希望大家看完之後能對WindowManager相關的知識點有所瞭解。
如果文章有不對的地方也歡迎大家批評指正,感謝閱讀!