为什么Dialog不能用Application的Context

转载 2016年08月29日 09:56:15

【原文地址 点击打开链接

有网友在我的一篇和Context相关的面试题文章提到这个问题。我觉得一两句话,不好说清楚(我需要一些图表),所以用这篇文章来回答一下。

先试一下用Application的上下文来创建Dialog,在调用它的show方法时程序会Crash,LogCat的异常信息如下:

Caused by: android.view.WindowManager$BadTokenException: Unable to add window -- token null is not for an application
                           at android.view.ViewRootImpl.setView(ViewRootImpl.java:685)
                           at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:342)
                           at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:93)
                           at android.app.Dialog.show(Dialog.java:316)

从字面上也很容易理解“BadTokenException: Unable to add window -- token null is not for an application”,发生一个BadTokenException的异常,不能添加Window。

在解释这个问题前,有必要先理清一些概念:

Window: 定义窗口样式和行为的抽象基类,用于作为顶层的view加到WindowManager中,其实现类是PhoneWindow。
每个Window都需要指定一个Type(应用窗口、子窗口、系统窗口)。Activity对应的窗口是应用窗口;PopupWindow,ContextMenu,OptionMenu是常用的子窗口;像Toast和系统警告提示框(如ANR)就是系窗口,还有很多应用的悬浮框也属于系统窗口类型。

WindowManager:用来在应用与window之间的管理接口,管理窗口顺序,消息等。

WindowManagerService:简称Wms,WindowManagerService管理窗口的创建、更新和删除,显示顺序等,是WindowManager这个管理接品的真正的实现类。它运行在System_server进程,作为服务端,客户端(应用程序)通过IPC调用和它进行交互。

Token:这里提到的Token主是指窗口令牌(Window Token),是一种特殊的Binder令牌,Wms用它唯一标识系统中的一个窗口。

下图显示了Activity的Window和Wms的关系:


Activity有一个PhoneWindow,当我们调用setContentView时,其实最终结果是把我们的DecorView作为子View添加到PhoneWindow的DecorView中。而最终这个DecorView,过WindowMnagerImpl的addView方法添加到WMS中去的,由WMS负责管理和绘制(真正的绘制在SurfaceFlinger服务中)。


Dialog的窗口属于什么类型

跟Activity对应的窗口一样,Dialog有一个PhoneWindow的实例。Dialog 的类型是TYPE_APPLICATION,属于应用窗口类型。

可以从Dialog的创建代码得到确认:

    Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        // 忽略一些代码
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        final Window w = new PhoneWindow(mContext);
        mWindow = w;
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

注意w.setWindowManager(mWindowManager, null, null)这句,把appToken设置为null。这也是Dialog和Activity窗口的一个区别,Activity会将这个appToken设置为ActivityThread传过来的token。

 public void setWindowManager(WindowManager wm, IBinder appToken, String appName)

