1. Window和WindowManager
Window
表示一个窗口的概念,如在创建对话框时就需要Window
来进行。Window
通过WindowManager
来操作Window
,但WIndow
的具体实现都需要使用WindowManagerService
1.1 Window
- WindowFlags
参数 | 作用 |
---|---|
FLAG_NOT_TOUCH_MODAL |
表示Window 不需要获取焦点,也不需要接受各种输入时间,此标记会同时启用FLAG_NOT_TOUCHABLE
|
FLAG_NOT_TOUCHABLE |
在此模式下,系统会将当前Window 区域以外的单击事件传递给底层的Window ,当前Window 区域的点击由自己处理 |
FLAG_SHOW_WHEN_LOCKED |
让Window 显示在锁屏上 |
- WindowType
- 应用
Window
:对应着一个Activity
- 子
Window
:不能单独存在,需要附属在特定的父Window
上 - 系统
Window
:需要声明权限才能创建的Window
,例如Toast
和系统状态栏
如果采用TYPE_SYSTEM_ERROR
,需要声明权限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
- Window分层
Z-index,应用Window的层级范围1-99,子Window范围1000-1999,系统Window层级范围是2000-2999
- WindowManager
- 增删改
Window
实际上是操作Window
里的View
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn = new Button(this);
btn.setText("TEST");
WindowManager.LayoutParams params = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
params.gravity = Gravity.LEFT| Gravity.TOP;
params.x = 100;
params.y = 300;
params.type = WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
getWindowManager().addView(btn, params);
}
}
btn.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
int rawX = (int) motionEvent.getX();
int rawY = (int) motionEvent.getY();
switch (motionEvent.getAction()) {
case MotionEvent.ACTION_MOVE:
params.x = rawX;
params.y = rawY;
mWindow.updateViewLayout(btn, params);
}
return false;
}
}
);
2. Window的内部机制
Window
每一个都对应着每一个View
和每一个ViewRootImpl
,因此Window不是实际存在的,而是以View
的形式存在的
2.1 Window的添加过程
@Override
public void addView(View view, ViewGroup.LayoutParams params) {
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
@Override
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
mGlobal.updateViewLayout(view, params);
}
@Override
public void removeView(View view) {
mGlobal.removeView(view, false);
}
WindowManagerGlobal
的实现步骤:
步骤一:检查参数是否合法,如果是子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;
// 如果是子View,则会调整参数
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
}
步骤二:创建ViewRootImpl并将View添加到列表中
在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>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();
-
mViews
存储的是所有Window
所对应的View
, -
mRoots
存储的是所有Window
所对应的ViewRootImpl
-
mParams
存储的是所有Window
所对应的布局参数 -
mDyingViews
则存储了那些正在被删除的View
对象,或者说是那些已经调用removeView
方法但是删除操作还未完成的Window
对象。
在addView
中通过如下方式将Window
的一系列对象添加到列表中:
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
步骤三:通过ViewRootImpl来更新界面并完成Window的添加过程
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
接着会通过mWindowSession
最终来完成Window
的添加过程。mWindowSession
的类型是IWindowSession
,它是一个Binder
对象,真正的实现类是Session
,也就是Window
的添加过程是一次IPC
调用。
在Session
内部会通过WindowManagerService
来实现Window
的添加
2.2 Window的删除过程
WindowManagerGlobal#removeView
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的Index
View curView = mRoots.get(index).getView(); // 找到待删除的View
removeViewLocked(index, immediate); // 进行删除
if (curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view
+ " but the ViewAncestor is attached to " + curView);
}
}
WindowManagerGlobal#removeViewLocked
private void removeViewLocked(int index, boolean immediate) {
ViewRootImpl root = mRoots.get(index); // 从mRoots数组中找到对应的root
View view = root.getView(); // 找到对应的View
if (view != null) {
InputMethodManager imm = InputMethodManager.getInstance();
if (imm != null) {
imm.windowDismissed(mViews.get(index).getWindowToken());
}
}
boolean deferred = root.die(immediate);
if (view != null) {
view.assignParent(null);
if (deferred) {
mDyingViews.add(view);
}
}
}
-
removeViewLocked
是通过ViewRootImpl
来完成删除操作的。在WindowManager
中提供了两种删除接口removeView
和removeViewImmediate
,它们分别表示异步删除和同步删除 -
removeViewImmediate
需要特别注意,一般来说不需要使用此方法来删除Window
以免发生意外的错误。这里主要说异步删除的情况,具体的删除操作由ViewRootImpl
的die
方法来完成。在异步删除的情况下,die
方法只是发送了一个请求删除的消息后就立刻返回了,这个时候View
并没有完成删除操作,所以最后会将其添加到mDyingViews
中,mDyingViews
表示待删除的View
列表。ViewRootImpl
的die
方法如下所示:
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();
return false;
}
if (!mIsDrawing) {
destroyHardwareRenderer();
} else {
Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
" window=" + this + ", title=" + mWindowAttributes.getTitle());
}
mHandler.sendEmptyMessage(MSG_DIE);
return true;
}
同步删除:doDie()
方法,内部会调用dispatchDetachedFromWindow
方法
异步删除:发送消息mHandler.sendEmptyMessage(MSG_DIE);
dispatchDetachedFromWindow
方法:
- 垃圾回收相关工作,比如清楚数据和消息、移除回调等
- 通过
Session
的remove
方法删除Window
,最终会调用WindowManagerService
的removeWindow
方法 - 调用
View
的dispatchDetachedFromWindow
- 调用
WindowManagerGlobal
的doRemoveView
方法刷新数据,包括mRoots
,mParams
以及mDyingViews
,需要将当前Window
所关联的这三类对象从列表中删除。
2.3 Window的更新过程
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);//替换掉老的LayoutParams
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index); //先删除再增加
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
}
}
首先它需要更新View
的LayoutParams
并替换掉老的LayoutParams
,接着再更新ViewRootImpl
中的LayoutParams
,这一步是通过ViewRootImpl
的setLayoutParams
方法来实现的。在ViewRootImpl
中会通过scheduleTraversals
方法来对View重新布局,包括测量,布局,重绘这三个过程。除了View本身的重绘以外,ViewRootImpl
还会通过WindowSession
来更新Window
的视图,这个过程最终是由WindowManagerService
的relayoutWindow()
来具体实现的,它同样是一个IPC
过程。
3. Window的创建过程
3.1 Activity的Window启动过程
3.2 Dialog的Window创建过程
3.3 Toast的Window创建过程
-
TN
是一个Binder
类,在NMS
处理Toast
请求时,需要Handler
将其从Binder
线程池中切换到发送Toast
的线程,因此Toast
无法在没有Looper
的线程中弹出 -
enqueueToast
将Toast
请求封装成ToastRecord
加入mToastQueue
,mToastQueue
最多存在50个弹出请求 -
ToastRecord
与NMS
之间通过TN
对象Callback
的show
,hide
方法完成Toast的显示和隐藏功能,都是IPC