理解Window和WindowManager(Android开发艺术探索读书笔记)

来源:互联网 发布:学校屏蔽网络如何破解 编辑:程序博客网 时间:2024/06/05 01:22

几个概念:
Window是一个窗口的概念。

Window是一个抽象类,它的具体实现是PhoneWindow。可以通过WindowManager来完成Window的创建。

Window的具体实现位于WindowMangerService中,WindowManager和WindowManagerService的交互是一个IPC 过程。

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

Window和WindowManager

WindowManager.LayoutParams中的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 也有层级的概念,应用Window的层级范围是1~99,子Window的层级范围是1000~1999,系统Window的层级范围是2000~2999。

WindowManger( extends ViewManager)三大功能
添加View(addView)
更新View(updateViewLayout)
删除View(removeView)

Window的内部机制
Window 和View 通过ViewRootImpl来建立联系。

Window的添加过程
WindowManager的真正实现是WindowManagerImpl,WindowManagerImpl委托给WindowManagerGlobal,

WindowManagerGlobal的addView方法主要分为如下几步:(具体分析看书上的源码)
1.检查参数是否合法,如果是子Window那么需要调整一些布局参数。

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

mViews(Window 对应的Views)
mRoots(Window对应的ViewRootImpls)
mParams(所有Window的布局参数)
mDyingViews(正在被删除的Views)

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

ViewRootImpl.setView()---->ViewRootImpl.requestLayout()---->ViewRootImpl.scheduleTraversals()

4.接着通过WindowSession最终来完成Window的添加过程,WindowSession内部通过WindowManagerService来实现Window的添加,Window的添加是一个IPC过程。

WindowSession.addToDispaly()---->WindowManagerService.addWindow()

在WindowManagerService内部会为每一个应用保留一个单独的Session。

Window的删除过程

WindowMangerImpl.removeView()---->WindowMangerGlobal.removeView()---->WindowMangerGlobal.removeViewLocked(int index=WindowMangerGlobal.findViewLocked())---->ViewRootImpl.die()---->ViewRootImpl.doDie()---->ViewRootImpl.dispatchDetachedFromWindow()

dispatchDetachedFromWindow方法主要做了四件事:
(1)垃圾回收相关的工作,比如清除数据和消息,移除回调。

(2)通过Session 的remove方法删除Window:mWindowSession.remove(mWindow),这同样是一个IPC过程,最终会调用WindowManagerService的removeWindow方法。

(3)调用View的dispatchDetachedFromWindow方法,在内部会调用View的onDetachedFromWindow()以及onDetachedFromWindowInternal()。对于onDetachedFromWindow()大家一定不陌生,当View从Window中移除时,这个方法就会被调用,可以在这个方法内部做一些资源回收的工作,比如终止动画、停止线程等。

(4)调用WindowManagerGlobal的doRemoveView方法刷新数据,包括mRoots.mParams以及mDyingViews,需要将当前Window所关联的这三类对象从列表中删除。

Window的更新过程

WindowManagerGlobal.updateViewLayout()----->ViewRootImpl.setLayoutParams()---->ViewRootImpl.scheduleTraversals()---->(measure,layout,draw)---->IPC调用WindowMangerService.relayoutWindow();

Window的创建过程

1.Activity的Window创建的过程

调用链:

ActivityThread.performLaunchActivity()---->Activity.attach()---->PolicyManager.makeNewWindow()

Activity的启动过程很复杂,最终会由ActivityThread中的performLaunchActivity()来完成整个启动过程。

在Activity的attach方法中,创建PhoneWindow对象和为其设置回调接口,比如onAttachedToWindow,onDetachedFromWindow,dispatchTouchEvent.

到这里,Window 已经创建完成了。下面分析Activity的视图是怎么附属在Window上的。

Activity.setContentView()---->PhoneWindow.setContentView()

PhoneWindow大致遵循以下几个过程:

1。如果没有DecorView, 那么就创建它。

为了初始化DecorView 结构,PhoneWindow还需要通过generateLayout 方法来加载具体的布局文件到DecorView中,具体的布局文件和系统版本以及主题有关。

DecorView.generateDecor()---->DecorView.generateLayout()

