欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。

程序员文章站 2022-06-23 10:09:01
目录欢迎使用Markdown编辑器Dialog的创建Dialog的show方法WMS对应窗口的管理如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入欢迎使用Markdown编辑器先来一张图展示,activity的结构和内容。可知 activity,phoneWindow,...

关系图

先来一张图展示,activity的结构和内容。可知 activity,phoneWindow,WindowManagerIMPL,viewRootIMPL是一对一的关系,WindowManagerIMPL内部维护一个全局WindowManagerGlobal对象。PhoneWIndow 中对视图的操作,都是通过WindowManagerIMPL代理实现,最终都是ViewRootImpl 和 WMS交互的完成的。
从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。

Dialog的创建

  Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
        if (createContextThemeWrapper) {
        	//可以看出来,dialog其实也支持主题的,就是个小型的activity,但是必须依附于activity,可以视为activity的子窗口,这个后面再说
            if (themeResId == Resources.ID_NULL) {
                final TypedValue outValue = new TypedValue();
                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);
                themeResId = outValue.resourceId;
            }
            mContext = new ContextThemeWrapper(context, themeResId);
        } else {
            mContext = context;
        }
		// 获取wms服务
        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
		//创建phonewindow
        final Window w = new PhoneWindow(mContext);
        
        ...
		...省略
		
		// 初始化phonewindow,注意后面两个参数为空
		//关联WindowManager与新Window,特别注意第二个参数token为null,也就是说Dialog没有自己的token
        //一个Window属于Dialog的话,那么该Window的mAppToken对象是null,
        //mAppToken对象保存在windowState对象中,对应着这个窗口是否在AMS中有ActivityRecord记录。
		w.setWindowManager(mWindowManager, null, null);
        w.setGravity(Gravity.CENTER);

        mListenersHandler = new ListenersHandler(this);
    }

从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。
appToken代表的是activity在AMS服务端的实体记录activityRecord,在ActivityThread内是以ibinder形式存在的,也就是远程通信对象,这个对象很重要,也注定dialog能否展示的关键。
我们来看下这个setWindowManager是怎么处理的:
从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。
也就是一个窗口实例会创建一个WindowManagerIMP的实例,一对一,activity一个对应一个,alertDIalog也对应一个。如果alerDialog的context使用的是activity的话,会获取到activity的WindowManagerIMP的实例,因为activity重写了这个方法。

由于传递的activity的context因此会调用activity重写的getSystemSevice方法,返回的其实是activity的mWIndowManager服务对象。
从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。

//	如果这个context是contextIMP对象的话,那么就是从app全局map中获取的单例,有兴趣自己去查看源码
 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

可以看见Context的WindowManager对每个APP来说是一个全局单例的,而Activity的WindowManager是每个Activity都会新创建一个的(其实你从上面分析的两个实例化WindowManagerImpl的构造函数参数传递就可以看出来,Activity中Window的WindowManager成员在构造实例化时传入给WindowManagerImpl中mParentWindow成员的是当前Window对象,而ContextImpl的static块中单例实例化WindowManagerImpl时传入给WindowManagerImpl中mParentWindow成员的是null值),所以上面模拟苹果浮动小图标使用了Application的WindowManager而不是Activity的,原因就在于这里;使用Activity的WindowManager时当Activity结束时WindowManager就无效了,所以使用Activity的getSysytemService(WINDOW_SERVICE)获取的是Local的WindowManager。同时可以看出来Activity中的WindowManager.LayoutParams的type为TYPE_APPLICATION。

Dialog的show方法

 public void show() { 
 
        if (!mCreated) {
            dispatchOnCreate(null);
        } else { 
        	....
        }
		...
        onStart();
        mDecor = mWindow.getDecorView();
 		...
        WindowManager.LayoutParams l = mWindow.getAttributes();
 		...
 		//这句话是关键
        mWindowManager.addView(mDecor, l);
        ...
        //发送handle消息,通知操作回调
        sendShowMessage();
    }

mWindowManager.addView(mDecor, l);这句话对应文章开头的对象结构图,调用链是:
mWindowManager.addView ===> windowManagerGlobal ===> 创建ViewRootImp ===>
ViewRootImp.setView ===> mWindowSession.addToDisplay 。
mWindowSession是viewRootImp的内部获取的WMS远程对象,用于跨进程调用WMS的方法。
这样就来到了WMS方法内部。

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) {
        int[] appOp = new int[1];
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }
        
        WindowState parentWindow = null; 
        
		...省略
		
        synchronized (mGlobalLock) {  
        	//这种情况一般不存在,只要正常流程displayContent 都是非空
            if (displayContent == null) {
                Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
                        + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            //displayContent对wms没有访问权限,这个只要是在application中应该都可以使用,不考虑
            if (!displayContent.hasAccess(session.mUid)) {
                Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
                        + "does not have access: " + displayId + ".  Aborting.");
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
			// 已经缓存的windowmap中是否已经存在该iWindow对象,该对象是viewRootImp在跨进程中的binder对象,
			// dialog多次添加的情况下,不处理,防止重复添加
            if (mWindowMap.containsKey(client.asBinder())) {
                Slog.w(TAG_WM, "Window " + client + " is already added");
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }
			// dialog窗口的类型是 APPLICATION_WINDOW 类型,这里不看,但是popwindow是该类型的
			// 还有就是 surfaceview的窗口类型也是这种,会获取所依附的父窗口,也就是activity
            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }

            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
                Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display.  Aborting.");
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }
            
      		...省略

