本篇文章主要介紹以下幾個知識點:
- Window 和 WindowManager
- Window 的內部機制
- Window 的創建過程
Window 表示一個窗口的概念,是一個抽象類,其具體實現是 PhoeWindow。
WindowManager 是外界訪問 Window 的入口,創建一個 Window 需要通過 WindowManager。
Android 中的所有視圖都是通過 Window 來呈現的(如 Activity、Dialog、Toast 等),Window 是 View 的直接管理者。
8.1 Window 和 WindowManager
下面代碼將一個 Button 添加到屏幕坐標為(100,300)的位置上,演示了通過 WindowManager 添加 Window 的過程:
mFloatingButton = new Button(this);
mFloatingButton.setText("button");
mLayoutParams = new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
// Flags 參數表 Window 的屬性,常用的有如下:
// 1. FLAG_NOT_FOCUSABLE 表示 Window 不需要獲取焦點,也不需要接收各種輸入事件,此標記會同時啟用 FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層具有焦點的 Window
// 2. FLAG_NOT_TOUCH_MODAL 此模式下,系統會將當前 Window 區域外的單擊事件傳遞給底層的 Window,當前 Window 區域內的單擊事件則自己處理。若不開啟此標記則其他 Window 將無法收到單擊事件。
// 3. FLAG_SHOW_WHEN_LOCKED 開啟此模式可以讓 Window 顯示在鎖屏的界面上
mLayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL ;
mLayoutParams.gravity = Gravity.LEFT;
mLayoutParams.x = 100;
mLayoutParams.y = 300;
mWindowManager.addView(mFloatingButton, mLayoutParams);
WindowManager 繼承了 ViewManager, 所提供的功能常用的有以下3個方法:
public interface ViewManager{
// 添加 View
public void addView(View view, ViewGroup.LayoutParams params);
// 更新 View
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
// 刪除 View
public void removeView(View view);
}
根據上面的方法,實現拖動 Window 的效果只需根據手指的位置來設定 LayoutParams 中的 x 和 y 的值即可:
- 給 View 設置
onTouchListener
; - 在
onTouch
方法中不斷更新 View 的位置。
代碼如下:
public boolean onTouch(View v, MotionEvent event){
int rawX = (int)event.getRawX();
int rawY = (int)event.getRawY();
switch(event.getAction()){
case MotionEvent.ACTION_MOVE:
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
break;
default:
break;
}
return false;
}
8.2 Window 的內部機制
Window 是一個抽象的概念,每一個 Window 都對應著一個 View 和一個 ViewRootImpl
,Window 和 View 通過 ViewRootImpl
來建立聯系,即 Window 是以 View 的形式存在。
下面從添加、刪除、更新來分析 Window 的內部機制。
8.2.1 Window 的添加過程
Window 的添加需通過接口 WindowManager 的 addView
來實現,而其真正的實現類是 WindowManagerImpl
:
public final class WindowManagerImpl implements WindowManager {
// 將所有的操作全部委托給 WindowManagerGlobal 來實現
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
private final Context mContext;
. . .
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
. . .
}
可以看出 WindowManagerImpl
并沒直接實現 Window 的三大操作,而是全交給了 WindowManagerGlobal
來處理:
public final class WindowManagerGlobal {
// 存儲所有 Window 所對應的 View
private final ArrayList<View> mViews = new ArrayList<View>();
// 存儲所有 Window 所對應的 ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
// 存儲所有 Window 所對應的布局參數
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
// 存儲那些正在被刪除的 View 對象
private final ArraySet<View> mDyingViews = new ArraySet<View>();
. . .
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// 1. 檢查參數是否合法,如果是子 Window 那么還需要調整一些布局參數
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");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
. . .
// 2. 創建 ViewRootImpl 并將 View 添加到列表中
root = new ViewRootImpl(view.getContext(), display);
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 {
// 3. 通過 ViewRootImpl 來更新界面并完成 Window 的添加過程
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
. . .
}
}
}
上面第3步由 ViewRootImpl
的 setView
方法來完成:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
. . .
// 1. requestLayout 來完成異步刷新請求
requestLayout();
. . .
// 2. 通過 WindowSession 最終來完成 Window 的添加過程
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
// mWindowSession 的類型是 IWindowSession,是一個 Binder 對象,
// 真正的實現類是 Session,也就是 Window 的添加過程是一次 IPC 調用
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
. . .
}
. . .
}
}
}
其中 setView
內部的 requestLayout
方法如下:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
// scheduleTraversals 是 View 的繪制入口
scheduleTraversals();
}
}
上面 setView
內部的 mWindowSession
,即 Session
,內部會通過 WindowManagerService
來實現 Window 的添加,具體怎么添加這里就不分析了。
以上就是 Window 的添加流程。
8.2.2 Window 的刪除過程
Window 的刪除過程和添加過程一樣是通過WindowManagerGlobal
來實現的, 其removeView
如下:
public final class WindowManagerGlobal {
. . .
public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IllegalArgumentException("view must not be null");
}
synchronized (mLock) {
// 1. 通過 findViewLocked 來查找待刪除的 View 的索引(查找過程就是建立的數組遍歷)
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
// 2. 調用 removeViewLocked 來做進一步的刪除
removeViewLocked(index, immediate);
if (curView == view) {
return;
}
throw new IllegalStateException("...");
}
}
}
上面的 removeViewLocked
如下:
private void removeViewLocked(int index, boolean immediate) {
// removeViewLocked 是通過 ViewRootImpl 來完成刪除操作的
ViewRootImpl root = mRoots.get(index);
View view = root.getView();
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
// 具體的刪除操作由 ViewRootImpl 的 die 方法完成
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
上面 ViewRootImpl
的 die
方法如下:
/**
* @param immediate True, do now if not in traversal. False, put on queue and do later.
* @return True, request has been queued. False, request has been completed.
*/
boolean die(boolean immediate) {
// Make sure we do execute immediately if we are in the middle of a traversal or the damage
// done by dispatchDetachedFromWindow will cause havoc on return.
if (immediate && !mIsInTraversal) {
// 若是同步刪除,則不發消息直接調用 doDie 方法
doDie();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(mTag, "... ");
}
// 若是異步刪除,則發送 MSG_DIE 消息,ViewRootImpl 中的 Handler 會處理此消息并調用 doDie 方法
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
上面 doDie
方法在內部會調用方法 dispatchDetachedFromWindow
來實現真正的刪除 View 邏輯。
方法 dispatchDetachedFromWindow
主要做四件事:
(1)垃圾回收相關的工作
(2)通過 Session 的 remove
方法刪除 Window
(3)在內部調用 View 的 onDetachedFromWindow()
以及 onDetachedFromWindowInternal()
,做資源回收工作。
(4)調用 WindowManagerGlobal
的 doRemoveView
方法刷新數據。
8.2.3 Window 的更新過程
Window 的更新過程還是看 WindowManagerGlobal
中的 updateViewLayout
方法,如下:
public final class WindowManagerGlobal {
. . .
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 的 LayoutParams
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
// 2. 更新 ViewRootImpl 中的 LayoutParams
// ViewRootImpl 會對 View 重新布局、更新 Window 的視圖
root.setLayoutParams(wparams, false);
}
}
}
8.3 Window 的創建過程
上面分析可知,View 是 Android 中的視圖的呈現方式,但 View 不能單獨存在,必須附著在 Window 上面,因此有視圖的地方就有 Window。
下面分析一些視圖元素中的 Window 的創建過程。
8.3.1 Activity 的 Window 創建過程
要分析 Activity 的 Window 創建過程就必須了解 Activity 的啟動過程。
Activity 的啟動過程很復雜,最終會由 ActivityThread
中的 performLaunchActivity()
來完成整個啟動過程,代碼如下:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
. . .
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
// 通過類加載器創建 Activity 的實例對象
activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
. . .
} catch (Exception e) { . . . }
try {
. . .
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, ". . . ");
// 調用其 attach 方法為其關聯運行過程中所依賴的一系列上下文環境變量
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);
. . .
}
} catch (Exception e) { . . . }
return activity;
}
在 Activity 的 attach
方法里,系統會創建 Activity 所屬的 Window 對象并為其設置回調接口:
// 創建 Window 對象
mWindow = PolicyManager.makeNewWindow(this);
// 實現 Window 的 Callback 接口
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
. . .
上面 Activity 的 Window 是通過 PolicyManager
的一個工廠方法來創建的,其 makeNewWindow
如下:
public Window makeNewWindow(Context context){
// Window 的具體實現是 PhoneWindow
return new PhoneWindow(context);
}
到這里 Window 已經創建完了,接下來分析 Activity 的視圖是如何附屬在 Window 上。
由于 Activity 的視圖由 setContentView
方法提供,只需看其實現即可:
public void setContentView(int layoutResID){
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
從上面代碼可知 Activity 將具體實現交給 Window 處理,而 Window 的具體實現是 PhoneWindow
,PhoneWindow
的 setContentView
方法大致遵循如下步驟:
1. 若無 DecorView
,則創建它
DecorView 是一個 FrameLayout,是 Activity 中的頂級 View,其創建過程由 installDecor
方法來完成,此方法內部通過 generateDecor
方法來直接創建 DecorView
:
protected DecorView generateDecor(){
return new DecorView(getContext(), -1);
}
2. 將 View 添加到 DecorView
的 mContentParent
中
步驟1已創建和初始化 DecorView
,接下來直接將 Activity 的視圖添加到 DecorView
的 mContentParent
中即可:
mLayoutInflater.inflate(layoutResID, mContentParent);
3. 回調 Activity 的 onContentChanged
方法通知 Activity 視圖已發生改變
由于 Activity 實現了 Window 的 Callback
接口,其布局文件已被添加到 DecorView
的 mContentParent
中,需要通知 Activity 做相應的處理:
final Callback cb = getCallback();
if(cb != null && !isDestroyed()){
cb.onContentChanged();
}
經過上面3個步驟,DecorView
已被創建和初始完畢,Activity 的布局文件也添加到了 DecorView
的 mContentParent
中。
接下來在 ActivityThread
的 handleResumeActivity
方法中會先調用 Activity 的 onResume
方法,再調用 Activity 的 makeVisible()
方法:
void makeVisible(){
if(!mWindowAdded){
ViewManager wm = getWindowManager();
vm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = ture;
}
mDecor.setVisibility(View.VISIBLE);
}
在上面 makeVisible
方法中,DecorView
真正地完成了添加和顯示這兩個過程,Activity 的視圖才能被用戶看到。
以上便是 Activity 中的 Window 的創建過程。
8.3.2 Dialog 的 Window 創建過程
Dialog 的 Window 的創建過程和 Activity 類似,有如下幾個步驟:
1. 創建 Window
Dialog 中的 Window 的創建同樣是通過 PolicyManager
的 makeNewWindow
方法來完成的,過程和 Activity 類似:
Dialog(Context context, int theme, boolean createContextThemeWrapper){
. . .
mWindowManger = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Window w = PolicyManage.makeNewWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
. . .
}
2. 初始化 DecorView
并將 Dialog 的視圖添加到 DecorView
中
這個過程也和 Acitivity 的類似,通過 Window 去添加指定的布局文件:
public void setContentView(int layoutResID){
mWindow.setContentView(layoutResID);
}
3. 將 DecorView
添加到 Window 中并顯示
在 Dialog 的 show
方法中,會通過 WindowManager
將 DecorView
添加到 Window 中:
mWindowManager.addView(mDecor, 1);
mShowing = true;
以上便是 Dialog 的 Window 創建過程,和 Activity 的類似。
注:普通的 Dialog 必須采用 Activity 的 Context,若采用 Application 的 Context 會報錯。
8.3.3 Toast 的 Window 創建過程
Toast 和 Dialog 不同,稍復雜。Toast 也是基于 Window 來實現的,由于具有定時取消功能,系統采用了 Handler。
具體的過程這里不再介紹了,有興趣的可去看看書。本篇文章主要是對 Window 有一個更加清晰的認識,理解 Window 和 View 的依賴關系。
本篇文章就介紹到這。