黑基网 首页 学院 编程开发 查看内容

浅析Android的窗口

2016-2-3 11:32| 投稿: lofor

摘要: 一、窗口的概念在开发过程中,我们经常会遇到,各种跟窗口相关的类,或者方法。但是,在 Android 的框架设计中,到底什么是窗口?窗口跟 Android Framework 中的 Window 类又是什么关系?以手Q 的主界面为例,如下图 ...

一、窗口的概念

在开发过程中,我们经常会遇到,各种跟窗口相关的类,或者方法。但是,在 Android 的框架设计中,到底什么是窗口?窗口跟 Android Framework 中的 Window 类又是什么关系?以手Q 的主界面为例,如下图所示,上面的状态栏是一个窗口,手Q 的主界面自然是一个窗口,而弹出的 PopupWindow 也是一个窗口,我们经常使用的 Toast 也是一个窗口。像 Dialog,ContextMenu,以及 OptionMenu 等等这些都是窗口。这些窗口跟 Window 类的关系是什么,或者窗口跟 Window 类描述的是同一个概念吗?

其实窗口的概念,从不同的角度来看,其含义是不一样的。我们知道,WindowManagerService(后面简称 WmS)管理所有的窗口。但是对于WmS来讲,一个窗口其实就是一个 View 类,而不是 Window 类。WmS 负责管理这些 View 的 Z-order,显示区域,以及把消息派发到对应的 View 中。View 本身并不能直接从 WmS 中接收消息,而是通过实现了 IWindow 接口的 ViewRootImpl.W 类来实现,以下是这些类的关系:

所以这里窗口分为两层概念:

(1)WmS 眼中的,窗口是可以显示用来显示的 View。对于 WmS 而言,所谓的窗口就是一个通过 WindowManagerGlobal.addView()添加的 View 罢了;<br>

(2)Window 类是一个针对窗口交互的抽象,也就是对于 WmS 来讲所有的用户消息是直接交给 View/ViewGroup 来处理的。而 Window 类把一些交互从 View/ViewGroup 中抽离出来,定义了一些窗口的行为,例如菜单,以及处理系统按钮,如“Home”,“Back”等等。由此可见,Window 描述的窗口只是在通用窗口的基础上,再抽象了一层,把符合某种规范的窗口统一了一下。Window 所描述的窗口,应该是通用窗口的一个子集。例如 PopupWindow 是一个窗口,但是分析其源码可以知道,该类并没有创建任何 Window 对象。而 Dialog 则是通过 PolicyManager.makeNewWindow(mContext) 创建了一个 Window 对象来管理窗口。当一个 Dialog 显示时,我们可以通过按 back 把它 dismiss 了,但是 PopupWindow 则不行,需要自己去处理。

二、窗口类型

添加一个窗口是通过 WindowManagerGlobal.addView()来完成的,分析 addView 方法的参数,有三个参数是必不可少的,view,params,以及 display。而 display 一般直接取 WindowMnagerImpl 中的 mDisplay,表示要输出的显示设备。view 自然表示要显示的 View,而 params 是 WindowManager.LayoutParams,用来描述这个 view 的些窗口属性,其中一个重要的参数 type,用来描述窗口的类型。

[Java] 纯文本查看 复制代码

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        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");
        }
         .....
      }

分析 WindowManager 对于 type 可赋值类型的描述可知,Framework 中定义了三种类型的窗口:

(1)应用窗口

Activity 对应的窗口就是应用窗口, 所有 Activity 默认的窗口类型是 TYPE _BASE _APPLICATION。WindowManager 的 LayoutParams 的默认构建方法的实现,可以看到默认类型是 TYPE _ APPLICATION。 Dialog 的窗口类型是 TYPE _ APPLICATION,而很多 Dialog 的子类,修改了窗口类似,如 ContextMenu,本质是用 Dialog 来实现的,但是在添加窗口前,修改了 type 类型,赋值为 TYPE _ APPLICATION _ ATTACHED _ DIALOG。从这个我们可以看到,WmS 并没有把应用窗口与子窗口区分得那么清楚。

