Dialog 对应的 Context 的探究

前言

创建Dialog的时候知道在Dialog的构造方法中需要一个上下文环境,而对这个“上下文”没有具体的概念结果导致程序报错,

于是发现Dialog需要的上下文环境只能是activity。

所以接下来这篇文章将会从源码的角度来彻底的理顺这个问题。

一、Dialog创建失败

在Dialog的构造方法中传入一个Application的上下文环境。看看程序是否报错:

Dialog dialog = new Dialog(getApplication()); 
     TextView textView = new TextView(this); 
     textView.setText("使用Application创建Dialog"); 
     dialog.setContentView(textView); 
     dialog.show(); 

运行程序,程序不出意外的崩溃了,我们来看下报错信息:

Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application 
    at android.view.ViewRootImpl.setView(ViewRootImpl.java:517) 
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:301) 
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:215) 
    at android.view.WindowManagerImpl$CompatModeWrapper.addView(WindowManagerImpl.java:140) 

这段错误日志,有两点我们需要注意一下

程序报了一个BadTokenException异常
程序报错是在ViewRootImpl的setView方法中

我们一定很疑惑BadTokenException到底是个啥,在说明这个之前我们首先需要了解Token,在了解了Token的概念之后,再结合ViewRootImpl的setView方法,就能理解BadTokenException这个到底是什么,怎么产生的。

二、Token分析

2.1 token详解

Token直译成中文是令牌的意思,android系统中将其作为一种安全机制,其本质是一个Binder对象,在跨进程的通行中充当验证码的作用。比如:在activity的启动过程及界面绘制的过程中会涉及到ActivityManagerService,应用程序,WindowManagerService三个进程间的通信,此时Token在这3个进程中充当一个身份验证的功能,ActivityManagerService与WindowManagerService通过应用程序的activity传过来的Token来分辨到底是控制应用程序的哪个activity。具体来说就是:

  1. 在启动Activity的流程当中,首先,ActivityManagerService会创建ActivityRecord由其本身来管理,同时会为这个ActivityRecord创建一个IApplication(本质上就是一个Binder)。
  2. ActivityManagerService将这个binder对象传递给WindowManagerService,让WindowManagerService记录下这个Binder。
  3. 当ActivityManagerService这边完成数据结构的添加之后,会返回给ActivityThread一个ActivityClientRecord数据结构,中间就包含了Token这个Binder对象。
  4. ActivityThread这边拿到这个Token的Binder对象之后,就需要让WindowManagerService去在界面上添加一个对应窗口,在添加窗口传给WindowManagerService的数据中WindowManager.LayoutParams这里面就包含了Token。
  5. 最终WindowManagerService在添加窗口的时候,就需要将这个Token的Binder和之前ActivityManagerService保存在里面的Binder做比较,验证通过说明是合法的,否则,就会抛出BadTokenException这个异常。

到这里,我们就知道BadTokenException是怎么回事了,然后接下来分析为什么使用Application上下文会报BadTokenException异常,而Activity上下文则不会。
在这里插入图片描述

2.2 为什么非要一个Token

因为在WMS那边需要根据这个Token来确定Window的位置(不是说坐标),如果没有Token的话,就不知道这个窗口应该放到哪个容器上了;

因为非Activity的Context它的WindowManger没有ParentWindow,导致在WMS那边找不到对应的容器,也就是不知道要把Dialog的Window放置在何处。

还有一个原因是没有SYSTEM_ALERT_WINDOW权限(当然要加权限啦,DisplayArea.Tokens的子容器,级别比普通应用的Window高,也就是会显示在普通应用Window的前面,如果不加权限控制的话,被滥用还得了)。

在获得SYSTEM_ALERT_WINDOW权限并将Dialog的Window.type指定为SYSTEM_WINDOW之后能正常显示,是因为WMS会为SYSTEM_WINDOW类型的窗口专门创建一个WindowToken(这下就有容器了),并放置在DisplayArea.Tokens里面(这下知道放在哪里了);
在这里插入图片描述
常规的Dialog显示,是这样的。

最底的那个绿色的WindowState,就是Dialog的窗口。

把Dialog的Window.type指定为SYSTEM_WINDOW之后,是这样的:
在这里插入图片描述
右边最底的那个WindowState就是SYSTEM_WINDOW类型的Dialog窗口,在层级关系上,跟隔壁的ActivityRecord是相等的。

Dialog窗口所在容器,就是刚刚说到的那个即时创建的WindowToken。

其实其他系统级别的窗口也是放置在这个WindowToken的父级容器DisplayArea.Tokens里面的,就像这样:
在这里插入图片描述

三、创建dialog流程分析

1、activity的界面最后是通过ViewRootImpl的setView方法连接WindowManagerService,从而让WindowManagerService将界面绘制到手机屏幕上。而从上面的异常日志中其实也可以看出,Dialog的界面也是通过ViewRootImpl的setView连接WindowManagerService,从而完成界面的绘制的。

我们首先来看Dialog的构造方法。不管一个参数的构造方法。两个参数的构造方法,最终都会调用到3个参数的构造方法:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean  
createContextThemeWrapper) {
    
        ...... 
        //1.创建一个WindowManagerImpl对象 
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 
        //2.创建一个PhoneWindow对象 
        final Window w = new PhoneWindow(mContext); 
        mWindow = w; 
        //3.使dialog能够响应用户的事件 
        w.setCallback(this); 
        w.setOnWindowDismissedCallback(this); 
        //4.为window对象设置WindowManager 
        w.setWindowManager(mWindowManager, null, null); 
        w.setGravity(Gravity.CENTER); 
        mListenersHandler = new ListenersHandler(this); 
    } 

这段代码可以看出dialog的创建实质上和activity界面的创建没什么两样,都需要完成一个应用窗口Window的创建,和一个应用窗口视图对象管理者WindowManagerImpl的创建。

然后Dialog同样有一个setContentView方法:

public void setContentView(@LayoutRes int layoutResID) {
    
        mWindow.setContentView(layoutResID); 
    } 
依然是调用PhoneWindow的setContentView方法。再接着我们来看下dialog的show方法: 
public void show() {
    
        ...... 
        //1.得到通过setView方法封装好的DecorView  
        mDecor = mWindow.ge
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值