大家好,欢迎来到IT知识分享网。
做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。
上下求索
本篇博客实际上是为我后面全面介绍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
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
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,就这些吧,有问题欢迎留言讨论。
参考资料:
以上。
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/131921.html