做Android有些年头了,Framework层三大核心View系统,WmS、AmS最近在研究中,这三大块,每一块都够写一个小册子来介绍,其中View系统的介绍,我之前有一个系列的博客(不过由于时间原因,该系列尚未收尾,后续分析仍在探究中),小伙伴们自行查找。WmS和AmS这两个也需要我们一个小块一个小块来啃,那么今天我们就先来看看WmS中涉及到的一个小小的变量token,这个东西到底是什么?

缘起

token这个东西有过几年开发经验的小伙伴应该都清楚,即使没有认真研究过也至少遇到过,在我们使用PopupWindow的时候,这个里边有一个方法是showAtLocation,该方法第一个参数是一个View,但是这个View是当前页面的任意一个View,那么这个View是干什么用的呢?我们来看看这个方法的注释:

/**
 * @param parent a parent view to get the {@link android.view.View#getWindowToken()} token from
 * @param gravity the gravity which controls the placement of the popup window
 * @param x the popup's x location offset
 * @param y the popup's y location offset
 */
public void showAtLocation(View parent, int gravity, int x, int y) {
    showAtLocation(parent.getWindowToken(), gravity, x, y);
}

我这里只列出了一部分注释,但是这一部分注释说的很明白了,使用View这个参数的目的是为了获取一个token。

OK,这个可能是很多小伙伴第一次间接用到token的情况,除了这里,在Android5.0中,有一个新控件,叫做SnackBar(android开发之SnackBar的使用),SnackBar在显示的时候也需要一个当前页面任意View,这里的目的和PopupWindow的原因类似,那么这个Token到底是什么?我们又为什么需要这样一个东西,OK,继续往下看。

上下求索

本篇博客实际上是为我后面全面介绍WmS做铺垫,所以在这里我暂时先不想用过多篇幅去介绍Window,Window我打算放到后面再说。我们这里就直接先来看什么是token。单从字面来理解,token有令牌、符号的含义,当我尝试去WmS中去寻找token变量的时候,在Android的framework层的好多个类中我都找到了,比如Activity中、Window中、WindowManager.LayoutParams中等等都有,我总结了如下一张表格:

我们看到,这么多类中都定义了token这个东东,而且这个token竟然是一个IBinder对象,有的类中虽然定义token时没有直接指定为IBinder,但是追根溯源,发现其实最终说的还是IBinder,比如View.AttachInfo这个类中。其实当小伙伴们看到IBinder之后,应该立马就想到了IPC,这是套路。我们知道,Android的framework框架在整个系统中扮演的角色相当于服务端,而我们开发的应用程序相当于客户端,Activity的创建、启动等操作都是通过IPC的方式来实现服务端和客户端之间的通信,所以说IPC在这里扮演了相当重要的角色。OK,说完了这些,我们来分别看看几个重要的token。

Activity中的token

要了解Activity中的token,我们得先明白Activity中的另外一个东西,叫做ActivityRecord,ActivityRecord是AmS中用来保存一个Activity信息的辅助类,这个类中有许多属性,这些属性可以从整体上分为两大部分:Activity所处的环境信息和运行状态信息。Activity所处的环境信息主要包括Activity所属的Package(对应变量为packageName)、所在进程名称(对应变量为processName)、图标(对应变量icon)、主题(对应变量theme)等;运行状态信息主要有idle、stop、finishing等,这些状态信息与Activity生命周期相关。这里有一个地方需要小伙伴们注意,在ActivityRecord中有一个变量叫做appToken,这个变量的类型是一个IApplicationToken.Stub,这里的Token提供了对ActivityRecord类的基本操作,看到这里,小伙伴们应该心里有数了,这个appToken也是一个Binder,可以进行IPC调用,这里我们一般是在WmS中对该对象进行IPC调用。

OK,说完了ActivityRecord,我们再来看一看Activity中的mToken变量。这个变量的本质是一个Binder,通过查找源码,我们发现,该变量的赋值是在Activity的attach方法中进行的,如下:

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {

		......
		......

        mToken = token;

		......
		......
    }