[Java] 纯文本查看 复制代码

public LayoutParams() {
        super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        type = TYPE_APPLICATION;
        format = PixelFormat.OPAQUE;
    }

(2)子窗口子窗口是指该窗口必须要有一个父窗口,父窗口可以是一个应用类型窗口,也可以是任何其他类型的窗口。例如前面手Q 界面中,点击右上角的按钮显示一个 PopupWindow,它就是一个子窗口,其类型一般 TYPE _ APPLICATION _ PANEL。既然称为子窗口,其与父窗口的关系是比较容易理解的。B 是 A 的子窗口,当 A 不可见时,B 也会不可见的。如果A不可见时添加B,B 也是不可见的,直到 A 可见为止,B 跟随一起可见。

(3)系统窗口

系统窗口跟应用窗口不同,不需要对应 Activity。跟子窗口不同,不需要有父窗口。一般来讲,系统窗口应该由系统来创建的,例如发生异常,ANR时的提示框,又如系统状态栏,屏保等。但是,Framework 还是定义了一些,可以被应用所创建的系统窗口,如 TYPE_ TOAST,TYPE _INPUT _ METHOD,TYPE _WALLPAPTER 等等。

token 的含义

相信大家对于 token 这个词并不陌生,在开发过程中经常遇到,例如 Bad Token 的异常。到底在 Android 框架中,token 代表什么?分析源码,我们发现,大多数 token 的对象,都表示一个 IBinder 对象。提到 IBinder,大家一点也不陌生,就是 Android 的 IPC 通信机制。在创建窗口过程中,涉及到的 IPC 通信,无非包含两方面,一个是 WmS 用来跟应用所在的进程进行通信的 ViewRootImpl.W 类的对象,另一个是指向一个 ActivityRecord 的对象,自然应该是WmS用来跟 AmS 进行通信的了。我们梳理了一下,token 以下几处的定义,分别来讲讲这里的 token 代表什么。

分析一下 View 的 AttachInfo 的赋值。ViewRootImpl 在构建方法里,会初始化一个 AttachInfo 实例,把它的 Session,以及 W类对象赋值给 AttachInfo。分析可以看到,AttachInfo 中的 mWindowToken,与mWindow 都是指向 ViewRootImpl 中的 mWindow(W类实例)。当一个 View attach 到窗口后,ViewRootImpl会执行performTraversals,如果发现是首次调用会,会把自己的 mAttachInfo 传递给根 View(通过dispatchAttachedToWindow),告诉 View 树现在已经 attch to Window 了,马上可以显示了。根 View(一般是 ViewGroup)会把这个信息,遍历地传递给 View 树中的每一个子 View,这样每个 View 的 mAttachInfo 都被赋值为 ViewRootImp 的 mAttachInfo了。

[Java] 纯文本查看 复制代码

//分析一下 View 中的 AttachInfo 的赋值,以及 ViewRootImpl 中的 mAttachInfo
    public ViewRootImpl(Context context, Display display) {
        ...
        mWindow = new W(this);
        ...
         mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this);
        ...
    }
    //看到mWindowToken其实就是IWindow实例
    AttachInfo(IWindowSession session, IWindow window, Display display,
                 ViewRootImpl viewRootImpl, Handler handler, Callbacks effectPlayer) {
           mSession = session;
           mWindow = window;
           mWindowToken = window.asBinder();
           mDisplay = display;
           mViewRootImpl = viewRootImpl;
           mHandler = handler;
           mRootCallbacks = effectPlayer;
    }
    // ViewRootImpl在第一次执行performTraversals时,会把自己的mAttachInfo传递给根View,然后由根View逐级传递下去
    private void performTraversals() {
       ...
        if (mFirst) {
            ...
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            ...
        }else{
            ...
        }
    }
    //ViewGroup.java
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
       mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
       super.dispatchAttachedToWindow(info, visibility);
       mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
       final int count = mChildrenCount;
       final View[] children = mChildren;
       for (int i = 0; i < count; i++) {
           final View child = children[i];
           child.dispatchAttachedToWindow(info,
                   visibility | (child.mViewFlags & VISIBILITY_MASK));
      }
    }
    //View.java
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {
       mAttachInfo = info;
       ...
     }   
