Window
表示窗口的概念,他是一個抽象類,他的真正實現類是PhoneWindow
,WindowManager
用來對Window
進行管理,是外接訪問Window
的入口,Window
操做的具體實現是在WindowManagerService
中,WindowMager
和WindowManagerService
交互是IPC
的過程java
Android中全部的視圖都是附加在Window上
上呈現的,無論Activity,Dialog,Toast
,他們的視圖都是附加在Window
上的,所以Window
其實是View
的直接管理者android
下面咱們來詳細的瞭解Window
app
咱們先來了解一下如何使用WindwoMagaer
來添加一個Window
ide
Button button = new Button(this);
button.setText("Window");
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION;
layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
layoutParams.gravity= Gravity.LEFT|Gravity.TOP;
layoutParams.x=100;
layoutParams.y=300;
WindowManager windowManager = getWindowManager();
windowManager.addView(button,layoutParams);
複製代碼
這段代碼能夠添加一個Window
,位置在(100,300)處,這裏面有倆個參數比較重要分別是,type
和flag
,下面分別介紹一下這倆個參數oop
type
參數表示Window
的類型,Window
有三種類型,分別是Application Window
(應用窗口),Sub Window
(子窗口)和System Window
(系統窗口),每一個大類型又包含多個小類型,他們都定義在WindowMager
的靜態內部類LayoutParams
中,下面對這三種類型進行講解佈局
Application Window(應用窗口)post
Activity就是典型的應用窗口,應用窗口包含的類型以下:ui
public static final int FIRST_APPLICATION_WINDOW = 1;
//窗口的基礎值,其餘窗口要大於這個值
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;
複製代碼
應用窗口就包括了以上幾中類型,其中最上方是起始值,最下方是結束值,也就是說應用窗口的Type值的範圍是1-99
,這個數值的大小涉及窗口的層級this
Sub Window(子窗口)spa
子窗口不可以獨立存在,要依附在其餘窗口上才行,PopupWindow
就屬於子窗口,子窗口的定義類型以下:
//子窗口的初始值
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;
public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3;
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;
複製代碼
能夠看出子窗口的type值範圍是1000-1999
System Window (系統窗口)
Toast,輸入法窗口,系統音量條窗口,系統錯誤窗口,都屬於系統窗口,系統窗口的類型定義以下:
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;
public static final int TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4;
@Deprecated
public static final int TYPE_TOAST = FIRST_SYSTEM_WINDOW+5;
@Deprecated
public static final int TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6;
@Deprecated
public static final int TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7;
...
public static final int LAST_SYSTEM_WINDOW = 2999;
複製代碼
系統窗口接近40個,這裏只列出一小部分,系統窗口的Type值在2000-2999之間
窗口的顯示次序
上面介紹的Type值越大,就意味着靠用戶越近,很顯然系統的窗口是最大的,他在應用窗口和子窗口的上方
Flag
就是窗口的標誌,用於控制Window
的顯示,一樣被定義在WindowManager
的內部類LayoutParams
中,一共有20多個,這裏列出一些經常使用的
type | 描述 |
---|---|
FLAG_ALLOW_LOCK_WHILE_SCREEN_ON | 只要窗口可見,就容許在開啓狀態的屏幕上鎖屏 |
FLAG_NOT_FOCUSABLE | 窗口不能獲取輸入焦點,設置該標誌的同時,FLAG_NOT_TOUCH_MODAL也會被設置 |
FLAG_NOT_TOUCH_MODAL | 將該窗口區域外的觸摸事件,傳遞給其餘Window,而本身只會處理窗口區域內的觸摸事件 |
FLAG_NOT_TOUCHABLE | 窗口不接受任何觸摸事件 |
FLAG_KEEP_SCREEN_ON | 只要窗口可見,就一直保持屏幕長亮 |
FLAG_LAYOUT_NO_LIMITS | 容許窗口超出屏幕外 |
FLAG_FULLSCREEN | 隱藏全部的屏幕裝飾窗口,好比遊戲視頻等全屏顯示 |
FLAG_SHOW_WHEN_LOCKED | 窗口能夠在鎖屏窗口之上顯示 |
FLAG_IGNORE_CHEEK_PRESSES | 當用戶臉貼近屏幕時(好比打電話時),不會響應此事件 |
FLAG_TURN_SCREEN_ON | 窗口顯示時將屏幕點亮 |
設置Window的Flag除了上方的方式外還能夠採用下面的方式
//第一種
Window window = getWindow();
window.addFlags();
//第二種
Window window = getWindow();
window.setFlags();
複製代碼
咱們在寫登錄界面的時候,默認彈出的軟鍵盤窗口可能會覆蓋輸入框下面的按鈕,爲了讓軟鍵盤按照指望的方式顯示,,WindowMagaer的靜態內部類LayoutParams中定義了軟鍵盤的相關模式,咱們介紹一下經常使用的
SoftInputMode | 描述 |
---|---|
SOFT_INPUT_STATE_UNSPECIFIED | 沒有指定狀態,系統會選擇一個合適的狀態或依賴於主題的設置 |
SOFT_INPUT_STATE_UNCHANGED | 不會改變軟鍵盤的狀態 |
SOFT_INPUT_STATE_HIDDEN | 當用戶進入該窗口時,軟鍵盤默認隱藏 |
SOFT_INPUT_STATE_ALWAYS_HIDDEN | 當窗口獲取焦點時,軟鍵盤老是隱藏 |
SOFT_INPUT_ADJUST_RESIZE | 當軟鍵盤彈出時,窗口會調整大小 |
SOFT_INPUT_ADJUST_PAN | 當軟鍵盤彈出時,窗口不須要調整大小,要確認輸入焦點是否可見 |
軟鍵盤模式能夠在AndroidManifest中設置
<activity android:name=".CameraActivity"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustPan">
</activity>
複製代碼
也能夠代碼設置
getWindow().setSoftInputMode();
複製代碼
WindowMagaer
所提供的功能很簡單,只有經常使用的三個方法即,添加View,更新View,刪除View
,這個三個方法定義在ViewManager
中,而WindowManager
繼承自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);
}
複製代碼
Window
是一個抽象概念,每個Window
都對應一個View
和一個ViewRootImpl
,Window
和View
經過ViewRootImpl
來創建聯繫,所以Window
不是實際存在的,他是以View
的形式存在的,在實際是一箇中,不能直接訪問Window
,只有經過WindowManager
才能訪問
Window
的添加是經過WindowManager
的addView
方法實現的,咱們WindowManager##addView
方法做爲入口來分析,WindowMagager
是一個接口,真正的實如今WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
複製代碼
咱們發現他其實把事情交給了WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
//註釋1
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");
}
····
//註釋2
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//註釋3
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
//註釋4
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
複製代碼
咱們首先了解幾個重要的變量
//儲存全部Window對應的View
private final ArrayList<View> mViews = new ArrayList<View>();
//儲存全部Window對應的ViewRoot
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//佈局參數列表
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
複製代碼
ViewRootImpl有不少的職責
咱們繼續看一下ViewRootImpl
的setView
方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
···
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
···
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
} catch (RemoteException e) {
}
}
複製代碼
這個方法首先會調用requestLayout
方法來完成一部刷新請求
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
複製代碼
scheduleTraversals
實際是View繪製的入口
而後調用mWindowSession.addToDisplay
方法,mWindowSession
是一個IWindowSession
類型的,是一個Binder
對象,用於進程間通訊,也就是說addToDisplay
方法實際上是運行在WMS
所在的進程system_server
進程
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, Rect outOutsets, InputChannel outInputChannel) {
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
複製代碼
addToDisplay
方法內部調用了WMS
的addWindow
方法並將自身也就是Session
傳入了進去,每一個應用程序都會有一個Session
,WMS
會用ArrayList
來保存起來,這樣全部的工做都交給了WMS
來作
WMS
會爲這個添加的窗口分配Surface
,並肯定窗口的顯示次序,負責顯示界面的是畫布Surface
,而不是窗口自己,WMS
會把Surface
交給SurfaceFlinger
處理,SurfaceFlinger
會把這些Surface
混合並繪製到屏幕上
Window
的更新過程和添加過程是相似的,須要調用WindowManager
的updateViewLayout
方法,而後會繼續進入WindowManagerGlobal
的updateViewLayout
方法,咱們直接從這個方法進行分析
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;
//註釋1
view.setLayoutParams(wparams);
synchronized (mLock) {
//註釋2
int index = findViewLocked(view, true);
//註釋3
ViewRootImpl root = mRoots.get(index);
//註釋4
mParams.remove(index);
//註釋5
mParams.add(index, wparams);
//註釋6
root.setLayoutParams(wparams, false);
}
}
複製代碼
ViewRoot
的setLayoutParams
方法,將更新的參數設置到ViewRootImpl
中,setLayoutParams方法最終會調用ViewRootImpl
的scheduleTraversals
方法,咱們看下這個方法void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
//註釋1
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
複製代碼
咱們看下注釋1,mChoreographer
翻譯爲編舞者
,用於接受系統的VSync
信號,在下一個幀渲染時控制一些操做,mChoreographer
的postCallback
方法用於添加回調,這個添加的回調,將在下一幀渲染時執行,這個添加的回調指的是TraversalRunnable
類型的mTraversalRunnable
,以下:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
複製代碼
這個方法內部調用了doTraversal
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
複製代碼
這個方法又調用了performTraversals
,這個方法中更新了Window
的視圖,而且完成了View的繪製流程,measure,layout,draw
,這樣就完成了View的更新
這個須要瞭解App的啓動過程,這個我就再也不重複說了,不瞭解的能夠看我以前的文章Android App啓動過程,他最後會調用performLaunchActivity
方法來完成整個啓動過程,這個方法內部會經過類加載器建立Activity的實例對象,並調用了attach
方法,爲其關聯運行中所依賴的一系列變量
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);
...
} catch (Exception e) {
...
}
try {
// 返回以前建立過的 application 對象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
...
if (activity != null) {
...
// attach 到 window 上
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);
...
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
...
}
} catch (Exception e) {
...
}
return activity;
}
複製代碼
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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
...
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);
setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
enableAutofillCompatibilityIfNeeded();
}
複製代碼
這個方法,會建立Activity
所屬的Window對
象併爲其設置回調接口,到這裏Window
已經建立完成了,下面咱們分析一下Activity的視圖是怎麼依附到Window
上的,因爲Activity的視圖是從setContentView
方法提供,咱們從setContentView
方法開始分析
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
複製代碼
咱們點進去發現他其實調用了PhoneWindow的setContentView方法,咱們看下這個方法
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 {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
複製代碼
這個方法主要作了如下幾件事
DecorView
不存在則建立DecorView
,DecorView
是Activity
中的頂級View
,通常來講他包括標題欄
和內容欄
,這個會隨着主題的改變而改變,反正內容欄必定存在,而且他有固定的id
爲android.R.id.content
, 建立DecorView
由installDecor
方法完成,內部會經過generateDecor
方法建立,這個時候DecorView
仍是一個空白的Framlayout
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//建立DecroView
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) {
//向DecorView添加內容
mContentParent = generateLayout(mDecor);
}
...
}
複製代碼
protected DecorView generateDecor(int featureId) {
...
return new DecorView(context, featureId, this, getAttributes());
}
複製代碼
DecorView
的結構,經過generateLayout
方法加載具體的佈局文件到DecorView中,併爲內容欄變量賦值ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
複製代碼
setContentView
的View
添加到內容欄中, mLayoutInflater.inflate(layoutResID, mContentParent);
這時Activity
的佈局文件就已經添加到了DecorView
的內容欄中onContentChanged
方法,通知Activity
視圖已經改變經過上方的步驟,如今DecorView
已經建立並初始化完成,Activity
的佈局也添加到DecorView
的內容欄中,可是這個時候DecorView
尚未被WindowManager
添加到Window
中
在ActivityThread
的handleResumeActivity
會調用Activity
的onResume
方法,而且會調用ViewManager
的addView
方法把DecorView
添加到Window
中
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
//調用Activity的onResume方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
...
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//獲取WindowMagaer
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
//把DecorView添加到Window
wm.addView(decor, l);
}
...
}
複製代碼
到這裏Activity的Window建立過程分析完畢
參考:《Android開發藝術探索》《Android進階解密》