OK,研究过Activity启动过程的小伙伴应该都清楚attach的调用位置吧,就是ActivityThread这个类啦,在ActivityThread的scheduleLaunchActivity方法中,我们拿到了参数token,这个token经过好几道程序,最终变为了上文的token。由于AmS为每一个创建的Activity都创建了一个ActivityRecord,由于Binder可以用来标识多个进程间的同一个对象,所以这里的mToken变量还有一个作用就是指向了ActivityRecord。在我们的窗口创建过程中,涉及到的IPC通信就是两方面:一个是指向某个W类的token,另一个是指向ActivityRecord的token,指向W类的token主要是用来实现WmS和应用所在进程通信,指向ActivityRecord的token则是实现WmS和AmS通信的。Activity中的mToken很明显是第二种。

Window中的mAppToken

在我们的Android系统中,每一个Window对象都有一个mAppToken变量,但是小伙伴注意区分Window和窗口(窗口本质上就是一个View,而Window是一个应用窗口的抽象,WmS把所有的用户消息发给View/ViewGroup,但是在View/ViewGroup处理消息的过程中,有一些操作是公共的,Window把这些公共行为抽象出来,是为Window)。由于窗口本质是一个View,和Window没有关系,所以这里mAppToken并不是W类的引用,那不是W类的引用,就只能是ActivityRecord的引用了,既然是ActivityRecord的引用,那么毫无疑问,mAppToken的主要功能就是实现WmS和AmS之间的通信。但是在实际开发中,由于Window并不总是对应一个Activity,我们常见的Dialog,ContextMenu等等中也会包含一个Window,这个时候就不牵涉WmS和AmS之间的通信问题了,那么这个时候Window中的mAppToken为空,否则mAppToken和Activity中的mToken是相同的。

WindowManager.LayoutParams中的token

WindowManager.LayoutParams中竟然会有一个token,很多小伙伴可能会觉得奇怪,其实仔细想想这也没什么,WindowManager类可以向WmS中添加一个窗口,窗口添加成功之后,我们还要和这个窗口进行通信,通信就需要Binder,也就是这里所说的token了。但是由于我们往WmS中添加的窗口类型可能会有差异,所以token的含义也会有差别。这里的差别主要体现在WindowManagerService的addWindow方法上,总结该方法,我们可以发现token的取值一共有三种情况:

1.如果我们创建的是一个应用窗口,比如Activity,那么这里的token的值和Window中的mAppToken的值相同,也就是和Activity的mToken的值相同,都是指向ActivityRecord对象,但是在调用addView方法的时候,系统会对这里的token的值进行调整,使之变为一个指向W的对象,这样,WmS就可以通过这个token进行IPC调用,从而控制窗口的行为。

2.如果我们创建的窗口为子窗口,即Dialog、PopupWindow等,那么token就为其父窗口的W对象,如果查找不到父窗口,或者父窗口的类型还是子窗口,那么都会抛出异常。

3.如果创建的是系统窗口,那么分两种情况,对于TYPE _ INPUT _ METHOD,TYPE _ VOICE _ INTERACTION,TYPE _ WALLPAPER,TYPE _ DREAM,TYPE _ ACCESSIBILITY _ OVERLAY这些系统窗口,token是不可以为null的,而对于其他的系统窗口,token可以为null,这里对应的源码如下(由于这里源码太长,我这里贴出一部分):

if (token == null) {
                if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "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_WM, "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_WM, "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_WM, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "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;
            }

View中的token