然后在Dialog的show方法中:

    public void show() {
        // 忽略一些代码
        mDecor = mWindow.getDecorView();

        WindowManager.LayoutParams l = mWindow.getAttributes();
        if ((l.softInputMode
                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {
            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();
            nl.copyFrom(l);
            nl.softInputMode |=
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
            l = nl;
        }

        try {
            mWindowManager.addView(mDecor, l);
            mShowing = true;

            sendShowMessage();
        } finally {
        }
    }

mWindow是PhoneWindow类型,mWindow.getAttributes()默认获取到的Type为TYPE_APPLICATION。

Dialog最终也是通过系统的WindowManager把自己的Window添加到WMS上。在addView前,Dialog的token是null(上面提到过的w.setWindowManager第二参数为空)。

Dialog初化始时是通过Context.getSystemServer 来获取 WindowManager,而如果用Application或者Service的Context去获取这个WindowManager服务的话,会得到一个WindowManagerImpl的实例,这个实例里token也是空的。之后在Dialog的show方法中将Dialog的View(PhoneWindow.getDecorView())添加到WindowManager时会给token设置默认值还是null。

如果这个Context是Activity,则直接返回Activity的mWindowManager,这个mWindowManager在Activity的attach方法被创建,Token指向此Activity的Token,mParentWindow为Activity的Window本身。如下的代码Activity重写了getSystemService这个方法:

    @Override
    public Object getSystemService(@ServiceName @NonNull String name) {
        if (getBaseContext() == null) {
            throw new IllegalStateException(
                    "System services not available to Activities before onCreate()");
        }

        if (WINDOW_SERVICE.equals(name)) {
            return mWindowManager;
        } else if (SEARCH_SERVICE.equals(name)) {
            ensureSearchManager();
            return mSearchManager;
        }
        return super.getSystemService(name);
    }

系统对TYPE_APPLICATION类型的窗口,要求必需是Activity的Token,不是的话系统会抛出BadTokenException异常。Dialog 是应用窗口类型,Token必须是Activity的Token。

问题的答案

那为什么一定要是Activity的Token呢?我想使用Token应该是为了安全问题,通过Token来验证WindowManager服务请求方是否是合法的。如果我们可以使用Application的Context,或者说Token可以不是Activity的Token,那么用户可能已经跳转到别的应用的Activity界面了,但我们却可以在别人的界面上弹出我们的Dialog,想想就觉得很危险。

如你跳到了微信界面了,这时在后台的某个应用里调用Dialog的show,那么微信的界面上会显示一个Dialog,这个Dialog可能会让用户输入密码什么的,而用户完全无法区分是不是微信弹出的。



文/goeasyway(简书作者)
原文链接:http://www.jianshu.com/p/628ac6b68c15
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。

android入门(七) 消息提示toast和Context

toast用于向用户显示一些帮助和提示信息 特点: 1、没有焦点; 2、显示时间有限,自动消失。我们简单的创建一个按钮,绑定onclick事件,如下调用Toast事件 Toast....
  • best789248
  • best789248
  • 2015年09月11日 00:00
  • 1289

(转)Context内存泄漏问题

今天看了一篇介绍Context的文章,写的不错,里面有一段内容平时开发时候没有注意到,摘抄如下: 在项目中,我们经常会遇到使用单例模式或者静态static变量,虽然使用静态类或者静态变量很方便,但是...
  • chenleicpp
  • chenleicpp
  • 2015年08月11日 10:37
  • 1116

一步步追踪Dialog的创建流程(一)

想搞明白Dialog到底是怎么创建的,打开Dialog.java,看看里面都做了什么? 一.Dialog的构造函数 从构造函数出发,所有的构造函数都会调用下面这个构造函数: public Dia...
  • u011913612
  • u011913612
  • 2016年06月20日 23:18
  • 1569

Android内存泄露问题(一)之context的引用

Android内存泄露在项目中出现情况较为频繁,如果严重的话可能会导致应用内存占用情况严重。     导致内存泄露的原因非常多,本文介绍下比较常见的context的引用导致的内存泄露!     A...
  • weigoss87
  • weigoss87
  • 2013年08月19日 19:31
  • 948

面试知识点1:Tomcat+Spring中有几个ApplicationContext?它们的关系是什么样的?

Tomcat+Spring下,Context的关系?
  • RoxLiu
  • RoxLiu
  • 2014年03月14日 15:45
  • 3155

Android深入理解Context(一)Context关联类和Application Context创建过程

Context也就是上下文对象,是Android较为常用的类,但是对于Context,很多人都停留在会用的阶段,这个系列会带大家从源码角度来分析Context,从而更加深入的理解它。...
  • itachi85
  • itachi85
  • 2017年06月01日 00:32
  • 2201

idea配置application context(web项目的默认访问路径)

idea的application context  eclipse大家应该都用过,eclipse访问项目的路径一般是localhost:8080/projectName, 当把IDE换成idea以后,...
  • qq_36666651
  • qq_36666651
  • 2017年11月11日 23:05
  • 1026

总结如何实现浮动层,主要是dialog的使用。

自定义一个类继承自Dialog类,然后在构造方法中,定义这个dialog的布局和一些初始化信息。 public class MenuDialog extends Dialog { publ...
  • chuyouyinghe
  • chuyouyinghe
  • 2016年04月13日 15:12
  • 286

Maven 部署webapp 到远程服务器

tomcat8 和tomcat6 部署有很大区别。在按照《Maven实战》这本书配置时(tomcat6),发现错误信息提示是部署到本地的tomcat。 在网上查看了官方文档,没有找到相关demo 。...
  • ZhangVeni
  • ZhangVeni
  • 2016年05月24日 14:54
  • 2188

Android Context完全解析,你所不知道的Context的各种细节

Context相信所有的Android开发人员基本上每天都在接触,因为它太常见了。但是这并不代表Context没有什么东西好讲的,实际上Context有太多小的细节并不被大家所关注,那么今天我们就来学...
  • sinyu890807
  • sinyu890807
  • 2015年11月09日 09:14
  • 96116
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:为什么Dialog不能用Application的Context
举报原因:
原因补充:

(最多只允许输入30个字)