Window和WindowManager
Window(abstract class)是一个抽象的概念。每个Window都对应一个View和一个ViewRootImpl,ViewRootImpl是联系桥梁。因此Window并不是实际存在的,是以View的形式存在。
我们通过WindowManager (interface extends ViewManager)访问Window。 ViewManager接口的三个方法如下。都是针对View,再次说明Window是以View的形式存在
public void addView(View view, ViewGroup.LayoutParams params);
public void updateViewLayout(View view, ViewGroup.LayoutParams params);
public void removeView(View view);
WindowManager通过下面的方式获取
mwindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
通过WindowManager添加View 的过程
mwindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
Button button = new Button(this);
button.setText("糖宝");
mlayoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSPARENT);
mlayoutParams.flags = LayoutParams.FLAG_NOT_TOUCH_MODAL
| LayoutParams.FLAG_NOT_FOCUSABLE
| LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mlayoutParams.gravity = Gravity.LEFT | Gravity.TOP;
mlayoutParams.x = 100;
mlayoutParams.y = 300;
mwindowManager.addView(button, mlayoutParams);
WindowManager真正实现是WindowManagerImpl,WindowManagerImpl又交给了WindowManagerGlobal工厂(WindowManagerGlobal.getInstance)来提供实例。WindowManagerImpl这种工作模式是典型的桥接模式
Windows分层
window是分层的,每个window都对应着z-ordered,层级大的会覆盖在校的上面
- 应用window的层级范围是1~99
- 子window的层级范围是1000~1999
- 系统window的层级范围是2000~2999
Window的创建过程
Window的具体实现是在WindowManagerService中,它的具体是现实PhoneWindow。WindowManager和WindowManagerService的交互是一个IPC的过程。
View是Android中的视图的呈现方式,但是View不能单独存在,它必须附着在Window这个抽象的概念上面,因此有视图的地方就有Window,我们平常使用的Activity、Dialog、Toast还有PopUpWindow和菜单都对应着一个Window。
Activity的window创建过程
核心流程:Activity.attach创建PhoneWindow, PhoneWindow创建DecorView,最后Activity的makeVisible显示DecorView
- ActivityThread中的performLaunchActivity完成Activity整个的启动过程,内部会创建Activity的实例,调用Activity的attach关联上下文环境变量
attach方法会创建Window对象(PhoneWindow), 并设置回调接口(Activity implements Window.Callback), 当Window接收到外界状态变化时会回调Activity的方法。例如onAttachedToWindow等
mWindow = PolicyManager.makeNewWindow(this) mWindow.setCallback(this) ......
到这里Window已经创建成功了
Activity的调用setContentView方法,Activy将具体的实现交给了PhoneWindow处理
public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
PhoneWindow的setContentView,会创建DecorView(FrameLayout,Activity的顶级View),DecorView一般包含标题栏和内容栏(android.R.id.content)。PhoneWindow会加载的布局文件,并将view添加到DecorView的mContentParent。回调Activity的onContentChanged通知Activity视图已经发生了变化。
到这里DecorView已经创建并初始化完毕。但是这时候Window, DectorView并没有被WindowManager识别,无法接收外界的输入信息
Activity的makeVisibale这里DecorView才真正完成了添加和显示,这里Activity的视图才能被用户看到
ActivityThread的handleResumeActivity会先调用Activity的OnResume方法,接着会调用Activity的makeVisible
void makeVisible() { if (!mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded = true; } mDecor.setVisibility(View.VISIBLE); }
Dialog的Window创建过程
过程与Activity的Window创建过程类似。
- 构造函数创建Window实例
Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
......
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
final Window w = new PhoneWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
Dialog的setContentView将Dialog视图添加到DecorView中
public void setContentView(@LayoutRes int layoutResID) { mWindow.setContentView(layoutResID); }
Dialog的show方法,显示DecorView
public void show() { ...... mDecor = mWindow.getDecorView(); mWindowManager.addView(mDecor, l); mShowing = true; sendShowMessage(); }
Dialogue的Window创建过程和Activity的Window创建过程非常类似,几乎没什么差别。
需要注意普通的Dialog必须采用Activity的Context,如果采用Application的Context会报错。
原因是Application没有应用token,应用token一般是只有Activity才拥有。
Service也没有token,所以也不能在service中弹出普通dialog。不过可以使用系统的Window指定对话框,因为系统对话框不需要使用token
Toast的Window创建过程
Toast和Dialog不同,过程稍显复杂。Toast也是基于Window,但是Toast具有定时取消功能,所以系统采用了Handler。
Toast内部有两类IPC过程:
1. Toast访问NotificationManagerService
2. NotificationManagerService回调Toast里的TN接口(extends ITransientNotification.Stub),这明显是Binder类
Toast属于系统Window,内部的视图由两种方式指定:一种是系统默认的样式;另一种通过setView方法来指定一个自定义的View。
Toast的show和cancel都是IPC过程。
show和cancel方法,NMS处理Toast请求时会跨进程回调TN实例的方法,方法运行在请求应用的Binder线程池中,需要Handler切换到当前线程,因此Toast必须在有Looper的线程中弹出。
```
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
TN tn = mTN; // 远程回调
tn.mNextView = mNextView;
try {
service.enqueueToast(pkg, tn, mDuration); // IPC
} catch (RemoteException e) {
// Empty
}
}
public void cancel() {
mTN.hide();
try {
getService().cancelToast(mContext.getPackageName(), mTN); // IPC
} catch (RemoteException e) {
// Empty
}
}
```
Toast的显示和隐藏是IPC过程,都是通过NotificationManagerService来实现。
enqueueToast会封装Toast请求为ToastRecord对象,加入mToastQueue队列(ArrayList)
对于非系统应用来说,mToastQueue最多能同时存在50个ToastRecord,这样做是为了防止DOS(Denial of Service,拒绝服务)。
因为如果某个应用弹出太多的Toast会导致其他应用没有机会弹出Toast。
小结
- Toast,有消息循环的线程才能调用
- 普通Dialog需要token,因此需要Activity的Context调用;系统Dialog不需要token
- Activity Window和Dialog创建过程类似,ActivityThread和WMS的IPC通信
- Toast创建跟NMS的IPC通信。所有非系统应用最多存在50个
关联小知识
- 系统应用:process是”android” 或者在系统进程中(sharedUserId设置+platform key)的应用
- linux下的UID是系统用户名的意思,Android系统修改了linux 的UID的含义
- 每个APP对应一个UID。通过设置sharedUserId,拥有同一个sharedUserId并且签名相同的两个APP可以跑在同一个进程中
- PID是进程id的意思,一个UID可以对应多个PID