第八章 理解Window和WindowManager
Window表示的是窗口的概念eg:悬浮窗,它是一个抽象类,具体实现是PhoneWindow,创建一个Window很简单,只需要通过WindowManager去实现,WindowManager是外界访问Window的入口,Window的具体实现是在WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC,Android中的所有视图(Activity、Dialog、Toast)都是通过Window来实现的,他们的视图都是直接附加在Window上的,因此Window是View的直接管理者,在之前的事件分发机制中我们说到:View的事件是通过WIndow传递给DecorView,然后DecorView传递给我们的View,就连Activity的setContentView,都是由Window传递的。本章内容:window和WindowManager、window的内部机制(添加、删除、更新)、Window的创建过程(Activity、Dialog、Toast) 。
(一)Window和WindowManager
场景:将一个Button添加到屏幕坐标为(100,300)的位置上。
代码示例:
Button btn_click= new Button(this);
btn_click .setText("button");
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
WindowManager.LayoutParams layout = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT
, WindowManager.LayoutParams.WRAP_CONTENT, 0, 0, PixelFormat.TRANSLUCENT);
layout.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
//Flag参数表示window的属性,通过这些选项控制Window的显示特性:
//1.FLAG_NOT_FOCUSABLE:表示窗口不需要获取焦点,也不需要接收各种事件,这属性会同时启动FLAG_NOT_TOUCH_MODAL,最终事件会传递给下层的具体焦点的window
//2.FLAG_NOT_TOUCH_MODAL:系统会将当前window区域以外的单击事件传递给底层的Window,当前的Window区域以内的单机事件自己处理,这个标记很重要,一般来说都需要开启,否则其他windows无法接受到点击事件。
//3.FLAG_SHOW_WHEN_LOCKE:开启这个属性可以让window显示在锁屏上
layout.gravity = Gravity.CENTER;
layout.type = WindowManager.LayoutParams.TYPE_PHONE;
//emm..这个是Type参数。
layout.x = 100;
layout.y = 300;
wm.addView(btn_click, layout);
1.1.Window的参数和相关理论
Type参数表示window的类型,window有三种类型:分别是应用window(Activity),子window(Dialog),系统window(系统状态栏和Toast) ,应用window是Activity,子Window不能单独存在,需要依赖一个父Window,比如常见的Dialog,系统window需要声明权限:比如Toast和系统的状态栏。
Window是分层的,每个Window都有对应的z-ordered,层级大的会覆盖在层级小的Window上面。在这三类中,应用Window是层级范围是1-99,子window的层级是1000-1999,系统的Window层级是2000-2999。这些层级范围对应着 WindowManager.LayoutParams的type参数,系统层级一般有蛮多值,一般用TYPE_SYSTEM_OVERLAY或TYPE_SYSTEM_ERROR,此时注意XML中声明权限,如果系统Window不声明权限,会报错。
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
1.2.WindowManager的三个功能
WindowManager主要提供了添加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效果,根据手指的位置来设定LayoutParams中的x、y值即可改变window的位置。(1)根据手指位置设定LayoutParams中x、y的值改变window的位置;(2)给View设置onTouchListener;(3)复写onTouch方法,不断更新位置。
代码示例:
btn_click.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_MOVE: {
int rawx = (int) (event.getX());//getRawX
int rawy = (int) (event.getY());//getRawY
layout.x = rawx;
layout.y = rawy;
wm.updateViewLayout(btn_click, layout);
break;
}
default:
break;
}
return false;
}
});
(二)window的内部机制
Window是一个抽象的概念,每一个Window对应着一个View和一个ViewRootImpl(ViewRootImpl建立Window和View的关系),Window并不是实际存在的,View才是window的实体。这是因为WindowManager定义的接口都是针对View,实际使用当中我们访问window是通过windowmanager的。下面从添加、删除、更新说起。
2.1.Window的添加过程
Window的添加过程是通过WindowManager的addView去实现的,WindowManager是一个接口,真正实现是WindowManagerImpl类。最后通过WindowManagerGlobal来实现。
步骤一:检查参数是否合法,如果是子Window(Dialog)还需要调整一下参数;
步骤二:创建ViewRootImpl并将View添加到列表中。 WindowManagerGlobal有如下几个列表。
private final ArrayList<View> mViews = new ArrayList<View>();
//mViews存储所有window所对应的View
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//mRoots存储是所有window所对应的ViewRootImpl
private final ArrayList<WindowManager.LayoutParams> mParams =
new ArrayList<WindowManager.LayoutParams>();
//mParams存储是所对应的布局参数
private final ArraySet<View> mDyingViews = new ArraySet<View>();
//mDyingViews则存储那些正在被删除的对象
AddView通过如下方式将window的一系列对象添加到列表中:
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
步骤三:通过ViewRootImpl来更新界面并完成Window的添加过程。 由ViewRootImpl的setView完成,它内部会通过requstLayout来完成异步刷新请求,scheduleTravers是View绘制的入口,会通过WindowSession最终来完成Window的添加过程,在session内部将window的添加请求交给windowManagerService来处理,在windowManagerService为每一个应用保留一个单独的Session。
2.2.Window的删除过程
WindowManager->WindowManagerImpl->WindowManagerGlobal的removeview方法。
步骤一:findViewLocked进行数组遍历来查找待删除的索引,然后调用removeViewLocked来做进一步的删除;
步骤二:通过ViewRootImpl来完成删除操作的,进行异步删除(removeView)和同步删除(removeViewImmediate)。同步删除直接删除即可,调用dodie方法。若为异步删除,removeViewLocked会通过die方法发送一个MSG_DIE(请求删除)消息,ViewRootImpl的Handler会处理此消息并调用dodie方法。dodie内部是dispatchDetachedFromWindow(核心)。
步骤三:dispatchDetachedFromWindow方法:1.垃圾回收:比如清除数据和消息,移除回调2.删除window: 通过Session的remove方法来删除window,这是一个IPC过程,会调用windowmanagerservice的removeWindow方法3.回收View资源: 调用view的dispatchDetachedFromWindow 方法,调用onDetachedFromWindow做了一些回收资源、终止动画、停止线程的一些操作 ;4.刷新数据: 调用WindowManagerGlobal的doRemoveView方法刷新数据。
2.3.Window的更新过程:
WindowManager->WindowManagerImpl->WindowManagerGlobal的updateviewlayout方法。步骤如下:1.更新下View和ViewRootimpl中的LayoutParams;2.对View重新布局;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;
//2.更新下ViewRootimpl中的LayoutParams
view.setLayoutParams(wparams);
synchronized (mLock) {
int index = findViewLocked(view, true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index, wparams);
root.setLayoutParams(wparams, false);
//1.更新View的LayoutParams替换老的LayoutParams
}
}
//3.在ViewRootImpl中通过scheduleTraversals方法来对View重新布局,包括测量布局重绘三个过程。
//4.ViewRootImpl还会通过WindowSession来更新Window的视图,IPC的过程,最终使用WindowManagerService的relayoutWindow来实现的。
(三)Window的创建过程
View是Android中视图的呈现方式,但是view不能单独存在,它必须依附在window这个抽象概念上。Android中提供window的地方有Activity、Dialog和Toast。除此之外还有PopUpWindow、菜单等。下面将分析Activity、Dialog和Toast视图元素的创建过程。
3.1.Activity的Window创建过程
3.1.1.创建Window
步骤一:创建PhoneWindow,通过PolicyManager的makenewwindow完成创建,创建的对象是PhoneWindow。
Activity的启动过程首先是理解它的生命周期,最终由ActivityThread中的perfromLaunchActivity()来完成整个启动过程。perfromLaunchActivity的attach方法中(为其关联运行过程中的上下文环境变量),attach方法中系统会创建activity所属的window对象并为其设置回调接口(PolicyManager的makeNewWindow方法实现)。activity实现了window的callback方法接口,因此当window接受到外界的状态改变的时候就会去调用activity的方法。譬如:onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent等等。
3.1.2.Activity视图依附在window的过程
通过setContentView方法将Activity视图附属在window上。Activity将具体实现交给window去处理,window具体实现是phonewindow。
phonewindow的setContentView主要遵循以下几个步骤:
步骤二:如果没有DecorView就去创建它; DecorView是一个FrameLayout,是Activity的顶级View,一般包含标题栏和内部栏content。内容栏有固定id:android.R.id.content。generateDecor方法来完成创建DecorView(空白的),generateLayout方法来加载具体的布局文件到DecorView中。
步骤三:将View添加到DecorView的mContentParent中: 直接将activity的视图添加到DecorView的mContentParent中即可,mLayoutInflater.inflate(layoutResID, mContentParent);Acitivity的布局文件已经添加进Decorview中了。
步骤四:回调Activity的onCreateChanged方法来通知Activity视图已经发生改变。
步骤五:调用Activity的onResume方法,接着会调用Activity的makeVisible(),DecorView才能真正完成添加和显示的过程。
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
3.2.Dialog的Window创建过程
大致与Window的创建过程类似。
3.2.1.创建过程:
步骤一:创建Window。
Dialog(Context context, int theme, boolean createContextThemeWrapper) {
...
mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
Window w = PolicyManager.makeNewWindow(mContext);
mWindow = w;
w.setCallback(this);
w.setOnWindowDismissedCallback(this);
w.setWindowManager(mWindowManager, null, null);
w.setGravity(Gravity.CENTER);
mListenersHandler = new ListenersHandler(this);
}
步骤二:初始化DecorView并将Dialog的视图添加到DecorView中。
public void setContentView(int layoutResID) {
mWindow.setContentView(layoutResID);
}
步骤三:将DecorView添加到window并且显示:Dialog的show方法。
mWindowManager.addView(mDecor, l);
mShowing = true;
注意:1.当Dialog被关闭时,通过WindowManager来移除:mWindowManager.removeView
Immediate(mDecor);2.普通的dialog有一个特殊的地方,那就是必须用activity的context,譬如:Dialog dialog = new Dialog(this); dialog.setTitle("Hello"); dialog.show();3.可以将Dialog设置为系统window。设定类型为TYPY_SYSTEM_OVERLAY;设定Mainfest.xml的权限。
Dialog.getWindow.setType(LayoutParams.TYPY_SYSTEM_OVERLAY);
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
3.2.2.AlertDialog和ProgressDialog:
//AlertDialog:当前界面弹出一个对话框。置顶于所有元素之上,屏蔽掉其他控件。
btn_click.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
AlertDialog.Builder dialog = new AlertDialog.Builder(MainActivity.this);
dialog.setTitle("This is Dialog")
.setMessage("Something important")
.setCancelable(false)
.setPositiveButton("OK", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
}
});
dialog.show();
}
});
//ProgressDialog:类似,会在对话框显示一个进度条,一般用于表示当前操作比较耗时,让用户耐心等待。
btn_click02.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setTitle("This is progressDialog");
progressDialog.setMessage("loading...");
progressDialog.setCancelable(true);
progressDialog.show();
}
});
3.3.Toast的Window创建过程
首先Toast也是基于Window来实现的,但是由于Toast具有定时取消的功能,所以系统采用了handler,Toast内部有两类IPC的过程,第一类Toast访问NotificationManagerService(NMS),第二类是NMS回调Toast的TN接口。显示和隐藏Toast是通过NMS实现的,IPC机制,
步骤一:Toast提供Show、Cacel方法去隐藏显示Toast。内部是IPC机制。
public void show() {
if (mNextView == null) {
throw new RuntimeException("setView must have been called");
}
INotificationManager service = getService();
String pkg = mContext.getOpPackageName();
//TN是Binder类,在Toast和NMS在IPC过程中,NMS处理Toast显示时会跨进程回调TN里面的方法。
// TN在Binder线程池中,需要Handler将其切换至当前线程中。使用了Handler意味着无法在没有Looper的线程中弹出
TN tn = mTN;
//自定义View。可以是系统默认,可以是自定义View。
tn.mNextView = mNextView;
try {
//三个参数分别是当前应用包名,tn表示远程回调,Toast时长。
//将Toast请求封装至ToastRecord对象并将其添加到一个名为mToastQueue的队列中。
//最多50个ToastRecord,太多来不及弹出,否则将会拒绝服务DOS。
service.enqueueToast(pkg, tn, mDuration);
} catch (RemoteException e) {
// Empty
}
}
步骤二:当ToastRecord对象被添加到mToastQueue的队列中时,通过showNextToastLocked完成Toast的显示,该显示是由ToastRecord的callback来完成的,这个callback实际上就是TN对象的远程Binder,通过callback来访问TN中的方法是需要跨进程来完成的。
void showNextToastLocked() {
ToastRecord record = mToastQueue.get(0);
while (record != null) {
if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
try {
record.callback.show(record.token);
scheduleTimeoutLocked(record);
return;
} catch (RemoteException e) {
Slog.w(TAG, "Object died trying to show notification " + record.callback
+ " in package " + record.pkg);
// remove it from the list and let the process die
int index = mToastQueue.indexOf(record);
if (index >= 0) {
mToastQueue.remove(index);
}
keepProcessAliveIfNeededLocked(record.pid);
if (mToastQueue.size() > 0) {
record = mToastQueue.get(0);
} else {
record = null;
}
}
}
}
步骤三:显示后,调用scheduleTimeoutLocked来发送一个延时消息。LLONG_DELAY是3.5s,SHORT_DELAY是2s。隐藏并移除。
Toast的显示、隐藏实际上是toast的TN这个类来实现的,分别对应的show、hide。TN的handleShow将Toast的视图添加进Window中,handleHide将Toast的视图从Window中移除。
public void show(IBinder windowToken) {
if (localLOGV) Log.v(TAG, "SHOW: " + this);
mHandler.obtainMessage(0, windowToken).sendToTarget();
}
@Override
public void hide() {
if (localLOGV) Log.v(TAG, "HIDE: " + this);
mHandler.post(mHide);
}
//handleshow的核心代码:
mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);
//handlehide的核心代码:
if (mView.getParent() != null) {
if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
mWM.removeViewImmediate(mView);
}
总结:任意View都是附属在一个Window上的,一个应用中的Window的个数=应用Window的个数(Activity) + 子Window的个数(Dialog) + 系统Window的个数(比如Toast,系统提示框)。