【Android 源码解析】Activity、Dialog、PopWindow、Toast窗口添加机制

一、WindowManager

WindowManager 是一个接口,它继承自 ViewManager,ViewManager 接口很简单,只提供了三个在 Activity 中添加和移除子 View 的抽象方法 addView、updateView、removeView:

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

WindowManager 接口继承了 ViewManager,同时自己还定义了一个内部类 LayoutParams,这个 LayoutParams 类是继承自 ViewGroup 的内部类 LayoutParams
WindowManager 的实现类是 WindowManagerImpl,实现 WindowManager 中定义的接口功能。
而 ViewGroup 类也实现了 ViewManager 接口,因为 ViewGroup 要添加或者删除子 View,ViewGroup 层层嵌套,最顶层的是 DecorView,最终显示在 Window 中,Window 是 View 的实际管理者。

二、Window

我们都知道 Window 是一个抽象类,唯一实现类是 PhoneWindow。创建一个 Window 很简单,只需要通过 WindowManager 即可完成,WindowManager 是外界访问 Window 的入口,Window 具体实现在 WindowManagerService 中,WindowManager 和 WindowManagerService 的交互是一个 IPC 过程,这里不作深究。
怎么通过 WindowManager 添加一个 Window 呢?很简单,使用 addView 方法
windowManager.addView(view,layoutParams);

1、WindowManager.LayoutParams

Window 的一些属性都是通过 WindowManager.LayoutParams 来定义的。

Window 类型

Window 是分层的,层级大的会覆盖在层级小的上面。
Window 有三种类型,

  1. 应用 Window。一个应用 Window 对应着 Activity,层级范围1-99;
  2. 子 Window。不能单独存在,需要依附在特定的父 Window 之中,比如一些 Dialog,层级范围 1000-1999;
  3. 系统 Window。需要声明权限才能创建,比如 Toast 和系统状态栏,层级范围2000-2999;
    Window 的层级范围对应着 WindowManager.LayoutParams 的 type 参数,WindowManager.LayoutParams 内部定义了一些静态常量值。需要注意的是,如果定义系统 Window,别忘记声明权限。
//以下定义都是描述窗口的类型
        public int type;
        //第一个应用窗口
        public static final int FIRST_APPLICATION_WINDOW = 1;
        //所有程序窗口的base窗口,其他应用程序窗口都显示在它上面
        public static final int TYPE_BASE_APPLICATION   = 1;
        //所有Activity的窗口
        public static final int TYPE_APPLICATION        = 2;
        //目标应用窗口未启动之前的那个窗口
        public static final int TYPE_APPLICATION_STARTING = 3;
        //最后一个应用窗口
        public static final int LAST_APPLICATION_WINDOW = 99;

        //第一个子窗口
        public static final int FIRST_SUB_WINDOW        = 1000;
        // 面板窗口,显示于宿主窗口的上层
        public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;
        // 媒体窗口(例如视频),显示于宿主窗口下层
        public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;
        // 应用程序窗口的子面板,显示于所有面板窗口的上层
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
        //对话框窗口
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
        //
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4;
        //最后一个子窗口
        public static final int LAST_SUB_WINDOW         = 1999;

        //系统窗口,非应用程序创建
        public static final int FIRST_SYSTEM_WINDOW     = 2000;
        //状态栏,只能有一个状态栏,位于屏幕顶端,其他窗口都位于它下方
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
        //搜索栏,只能有一个搜索栏,位于屏幕上方
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
        //电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
        //系统警告提示窗口,出现在应用程序窗口之上
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
        //锁屏窗口
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
        //信息窗口,用于显示Toast
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
        //系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        //电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
        //系统对话框窗口
        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
        //锁屏时显示的对话框
        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
        //系统内部错误提示,显示在任何窗口之上
        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;
        //内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖
        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
        //内部输入法对话框,显示于当前输入法窗口之上
        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
        //墙纸窗口
        public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;
        //状态栏的滑动面板
        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
        //安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘
        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
        //最后一个系统窗口
        public static final int LAST_SYSTEM_WINDOW      = 2999;
Window 显示特性

WindowManager.LayoutParams 的 flags 属性定义了 Window 的显示特性