[/i]
WindowManager.LayoutParams 中的 type 与 token

WindowManager.LayoutParams 用来描述一个窗口的特性,最终在添加窗口时,会传递给 WmS。而且 WmS 会保存在 WindowState 的 mAttrs 中。LayoutParams 有很多参数,但是跟窗口创建相关的参数,最重要的就是 type 与 token 了,这里我们可以通过分析 WmS 的 addWindow 代码的可以知道:

[Java] 纯文本查看 复制代码

[i]
    //WindowManagerService.java addWindow 
    ...
    //权限检查,需要用到type类型,会检查窗口类型是否合法,如果是系统窗口类型
    //还需要进行权限检查,详见PhoneWindowManager.java
    int res = mPolicy.checkAddPermission(attrs, appOp);
    ...
    //如果是子窗口类型,还会检查其父窗口是否存在,如果父窗口不存在,直接抛出异常
    if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                attachedWindow = windowForClientLocked(null, attrs.token, false);
                if (attachedWindow == null) {
                     Slog.w(TAG, "Attempted to add window with token that is not a window: " + attrs.token + ".  Aborting.");
                     return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                 }
                 if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
                       && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG, "Attempted to add window with token that is a sub-window: " + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                 }
      }
      ...
     //如果是类型是TYPE_PRIVATE_PRESENTATION ,还会检查相应的显示设备
     if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
            Slog.w(TAG, "Attempted to add private presentation window to a non-private display.  Aborting.");
            return WindowManagerGlobal.ADD_PERMISSION_DENIED;
        }
     ...    
    //根据LayoutParams中的token,会检索WmS中保存的WindowToken,
    //由引可见,不同的窗口类型,其对应的token是有区别的。WmS要根据窗口类型来检查其传递过来的token是否合法。
    boolean addToken = false;
    WindowToken token = mTokenMap.get(attrs.token);
    if (token == null) {
       if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                Slog.w(TAG, "Attempted to add application window with unknown token " + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
    if (type == TYPE_INPUT_METHOD) {
                Slog.w(TAG, "Attempted to add input method window with unknown token " + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
    if (type == TYPE_VOICE_INTERACTION) {
                Slog.w(TAG, "Attempted to add voice interaction window with unknown token "+ attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
    if (type == TYPE_WALLPAPER) {
                Slog.w(TAG, "Attempted to add wallpaper window with unknown token " + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
    if (type == TYPE_DREAM) {
                Slog.w(TAG, "Attempted to add Dream window with unknown token " + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
    if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                Slog.w(TAG, "Attempted to add Accessibility overlay window with unknown token "  + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
            token = new WindowToken(this, attrs.token, -1, false);
            addToken = true;
    } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
            AppWindowToken atoken = token.appWindowToken;
            if (atoken == null) {
                Slog.w(TAG, "Attempted to add window with non-application token " + token + ".  Aborting.");
                return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
            } else if (atoken.removed) {
                Slog.w(TAG, "Attempted to add window with exiting application token "  + token + ".  Aborting.");
                return WindowManagerGlobal.ADD_APP_EXITING;
            }
            if (type == TYPE_APPLICATION_STARTING && atoken.firstWindowDrawn) {
                // No need for this guy!
                if (localLOGV) Slog.v(
                        TAG, "**** NO NEED TO START: " + attrs.getTitle());
                return WindowManagerGlobal.ADD_STARTING_NOT_NEEDED;
            }
        } else if (type == TYPE_INPUT_METHOD) {
            if (token.windowType != TYPE_INPUT_METHOD) {
                Slog.w(TAG, "Attempted to add input method window with bad token "  + attrs.token + ".  Aborting.");
                  return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (type == TYPE_VOICE_INTERACTION) {
            if (token.windowType != TYPE_VOICE_INTERACTION) {
                Slog.w(TAG, "Attempted to add voice interaction window with bad token "  + attrs.token + ".  Aborting.");
                  return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (type == TYPE_WALLPAPER) {
            if (token.windowType != TYPE_WALLPAPER) {
                Slog.w(TAG, "Attempted to add wallpaper window with bad token "   + attrs.token + ".  Aborting.");
                  return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (type == TYPE_DREAM) {
            if (token.windowType != TYPE_DREAM) {
                Slog.w(TAG, "Attempted to add Dream window with bad token " + attrs.token + ".  Aborting.");
                  return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (type == TYPE_ACCESSIBILITY_OVERLAY) {
            if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
                Slog.w(TAG, "Attempted to add Accessibility overlay window with bad token " + attrs.token + ".  Aborting.");
                return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
            }
        } else if (token.appWindowToken != null) {
            Slog.w(TAG, "Non-null appWindowToken for system window of type=" + type);
            // It is not valid to use an app token with other system types; we will
            // instead make a new token for it (as if null had been passed in for the token).
            attrs.token = null;
            token = new WindowToken(this, null, -1, false);
            addToken = true;
        }   
      ...
    //经过一系列的检查之后,最后会生成窗口在WmS中的表示WindowState,并且把LayoutParams赋值给WindowState的mAttrs
    win = new WindowState(this, session, client, token, attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);  
    ...  
    if (addToken) {
     mTokenMap.put(attrs.token, token);
    } 
    ...
     mWindowMap.put(client.asBinder(), win);   
[/i]
总结一下:

(1) 窗口类型必须是指定合法范围内的,即应用窗口,子窗口,系统窗口中的一种,否则检查会失败;

(2) 如果是系统,需要进行权限检查

以下类型不需要特别声明权限

TYPE _ TOAST,TYPE _ DREAM,TYPE _ INPUT _ METHOD,TYPE _ WALLPAPER,TYPE _ PRIVATE _ PRESENTATION,TYPE _ VOICE _ INTERACTION,TYPE _ ACCESSIBILITY _ OVERLAY

以下类型需要声明使用权限:android.permission.SYSTEM _ ALERT _ WINDOW

TYPE _ PHONE,TYPE _ PRIORITY _ PHONE,TYPE _ SYSTEM _ ALERT,TYPE _ SYSTEM _ ERROR,TYPE _ SYSTEM _ OVERLAY

其他的系统窗口,需要声明权限:android.permission.INTERNAL _ SYSTEM _ WINDOW

(3) 如果是应用窗口,通过 token 检索出来的 WindowToken,一定不能为空,而且还必须是 Activity 的 mAppToken,同时对应的 Activity 还必须是没有被 finish。之前分析 Activity 的启动过程我们知道,Activity 在启动过程中,会先通过 WmS 的 addAppToken( )添加一个 AppWindowToken 到 mTokenMap 中,其中 key 就用了 IApplicationToken token。而 Activity 中的 mToken,以及 Activity 对应的 PhoneWindow 中的 mAppToken 就是来自 AmS 的 token (代码见 Activity 的 attach 方法)。

(4) 如果是子窗口,会通过 attrs.token 去通过 windowForClientLocked 查找其父窗口,如果找不到其父窗口,会抛出异常。或者如果找到的父窗口的类型还是子窗口类型,也会抛出异常。这里查找父窗口的过程,是直接取了 attrs.token 去 mWindowMap 中找对应的 WindowState,而 mWindowMap 中的 key 是 IWindow。所以,由此可见,创建一个子窗口类型,token 必须赋值为其父窗口的 ViewRootImpl 中的 W 类对象 mWindow。

(5) 如果是如下系统窗口,TYPE _ INPUT _ METHOD,TYPE _ VOICE _ INTERACTION,TYPE _ WALLPAPER,TYPE _ DREAM,TYPE _ ACCESSIBILITY _ OVERLAY,token 不能为空,而且通过 token 检索到的 WindowToken 的类型不能是其本身对应的类型。

(6) 如果是其他系统窗口,会直接把 attrs 中的 token 给清除了,不需要 token。因此其他类型的系统窗口,LayoutParams 中 token 是可以为空的。

(7) 检查通过后,如果需要创建新的 WindowToken,会以 attrs.token 为 key,add 到 mTokenMap 中。

(8) WindowState 创建后,会以 IWindow 为 key (对应应用进程中的 ViewRootImpl.W 类对象 mWindow,重要的事强调多遍!!),添加到 mWindowMap 中。

由此可见,我们要成功添加一个窗口,对于 type 与 token 的赋值是有要求的,否则先不说能否正确显示,直接就创建失败了。那 type 与 token 是如何赋值的呢?最直接来讲,就是在调用 WindowManagerImpl 的 addView 方法前,把值赋好就可以了。但是,分析 FrameWork 所提供的一些窗口的显示,如 Dialog 等,并没有看到在调用 addView 之前,对 token 赋值呢。其窗口类型是应用窗口,根据前面所描述的检查,token 肯定不能为 null 的,而且还必须是 Activity 的 mAppToken,否则创建失败的。这里我们分析一下,在 token 没有赋值的情况下,调用 addView 会做哪些处理。代码就要回到 WindowManagerImpl 开始了。

[Java] 纯文本查看 复制代码

[i]
    //WindowManagerImpl.java addView
    //applyDefaultToken会检查,有没有设置默认token,如果有设置,而且没有设置父窗口的情况下,token又是null的话,直接把默认token赋值给token吧。
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
       applyDefaultToken(params);
       mGlobal.addView(view, params, mDisplay, mParentWindow);
    }
    //WindowManagerGlobal.java addView
    //如果有设置父窗口,会通过adjustLayoutParamsForSubWindow来调整params。
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    }
    //Window.java adjustLayoutParamsForSubWindow
    if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
        wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
        //如果是子窗口类型,而且token为null,直接取父窗口的AttachInfo中的mWindowToken,其实就是父窗口对应的ViewRootImpl中的W类对象mWindow。
        if (wp.token == null) {
            View decor = peekDecorView();
            if (decor != null) {
                wp.token = decor.getWindowToken();
            }
        }
        ...
    }else {
    //如果token为null,直接取父窗口的mAppToken吧。
    if (wp.token == null) {
            wp.token = mContainer == null ? mAppToken :           mContainer.mAppToken;
      }
    }
[/i]

这里的父窗口并不一定是真正意义上的父窗口,有可能就是描述一个窗口的 PhoneWindow 对象本身。有可能 PhoneWindow a,需要添加 a 窗口时,这里 parentWindow 有可能就是 a 对象本身。这里  WindowManagerImpl 中的 mParentWindow,到底代表什么,跟 WindowManagerImpl 的创建有关。例如 Activity 添加窗口的分析可知:

[Java] 纯文本查看 复制代码

[i]
    //Activity.java attach方法, 给PhoneWindow设置WindowManager
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    // Window.java 这里mWindowManager其实是一个WindowManagerImpl对象,而且是重新创建一个新对象的。这里调用了createLocalWindowManager来创建一个新的WindowManagerImpl,传递的parentWindow就是它自己的。这表示PhoneWindow创建一个WindowManagerImpl对象时,把它自己作为parentWindow传递给WindowManagerImpl。
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    mAppToken = appToken;
    mAppName = appName;
    mHardwareAccelerated = hardwareAccelerated
            || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
    if (wm == null) {
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    } 
    //WindowManagerImpl.java
    public WindowManagerImpl createLocalWindowManager(Window parentWindow){
    return new WindowManagerImpl(mDisplay, parentWindow);
    }        
[/i]

这里再总结一下,添加窗口时,WindowManager.Layout 的 token 的一些赋值情况:

(1) 无论是应用窗口,还是子窗口,以及部分系统窗口,token 是一定要赋值的。否则创建窗口会抛异常。对于应用窗口,token 必须是某个 Activity 的 mToken。对于子窗口而言,token 必须是其父窗口的 IWindow 对象。对于部分系统窗口而言,token 检索到的 WindowToken 的类型,不能再是其本身的类型了。

(2) 可以在调用 WindowManagerImpl 的 addView 创建窗口前,直接把 token 赋值好。

(3) 如果调用 WindowManagerImpl 的 addView 前,没有把 token 赋值好。这里会走一些默认逻辑,以保证 token 的合法性:

A: 检查有没有设置默认 token,如果有,而且父窗口为 null,直接取设置的默认 token 吧。目前还没有遇到用这种方法设置 token 的情况。

B: 如果有设置父窗口,则会调用父窗口的 adjustLayoutParamsForSubWindow 来检查 token。对于子窗口类型,会把父窗口的 IWindow 对象赋值给 token。对于应用窗口,或者系统窗口,直接取父窗口的 mAppToken。(父窗口是一个 Window 对象,其实例是 PhoneWindow。)

三、窗口的创建与移除

在分析窗口的创建与移除之前,我们先简单来介绍一下 Android 的 GUI 系统,它包含以下部分内容:<br>

(1)窗口和图形系统—Window and View Manager System<br>

(2)显示合成系统 — Surface Flinger<br>

(3)用户输出系统 — InputManager System<br>

(4)应用框架系统 — Activity Manager System<br>

它们之间的关系,如下图所示:

简单来讲,Activity Manager System(重点是 ActivityManagerService,简称 AmS)负责 Activity 的启动,以及生命周期的管理。一个 Activity 对应一个应用窗口,这个窗口的创建以及管理是 Window and View Manager System 的职责。例如前面的截图,手机屏幕上显示了三个窗口,状态栏窗口,手Q 主界面窗口,以及一个 PopupWindow。View 系统管理每个窗口中复杂的布局,最终这个 View Hierarchy 最顶端的根 View 会被作为窗口,添加到 Window Manager System 中。Window Manager System 管理着所有这些添加的窗口,负责管理这些窗口的层次,显示位置等内容。每个窗口都有一块自己的Surface,Surface Flinger 负责把这些 Surface 合成一块 FrameBuffer。

也就是说 Window Manager System 负责窗口的创建与移除,以及显示状态的管理。具体绘制是由 Suerface Flinger 来负责的。

3.1 应用窗口的创建

首先,我们来分析应用窗口的创建,这也是我们开发过程中,最先遇到的。从开发第一个 Hello World 的 Android 应用开始,我们就已经在接触应用窗口了。

我们知道每个 Activity 对应一个 PhoneWindow,当我们调用 setContentView 时,其实最终结果是把我们的 View 树作为子 View 添加到 PhoneWindow 的 DecorView 中。也就是每个 Activity 的根 View 其实是一个 DecorView。而最终这个 DecorView,又是在 ActivityThread 的 handleResumeActivity 方法中,通过 WindowMnagerImpl 的 addView 方法添加到 WmS 中去的。 PhoneWindow 的 setContentView 又做了哪些事情呢?只是通过 installDecor()方法,给窗口初始化一了些装饰。而所谓的装饰就是指界面上看到的标题栏,导航栏 ActionBar。而我们通过 Activity 的 setContentView 设置的 View,是作为窗口的内容(如下图所示,是作为ID为android.R.content 的 FrameLayout 的子 View)。这里,我们是不是就理解了,窗口的标题栏是如何被添加的。以及窗口的一些属性为什么要在 setContentView 调用之前被设置了。因为,generateLayout 方法负责根据窗口的属性,最终决定采用不同的布局来生成窗口的顶层布局。而 generateLayout 只在 mContentParent 为 null 的时候被调用,而 mContentParent 只有在第一次调用 setContentView 时为 null,此后就不再为 null 了。

从上面的图可以知道,PhoneWindow 只是负责处理一些应用窗口通用的逻辑。但是真正完成把一个 View,作为窗口添加到 WmS 的过程是由 WindowManager 来完成的。WindowManager 在应用程序端的实现是 WindowManagerImpl,也就是说,我们通过 Activity 的 getWindowManager 获取到的实际上是 WindowManagerImpl。因此在在 ActivityThread 的 handleResumeActivity 方法中,有调用 WindowManagerImpl 的 addView,把 PhoneWindow 准备好的 DecorView 作为窗口添加到 WmS 中。代码如下所示:

[Java] 纯文本查看 复制代码

[i]
    if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                wm.addView(decor, l);
            }
[/i]

窗口的添加过程如上图所示,我们知道 WmS 运行在单独的进程中。这里 IWindowSession 执行的 addtoDisplay 操作应该是 IPC 调用。每个应用窗口创建时,都会创建一个 ViewRootImpl 对象。分析了后面子窗口的创建,以及系统窗口的创建后,我们会知道其实任何一个窗口的创建,最终都是会创建一个 ViewRootImpl对象。ViewRootImpl 是一很重要的类,类似 ActivityThread 负责跟AmS通信一样,ViewRootImpl 的一个重要职责就是跟 WmS 通信,它通静态变量 sWindowSession(IWindowSession实例)与 WmS 进行通信。每个应用进程,仅有一个 sWindowSession 对象,它对应了 WmS 中的 Session 子类,WmS 为每一个应用进程分配一个 Session 对象。WindowState 类有一个 IWindow mClient 参数,是在构造方法中赋值的,是由 Session 调用 addWindow 传递过来了,对应了 ViewRootImpl 中的 W 类的实例。

这里梳理了一下,这些类的对应关系。由此可以看到,创建一个窗口的本质过程,就是在 WmS 端创建一个用来表示这个窗口的 WindowState 对象。 WmS 负责管理这里些 WindowState 对象。

到此为止,一个应用窗口的创建过程就结束了。

总结一下,对于Acivity对应的应用窗口创建:

(1) 其 type 类型是TYPE _ BASE _ APPLICATION,定义为应用窗口;<br>

(2) 用来执行 addView 操作的 WindowManager,到底是哪个呢?分析前面的源码,是通过 Activity 的 getWindowManager 来获取的,而返回的是 Activity 中的 mWindowManager,而它的赋值又是在 attch 方法中,直接取的 Window 的 mWindowManager。Window 的 mWindowManager 又是在 attach 方法中,通过 mWindow 的 setWindowManager 来初始化的。通过前面的分析可以知道,最终 Window 中的 mWindowManager,是通过 createLocalWindowManager(this) 创建一个新的 WindowManagerImpl 对象,这个对象的 mParentWindow 就是 Activity 中的 mWindow。<br>

(3) 基 token 呢?查看添加窗口过程,在调用 addView 方法之前,没有为 token 赋值呢?这里走的是默认的赋值逻辑。而 Activity 对应的 WindowManagerImpl 实例中,mParentWidnow 是其对应的 PhoneWindow。所以在默认检查逻辑中,会走它自己的 PhoneWindow 的 adjustLayoutParamsForSubWindow,把 mAppToken 赋值给 token。

到此为止,type 与 token 都合法了,可以创建窗口了。Window 中的 mAppToken 在 Activity 的 attach 方法中已经通过调用 Window 的 setWindowManager 赋值好了,就是 Activity 中的 mToken 的值。

Dialog 对应的应用窗口的创建

小编推荐:欲学习电脑技术、系统维护、网络管理、编程开发和安全攻防等高端IT技术,请 点击这里 注册黑基账号,公开课频道价值万元IT培训教程免费学,让您少走弯路、事半功倍,好工作升职加薪!



免责声明:本文由投稿者转载自互联网,版权归原作者所有,文中所述不代表本站观点,若有侵权或转载等不当之处请联系我们处理,让我们一起为维护良好的互联网秩序而努力!联系方式见网站首页右下角。

1

鲜花

握手

雷人

路过

鸡蛋

刚表态过的朋友 (1 人)

  • 鲜花

    匿名

相关阅读

发表评论

最新评论

引用 游客 2017-11-30 14:54
KfrFo9  <a href="http://kjfljyjcmqfg.com/">kjfljyjcmqfg</a>, [url=http://ynhiebargnkb.com/]ynhiebargnkb[/url], [link=http://gosumafjughd.com/]gosumafjughd[/link], http://lrgbgiajjtkr.com/
引用 游客 2017-11-29 14:36
iydSfK http://www.LnAJ7K8QSpfMO2wQ8gO.com

查看全部评论(2)


新出炉

返回顶部