Android中Window 和 WindowManager
Window 是一个抽象类,具体实现是 PhoneWindow,通过 WindowManager 创建。
WindowManager是外界访问Window的入口,Window 的具体实现位于 WindowManagerService 中
WindowManager 和 WindowManagerService 的交互是一个 IPC 的过程
Andorid 中所有的视图都是通过 Window 来呈现的
不管是 Activity 、Dialog 还是 Taost
因此, Window 实际是 View 的直接管理者 !!!
1. Window 和 WindowManager
WindowManager 添加 Window 的过程
将一个 Button 添加到屏幕坐标为(100,,300)的位置上。其中,Flags 和 type 这两个参数比较重要。
mFloatingButton = new Button(this);
mFloatingButton.setText("button");
mLayoutParams = new WindowManager.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 (mFloatignButton, mLayoutParams);
Flags 参数表示 Window 的属性
FLAG_NOT_FOCUSABLE
表示Window 不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用 FLAG_NOT_TOUCH_MODAL ,最终事件会直接传递给下层的具体焦点的 Window
FLAG_NOT_TOUCH_MODAL
在此模式下,系统会将当前 Window 区域以外的单击事件传递给底层的 Window ,当前 Window 区域以内的单击事件则自己处理。这个标记很重要,一般来说都需要开启此标记,否则其他 Window 将无法收到单击事件。
FLAG_SHOW_WHEN_LOCKED
开启此模式可以让 Window 显示在锁屏的界面上。
Type 参数表示 Window 的类型, 应用 Window 、子 Window 和 系统 Window
- 应用 Window 对应着一个 Activity , 层级 1-99
- 子Window 不能单独存在,需要依附在特定的父Window之中 , 层级 1000-1999
系统Window 是需要声明权限在能创建的 Window, 比如 Toast 和 系统状态栏这些都是系统 Window , 2000-2999
一般选用 TYPE_SYSTEM_OVERLAY 或者 TYPE_SYSTEM_ERROR
注: Window 是分层的,每个Window 都有对应的 z-ordered , 层级大的会覆盖在层级小的 Window 的上面。
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 的位置。
首先给 View 设置 onTouchListener : mFloatingButton.setOnTouchListener(this)
然后在onTouch 方法中不断更新View 的位置即可。
public boolean onTouch(View v, MotionEvent envet){
int rawX = (int) event.getRawX();
int rawY = (int) event.getRwaY();
switch(event.getAction()){
case MotionEvent.ACTION_MOVE:{
mLayoutParams.x = rawX;
mLayoutParams.y = rawY;
mWindowManager.updateViewLayout(mFloatingButton, mLayoutParams);
break;
}
default:
break;
}
return false;
}
2. Window 的内部机制
Window 是一个抽象的概念,每一个Window 都对应着一个 View 和一个 ViewRootImpl 。
Window 和 View 通过 ViewRootImpl 来建立联系,因此Window 并不是实际存在的,它是以View 的形式存在。
View 才是 Window 存在的实体。
1. Window 的添加过程
Window 的添加过程需要通过 WindowManager 的 addView 来实现, WindowManager 是一个接口,它的真正实现是 WindowManagerImpl 类。
@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 来处理
WindowManagerGlobal 以工厂的形式向外提供自己的实例。
WindowManagerGlobal 的 addView 方法主要分为如下步
检查参数是否合法,如果是子 Window 那么还需要调整一些布局参数
if (view == null){
throw new IllegalArgumentException("view must not be null");
}
if (display == null){
throw new IllegalArgumentException("display must 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 并将 View 添加到列表中
在 WindowManagerGlobal 内部有如下几个列表比较重要:
private final ArrayList<Viwe>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> mDyingVies = 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);
mRoot.add(root);
mParams.add(wparams);
通过 ViewRootImpl 来更新界面并完成 Window 的添加过程
由ViewRootImpl 的 setView 方法来完成。View 的绘制过程是由 ViewRootImpl 来完成的。
setView 内部通过 requestLayout 来完成异步刷新请求。
public void requestLayout(){
if (!mHandlingLayoutInLayoutRequest){
checkThread();
mLayoutRequested = true;
// 实际是VIEW 绘制的入口
scheduleTraversals();
}
}
接着会通过 WindowSession 最终来完成 Window 的添加过程。
在下面的代码中 mWindowSession 的类型是 IWindowSession , 它是一个 Binder 对象,真正实现类是 Session, 也就是 Window 的添加过程是一次IPC 调用
try{
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindsSeesion.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mInputChannel);
}catch (RemoteException e){
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
mInputChannel = null;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
throw new RuntimeException("Adding window failed", e);
}
在 Session 内部会通过 WindowManagerService 来实现 Window 的添加
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outCotentInsets,
InputChannel outInputChannel){
return mService.addWindow(this, window, seq, attrs, viewVisibility, disalayId, outContentInsets, outInputChannel);
}
如此一来,Window 的添加请求就给 WindowManagerService 去处理了。在WindowManagerService 内部会为每一个应用保留一个单独的 Session.
2. Window 的删除过程
先通过 WindowManagerImpl后,再进一步通过 WindowManagerGlobal 来实现的。
public void removeView(View view , boolean immediate){
if (view == null){
throw new IllegalArgumentException("view must not be null");
}
synchronized(mLock){
// 先通过 findViewLocked 来查找待删除的 View 的索引,建立数组遍历
int index = findViewLocked(view, true);
View curView = mRoots.get(index).getView();
// 调用removeViewLocked 来做进一步的删除
removeViewLocked(index, immediate);
if (curView == view){
return;
}
throw new IllegalStateExceotion("Calling with view " + view + "but the
ViewAncestor is attached to " + curView);
}
}
private void removeViewLocked(int index, boolean immediate){
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());
}
}
boolean deferred = root.die(immediate);
if(view != null){
view.assignParent(null);
if (deferred){
mDyingViews.add(view);
}
}
}
removeViewLocked 是通过 ViewRootImpl 来完成删除操作。
WindowManager 中提供了两种删除接口 removeView 和 removeImmdiate
分别代表 异步删除和 同步删除
3. Window 的更新过程
由WindowManagerGlobal 的 updateViewLayout 方法实现。
4. Window 的创建过程
Activity 的 Window 是通过 PolicyManager 的一个工厂方法来创建的。
Activity 的视图是如何附属在 Window 上,由于Activity 的视图由setContentView 方法提供。
public void setContentView(int layoutResID){
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
这里可以看出,Activity 将具体实现交给了 Window 处理,而Window 的具体实现是 PhoneWindow .
这里主要看 PhoneWindow
如果没有 DecorCView ,那么就创建它
DecorView 是一个 FrameLayout , 是Activity 中的顶级 View , 一般来说它的内部包含标题栏和内部栏。
不管怎样,内容栏是一定要存在的,并且内容来具体固定的 id ,那就是 “ content" .完整的 id 是 android.R.content.
DecorView 的创建过程由 installDecor 方法来完成,在方法内部会通过 generateDecor 来直接创建 DecorView ,这时其还是一个空白的FrameLayout:
protected DecorView generateDecor(){
return new DecorView(getContext(), -1);
}
为了初始化 DecorView 的结构,PhoneWindow 还需要通过 generateLayout 方法来加载具体的布局文件到 DecorView 中。
View in = mLayoutInflater.inflate(layoutResource, null);
decor.addView(in , new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
mContentRoot = (ViewGroup) in;
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 该id 对应的ViewGroup 就是 mContentParent
public static final inT ID_ANDROID_CONTENT = com.android.internal.R.id.content
- 将View 添加到 DecorView 的 mContentParent 中
// 直接将 Activity 的视图添加到 Decorview 的 mContentParent 中
mLayoutInflater.inflate(layoutResID, mContentParent)
// Activity 的布局文件被添加到 DecorView 的 mContentParent中, setContentView
- 回调Activity 的 onContentChanged 方法通知 Activity 视图已经改变
final Callback cb = getCallback();
if (cb != null && !isDestroyed()){
cb.onContentChanged();
}
在 makeVisible 方法中,DecorView 真正地完成了添加和显示这两个过程,此时 Activity 的视图才能被用户看到。
void makeVisible(){
if (!mWindowAdded){
ViewManager wm = getWindiowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisihility(View.VISIBLE);
}
在 makeVisible 方法中,DecorView 真正地完成了添加和显示这两个过程,此时 Activity 的视图才能被用户看到。
void makeVisible(){
if (!mWindowAdded){
ViewManager wm = getWindiowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisihility(View.VISIBLE);
}