//窗口特征标记
        public int flags;
        //当该window对用户可见的时候,允许锁屏
        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;
        //窗口后面的所有内容都变暗
        public static final int FLAG_DIM_BEHIND        = 0x00000002;
        //Flag:窗口后面的所有内容都变模糊
        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
        //窗口不能获得焦点
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        //窗口不接受触摸屏事件
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        //即使在该window在可获得焦点情况下,允许该窗口之外的点击事件传递到当前窗口后面的的窗口去
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
        //当手机处于睡眠状态时,如果屏幕被按下,那么该window将第一个收到触摸事件
        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
        //当该window对用户可见时,屏幕出于常亮状态
        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
        //:让window占满整个手机屏幕,不留任何边界
        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
        //允许窗口超出整个手机屏幕
        public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
        //window全屏显示
        public static final int FLAG_FULLSCREEN      = 0x00000400;
        //恢复window非全屏显示
        public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
        //开启窗口抖动
        public static final int FLAG_DITHER             = 0x00001000;
        //安全内容窗口,该窗口显示时不允许截屏
        public static final int FLAG_SECURE             = 0x00002000;


        //锁屏时显示该窗口
        public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
        //系统的墙纸显示在该窗口之后
        public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
        //当window被显示的时候,系统将把它当做一个用户活动事件,以点亮手机屏幕
        public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
        //该窗口显示,消失键盘
        public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
        //当该window在可以接受触摸屏情况下,让因在该window之外,而发送到后面的window的触摸屏可以支持split touch
        public static final int FLAG_SPLIT_TOUCH = 0x00800000;
        //对该window进行硬件加速,该flag必须在Activity或Dialog的Content View之前进行设置
        public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
        //让window占满整个手机屏幕,不留任何边界
        public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
        //透明状态栏
        public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
        //透明导航栏
        public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;

除了 type 和 flags 外,WindowManager.LayoutParams 还有一些属性,这里说几个比较重要的:

//窗口的起点坐标
public int x;
public int y;
//窗口内容的对齐方式
public int gravity;
//描述窗口的宽度和高度,是父类 ViewGroup.LayoutParams 的成员变量
public int width;
public int height;

2、Window 内部机制

Window 是一个抽象概念,并不是实际存在的,而是以 View 的形式存在的,这从 WindowManager 的定义可以看出来,addView,updateView,removeView 都是针对 View 的,View 才是 Window 的实体,在实际使用中必须通过 WindowManager 才能访问 Window。
每个 Window 都对应着一个 View 和 ViewRootImpl,Window 和 View 是通过 ViewRootImpl 联系起来的。

Window 添加过程

Window 的添加、更新和删除需要 WindowManager 的 addView、updateView、removeView 方法,WindowManager 是接口,真正的实现是 WindowManagerImpl,但是其实 WindowManagerImpl 也没有直接实现 WindowManager 的三大操作,而是交给 WindowManagerGlobal 来处理。WindowManagerImpl 这种工作模式是典型的桥接模式
WindowManagerImpl 的 addView 方法主要分如下几步:
1)检查 view、diaplay、params 等参数是否合法,如果是子 Window(parentWindow != null) 还需要调整一些布局参数,LayoutParams 必须是 WindowManager.LayoutParams
2)创建 ViewRootImpl 并将 View 加入 List
WindowManagerGlobal 有几个 ArrayList 比较重要:

//存储所有 Window 对应的 View
private final ArrayList<View> mViews = new ArrayList<View>();
//存储所有 Window 对应的 ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//存储所有 Window 的布局参数 LayoutParams
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
//存储正在被删除的 View 对象,已经调用 removeView 还未执行
private final ArrayList<> mDyingViews = new ArrayList<>();

在 addView 中如下方式把一系列对象添加到 List

root = newViewRootImpl(view.getContext,display);
view.setLayoutParams(wparams);

mViews.add(view);
mRoots.add(root);
mParams.add(wparam);

3)通过 ViewRootImpl 来更新界面并完成 Window 的添加过程
这个步骤由 ViewRootImpl 的 setView 来完成,setView 内部通过 requestLayout 完成异步刷新请求,requestLayout 内部调用 scheduleTraversals 来进行 View 的绘制。
接着通过 WindowSession 完成 Window 的添加过程,Session 内部是通过 WindowManagerService 实现 Window 的添加的。

Window 的删除过程

WindowManagerGlobal 的 removeView 先通过 findViewLocked 来查找待删除的 View 的索引,然后调用 removeViewLoacked 来做进一步删除,内部是通过 ViewRootImpl 来完成删除操作的

Window 更新过程

首先更新 View 的LayoutParams,接着更新 ViewRootImpl 中的 LayoutParams,这一步是通过 ViewRootImpl 的 setLayoutParams 来实现的,在 ViewRootImpl 中会通过 scheduleTraversals 来对 View 重新布局,并通过 WindowSession 来更新 Window 视图。

