Android Window 和 WindowManager 解析

一、前言


Window 表示一个窗口,是一个抽象类,它的具体实现是 PhoneWindow。有时候我们需要在桌面上显示一个类似悬浮窗的东西,这种效果就需要用 Window 来实现。

 

二、Window 和 WindowManager 使用及属性


我们对 Window 的操作是通过 WindowManager 来完成的,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);
}

这三个方法其实就是 WindowManager 对外提供的主要功能,即添加 View、更新 View 和删除 View。接下来来看一个通过 WindowManager 添加 Window 的例子,代码如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        Button floatingButton = new Button(this);
        floatingButton.setText("button");
        WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                0, 0,
                PixelFormat.TRANSPARENT
        );
        // flag 设置 Window 属性
        layoutParams.flags= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
        // type 设置 Window 类别(层级)
        layoutParams.type = WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY;
        layoutParams.gravity = Gravity.CENTER;
        WindowManager windowManager = getWindowManager();
        windowManager.addView(floatingButton, layoutParams);

    }
}

代码中并没有调用 Activity 的 setContentView 方法,而是直接通过 WindowManager 添加 Window,其中设置为系统 Window,所以应该添加权限:

效果如下,第二个界面是锁屏界面,由于按钮是处于较大层级的系统 Window 中的,所以可以看到 button:

è¿éåå¾çæè¿°

上述代码将一个 Button 添加到屏幕中央位置。其中 WindowManager.LayoutParams 中的 flags 和 type 这两个参数比较重要,下面对其进行说明:

Flags 参数表示 Window 的属性,它有很多选项,通过这些选项可以控制 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 对应一个 Acitivity,子 Window 不能单独存在,需要依附在特定的父 Window 中,比如常见的一些 Dialog 就是一个子 Window。系统 Window 是需要声明权限才能创建的 Window,比如 Toast 和系统状态栏都是系统 Window。

Window 是分层的,每个 Window 都有对应的 z-ordered,层级大的会覆盖在层级小的 Window 上面,这和 HTML 中的 z-index 概念是完全一致的。在三种 Window 中, 对应的层级范围可以用下面这个表格来直观的表示:

Window  层级
应用 Window 1~99
子 Window 1000~1999
系统 Window 2000~2999

这些层级范围对应着 WindowManager.LayoutParams 的 type 参数,如果想要 Window 位于所有 Window 的最顶层,那么采用较大的层级即可,很显然系统 Window 的层级是最大的,而且系统层级有很多值,一般我们可以选用 TYPE_SYSTEM_OVERLAY 或者 TYPE_SYSTEM_ERROR。当我们采用系统层级时,需要声明权限(上述代码中)。

对开发者来说,WindowManager 常用的就是这三个功能,它可以创建一个 Window 并向其添加 View,还可以更新 Window 中的 View,另外如果想要删除一个 Window,那么只需要删除它里面的 View  即可。由此来看,WindowManager 操作 Window 的过程更像是在操作 Window 中的 View。我们时常见到那种可以拖动的 Window 效果,其他是很好实现的,只需要根据手指的位置来设定 LayoutParams 中的 x 和 y 的值即可改变 Window 的位置。

 

三、Window 内部机制


1、Window 的添加过程

Window 的添加过程需要通过 WindowManager 的 addView 来实现,WindowManager 是一个接口,它的真正实现是 WindowManagerImpl 类。在 WindowManagerImpl 中,Window 的三大操作的代码实现如下:

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

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

1、检查参数合法性,如果是子 Window 还需要调整一些布局参数

if(view == null){
   throw new IllegalArgumentException("view must not be null");
}

if(display == null){
   throw new IllegalArgumentException("display must not 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);
}

 2、创建 ViewRootImpl 并将 View 添加到集合中

在 WindowManagerGlobal 内部有如下几个集合比较重要:

private final ArrayList mViews = new ArrayList();
private final ArrayList mRoots = new ArrayList();
private final ArrayList mParams = new ArrayList();
private final ArraySet mDyingViews = new ArraySet();

在上面的集合中,各自存储的内容如下:

集合     存储内容
mViews Window 所对应的 View
mRoots Window 所对应的 ViewRootImpl
mParams Window 所对应的布局参数
mDyingViews 正在被删除的 View 对象

在 addView 中通过如下方式将 Window 的一系列对象添加到列表中:

root = new ViewRootImpl(view.getContext(),display);
view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparams);

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

