什麼是戰略性瞭解:知其然,無需知其所以然,也就是知道整體結構框架即可,不追根內部具體實現。
注:文章前面會探索API 14的源碼,後面會探索API 28的源碼
WindowManager直譯過來叫做窗口管理者,用了這麼多年Window的我們,應該對窗口這個概念並不陌生,打開應用,彈出應用窗口,使用應用。電腦屏幕大,所以我們可以在一個電腦屏幕上看到多個應用,手機屏幕小,只看一個應用的體驗比較好。
如果是我們要寫一個類叫做WindowManager,我們會寫哪些方法呢?每個應用都給他分配一個WindowManager,然後添加界面,刪除界面,更新界面,差不多就這些方法吧,等有需求了再拓展嘛,嗯嗯,大概是這個邏輯。看看實際是怎麼實現的吧。
ViewManager探索
根據源碼,我們很輕易的發現WindowManager其實是一個接口,並且繼承ViewManager,ViewManager也是個接口。
public interface WindowManager extends ViewManager
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);
}
添加View,更新View佈局,移除View,方法名字倒是一目瞭然的,但是爲啥沒有deleteView,難道Android窗口這裏不會刪除View嗎,感覺移除操作很簡單,只需要把View設置爲不可見就行了,也不知道內部怎麼實現的,根據方法名字猜想一波吧。
WindowManager探索
我們還是來看看繼承了ViewManager的WindowManager,看看他又實現了啥,最開始猜想的添加刪除view居然是
ViewManager的方法,現在都不知道WindowManager這個類是幹嘛的了,看波源碼再做打算。
public interface WindowManager extends ViewManager {
public static class BadTokenException extends RuntimeException {
public BadTokenException() {}
public BadTokenException(String name) {
super(name);
}
}
public Display getDefaultDisplay();
public void removeViewImmediate(View view);
public boolean isHardwareAccelerated();
public static class LayoutParams extends ViewGroup.LayoutParams
implements Parcelable{
// 各種屬性
}
}
內部居然有個異常類,倒是挺簡單;還有個靜態公共類,是我們熟悉的LayoutParams,原來是在這裏定義的。
接着是三個待實現的接口方法,我們來猜猜是幹嘛的:
// 獲取默認Display實例
public Display getDefaultDisplay();
// 立刻移除View
public void removeViewImmediate(View view);
// 是否使用硬件加速
public boolean isHardwareAccelerated();
猜測完畢,源碼看到這裏,下面兩個方法倒是還是好理解,第一個方法不知道Display是啥東西,感覺是跟屏幕有點關係,看看源碼大部分都是一些get的方法,聽着都像是獲取屏幕都信息:
// 隨便摘要一些方法
public class Display {
...
public void getSize(Point outSize){...}
public int getDisplayId(){...}
public int getWidth(){...}
public int getHeight(){...}
public void getRealSize(Point outSize){...}
...
}
大概瞭解一些方法是幹嘛的,不求甚解。確實,主要的一些方法還是跟屏幕有關係。
WindowManagerImpl探索
既然大概知道了WindowManager的結構,那麼來了解一下WindowManager的子類吧。
WindowManager的子類有這麼幾個,除去Test旗下的類,在剩下的類中,有一個類帶着主角臉,那就是WindowManagerImpl,既然這個類自帶主角臉,那麼我們主要就來探索一下這個類。
進去這個類之後,發現這個類只有500多行的代碼,難得源碼才500多行,好好研究一下。既然繼承了WindowManager和ViewManager接口,不然先看看WindowManagerImpl對這幾個方法具體這麼實現的。
ViewManager方法的具體實現
首先看看ViewManager的那三個方法:
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
第一個是addView方法是吧。那就來吧:
public void addView(View view) {
addView(view, new WindowManager.LayoutParams(
WindowManager.LayoutParams.TYPE_APPLICATION, 0, PixelFormat.OPAQUE));
}
public void addView(View view, ViewGroup.LayoutParams params) {
addView(view, params, null, false);
}
public void addView(View view, ViewGroup.LayoutParams params, CompatibilityInfoHolder cih) {
addView(view, params, cih, false);
}
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
...
}
沒想到居然有這麼多addView方法,而且又是以這種形式調用的,最終還是會調用最下面的這個方法,那麼我們就來具體看看最下面的這個方法是怎麼實現的吧。
爲了便於閱讀,我將addView方法的內部劃分成了三個部分,我們一個一個的來看,大事化小。(注:這三個部分合起來並非addView所有源碼,我只是挑出了主要代碼)
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
// 第一部分
// 第二部分
// 第三部分
}
我們首先來看第一部分的源碼:
private View[] mViews;
private ViewRootImpl[] mRoots;
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
synchronized (this) {
int index = findViewLocked(view, false);
if (index >= 0) {
if (!nest) {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
root = mRoots[index];
root.mAddNesting++;
// Update layout parameters.
view.setLayoutParams(wparams);
root.setLayoutParams(wparams, true);
return;
}
// 第二部分
// 第三部分
}
root.setView(view, wparams, panelParentView);
}
這些代碼看着並非那麼晦澀難懂,雖然不知道findViewLocked這個方法是幹嘛的,但是我們可以繼續看下去,畢竟就兩個if而已,我們看到第二個if拋了個異常,條件是!nest
,但是根據前面看到的addView方法的一系列的引用,好像此處的nest變量爲false,那麼這個!nest
就一定爲true了,所以這個異常一定會拋出來,我們還是看看第一個if的index>=0
在什麼情況下進去吧,再往上看就是findViewLocked
方法了,姑且進去一探究竟。
private View[] mViews;
private int findViewLocked(View view, boolean required) {
synchronized (this) {
final int count = mViews != null ? mViews.length : 0;
for (int i = 0; i < count; i++) {
if (mViews[i] == view) {
return i;
}
}
if (required) {
throw new IllegalArgumentException(
"View not attached to window manager");
}
return -1;
}
}
看來WindowManagerImpl是用View[]來儲存view的,這個方法是用來檢查WindowManagerImpl是否已經擁有這個view了。
我們再回到addView方法:
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
synchronized (this) {
int index = findViewLocked(view, false);
if (index >= 0) {
if (!nest) {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
}
// 第二部分
// 第三部分
}
}
再看這個if判斷好像就很合理了,如果該view已經被添加了,就拋出異常。
我們接着來看第二部分的代碼:
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
if (false) Log.v("WindowManager", "addView view=" + view);
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException(
"Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
synchronized (this) {
// 第一部分
if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
final int count = mViews != null ? mViews.length : 0;
for (int i = 0; i < count; i++) {
if (mRoots[i].mWindow.asBinder() == wparams.token) {
panelParentView = mViews[i];
}
}
}
root = new ViewRootImpl(view.getContext());
root.mAddNesting = 1;
if (cih == null) {
root.mCompatibilityInfo = new CompatibilityInfoHolder();
} else {
root.mCompatibilityInfo = cih;
}
view.setLayoutParams(wparams);
// 第三部分
}
root.setView(view, wparams, panelParentView);
}
第二部分的代碼,第一個判斷涉及binder,不在我們的討論範圍,我們就暫且忽視。(一旦討論會涉及更多其他類的源碼,還有AIDL類,涉及源碼太多,就此打住)
接下來就涉及ViewRootImpl,所以第二部分的代碼就此跳過吧,我們來看看第三部分。
private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
if (false) Log.v("WindowManager", "addView view=" + view);
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException(
"Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
synchronized (this) {
// 第一部分
// 第二部分
if (mViews == null) {
index = 1;
mViews = new View[1];
mRoots = new ViewRootImpl[1];
mParams = new WindowManager.LayoutParams[1];
} else {
index = mViews.length + 1;
Object[] old = mViews;
mViews = new View[index];
System.arraycopy(old, 0, mViews, 0, index - 1);
old = mRoots;
mRoots = new ViewRootImpl[index];
System.arraycopy(old, 0, mRoots, 0, index - 1);
old = mParams;
mParams = new WindowManager.LayoutParams[index];
System.arraycopy(old, 0, mParams, 0, index - 1);
}
index--;
mViews[index] = view;
mRoots[index] = root;
mParams[index] = wparams;
}
root.setView(view, wparams, panelParentView);
}
第三部分的代碼顯的中規中矩,如果被添加的view是第一個,就新建一個數據放到第一個位置,如果是第N個,就將原來的數組複製一份,並新增一個位置,將要添加的view放到新增的位置上。然後調用ViewRootImpl的setView方法,將View加載進去。(setView方法的內容很多,你甚至可以直接通過追蹤源碼找到view的事件分發那裏去)
到此爲止,我們就算了解了addView的方法。從整個結構上來看,addView的結構很清晰,將view添加進去。不過記錄了添加的view罷了。
接着我們來看具體首先的第二個方法:updateViewLayout
public interface ViewManager
{
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
}
源碼很短:
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
if (!(params instanceof WindowManager.LayoutParams)) {
throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
}
final WindowManager.LayoutParams wparams
= (WindowManager.LayoutParams) params;
view.setLayoutParams(wparams);
synchronized (this) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots[index];
mParams[index] = wparams;
root.setLayoutParams(wparams, false);
}
}
找到View對應的ViewRootImpl類,然後調用setLayoutParams方法,間接刷新界面,也算對得起這個方法的名字。
接着我們來看removeView方法:
public void removeView(View view) {
synchronized (this) {
int index = findViewLocked(view, true);
View curView = removeViewLocked(index);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
首先查看要被移除的view的位置是哪一個,然後調用removeViewLocked,我們來看看removeViewLocked做了啥:
View removeViewLocked(int index) {
ViewRootImpl root = mRoots[index];
View view = root.getView();
// Don't really remove until we have matched all calls to add().
root.mAddNesting--;
if (root.mAddNesting > 0) {
return view;
}
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
if (imm != null) {
imm.windowDismissed(mViews[index].getWindowToken());
}
}
root.die(false);
finishRemoveViewLocked(view, index);
return view;
}
裏面的代碼涉及了很多位置的方法,不過我們依然可以根據方法名來判斷這個方法具體幹了什麼,不過裏面有行註釋能夠給我們提供不少幫助,翻譯過來:在調用add()方法之前,並沒有真正移除view。
不過這個方法總體就是移除指定的view了。(不求甚解)
WindowManager方法的具體實現
現在我們來看看關於WindowManager接口的具體實現過程:
public interface WindowManager extends ViewManager {
public Display getDefaultDisplay();
public void removeViewImmediate(View view);
public boolean isHardwareAccelerated();
}
WindowManager接口的具體實現就要簡單的多了。
public Display getDefaultDisplay() {
return new Display(Display.DEFAULT_DISPLAY, null);
}
獲取默認Display原來就是獲取Display的默認實例。
public void removeViewImmediate(View view) {
synchronized (this) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots[index];
View curView = root.getView();
root.mAddNesting = 0;
root.die(true);
finishRemoveViewLocked(curView, index);
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
這個方法倒是和removeView方法很類似。
public boolean isHardwareAccelerated() {
return false;
}
這個方法就顯的異常簡單了。
到目前爲止,我們就算是把WindowManager的這6個方法的具體實現給看完了,那我們又能得出什麼呢,總不能白看吧。
我們發現,WindowManager確實主要是用來管理view的,主要就是view的那幾個操作,增加刪除和更新操作,但這裏的刪除更新,並不是由WindowManager本身來實現的,而是藉助ViewRootImpl類來進行真正具體的操作。本身的主要任務是通過數組的方式記錄View和對應的ViewRootImpl,不愧是管理者,指揮其他人幫你幹事就行了,然後我們指揮管理者。
基於API28的探索
前面的代碼都是基於API14的代碼,爲什麼我要用API14的代碼講解之後再用API28的代碼呢,因爲低級的API往往比高級的API源碼要簡單很多,但是Android的源碼,這些代碼的主要架構是不會變的,現在我們已經瞭解了API14的源碼,那麼現在我們來了解API28的源碼就會快很多,試試?
API28的ViewManager類倒是沒有變化,不過WindowManager倒是變了不少,現在來看看這個接口變成什麼樣子了:
public interface WindowManager extends ViewManager {
// 一堆屬性
// 兩個異常類
public Display getDefaultDisplay();
public void removeViewImmediate(View view);
public interface KeyboardShortcutsReceiver {
void onKeyboardShortcutsReceived(List<KeyboardShortcutGroup> result);
}
public void requestAppKeyboardShortcuts(final KeyboardShortcutsReceiver receiver, int deviceId);
public Region getCurrentImeTouchRegion();
public static class LayoutParams extends ViewGroup.LayoutParams
implements Parcelable{
// 各種屬性
}
}
我們可以看到,還是有不少變化,新增了2個方法,去掉了isHardwareAccelerated這個方法。
子類也由多個變成了一個:
WindowManagerImpl的變化更大,從原來的500多行,變成了100來行,難得源碼變少。
大概看看裏面長什麼樣:
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.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);
}
...
}
原來裏面的方法都被mGlobal給承包了,以前是通過ViewRootImpl實現的東西,現在由WindowManagerGlobal實現,所以這到底是個啥。
進來WindowManagerGlobal這個類之後,發現這個類,跟API14的WindowManagerImpl特別像,以前WindowManagerImpl有的方法,現在在WindowManagerGlobal這個類裏都能找到,而且還做了拓展,並且實現方式也很類似,看來Google將原來WindowManagerImpl的方法都讓WindowManagerGlobal去管理了。而且儲存view的形式也從數組變成了ArrayList,想必是要管理更多的View了。
總結
通過對WindowManager類的探索,我們大致瞭解到了,其實這個類主要是對view進行管理,添加移除什麼的,但是這只是個管理類,所以具體的實現不可能寫在這個類裏面,若要了解具體如何實現的,還需進入ViewRootImpl類裏面細細探索,這個類纔是真正的大頭,我們只是站在了一個頂端的位置俯瞰整個結構,還需深入每一個模塊才能知其所以然。
我們也知道了老代碼和新代碼雖然有差異,但是核心的思想還是不會變的,如果要想了解某些模塊的具體實現流程,在啃不動源碼的時候,不妨啃啃老代碼,老代碼沒有花裏胡哨(各種各樣)的類去裝飾,側重核心,可以很快了解內部機制。(其實本篇博客的目的是這個😂)