三、Window 创建过程

Activity 窗口添加流程

前面的文章说过,Activity 的实例化是在 ActivityThread 的 performLaunchActivity 方法开始的,在创建好 ContextImpl 对象后调用了 Activity 的 attach 方法,把 ContextImpl 的实例赋值给 mBase 成员变量,除此之外,attach 方法里面还做了初始化 Activity 成员变量 mWindow 和 mWindowManager 的工作:

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类型的mWindow对象,实际为PhoneWindow类实现了抽象Window类
        mWindow = PolicyManager.makeNewWindow(this);
        ......
        //通过抽象Window类的setWindowManager方法给Window类的成员变量WindowManager赋值实例化
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        ......
        //把抽象Window类相关的WindowManager对象拿出来关联到Activity的WindowManager类型成员变量mWindowManager
        mWindowManager = mWindow.getWindowManager();
        ......
    }
  1. 通过 PolicyManager 的 makeNewWindow 方法创建一个 PhoneWindow 对象,赋值给 Activity 的成员变量mWindow
  2. 通过 Window 类的 setWindowManager 方法给 Window 的 mWindowManager 成员变量赋值实例化;
  3. 把前面实例化的 Window 的成员变量 mWindowManager 赋值给 Activity 的成员变量 mWindowManager,使 Activity 和 WindowManager 关联起来;

再看 Window 的 setWindowManager 方法,看WindowManager 的实例是怎么创建的:

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        ......
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        //实例化Window类的WindowManager类型成员mWindowManager
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

就是调用穿进去的或者重新获取的 WindowManagerImpl 对象的 createLocalWindowManager 方法

public final class WindowManagerImpl implements WindowManager {
    ......
    private WindowManagerImpl(Display display, Window parentWindow) {
        mDisplay = display;
        mParentWindow = parentWindow;
    }
    ......
    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mDisplay, parentWindow);
    }
    ......
}

而 createLocalWindowManager 方法很简单,就是通过 WindowMangerImpl 两个参数的构造函数 new 了一个 WindowManagerImpl 对象。
那在 setWindowManager 中传进去的第一个参数 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE) 是从哪来的呢?
在 Context 的实现类 ContextImpl 中有静态代码块:

class ContextImpl extends Context {
    ......
    //静态代码块,类加载时执行一次
    static {
        ......
        //这里有一堆类似的XXX_SERVICE的注册
        ......
        registerService(WINDOW_SERVICE, new ServiceFetcher() {
                Display mDefaultDisplay;
                public Object getService(ContextImpl ctx) {
                    //搞一个Display实例
                    Display display = ctx.mDisplay;
                    if (display == null) {
                        if (mDefaultDisplay == null) {
                            DisplayManager dm = (DisplayManager)ctx.getOuterContext().
                                    getSystemService(Context.DISPLAY_SERVICE);
                            mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
                        }
                        display = mDefaultDisplay;
                    }
                    //返回一个WindowManagerImpl实例
                    return new WindowManagerImpl(display);
                }});
        ......
    }
    //这就是你在外面调运Context的getSystemService获取到的WindowManagerImpl实例
    @Override
    public Object getSystemService(String name) {
        ServiceFetcher fetcher = SYSTEM_SERVICE_MAP.get(name);
        return fetcher == null ? null : fetcher.getService(this);
    }
    //上面static代码块创建WindowManagerImpl实例用到的方法
    private static void registerService(String serviceName, ServiceFetcher fetcher) {
        if (!(fetcher instanceof StaticServiceFetcher)) {
            fetcher.mContextCacheIndex = sNextPerContextServiceCacheIndex++;
        }
        SYSTEM_SERVICE_MAP.put(serviceName, fetcher);
    }
}