上面步骤中,分析了一部分意外情况,还没到dialog的context是非activity的情况。
可以先说下这几个参数是啥:

 Session session, //一个应用会创建一个这个对象,单例保存在viewRootImp中,用于和WMS通信
 IWindow client, //客户端viewRootImp对象的内部类binder实例,传递给WMS,用于WMS远程调用
 LayoutParams attrs, //客户端的windowManagerImp内部的布局对象 

从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。
dialog的layoutParams是TYPE_APPLICATION因此不会执行到 SUB_WINDOW判断里面的内容。

		AppWindowToken atoken = null;
        final boolean hasParent = parentWindow != null;
        // Use existing parent window token for child windows since they go in the same token
        // as there parent window so we can apply the same policy on them.
        WindowToken token = displayContent.getWindowToken(
                hasParent ? parentWindow.mAttrs.token : attrs.token);
        // If this is a child window, we want to apply the same type checking rules as the
        // parent window type.
        final int rootType = hasParent ? parentWindow.mAttrs.type : type;

        boolean addToastWindowRequiresToken = false;

        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;
            }

再往下看,getWindowToken是根据我们传递进来的attrs.token也就是客户端的IWindow对象,从map集合中获取windowToken对象,很显然,如果我们不是传递的activity的token,那么map集合中不可能存在iwindow对象,因为我们都没有保存过怎么可能存在这个对象。这样走走到了token==null的判断里面去了,我们上面说过alertDialog的窗口类型就是APPLICATION_WINDOW类型的,因此下面就会返回WindowManagerGlobal.ADD_BAD_APP_TOKEN;这个错误给我们的客户端。

viewRootIMP对于返回结果的处理

从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。
从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。
从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。
由于我的手机没展示错误就闪退了,只能看日志报错,所有上面日志的报错就对应着,viewrootIMP所抛出的异常错误,结果很明显了,就是application之所以不能创建dialog对象,是因为WMS服务端查询不到对应的windowToken对象,所有会报错误给客户端。

popWindow的创建过程

popWindow创建过程和AlertDialog差不多不赘述,来看下如何展示的:
从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。
可以看到需要外部传入一个view,然后获取这个view的windowToken,而创建的窗口类型是:

 /**
   * Window type: a panel on top of an application window.  These windows
      * appear on top of their attached window.
*/
  public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW;
  private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;

上面看到的就是popupWindow的窗口类型,就是子窗口类型,如果我们使用的是或者不是activity的Token的话,我们按照这个思路去WMS寻找下答案。

		
       if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
           parentWindow = windowForClientLocked(null, attrs.token, false);
           if (parentWindow == null) {
               Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
                     + attrs.token + ".  Aborting.");
               return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
           }
           if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                   && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
               Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
                       + attrs.token + ".  Aborting.");
               return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
           }
       }

那么进入到这个if判断里面去,parentWindow是根据attrs.token来获取的,也就是这个Token对象对应的WindowState,WindowState对象是activity第一次添加窗口的时候创建的,成员变量是mAppToken,保存的是AMS中传递过来ActivityReord对象来进行创建的,因此如果我们的Token不是对应着AMS中的activity的话,那么这个parentWindow也就是个null,接着就会报异常给客户端,viewRootIMP中回对异常进行处理,看下是怎么处理的:
从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。
一样的和AlertDialog的异常是一样的。

偷老罗的一张图展示下,三个进程之间的关系,在我们的AMS的通知我们的ActivityThread进行activity的启动的时候,在AMS中相应把ActivityRecord对象传递给WMS中,在WMS中创建AppWindowToken对象,在Activity第一次添加窗口到WMS中的时候,会将viewRootIMP的iWindow对象,appWindowToken,作为成员变量,创建出windowState对象,这样我们接着在activity中启动AlertDialog的话,添加dialog的window到WMS中,判断这个token是否已经存在,显然如果是activity的Token肯定可以找得到,如果是application的Token那明显是空的,所以会报错。

从源码分析Dialog,PopWindow为啥需要用activity的context创建,而不能用application。
参考:
https://blog.csdn.net/Luoshengyang/article/details/8275938
https://blog.csdn.net/yanbober/article/details/46361191

本文地址:https://blog.csdn.net/u012345683/article/details/107398229