在学习 View 的工作原理时,我们知道 View 的绘制过程是由 ViewRootImpl 来完成的,这里当然也不例外,具体是通过 ViewRootImpl 的 setView 方法来实现的。在 setView 内部会通过 requestLayout 来完成异步刷新请求,如下:

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

可以看到 scheduleTraversals 方法是 View 绘制的入口,继续查看它的实现:

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), 
          mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mInputChannel);

在上述代码中,mWindowSession 的类型是 IWindowSession,它是一个 Binder 对象,真正的实现类是 Session,也就是 Window 的添加过程是一次 IPC 调用。在 Session 内部会通过 WindowManagerService 来实现 Window 的添加,代码如下:

public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams, attrs, int viewVisibility, 
            int displayId, Rect outContentInsets, InputChannel outInputChannel){
   return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outInputChannel);
}

 终于,Window 的添加请求移交给 WindowManagerService 手上了,在 WindowManagerService 内部会为每一个应用保留一个单独的 Session,具体 Window 在 WindowManagerService 内部是怎么添加的,就不对其进一步的分析,因为到此为止我们对 Window 的添加这一从应用层到 Framework 的流程已经清楚了,下面通过图示总结一下: 

Android Window 和 WindowManager 解析_第1张图片

理解了 Window 的添加过程,Window 的删除过程和更新过程都是类似的,也就容易理解了,它们最终都会通过一个 IPC 过程将操作移交给 WindowManagerService 这个位于 Framework 层的窗口管理服务来处理。 

2、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 来做进一步的删除,代码如下:

private View removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots[index];
    View view = root.getView();

    if (view != null) {
        InputMethodManager imm = InputMethodManager.getInstance(view.getContext());
        if (imm != null) {
            imm.windowDismissed(mViews.get(index).getWindowToken());
        }
    }
    boolean deffrred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deffrred) {
            mDyingViews.add(view);
        }
    }
}

 removeViewLocked 是通过 ViewRootImpl 来完成删除操作的,在 WindowManager 中提供了两种删除接口 removeView 和 removeViewImmediate,它们分别表示异步删除和同步删除,其中 removeViewImmediate 使用起来需要特别注意,一般来说不需要使用此方法来删除 Window 以免发生意外的错误。这里主要说异步删除的情况,具体的删除操作由 ViewRootImpl 的 die 方法来完成。在异步删除的情况下,die 方法只是发送了一个请求删除的消息后就立刻返回了,这个时候 View 并没有完成删除操作,所以最后会将其添加到 mDyingViews 中,mDyingViews 表示待删除的 View 列表。ViewRootImpl 的 die 方法如下:

public 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 中移除时,这个方法就会被调用,可以在这个方法内部做一些资源回收的工作,比如终止动画、停止线程等。

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

3、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。哪些地方有视图呢?Android 可以提供视图的地方有 Activity、Dialog、Toast,除此之外,还有一些依托 Window 而实现的视图,比如 PopUpWindow(自定义弹出窗口)、菜单,它们也是视图,有视图的地方就有 Window,因此 Activity、Dialog、Toast 等视图都对应着一个 Window。这也是面试中常问到的一个知识点:一个应用中有多少个 Window?下面分别分析 Activity、Dialog以及 Toast 的 Window 创建过程。

1、Activity 的 Window 创建过程

在了解了 Window 的概念及意义后,我们自然就清楚 Activity 的 Window 创建时机,Window 本质就是一块显示区域,所以关于 Activity 的 Window 创建应该发生在 Activity 的启动过程,Activity 的启动过程很复杂,最终会由 ActivityThread 中的 performLaunchActivity() 来完成整个启动过程,在这个方法内部会通过类加载器创建 Activity 的实例对象,并调用其 attach 方法为其关联运行过程中所依赖的一系列上下文环境变量。

Activity 的 Window 创建就发生在 attach 方法里,系统会创建 Activity 所属的 Window 对象并为其设置回调接口,代码如下:

mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);

可以看到, Window 对象的创建是通过 PolicyManager 的 makeNewWindow 方法实现的,由于 Activity 实现了 Window 的 Callback 接口,因此当 Window 接受到外界的状态改变时就会回调 Activity 的方法。Callback 接口中的方法很多,有几个是我们非常熟悉的,如 onAttachedToWindow、onDetachedFromWindow、dispatchTouchEvent 等等。