在静态代码块中注册了一堆服务,其中就包括 WINDOW_SERVICE 服务,返回了一个 WindowManagerImpl 实例。
这个 WindowManagerIMpl 与 Activity 中关联的 WindowManagerImpl 实例的不同之处在于这个是通过一个参数的构造方法创建的,其实也就是 parentWindow 是 null,而Activity 中 Window 的 WindowManager 成员在构造实例化时传入给 WindowManagerImpl 中 mParentWindow 成员的是当前 Window 对象,;还要就是静态代码块是只在初始时加载一次,所以这个 WindowManager 是全局单例的。
每一个 Activity 都会新创建一个 WindowManager 实例来显示 Activity 的界面的,在 setContentView 触发 Activity 的 resume 状态后会调用 makeVisible 方法,其中就是获取 Activity 的 mWindowManager 成员 addView 的:

 void makeVisible() {
        if (!mWindowAdded) {
            //也就是获取Activity的mWindowManager
            //这个mWindowManager是在Activity的attach中通过mWindow.getWindowManager()获得
            ViewManager wm = getWindowManager();
            //调运的实质就是ViewManager接口的addView方法,传入的是mDecorView
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

可以看到这里 addView 的 View 参数是 mDecor 也就是 Phone Window 的内部类 DecorView。
Context的WindowManager对每个APP来说是一个全局单例的,而Activity的WindowManager是每个Activity都会新创建一个的(其实你从上面分析的两个实例化WindowManagerImpl的构造函数参数传递就可以看出来,Activity中Window的WindowManager成员在构造实例化时传入给WindowManagerImpl中mParentWindow成员的是当前Window对象,而ContextImpl的static块中单例实例化WindowManagerImpl时传入给WindowManagerImpl中mParentWindow成员的是null值),使用 Activity 的 getSysytemService(WINDOW_SERVICE) 获取的是 Local 的WindowManager。

Dialog 窗口添加显示机制

从 Dialog 的构造函数说起:

public class Dialog implements DialogInterface, Window.Callback,
        KeyEvent.Callback, OnCreateContextMenuListener, Window.OnWindowDismissedCallback {
    ......
    public Dialog(Context context) {
        this(context, 0, true);
    }
    //构造函数最终都调运了这个默认的构造函数
    Dialog(Context context, int theme, boolean createContextThemeWrapper) {
        //默认构造函数的createContextThemeWrapper为true
        if (createContextThemeWrapper) {
            //默认构造函数的theme为0
            if (theme == 0) {
                TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme,
                        outValue, true);
                theme = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, theme);
        } else {
            mContext = context;
        }
        //mContext已经从外部传入的context对象获得值(一般是个Activity)!!!非常重要,先记住!!!

        //获取WindowManager对象
        mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        //为Dialog创建新的Window
        Window w = PolicyManager.makeNewWindow(mContext);
        mWindow = w;
        //Dialog能够接受到按键事件的原因
        w.setCallback(this);
        w.setOnWindowDismissedCallback(this);
        //关联WindowManager与新Window,特别注意第二个参数token为null,也就是说Dialog没有自己的token
        //一个Window属于Dialog的话,那么该Window的mAppToken对象是null
        w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);
        mListenersHandler = new ListenersHandler(this);
    }
    ......
}

Dialog 的 Window 也是通过 PolicyManager.makeNewWindow 方法创建的,但是 WindowManager 是 context.getSystemService 方法获取的,也就是说没有新建 WindowManager 的实例,这个 context 是通过构造方法传进来的,一般是 Activity Context,所以 Dialog 的 WindowManager 其实是 Activity 的 mWindowManager,并通过 Window 类的 setWindowManager 方法与 Window 关联,Dialog 类也实现了 Window.Callback,Window.OnWindowDismissedCallback并给 Window.setCallback,所以 Dialog 能够接受到点击等事件。
至此Dialog的创建过程Window处理已经完毕,接下来我们继续看看Dialog的show与cancel方法:

  public void show() {
        ......
        if (!mCreated) {
            //回调Dialog的onCreate方法
            dispatchOnCreate(null);
        }
        //回调Dialog的onStart方法
        onStart();
        //类似于Activity,获取当前新Window的DecorView对象,所以有一种自定义Dialog布局的方式就是重写Dialog的onCreate方法,使用setContentView传入布局,就像前面文章分析Activity类似
        mDecor = mWindow.getDecorView();
        ......
        //获取新Window的WindowManager.LayoutParams参数,和上面分析的Activity一样type为TYPE_APPLICATION
        WindowManager.LayoutParams l = mWindow.getAttributes();
        ......
        try {
            //把一个View添加到Activity共用的windowManager里面去
            mWindowManager.addView(mDecor, l);
            ......
        } finally {
        }
    }

