时间:2023-03-03 11:18:22 | 栏目:JAVA代码 | 点击:次
为了搞清楚WindowToken的作用是什么,看一下其位于WindowToken.java中的定义。虽然它没有定义任何函数,但其成员变量的意义却很重要。
既然应用组件在创建一个窗口时必须指定一个有效的WindowToken才行,那么WindowToken究竟该如何声明呢?
在SampleWindow应用中,使用wms.addWindowToken()函数声明mToken作为它的令牌,所以在添加窗口时,通过设置lp.token为mToken向WMS进行出示,从而获得WMS添加窗口的许可。这说明,只要是一个Binder对象(随便一个),都可以作为Token向WMS进行声明。对于WMS的客户端来说,Token仅仅是一个Binder对象而已。
为了验证这一点,来看一下addWindowToken的代码,如下所示:
WindowManagerService.java::WindowManagerService.addWindowToken()
@Override publicvoid addWindowToken(IBinder token, int type) { // 需要声明Token的调用者拥有MANAGE_APP_TOKENS的权限 if(!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "addWindowToken()")) { thrownew SecurityException("Requires MANAGE_APP_TOKENS permission"); } synchronized(mWindowMap){ ...... // 注意其构造函数的参数与addWindow()中不同,最后一个参数为true,表明这个Token // 是显式申明的 wtoken= new WindowToken(this, token, type, true); mTokenMap.put(token,wtoken); ...... } }
使用addWindowToken()函数声明Token,将会在WMS中创建一个WindowToken实例,并添加到mTokenMap中,键值为客户端用于声明Token的Binder实例。与addWindow()函数中隐式地创建WindowToken不同,这里的WindowToken被声明为显式的。隐式与显式的区别在于,当隐式创建的WindowToken的最后一个窗口被移除后,此WindowToken会被一并从mTokenMap中移除。显式创建的WindowToken只能通过removeWindowToken()显式地移除。
addWindowToken()这个函数告诉我们,WindowToken其实有两层含义:
注意 在一般情况下,称显示组件(客户端)一侧Binder的实例为Token,而称WMS一侧的WindowToken对象为WindowToken。但是为了叙述方便,在没有歧义的前提下不会过分仔细地区分这两个概念。
接下来,看一下各种显示组件是如何声明WindowToken的。
(1) Wallpaper和InputMethod的Token
Wallpaper的Token声明在WallpaperManagerService中。参考以下代码:
WallpaperManagerService.java::WallpaperManagerService.bindWallpaperComponentLocked()
BooleanbindWallpaperComponentLocked(......) { ...... WallpaperConnection newConn = new WallpaperConnection(wi, wallpaper); ...... mIWindowManager.addWindowToken(newConn.mToken, WindowManager.LayoutParams.TYPE_WALLPAPER); ...... }
WallpaperManagerService是Wallpaper管理器,它负责维护系统已安装的所有的Wallpaper并在它们之间进行切换,而这个函数的目的是准备显示一个Wallpaper。newConn.mToken与SampleWindow例子一样,是一个简单的Binder对象。这个Token将在即将显示出来的Wallpaper被连接时传递给它,之后Wallpaper即可通过这个Token向WMS申请创建绘制壁纸所需的窗口了。
注意 :WallpaperManagerService向WMS声明的Token类型为TYPE_WALLPAPER,所以,Wallpaper仅能本分地创建TYPE_WALLPAPER类型的窗口。
相应的,WallpaperManagerService会在detachWallpaperLocked()函数中取消对Token的声明:
WallpaperManagerService.java::WallpaperManagerService.detachWallpaperLocked()
booleandetachWallpaperLocked(WallpaperData wallpaper){ ...... mIWindowManager.removeWindowToken(wallpaper.connection.mToken); ...... }
再此之后,如果这个被detach的Wallpaper想再要创建窗口便不再可能了。
WallpaperManagerService使用WindowToken对一个特定的Wallpaper做出了如下限制:
InputMethod的Token的来源与Wallpaper类似,其声明位于InputMethodManagerService的startInputInnerLocked()函数中,取消声明的位置在InputmethodManagerService的unbindCurrentMethodLocked()函数。InputMethodManagerService通过Token限制着每一个InputMethod的窗口类型以及显示生命周期。
(2) Activity的Token
Activity的Token的使用方式与Wallpaper和InputMethod类似,但是其包含更多的内容。毕竟,对于Activity,无论是其组成还是操作都比Wallpaper以及InputMethod复杂得多。对此,WMS专为Activity实现了一个WindowToken的子类:AppWindowToken。
既然AppWindowToken是为Activity服务的,那么其声明自然在ActivityManagerService中。具体位置为ActivityStack.startActivityLocked(),也就是启动Activity的时候。相关代码如下:
ActivityStack.java::ActivityStack.startActivityLocked()
private final void startActivityLocked(......) { ...... mService.mWindowManager.addAppToken(addPos,r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen); ...... }
startActivityLocked()向WMS声明r.appToken作为此Activity的Token,这个Token是在ActivityRecord的构造函数中创建的。随然后在realStartActivityLocked()中将此Token交付给即将启动的Activity。
ActivityStack.java::ActivityStack.realStartActivityLocked()
final boolean realStartActivityLocked(......) { ...... app.thread.scheduleLaunchActivity(newIntent(r.intent), **r.appToken,** System.identityHashCode(r), r.info, newConfiguration(mService.mConfiguration), r.compat, r.icicle, results, newIntents,!andResume, mService.isNextTransitionForward(),profileFile, profileFd, profileAutoStop); ...... }
启动后的Activity即可使用此Token创建类型为TYPE_APPLICATION的窗口了。
取消Token的声明则位于ActivityStack.removeActivityFromHistoryLocked()函数中。
Activity的Token在客户端是否和Wallpaper一样,仅仅是一个基本的Binder实例呢?其实不然。看一下r.appToken的定义可以发现,这个Token的类型是IApplicationToken.Stub。其中定义了一系列和窗口相关的一些通知回调,它们是:
AMS通过ActivityRecord表示一个Activity。而ActivityRecord的appToken在其构造函数中被创建,所以每个ActivityRecord拥有其各自的appToken。而WMS接受AMS对Token的声明,并为appToken创建了唯一的一个AppWindowToken。因此,这个类型为IApplicationToken的Binder对象appToken粘结了AMS的ActivityRecord与WMS的AppWindowToken,只要给定一个ActivityRecord,都可以通过appToken在WMS中找到一个对应的AppWindowToken,从而使得AMS拥有了操纵Activity的窗口绘制的能力。例如,当AMS认为一个Activity需要被隐藏时,以Activity对应的ActivityRecord所拥有的appToken作为参数调用WMS的setAppVisibility()函数。此函数通过appToken找到其对应的AppWindowToken,然后将属于这个Token的所有窗口隐藏。
注意: 每当AMS因为某些原因(如启动/结束一个Activity,或将Task移到前台或后台)而调整ActivityRecord在mHistory中的顺序时,都会调用WMS相关的接口移动AppWindowToken在mAppTokens中的顺序,以保证两者的顺序一致。在后面讲解窗口排序规则时会介绍到,AppWindowToken的顺序对窗口的顺序影响非常大。