再回到 Window 的创建,可以看到 Activity 的 Window 是通过 PolicyManager 的一个工厂方法来创建的,但是在 PolicyManager 的实际调用中,PolicyManager 的真正实现是 Policy 类,Policy 类中的 makeNewWindow 方法的实现如下:

public Window  makeNewWindow(Context context){
   return new PhoneWindow(context);
}

可以看出,Window 的具体实现类的确是 PhoneWindow。到这里 Window 已经创建完成了,下面分析 Activity 的视图是怎么附属到 Window 上的,而 Activity 的视图由 setContentView 提供,所以从 setContentView 入手,它的源码如下:

public void setContentView(int layoutResID){
   getWindow().setContentView(layoutResID);
   initWindowDecorActionBar();
}

可以看到,Activity 将具体实现交给了 Window 处理,而 Window 的具体实现是 PhoneWindow,所以只需要看 PhoneWindow 的相关逻辑即可,它的处理步骤如下:

(1)、如果没有 DecorView 就创建一个

DecorView 是 Activity 中的顶级 View,是一个 FrameLayout,一般来说它的内部包含标题栏和内容栏,但是这个会随着主题的变化而改变,不管怎么样,内容栏是一定存在的,并且有固定的 id:”android.R.id.content”,在 PhoneWindow 中,通过 generateDecor 方法创建 DecorView,通过 generateLayout 初始化主题有关布局。

(2)、将 View 添加到 DecorView 的 mContentParent 中

这一步较为简单,直接将 Activity 的视图添加到 DecorView 的 mContentParent 中即可,由此可以理解 Activity 的 setContentView 这个方法的来历了,为什么不叫 setView 呢?因为 Activity 的布局文件只是被添加到 DecorView 的 mContentParent 中,因此叫 setContentView 更加具体准确。

(3)、回调 Activity 的 onContentChanged 方法通知 Activity 视图已经发生改变

前面分析到 Activity 实现了 Window 的 Callback 接口,这里当 Activity 的视图已经被添加到 DecorView 的 mContentParent 中了,于是需要通知 Activity,使其方便做相关的处理。Activity 的 onContentChanged 方法是个空实现,我们可以在子 Activity 中处理这个回调。这个过程的代码如下:

final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
    cb.onContentChanged();
}

经过上面的三个步骤,DecorView 已经被创建并初始化完毕,Activity 的布局文件也已经成功添加到了 DecorView 的 mContentParent 中,但是这个时候 DecorView 还没有被 WindowManager 正式添加到 Window 中。这里需要正确理解 Window 的概念,Window 更多表示的是一种抽象的功能集合,虽然说早在 Activity 的 attach 方法中 Window 就已经被创建了,但是这个时候由于 DecorView 并没有被 WindowManager 识别,所以这个时候的 Window 无法提供具体功能,因为它还无法接收外界的输入信息。

在 ActivityThread 的 handleResumeActivity 方法中,首先会调用 Acitivy 的 onResume 方法,接着会调用 Acitivy 的 makeVisible() 方法,正是在 makeVisible 方法中,DecorView 才真正的完成了显示过程,到这里 Activity 的视图才能被用户看到,代码如下:

void makeVisible(){
   if(!mWindowAdded){
      ViewManager wm = getWindowManager();
      wm.addView(mDecor, getWindow().getAttributes());
      mWindowAdded = true;
   }
   mDecor.setVisibility(View.VISIBLE);
}

2、Dialog 的 Window 创建过程

Dialog 的 Window 的创建过程与 Activity 类似,步骤如下:

(1)、创建 Window

Dialog 中 Window 同样是通过 PolicyManager 的 makeNewWindow 方法来完成的,创建后的对象也是 PhoneWindow。

(2)、初始化 DecorView 并将 Dialog 的视图添加到 DecorView 中

这个过程也和 Activity 类似,都是通过 Window 去添加指定布局文件:

public void setContentView(int layoutResID){
   mWindow.setContentView(layoutResID);
}

(3)、将 DecorView 添加到 Window 中并显示

在 Dialog 的 show 方法中,会通过 WindowManager 将 DecorView 添加到 Window 中,如下:

mWindowManager.addView(mDecor, 1);
mShowing = true;

