《理解Window和WindowManager》——笔记

dex是什么文件本文为学习任玉刚的《Android开发艺术探索》第八章理解Window和WindowManager 所做的笔记。

Window表示一个窗口的概念。
它是一个抽象类,它的具体实现是PhoneWindow。
创建一个Window是很简单的事,只需要通过WindowManager即可完成。

WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService中,WindowManager和WindowManagerService的交互是一个IPC过程。

Android中所有的视图都是通过Window来呈现的,不管是Activity、Dialog还是Toast,它们的视图实际上都是附加在Window上的,因此Window实际上是View的直接管理者。

View的事件分发机制也可以知道,单击事件由Window传递给DecorView,然后再由DecorView传递给我们的View,就连Activity的设置视图的方法setContentView在底层也是通过Window来完成的。

Window和WindowManager

代码演示了通过WindowManager添加Window的过程

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(mFloatingButton,mLayoutParams):

上面代码将一个Button添加屏幕坐标为(100,300)的位置上。WindowManager.LayutParams中的flags和type这两个参数比较重要。
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。应用类Window对应着一个Activity.子Window不能单独存在,它需要附属在特定的父Window之中,比如常见的一些Dialog就是一个子Window。系统Window是需要声明权限才能创建的Window,比如Toast和系统状态栏这些都是系统Window。

Window是分层的,每个Window都是对应的z-ordered,层级大的会覆盖层级小的Window的上面,这和HTML中的z-index的概念是完全一致的。在三类Window中,应用Window的层级范围是1~99,子Window的层级范围是1000-1999,系统Window的层级范围是2000-2999,这些层级范围对应着WindowManager.LayoutParams的type参数。如果想要Window位于所有Window的最顶层,那么采用较大的层级即可。很显然系统Window的层级是最大的,而且系统层级有很多值,一般我们选用TYPE_SYSTEM_OVERLAY或者TYPE_SYSTEM_ERROR,如果采用TYPE_SYSTEM_ERROR,只需要为type参数指定这个层级即可:mLayoutParams.type = LayoutParams.TYPE_SYSTEM_ERROR;同时声明权限:<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
因为系统类型的Window是需要检查权限的,如果不在AndroidManifest中使用相应的权限,那么创建Window的时候就会报错。

WindowManager所提供的功能很简单,常用的只有三个方法,即添加View、更新View和删除View,这三个方法定义在ViewManager中,而WindowManager继承了ViewManager.

Window的内部机制

Window是一个抽象的概念,每一个Window都对应着一个View和一个ViewRootImpl,Window和View通过ViewRootImpl来建立联系,因此Window并不是实际存在的,它是以View的形式存在。

这点从WindowManager的定义也可以看出,它提供的三个接口方法addView、updateViewLayout以及removeView都是针对View的,这说明View才是Window存在的实体。

在实际使用中无法直接访问Window,对Window的访问必须通过WindowManager.

Window的添加过程

Window的添加过程需通过WindowManager的addView来实现,WindowManager是一个接口,它的真正实现是WindowManagerImpl类。

WindowManagerImpl并没有直接实现Window的三大操作,而是全部交给了WindowManagerGlobal来处理,WindowManagerGlobal以工厂的形式提供自己的实例,在WindowManagerGlobal以工厂的形式向外提供自己的实例,在WindowManagerGlobal中有如下一段diamante:private final WindowManagerGlobal = WindowManagerGlobal.getInstance().WindowManagerImpl这种工作模式就是典型的桥接模式,将所有的操作全部委托给WindowManagerGlobal来实现。
WindowManagerGlobal的addView方法主要分为如下几步:

1.检查参数是否合法,如果是子Window那么还需要调整一些布局参数

2.创建ViewRootImpl并将View添加到列表中

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的添加过程

这个步骤由ViewRootImpl的setView方法来完成,View的绘制过程是由ViewRootImpl来完成的,在setView内部会通过requestLayout来完成异步刷新请求。
下面代码中,scheduleTraversals实际是View绘制的入口:

public void requestLayout() {
	if(!mHandlingLayoutInLayoutRequest) {
	checkThread();
	mLayoutRequested = true;
	scheduleTraversals);
	}
}

接着会通过WindowSession最终来完成Window的添加过程。mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,也就是Window的添加过程是一次IPC调用。
在Session内部会通过WindowManagerService来是实现Window的添加。
在WindowManagerService内部会为每一个应用保留一个单独的Session。

Window的删除过程

Window的删除过程和添加过程相同,都是先通过WindowManagerImpl后,再进一步通过WindowManagerGlobal来实现的。
下面代码是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 curView = mRoots.get(index).getView();
removeViewLocked(index,immediate);
if(curView == view) {
return;
}
throw new IllegalStateException("Calling with view " + view + " but the ViewAncestor is attached to " + curView);
}
}

首先通过findViewLocked来查找待删除的View的索引,这个查找过程就是建立的数组遍历,然后再调用removeViewLocked来做进一步的删除。

判断是否为null很重要的,把提供方法的人当做傻子
慢慢体会别人的代码

private void removeViewLocked(int indexs,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和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;
}

在die方法内部只是做了简单的判断,如果是异步删除,那么就发送一个MSG_DIE的消息,ViewRootImpl中的Handler会处理此消息并调用doDie方法,如果是同步删除(立即删除),那么就不发消息直接调用doDie方法,这就是这两种删除方式的区别。在doDie内部会调用dispatchDetachedFromWindow方法,真正删除View的逻辑在dispatchDetachedFromWindow犯法内部实现

dispatchDetachedFromWindow方法主要是做四件事:
(1)垃圾回收相关的工作,比如清除数据和消息、移除回调。
(2)通过Session的remove方法删除Window:mWindowSession.remove(mWindow),这同样是一个IPC过程,最终会调用WindowManagerService的removeWindow方法。
(3)调用View的dispatchDetachedFromWindow方法,在内部会调用View的onDetachedFromWindow()以及onDetachedFromWindowInternal().对于onDetachedFromWindow()大家一定不陌生,当View从Window中移除时,这个方法就会被调用,可以在这个方法内部做一些资源回收的工作,比如终止动画、停止线程等

Window的更新过程

分析Window的更新过程,还是要看WindowManagerGlobal的updateViewLayout方法

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);
synchronized(mLock) {
int index = findViewLocked(view,true);
ViewRootImpl root = mRoots.get(index);
mParams.remove(index);
mParams.add(index,wparams);
root.setLayoutParams(wparams,false);

updateViewLayout方法做得事情就比较简单了,首先它需要更新View的LayoutParams并替换掉老的LayoutParams,接着在更新ViewRootImpl中的LayoutParams,这一步是通过ViewRootImpl的setLayoutParams方法来实现。在ViewRootImpl中会通过scheduleTraversals方法来对View重新布局,包括测量、布局、重绘这三个过程。除了View本身的重绘以外,ViewRootImpl还会通过WindowSession来更新Window的视图,这个过程最终是由WindowManagerService的relayoutWindow()来具体实现的,它同样是一个IPC过程。

Window的创建过程

View是Android中的视图的呈现方式,但是View不能单独存在,必须要附着在Window这个抽象的概念上面,因此有视图的地方就有Window.

Activity的Window创建过程

Activity的启动过程很复杂,最终肯定会由ActivityThread中的performLaunchActivity()来完成整个启动过程,在这个方法内部会通过类加载器创建Activity的实例对象,并调用其attach方法为其关联运行过程中所依赖的一系列上下文环境变量.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值