View中并不直接存在一个token,但是View中有一个mAttachInfo,这个变量中token,所以在这里我们需要先分析一下这个mAttachInfo到底是个什么东西?在View中和ViewRootImpl 中都有一个mAttachInfo,而一个应用中的每一个View都对应了一个ViewRootImpl对象,ViewRootImpl中有一个mAttachInfo对象,这个对象是在ViewRootImpl被构造的时候创建的。ViewRootImpl中的mAttachInfo对象的数据类型就是View.AttachInfo,所以说这两个对象其实是同一种类型,但是到底是不是同一个东西,这个还需要我们进一步考究。当一个View在屏幕上显示出来的时候,它必须经历一个过程就是View的绘制,了解过View绘制过程的小伙伴应该都明白,View绘制有一个核心方法就是ViewRootImpl.performTraversals,在这个方法中,系统会去调用View/ViewGroup的dispatchAttachedToWindow方法,源码如下:

host.dispatchAttachedToWindow(mAttachInfo, 0);

这个时候又回到了View方法,在View的dispatchAttachToWindow方法中,ViewRootImpl中的mAttachInfo竟然赋值给了View中的mAttachInfo了。也就是说,View中的mAttachInfo和ViewRootImpl中的mAttachInfo其实是同一个东西,mAttachInfo中有三个Binder变量,这个我们来看其中两个:

mWindowToken,这个表示的是当前窗口所对应的W对象,因为View本身并不能直接从WmS中接收消息,要通过W类才能实现,故此mWindowToken指向了该窗口对应的W对象。

mPanelParentWindowToken,如果该窗口为子窗口,那么该变量就是父窗口中的W对象。

总结

OK,以上就是我们WmS系统中几个常见的token,这些token基本都和Binder脱不了关系,Binder又是为了进行IPC调用,WmS中的IPC调用大致又可以分为两类,所以总结起来就是这样:窗口的创建一般会涉及到两方面的IPC通信,一个是WmS和应用所在进程进行通信,还有一个就是WmS和AmS进行通信。就是这两种,第一种对应的token指向一个ViewRootImpl.W对象,第二种对应的token指向一个ActivityRecord对象。

最后再说说开篇说的PopupWindow中的问题,很明显PopupWindow中的第一个参数是为了获取View中的mAttachInfo,进而获取mAttachInfo中的指向W类的Binder对象,然后通过该对象就能获取用户的输入了。

OK,就这些吧,有问题欢迎留言讨论。

参考资料:

1.Android Binder机制(一) Binder的设计和框架

2.Binder系列—开篇

3.浅析Android的窗口

以上。

WmS详解(一)之token到底是什么?基于Android7.0源码的更多相关文章

  1. WmS详解(二)之如何理解Window和窗口的关系?基于Android7.0源码

    上篇博客(WmS详解(一)之token到底是什么?基于Android7.0源码)中我们简要介绍了token的作用,这里涉及到的概念非常多,其中出现频率最高的要数Window和窗口这一对搭档了,那么我们 ...

  2. WmS简介(三)之Activity窗口是如何创建的?基于Android7.0源码

    OK,在前面两篇博客中我们分别介绍了WmS中的token,同时也向小伙伴们区分了Window和窗口的区别,并且按照type值的不同将Android系统中的窗口分为了三大类,那么本篇博客我们就来看看应用 ...

  3. 详解Nacos 配置中心客户端配置缓存动态更新的源码实现

    Nacos 作为配置中心,当应用程序去访问Nacos动态获取配置源之后,会缓存到本地内存以及磁盘中. 由于Nacos作为动态配置中心,意味着后续配置变更之后需要让所有相关的客户端感知,并更新本地内存! ...

  4. Webpack探索【16】--- 懒加载构建原理详解(模块如何被组建&如何加载)&源码解读

    本文主要说明Webpack懒加载构建和加载的原理,对构建后的源码进行分析. 一 说明 本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack懒加载构建原理. 本文使用的 ...

  5. Webpack探索【15】--- 基础构建原理详解(模块如何被组建&如何加载)&源码解读

    本文主要说明Webpack模块构建和加载的原理,对构建后的源码进行分析. 一 说明 本文以一个简单的示例,通过对构建好的bundle.js源码进行分析,说明Webpack的基础构建原理. 本文使用的W ...

  6. 详解Java线程池的ctl(线程池控制状态)【源码分析】

    0.综述 ctl 是线程池源码中常常用到的一个变量. 它的主要作用是记录线程池的生命周期状态和当前工作的线程数. 作者通过巧妙的设计,将一个整型变量按二进制位分成两部分,分别表示两个信息. 1.声明与 ...

  7. mybatis 详解(三)------入门实例(基于注解)

    1.创建MySQL数据库:mybatisDemo和表:user 详情参考:mybatis 详解(二)------入门实例(基于XML) 一致 2.建立一个Java工程,并导入相应的jar包,具体目录如 ...

  8. spark最新源码下载并导入到开发环境下助推高质量代码(Scala IDEA for Eclipse和IntelliJ IDEA皆适用)(以spark2.2.0源码包为例)(图文详解)

    不多说,直接上干货! 前言   其实啊,无论你是初学者还是具备了有一定spark编程经验,都需要对spark源码足够重视起来. 本人,肺腑之己见,想要成为大数据的大牛和顶尖专家,多结合源码和操练编程. ...

  9. WmS具体解释(二)之怎样理解Window和窗体的关系?基于Android7.0源代码

    上篇博客(WmS具体解释(一)之token究竟是什么?基于Android7.0源代码)中我们简要介绍了token的作用,这里涉及到的概念非常多,当中出现频率最高的要数Window和窗体这一对搭档了,那 ...