从上面三个步骤可以发现,Dialog 的 Window 创建过程和 Activity 创建过程很类似,当 Dialog 关闭时,它会通过 WindowManager 来移除 DecorView。普通的 Dialog 必须采用 Activity 的 Context,如果采用 Application 的 Context 就会报错。这是因为没有应用 token 导致的,而应用 token 一般只有 Activity 拥有,另外,系统 Window 比较特殊,可以不需要 token。

3、Toast 的 Window 创建过程

Toast 与 Dialog 不同,它的工作过程稍显复杂,首先 Toast 也是基于 Window 来实现的,但是由于 Toast 具有定时取消这一功能,所以系统采用了 Handler。在 Toast 内部有两类 IPC 过程,一类是 Toast 访问 NotificationManagerService,第二类是 NotificationManagerService 回调 Toast 里的 TN 接口。NotificationManagerService 同 WindowManagerService 一样,都是位于 Framework 层的服务,下面简称 NotificationManagerService 为 NMS。

Toast 属于系统 Window,它内部的视图可以是系统默认样式,也可以通过 setView 方法来指定一个自定义 View,不管如何,它们都对应 Toast 的内部成员 mNextView,Toast 提供 show 和 cancel 分别用于显示和隐藏 Toast,它们内部是一个 IPC 过程,代码如下:

    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 请求所在的线程。注意,由于这里使用了 Handler,所以意味着 Toast 无法在没有 Looper 的线程中弹出,这是因为 Handler 需要使用 Looper 才能完成切换线程的功能。

在 Toast 的显示过程中调用了 NMS 中的 enqueueToast 方法, enqueueToast 方法内部将 Toast 请求封装为 ToastRecord 对象并将其添加到一个名为 mToastQueue 的队列中,mToastQueue 其实是一个 ArrayList,对于非系统应用来说,mToastQueue 中最多能同时存在 50 个 ToastRecord,用于防止 DOS (Denial of Service 拒绝服务)。

当 ToastRecord 添加到 mToastQueue 中后,NMS 就会通过 showNextToastLocked 方法来顺序显示 Toast,但是 Toast 真正的显示并不是在 NMS 中完成的,而是由 ToastRecord 的 callback 来完成的:

void showNextToastLocked (){
   ToastRecord record = mToastQueue.get(0);
   while(record != null){
       if(DBG) 
          Slog.d(TAG,"show pkg=" + record.pkg + "callback=" + record.callback);
       try{
          record.callback.show();
          scheduleTimeoutLocked(record);
          return;
        }

       ...

}

这个 callback 就是 Toast 中的 TN 对象的远程 Binder,最终被调用的 TN 中的方法会运行在发起 Toast 请求的应用的 Binder 线程池中,从以上代码可以看出,Toast 显示以后,NMS 还调用了 sheduleTimeoutLocked 方法,此方法中首先进行延时,具体的延时时长取决于 Toast 的显示时长,延迟相应时间后,NMS 会通过 cancelToastLocked 方法来隐藏 Toast 并将它从 mToastQueue 中移除,这时如果 mToastQueue 中还有其他 Toast,那么 NMS 就继续显示其他 Toast。Toast 的隐藏也是通过 ToastRecord 的 callback 来完成的,同样也是一次 IPC 过程。

从上面的分析,可以知道 NMS 只是起到了管理 Toast 队列及其延时的效果,Toast 的显示和隐藏过程实际上是通过 Toast 的 TN 类来实现的,TN 类的两个方法 show 和 hide,是被 NMS 以跨进程的方式调用的,因此它们运行在 Binder 线程池中,为了将执行环境切换到 Toast 请求所在的线程,在它们内部使用了 Handler。

Toast 毕竟是要在 Window 中实现的,因此它最终还是要依附于 WindowManager,TN 的 handleShow 中代码如下:

mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
mWM.addView(mView, mParams);

TN 的 handleHide 方法同样需要通过 WindowManager 来实现视图的移除,这里就不再贴出。

 

五、总结


下面让我们再次认清一些概念:任何 View 都是附属在一个 Window 上面的,Window 表示一个窗口的概念,也是一个抽象的概念,Window 并不是实际存在的,它是以 View 的形式存在的。WindowManager 是外界也就是我们访问 Window 的入口,Window 的具体实现位于 WindowManagerService 中,WindowManagerService 和 WindowManager 的交互是一个 IPC 过程。相信读完本文后,对 Window 会有一个更加清晰的认识,同时能够深刻理解 Window 和 View 的依赖关系。

你可能感兴趣的:(Android)