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

几个概念:
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()----->
ViewRoot
Impl.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
@Override
public void show(){
    if(localLOG) Log.v(TAG."SHOW: "+this);
    mHandler.post(mShow);
}

//schedule handleHide into the right thread
@Override
public 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();

你可能感兴趣的:(Android,Foundation,android,android开发,读书笔记)