2.将View添加到DecorView的mContentParent中

3.回调Activity的onContentChanged方法通知Activity视图已经发生改变。
调用onContentChanged

经过以上三个步骤,含有Content 的DecorView已经创建完成,但是这个时候DecorView还没被WindowManager正式添加到Window中。还要经过以下流程:

ActivityThread.handleResumeActivity()---->回调Activity.onResume()---->Activity.makeVisible()----->WindowManager.addView(mDecor)

2.Dialog的Window创建过程
Dialog的Window的创建过程和Activity类似,有如下几个步骤:

1.创建Window
PolicyManager.makeNewWindow()

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

Dialog.setContentView()---->PhoneWindow.setContentView()

3.将DecorView添加到Window中并显示(WindowManager.addView(mDecor) ),当Dialog被关闭时,它会通过WindowManager来移除DecorView:mWindowManager.removeViewImmediate(mDecor)

普遍的Dialog有一个特殊之处,那就是必须要采用Activity的context,如果使用Application的Context,就会报没有应用token的Exception,因为应用token一般只有Activity才有,如果使用系统Window 就没有上述问题,不过要修改Window type和加入相应权限。

3.Toast的Window创建过程

Toast属于系统Window

public void show(){    if(mNextView == null){        throw new RuntimeException("setView must have been called");        INotificationManager service = getService();        String pkg = mContext.getOpPackageName();        TN tn = mTN;        tn.mNextView = mNextView;        try{        service.enqueueToast(pkg,tn,mDuration);        } catch (RemoteException  e){            //Empty;        }    }}public void cancel(){    mTN.hide();    try{        getService().cancelToast(mContext.getPackageName(),mTN);    } catch (RemoteException e){        // Empty    }}

从上面的代码可以看到,显示和隐藏Toast都需要NMS来实现,由于NMS运行在系统的进程中,所以只能通过远程调用的方式来显示和隐藏Toast。需要注意的是TN这个类,它是一个Binder类,在Toast和NMS进行IPC的过程中,当NMS处理Toast的显示或隐藏请求时会垮进程回调TN中的方法,这个时候由于TN运行在Binder线程池中,所以需要通过Handler将其切换到当前线程中,所以这意味着Toast无法在没有Looper的线程中弹出,这是因为Handler需要使用looper才能完成切换线程的功能。

先看NMS的Toast的显示过程到隐藏过程:

NMS.enqueueToast()---->NMS.showNextToastLocked()---->(TN)callback.show()、scheduleTimeoutLocked()--handler-->NMS.cancelToastLocked()---->callback.hide()

enqueueToast首先将Toast请求封装为ToastRecord对象并将其添加到一个名为mToastQueue的队列中。mToastQueue其实是一个ArrayList。对于非系统应用来说,mToastQueue中最多能同时存在50个ToastRecord,这样做是为了防止DOS(Denial of Service)。

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

Toast的隐藏也是通过ToastRecord的callback来完成的,这同样也是一次IPC过程。

通过上面的分析,Toast的显示和影响过程实际上是通过Toast中的TN这个类来实现的,它有两个方法show和hide,分别对应Toast的显示和隐藏。由于这两个方法是被NMS以跨进程的方式调用的,因此它们 运行在Binder线程池中。为了将执行环境切换的Toast请求所在的线程,在它们的内部使用了Handler。

//schedule handleShow into the right thread@Overridepublic void show(){    if(localLOG) Log.v(TAG."SHOW: "+this);    mHandler.post(mShow);}//schedule handleHide into the right thread@Overridepublic void hide(){    if(localLOG) Log.v(TAG."HIDE: "+this);    mHandler.post(mHide);}

上述代码中,mShow和mHide是两个Runnale,它们内部分别调用了handleShow和撼动了Hide方法,由此可见,handleShow和handleHide才是真正完成显示和隐藏Toast的地方。TN的handleShow中会将Toast的视图添加到Window中

callback.show()----->Handler.post(mShow)---->TN.handleShow()--->WindowManager.addView();
callback.hide()----->Handler.post(mHide)---->TN.handleHide()---->WindowManager.removeView();
0 0
原创粉丝点击