就是把获取要添加的 Window 的 mDecor 和 LayoutParams,调用 WindowManager 的 addView 方法完成添加。
Activity 和 Dialog 共用了一个 Token 对象,Dialog 必须依赖于 Activity 而显示(通过别的 context 搞完之后 token 都为 null,最终会在 ViewRootImpl 的 setView 方法中加载时因为 token 为 null 抛出异常),所以 Dialog 的 Context 传入参数一般是一个存在的 Activity,如果 Dialog 弹出来之前 Activity 已经被销毁了,则这个 Dialog 在弹出的时候就会抛出异常,因为 token 不可用了。在 Dialog 的构造函数中我们关联了新 Window 的 callback 事件监听处理,所以当 Dialog 显示时 Activity 无法消费当前的事件,直接回调了 Dialog 的 Window 的 Callback 监听,Activity 的 Window 接收不到。

PopupWindow 窗口添加显示机制

PopWindow 实质就是弹出式菜单,它与 Dialag 不同的地方是不会使依赖的 Activity 组件失去焦点(PopupWindow 弹出后可以继续与依赖的 Activity 进行交互),Dialog 却不能这样。同时PopupWindow 与 Dialog 另一个不同点是 PopupWindow 是一个阻塞的对话框,如果你直接在 Activity 的 onCreate 等方法中显示它则会报错,所以 PopupWindow 必须在某个事件中显示地或者是开启一个新线程去调用。
先看 PopupWindow 最常用的一种构造函数:

public class PopupWindow {
    ......
    //我们只分析最常用的一种构造函数
    public PopupWindow(View contentView, int width, int height, boolean focusable) {
        if (contentView != null) {
            //获取mContext,contentView实质是View,View的mContext都是构造函数传入的,View又层级传递,所以最终这个mContext实质是Activity!!!很重要
            mContext = contentView.getContext();
            //获取Activity的getSystemService的WindowManager
            mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        //进行一些Window类的成员变量初始化赋值操作
        setContentView(contentView);
        setWidth(width);
        setHeight(height);
        setFocusable(focusable);
    }
    ......
}

mContext 变量是通过 contentView 的 getContext 赋值的,contentView 实质是 View,View 的 context 是通过构造函数传入的,并且是层层传递的,所以这个 context 实际是 Activity。然后获取 Activity 的 WindowManager,但是并没有创建新的 Window
再看展示方法:

public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
        ......
        //anchor是Activity中PopWindow准备依附的View,这个View的token实质也是Activity的Window中的token,也即Activity的token
        //第一步   初始化WindowManager.LayoutParams
        WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken());
        //第二步
        preparePopup(p);
        ......
        //第三步
        invokePopup(p);
    }

private WindowManager.LayoutParams createPopupLayout(IBinder token) {
        //实例化一个默认的WindowManager.LayoutParams,其中type=TYPE_APPLICATION
        WindowManager.LayoutParams p = new WindowManager.LayoutParams();
        //设置Gravity
        p.gravity = Gravity.START | Gravity.TOP;
        //设置宽高
        p.width = mLastWidth = mWidth;
        p.height = mLastHeight = mHeight;
        //依据背景设置format
        if (mBackground != null) {
            p.format = mBackground.getOpacity();
        } else {
            p.format = PixelFormat.TRANSLUCENT;
        }
        //设置flags
        p.flags = computeFlags(p.flags);
        //修改type=WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,mWindowLayoutType有初始值,type类型为子窗口
        p.type = mWindowLayoutType;
        //设置token为Activity的token
        p.token = token;
        ......
        return p;
    }

private void preparePopup(WindowManager.LayoutParams p) {
        ......
        //有无设置PopWindow的background区别
        if (mBackground != null) {
            ......
            //如果有背景则创建一个PopupViewContainer对象的ViewGroup
            PopupViewContainer popupViewContainer = new PopupViewContainer(mContext);
            PopupViewContainer.LayoutParams listParams = new PopupViewContainer.LayoutParams(
                    ViewGroup.LayoutParams.MATCH_PARENT, height
            );
            //把背景设置给PopupViewContainer的ViewGroup
            popupViewContainer.setBackground(mBackground);
            //把我们构造函数传入的View添加到这个ViewGroup
            popupViewContainer.addView(mContentView, listParams);
            //返回这个ViewGroup
            mPopupView = popupViewContainer;
        } else {
            //如果没有通过PopWindow的setBackgroundDrawable设置背景则直接赋值当前传入的View为PopWindow的View
            mPopupView = mContentView;
        }
        ......
    }

private void invokePopup(WindowManager.LayoutParams p) {
        if (mContext != null) {
            p.packageName = mContext.getPackageName();
        }
        mPopupView.setFitsSystemWindows(mLayoutInsetDecor);
        setLayoutDirectionFromAnchor();
        mWindowManager.addView(mPopupView, p);
    }

