理解Window和WindowManager
Window和WindowManager
Window
Window是一个抽象类,它的具体实现是PhoneWindow。我们可以通过WindowManager创建一个Window。WidowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。
Android中的所有视图都是通过Window来呈现的,不管是Activity、Dialog还会Toast,他们的视图实际上都是附加在Window上的,因此Window实际上是View的直接管理者。
我们来使用WindowManger在向布局中添加一个按钮:具体实现如下
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
WindowManager manager=getWindowManager();
Button bt=new Button(this);
bt.setText("button");
WindowManager.LayoutParams params=
new WindowManager.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,0,0,
PixelFormat.TRANSPARENT
);
params.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
params.gravity= Gravity.LEFT|Gravity.TOP;
params.x=100;
params.y=300;
manager.addView(bt,params);
}
其中flags和type这两个参数比较重要。
flags
Flags表示Window的属性,它有多选项,通过这些选项可以控制Window的显示特性。
FLAG_NOT_FOCUSABLE
表示Window不需要获取焦点,也不需要接受各种输入时间,此标记会同时启用FLAG_NOT_TOUCH_MODAL,最终时间会直接传递给下层的具有焦点的Window。
FLAG_NOT_TOUCH_MODAL
在此模式下,系统会将当前Window区域以外的单击事件传递给底层的Window,当前Window区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他Window将无法收到单击事件。
FLAG_SHOW_WHEN_LOCKED
开启此模式可以让Window显示在锁屏的界面上。
type
type参数表示Window的类型,Window有三种类型,分别是应用Window、子Window和系统Window。
应用类Window对应着一个Activity。
子类不能单独存在,他需要负数在特定的父Window中,比如常见的一些Dialog就是一个子Window。
系统Window是需要声明权限才能创建的Widow,比如Toast和系统状态栏都是系统Window。
此外,系统Window使用需要在AndroidMainfest中使用相应的权限。
WindowManager
WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager;
public interface ViewManager {
void addView(View var1, LayoutParams var2);
void updateViewLayout(View var1, LayoutParams var2);
void removeView(View var1);
}
我们还使用上面那个例子:我们来实现一个可以拖动的Window,我们来给按钮加一个监听:
bt.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent ev) {
int rawX= (int) ev.getRawX();
int rawY= (int) ev.getRawY();
switch (ev.getAction()){
case MotionEvent.ACTION_MOVE:
params.x=rawX;
params.y=rawY;
/**在这里不断更新按钮的位置*/
manager.updateViewLayout(bt,params);
break;
}
return false;
}
});
Window的内部机制
Window是一个抽象的概念,是以View的形式存在。通过WindowManager来操作View。
Window的添加过程
Window的添加过程需要通过WindowManager的addView来实现,WindowManager是一个接口。它的实现类是WindowManagerImpl类。WindowManagerImpl并没有直接实现Window的三大操作,而是通过WindowManagerGloba来处理。
a. WindowManagerGlobal中的addView
b. 检查参数是否合法;
c. 如果子Window还需要调节布局参数;
d. 创建ViewRootImpl并将View添加到列表中;
e. 通过ViewRootImpl的setView来更新界面并完成Window的添加过程:
requestLayout中的scheduleTraversals是View绘制的入口,最终通过WindowSession来完成Window的添加过程,注意其实这里是个IPC过程,最终会通过WindowManagerService的addWindow方法来实现Window的添加。
Window的删除过程
Window的删除过程和添加过程一样,都是先通过WindowManagerImpl后,再进一步通过WindowManagerGloba来实现的。
a. WinodwManagerGlobal中的removeView;
b. findViewLocked来查找待删除待View的索引,再调用removeViewLocked来做进一步删除;
c. removeViewLocked通过ViewRootImpl的die方法来完成删除操作,包括同步和异步两种方式,同步方式可能会导致意外的错误,不推荐,一般使用异步的方式,其实就是通过handler发送了一个删除请求,将View添加到mDyingViews中;
d. die方法本质调用了doDie方法,真正删除View的逻辑在该方法的dispatchDetachedFromWindow方法中,主要做了四件事:垃圾回收,通过Session的remove方法删除Window,调用View的dispatchDetachedFromWindow方法同时会回调View的onDetachedFromWindow以及onDetachedFromWindowInternal,调用WindowManagerGlobal的doRemoveView刷新数据。
具体需要研究源码。
Window的更新过程
Window的更新还是通过WindowManagerGloba的updateViewLayout方法。
a. WindowManagerGlobal的updateViewLayout;
b. 更新View的LayoutParams;
c. 更新ViewImple的LayoutParams,实现对View的重新测量,布局,重绘;
d. 通过WindowSession更新Window的视图,WindowManagerService.relayoutWindow()。
Window的创建过程
Activity的Window创建过程
当我们启动一个新的Activity时,相当于重新创建一个Activity。我们来具体分析下:
1、在Activity的attach中,系统会创建Activity所属的Window并为其设置回调(CallBack)。
2、Window是通过PolicyManager工厂类中的makeNewWindw()方法创建
3、makeNewWindw()方法中,通过new PhoneWindow()返回Window对象,Window的具体实现类是PhoneWindow。
4、Window创建好之后,通过PhoneWindow的setContentView将Activity与Window进行关联,这个方法大致步骤:
(1)创建DecorView(是Activity中的顶级View),由installDecor方法来完成,在方法内部会通过generateDecor方法直接创建DecorView。它的id是android.R.id.content。就是一个空白的FrameLayout
(2)将View添加到DecorView的mContentParent中,通过LayoutInflate.inflate()来添加。
(3)回调Activity的onContentChange方法通知Activity视图已经发生变化
(4) Activity onResume的时候会调用Activity的makeVisible方法真正完成DecorView的添加和显示。
Dialog的Window创建过程
Dialog的Window的创建过程和Activity类似,有以下步骤:
1.创建Window
通过PolicyManager的makeNewWindow方法创建Window;
2、初始化DecorView并将Dialog的视图添加到DecorView中
通过setContentView添加视图
3、将DecorView添加到Window中并显示
在Dialog的show方法中,会通过WindowManager将DecorView添加到Window中。
当Dialog关闭时,会通过WindowManager来移除DecorView
mWindowManager.removeViewImmediate(mDecor);
这里需要注意的是:普通的Dialog必须采用Activity的Context,如果采用Application的Context,会报错。(没有应用token的错误,token一般只有Activity拥有)
Toast的Window创建过程
Toast和Dialog不同,但也是基于Window来实现的,但是Toast有定时取消功能,所以系统采用了Handler。
在Toast的内部有两类IPC过程,
第一类是Toast访问NotifycationManagerService(NMS)
第二类是NofitycationManager回调Toast里的TN接口。
Toast属于系统Window,它内部的视图由两种方式指定:
1、系统默认样式,内部视图mNextView一种为系统默认样式
2、通过setView方法指定一个自定义View
Toast的显示和隐藏:
Toast的显示和隐藏都需要通过NMS来实现,由于NMS运行在系统的进程中,所以只能通过远程调用的方式来显示和隐藏Toast。
TN是一个Binder类,当NMS处理Toast的显示和隐藏请求时会跨进程回调TN中的方法,由于NMS运行在Binder线程池中,所以需要通过Handler将其切换到当前线程中。注意::由于这里使用了Handler,所以这意味着Toast无法再没有Looper的线程中弹出,这是因为Handler需要使用Looper才能完成切换线程的功能。
Toast显示时,调用了NMS中的enqueueToast方法。它内部首先将Toast请求封装为ToastRecord对象并将其添加到一个名为mToastQueue的队列中。(非系统应用最多塞50个)
当ToastRecord被添加到mToastQueue中后,NMS就会通过showNextToastLocked的callback来完成的,Toast显示由ToastRecord的callback方法中的show方法完成。这个callback实际上就是Toast中的TN对象的远程Binder所以最终调用的是TN中的方法,并运行在发起Toast请求应用的Binder线程池中。
Toast显示以后,NMS还会通过scheduleTimeoutLocked方法来发送一个延时消息(具体的延时取决于Toast的时长),延时后NMS会通过cancelToastLocked方法来隐藏Toast并将其从mToastQueue中移除。隐藏是通过ToastRecord的callback中的hide方法来实现的。
callback回调TN的show和hide方法后,会通过handler发送两个Runnable,里面的handleShow和handleHide方法是真正完成显示和隐藏Toast的地方。handleShow方法中将Toast的视图添加到Window中,handleHide方法将Toast视图从Window中移除。