您的位置:首页 > 移动开发 > Android开发

《Android 开发艺术探索》笔记——(8)Window 和 WindowManager

2016-05-02 15:16 597 查看
Android 中的 Window 有两种概念,一种是 Window 抽象类,它的具体实现是 PhoneWindow,用来进行操作,比如生成 DecorView,另一种是以 View 为实体的抽象概念,用来显示。

外界访问 Window (显示)要通过 WindowManager,WindowManager 会进一步访问 WindowManagerService(WMS),WMS 再去生成Window(显示),WindowManager 和 WindowManagerService 的交互是一个IPC过程。

WindowManager –> WindowManagerService –> Window

Window 通过 ViewRootImpl 对 View 进行管理。

Window 和 WindowManager

以添加 Window 为例展示 Window 和 WIndowManager 的关系:

WindowManager.addView(view, layoutParams);

可以看到,WindowManager 添加 Window 其实是在添加 View,然后它会生成 View 对应的 ViewRootImpl, View 和 ViewRootImpl 一起就代表了 Window(显示)。上面 layoutParams 是 WindowManager.LayoutParams,其中 flags 和 type 两个参数比较重要。

Flags 表示 Window 的属性,三种常用的:

FLAG_NOT_FOCUSABLE

表示 Window 不需要获取焦点,也不需要输入事件。

FLAG_NOT_TOUCH_MODAL

当前 WIndow 区域之外的点击事件会传递给下面的 Window,一般都会开启。

FLAG_SHOW_WHEN_LOCKED

让 Window 显示在锁屏上。

Type 表示 Window 的类型,有三种,每种 Window 都有不同的层级:

应用 Window (Activity) 层级:1-99

子 Window (Dialog) 层级:1000-1999

系统 Window (Toast 和 系统状态栏) 层级:2000-2999

不同层级范围对应着 WindowManager.LayoutParams 的 type 参数,有很多对应的值。若采用系统 Window, 要声明相应权限,如使用TYPE_SYSTEM_ERROR 就要声明

< user-permission android:name=”android.permission.SYSTEM_ALERT_WINDOW” />。

WindowManager 常用的有三个方法,即添加VIew,更新VIew,删除View。它们定义在 ViewManager 中,WindowManager 继承了 ViewManager。

Window的内部机制

Window 是一个抽象概念,每一个 Window 都对应着一个 View 和 一个 ViewRootImpl。View 是 Window 的实体,通过 ViewRootImpl 与 Window 建立联系。



添加、删除和更新都是通过这样的过程来完成的:

WindowManager(接口)→

WindowManagerImpl (类)→

WindowManagerGlobal (工厂)。

Window 的添加过程

实际上通过 WindowManagerGlobal.addView() 来完成。

过程:

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

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

通过 ViewRootImpl 来更新界面并完成 Window 的添加过程

View 的绘制过程都是由 ViewRootImpl 来完成的,所以这里也由ViewRootImpl 的 setView 方法来完成,setView 内部会通过 requestLayout 来完成异步刷新请求。其中 scheduleTraversals() 就是绘制的入口。

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


接着会通过 WindowSession 最终来完成 Window 的添加过程。WindowSession 的类型是 IWindowSession,它时一个 Binder 对象,真正的实现是 Session,在 Session 内部会通过 WindowManagerService 来实现 Window (显示)的添加。

Window 的删除过程

实际上通过 WindowManagerGlobal.removeView() 来完成。

public void removeView(View view, boolean immediate) {
if (view == null) {
throw new IlleagalArgumentException("view must not be null");
}

synchronized (mLock) {
int index = findViewLocked(view, true);//找到要删除的 view 的索引
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);
}
}


进一步的删除在 removeViewLocked() 中完成,代码如下:

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);
}
}
}


root 的 die 方法根据参数 imediate 判断是异步删除还是同步删除。

异步:die 会发一个消息给 ViemRootImpl 的 Handler, Handler 会处理此消息并调用 doDie 方法。

同步:直接调用 doDie 方法。

若是异步,则 die 方法后 view 并没有被删除,view 将被放入 mDyingViews 中等待删除。

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 的 setLayoutParams 方法来更新 ViewRootImpl 的 LayoutParams。

Window 的创建过程

这里指操作上的 Window

Activity 的 Window 创建过程

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

在 Activity 的 attach() 方法中,系统会通过 PolicyManager.makeNewWindow() 创建 Activity 所属的 Window 对象并为其设置回调接口。

实际中,PolicyManager 的真正实现是 Policy 类,Policy 类中的 makeNewWindow 会返回一个 PhoneWindow 对象。

大致过程如下(==> 为内部,—> 为之后):

performLaunchActivity() ===>

activity = mInstrumentation.newActivity(…) —>

activity.attach() ===>

mWindow = PolicyManager.makeNewWindow(this) ===>

return new PhoneWindow(context);

现在 Window 已经创建完成,下面分析 Activity 的视图是怎样附属在 Window 上的,大致过程如下:

Activity.setContentView() ==>

getWindow.setContentView() ==>

PhoneWindow.setContentView()

PhoneWindow.setContentView() 方法大致有以下步骤:

如果没有 DecorView , 就创建它

将 View 添加到 DecorView 的 mContentParent 中

回调 Activity 的 onContentChanged 方法通知 Activity 视图已经发生变化

Dialog 的 Window 创建过程

Dialog 的 Window 创建过程与 Activity 类似,有一下几个步骤:

创建 Window

初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中

将 DecorView 添加到 WindowManager

Dialog 的 Window 创建过程与 Activity 很类似,几乎没什么区别。当 Dialog 被关闭,它会通过 WindowManager 来移除 DecorView:mWindowManager.removeViewImmediate(mDecor)(同步)。

普通的 Dialog 必须采用 Activity 的 Context,因为需要 Activity 的应用 Token(?)。

Toast 的 Window 创建过程

Toast 的 Window 的创建过程就比较复杂了。由于 Toast 具有定时取消这一功能,所以系统采用了 Handler。

Toast 提供了 show 和 cancel 分别用于显示和隐藏 Toast,它们内部是一个 IPC 过程。

Toast 的显示:

INotificationManager service = getService() —>

service.enqueueToast(pkg, tn, mDuration) ===>

将 Toast 请求封装为 ToastRecord 添加到 mToastQueue 队列中 —>

showNextToastLocked() ===>

ToastRecord.callback.show() (显示) —>

scheduleTimeoutLocked(record) (延时)

Toast 的隐藏与显示类似,也是通过 ToastRecord 的 callback 来完成的。

ToastRecord.callback.hide() (隐藏)

callback 实际上是 Toast 中的 TN 对象的远程 Binder,通过 callback 来访问 TN 中的方法是需要跨进程来完成的,最终被调用的 TN 中的方法会运行在发起 Toast 请求的应用的 Binder 线程池中。

所以,Toast 的显示和隐藏过程实际上是通过 Toast 中 TN 这个类来实现的,它有两个方法 show 和 hide,即显示和隐藏。由于这两个方法被 NotificationManagerService 以跨进程的方式调用的,因此它们运行在 Binder 线程池中。为了将执行环境切换到 Toast 请求所在线程,在它们内部使用了 Handler。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息