anchor 是 Activity 中 PopWindow 准备依附的 View,这个 View 的 token 实质也是 Activity 的 Window 中的 token,也即 Activity 的 token。
第一步是创建一个 WindowManager.LayoutParams;
第二步 preparePopup 方法的作用就是判断设置 View,如果有背景则会在传入的 contentView 外面包一层PopupViewContainer(实质是一个重写了事件处理的 FrameLayout)之后作为 mPopupView,如果没有背景则直接用 contentView 作为 mPopupView。
PopupViewContainer 是一个 PopWindow 的内部私有类,它继承了 FrameLayout,在其中重写了 Key 和 Touch 事件的分发处理逻辑。同时查阅 PopupView 可以发现,PopupView 类自身没有重写 Key 和 Touch 事件的处理,所以如果没有将传入的 View对象放入封装的 ViewGroup 中,则点击 Back 键或者PopWindow 以外的区域 PopWindow 是不会消失的(其实PopWindow 中没有向 Activity 及 Dialog 一样 new 新的 Window ,所以不会有新的 callback 设置,也就没法处理事件消费了)。这也是为什么我们设置 PopupWindow 点击外部消失之前要先设置背景才有效。
第三步就是使用 WindowManager addView,因为 PopupWindow 没有 new 新的 Window,所以 mDecor 不是从 Window 里面获取的,而是在 preparPopup 方法中调用 createDecorView 方法生成的:

 private PopupDecorView createDecorView(View contentView) {
        final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
        final int height;
        if (layoutParams != null && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
            height = ViewGroup.LayoutParams.WRAP_CONTENT;
        } else {
            height = ViewGroup.LayoutParams.MATCH_PARENT;
        }

        final PopupDecorView decorView = new PopupDecorView(mContext);
        decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height);
        decorView.setClipChildren(false);
        decorView.setClipToPadding(false);

        return decorView;
    }
Toast 窗口添加显示机制

我们常用的Toast窗口其实和前面分析的Activity、Dialog、PopWindow都是不同的,因为它和输入法、墙纸类似,都是系统窗口。
通过分析TN类的handler可以发现,如果想在非UI线程使用Toast需要自行声明Looper,否则运行会抛出Looper相关的异常;UI线程不需要,因为系统已经帮忙声明。
在使用Toast时context参数尽量使用getApplicationContext(),可以有效的防止静态引用导致的内存泄漏。
有时候我们会发现Toast弹出过多就会延迟显示,因为上面源码分析可以看见Toast.makeText是一个静态工厂方法,每次调用这个方法都会产生一个新的Toast对象,当我们在这个新new的对象上调用show方法就会使这个对象加入到NotificationManagerService管理的mToastQueue消息显示队列里排队等候显示;所以如果我们不每次都产生一个新的Toast对象(使用单例来处理)就不需要排队,也就能及时更新了。
总结一下:添加 Window 就是WindowManager 的 addView 方法,需要三个对象,WindowManager、View、LayoutParams

  • Activity 的添加操作是在 makeVisible 方法里wm.addView(mDecor, getWindow().getAttributes()); WindowManager 是在 Window 的 setWindowManager 方法里 调用 WindowManagerImpl 的 createLocalWindowManager 方法
    通过 WindowManagerImpl(display,parentWindow) 构造函数构创建的,mDecor 是在 Window 的 setContentView 方法中调用 initDecor 方法创建的,LayoutParams 是 Window 的 getAttributes 方法获得一个默认为 MATCH_PARENT 的 LayoutParams 成员变量。
  • Dialog 的 addView 是在 show 方法里调用的,WindowManger 是context.getSystemService(Context.WINDOW_SERVICE)获取的 Activity 的 WindowManager,mDecor 是 mWindow.getDecorView(); 获取到 Window 的 DecorView,LayoutParams 也是 Window 的 MATCH_PARENT 的 LayoutParamms 成员变量
  • PopupWindow 也是 mContext.getSystemService(Context.WINDOW_SERVICE) 获取的 Activity 的 WindowManager,因为 PopupWindow 没有创建新的 Window, 添加的 mPopupView 是在 preparePopup 方法中在传入的 contentView 外面包一层 PopupViewContainer 或者直接就是 contentView,LayoutParams 是在 showXXX 方法中调用的 createPopupLayout 方法中构造的。
    参考:
    Android应用Activity、Dialog、PopWindow、Toast窗口添加机制及源码分析
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容