Android之window机制token验证

本文探讨了Android中对话框展示时为何需要Activity的Context而非Application Context,通过源码分析揭示了token在window机制和Context机制中的角色。文章介绍了token的创建、验证流程,以及它如何限制不同Context的UI操作权限。最后,作者强调理解源码设计的重要性,提倡以问题为导向的源码阅读方法。
摘要由CSDN通过智能技术生成

文章已授权『郭霖』公众号发布

前言

很高兴遇见你~ 欢迎阅读我的文章

这篇文章讲解关于window token的问题,同时也是Context机制Window机制这两篇文章的一个补充。如果你对Android的Window机制和Context机制目前位了解过,强烈建议你先阅读前面两篇文章,可以帮助理解整个源码的解析过程以及对token的理解。同时文章涉及到Activty启动流程源码,读者可先阅读Activity启动流程这篇文章。文章涉及到这些方面的内容默认读者已经阅读且了解,不会对这方面的内容过多阐述,如果遇到一些内容不理解,可以找到对应的文章看一下。那么,我们开始吧。

当我们想要在屏幕上展示一个Dialog的时候,我们可能会在Activity的onCreate方法里这么写:

override fun onCreate(savedInstanceState: Bundle?) {
   
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    val dialog = AlertDialog.Builder(this)
    dialog.run{
   
        title = "我是标题"
        setMessage("我是内容")
    }
    dialog.show()
}

他的构造参数需要一个context对象,但是这个context不能是ApplicationContext等其他context,只能是ActivityContext(当然没有ApplicationContext这个类,也没有ActivityContext这个类,这里这样写只是为了方便区分context类型,下同)。这样的代码运行时没问题的,如果我们使用Application传入会怎么样呢?

override fun onCreate(savedInstanceState: Bundle?) {
   
    ...
    // 注意这里换成了ApplicationContext
    val dialog = AlertDialog.Builder(applicationContext)
    ...
}

运行一下:

报错了,原因是You need to use a Theme.AppCompat theme (or descendant) with this activity.,那我们给他添加一个Theme:

override fun onCreate(savedInstanceState: Bundle?) {
   
    ...
    // 注意这里添加了主题
    val dialog = AlertDialog.Builder(applicationContext,R.style.AppTheme)
    ...
}

好了再次运行:

嗯嗯?又崩溃了,原因是:Unable to add window -- token null is not valid; is your activity running?token为null?这个token是什么?为什么同样是context,使用activity没问题,用ApplicationContext就出问题了?他们之间有什么区别?那么这篇文章就围绕这个token来展开讨论一下。

文章采用思考问题的思路来展开讲述,我会根据我学习这部分内容时候的思考历程进行复盘。希望这种解决问题的思维可以帮助到你。
对token有一定了解的读者可以看到最后部分的整体流程把握,再选择想阅读的部分仔细阅读。

什么是token

首先我们看到报错是在ViewRootImpl.java:907,这个地方肯定有进行token判断,然后抛出异常,这样我们就能找到token了,那我们直接去这个地方看看。:

ViewRootImpl.class(api29)
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
   
    ...
    int res;
    ...
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                        mTempInsets);
    ...
    if (res < WindowManagerGlobal.ADD_OKAY) {
   
        ...
        switch (res) {
   
            case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
            case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                /*
                *	1
                */
                throw new WindowManager.BadTokenException(
                    "Unable to add window -- token " + attrs.token
                    + " is not valid; is your activity running?");    
                ...
        }
        ...
    }
    ...
}

我们看到代码就是在注释1的地方抛出了异常,是根据一个变量res来判断的,这个res来自方法addToDisplay,那么token的判断肯定在这个方法里面了,res只是一个 判断的结果,那么我们需要进到这个addToDisplay里去看一下。mWindowSession的类型是IWindowSession,他是一个接口,那他的实现类是什么?找不到实现类就无法知道他的具体代码。这里涉及到window机制的相关内容,简单讲一下:

WindowManagerService是系统服务进程,应用进程跟window联系需要通过跨进程通信:AIDL,这里的IWindowSession只是一个Binder接口,他的具体实现类在系统服务进程的Session类。所以这里的逻辑就跳转到了Session类的addToDisplay方法中。关于window机制更加详细的内容,读者可以阅读Android全面解析之Window机制这篇文章进一步了解,限于篇幅这里不过多讲解。

那我们继续到Session的方法中看一下:

Session.class(api29)
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
   
   	final WindowManagerService mService; 
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState) {
   
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
                outInsetsState);
    }
}

可以看到,Session确实是继承自接口IWindowSession,因为WMS和Session都是运行在系统进程,所以不需要跨进程通信,直接调用WMS的方法:

public int addWindow(Session session, IWindow client, int seq,
        LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
        InsetsState outInsetsState) {
   
   	...
    WindowState parentWindow = null;
    ...
	// 获取parentWindow
    parentWindow = windowForClientLocked(null, attrs.token, false);
    ...
    final boolean hasParent = parentWindow != null;
    // 获取token
    WindowToken token = displayContent.getWindowToken(
        hasParent ? parentWindow.mAttrs.token : attrs.token);
    ...
  	// 验证token
    if (token == null) {
   
    if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
   
          Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                           + attrs.token + ".  Aborting.");
            return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
       ...//各种验证
    }
    ...
}

WMS的addWindow方法代码这么多怎么找到关键代码?还记得viewRootImpl在判断res是什么值的情况下抛出异常吗?没错是WindowManagerGlobal.ADD_BAD_APP_TOKEN和WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN,我们只需要找到其中一个就可以找到token的判断位置,从代码中可以看到,当token==null的时候,会进行各种判断,第一个返回的就是WindowManagerGlobal.ADD_BAD_APP_TOKEN,这样我们就顺利找到token的类型:WindowToken。那么根据我们这一路跟过来,终于找到token的类型了。再看一下这个类:

class WindowToken extends WindowContainer<WindowState> {
   
    ...
    // The actual token.
    final IBinder token;
}
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值