随机推荐

  1. SQL Server 查询性能优化——创建索引原则(二)

    三:索引的建立原则 一般来说,建立索引要看数据使用的场景,换句话来说哪些访问数据的SQL语句是常用的,而这些语句是否因为缺少索引(也有可能是索引过多)变的效率低下.但绝不是所有的SQL语句都要建立索引 ...

  2. Python系列 - optparse

    我们知道sys.argv[] 可以获得命令行参数 同样,optparse 对此提供了更为强大的功能. import optparse class ArgvHandler(object): def __ ...

  3. 初识webgl--我的webgl学习第一课(基于threeJs)

    一,我为什么要学习webgl 一个偶然的机会,在和朋友的聊天过程中,听说了webgl,也许过去也看到过,但是没有特别在意过.原来,JavaScript也可以很好的渲染并在网页上显示三维动画,不用借助插 ...

  4. webpack模块化管理和打包工具

    Webpack简介 webpack是当下最热门的前端资源模块化管理和打包工具.它可以将许多松散的模块按照依赖和规则打包成符合生产环境部署的前端资源.还可以将按需加载的模块进行代码分隔,等到实际 需要的 ...

  5. hdu 5314 动态树

    Happy King Time Limit: 10000/5000 MS (Java/Others)    Memory Limit: 262144/262144 K (Java/Others)Tot ...

  6. bzoj4710: [Jsoi2011]分特产 组合+容斥

    4710: [Jsoi2011]分特产 Time Limit: 10 Sec  Memory Limit: 128 MBSubmit: 289  Solved: 198[Submit][Status] ...

  7. WebStorm配置node.js调试

    最近因为工作关系,一直在做node.js的开发,学习了koa框架,orm框架sequelize,以及swagger文档的配置.但是,最近因为swagger文档使用了es6的修饰器那么个东西(在java ...

  8. SpringBoot 使用MultipartFile上传文件相关问题解决方案

    1.当上传时未配置上传内容大小,会报错[org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException ...

  9. Maven的pom.xml文件结构之基本配置packaging和多模块聚合结构(微服务)

    1. packaging packaging给出了项目的打包类型,即作为项目的发布形式,其可能的类型.在Maven 3中,其可用的打包类型如下: jar,默认类型 war ejb ear rar pa ...

  10. 聪明的搜索算法’ A*算法

    A*算法     是一种启发式的搜索算法. 了解BFS.DFS或者Dijkstra算法的人应该知道.这些算法都是一种向四周盲目式搜索的方法.   启发式搜索:     启发式搜索就是在状态空间中的搜索 ...