经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 移动开发 » Android » 查看文章
Android中WindowManager与WMS的解析
来源:jb51  时间:2019/1/15 9:17:09  对本文有异议

最近在改bug的时候发现在windowManager.addView的时候会发生莫名其妙的崩溃,那个崩溃真的是让你心态爆炸,潜心研究了两天window相关的东西,虽然不是很深奥的东西,本人也只是弄清楚了window的添加逻辑,在此分享给大家:

一、悬浮窗的概念

在android中,无论我们的app界面,还是系统桌面,再或者是手机下方的几个虚拟按键和最上方的状态栏,又或者是一个吐司。。。我们所看到的所有界面,都是由一个个悬浮窗口组成的。

但是这些窗口有不同的级别:

  1. 系统的是老大,是最高级别,你没见过你下载的什么app把你的下拉菜单盖住了吧-。=

  2. 其次是每一个应用,都有自己的一个应用级别窗口。

  3. 在应用之内能创建好多的界面,所以还有一种是应用内的窗口。

基于上述三种,android把悬浮窗划分成三个级别,并通过静态int型变量来表示:

  1. /**
  2.      * Start of system-specific window types. These are not normally
  3.      * created by applications.
  4.      **/
  5.     public static final int FIRST_SYSTEM_WINDOW   = 2000;
  6.     /**
  7.      * End of types of system windows.
  8.      **/
  9.     public static final int LAST_SYSTEM_WINDOW   = 2999;

2000~2999:在系统级别的悬浮窗范围内,一般我们要想创建是需要申请权限。

  1. public static final int FIRST_SUB_WINDOW = 1000;
  2.     /**
  3.      * End of types of sub-windows.
  4.      **/
  5.     public static final int LAST_SUB_WINDOW = 1999;

1000~1999:子窗口级别的悬浮窗,他如果想要创建必须在一个父窗口下。

  1. public static final int TYPE_BASE_APPLICATION  = 1;
  2. public static final int LAST_APPLICATION_WINDOW = 99;

1~99:应用程序级别的悬浮窗,作为每个应用程序的基窗口。

在每段的范围内都有众多个窗口类型,这个具体就不说了,因为太多了根本说不完。。

但是说了这么半天,悬浮窗到底是个啥东西,可能这个名词听得很多,但是仔细想想android中用到的哪个控件还是哪个类叫悬浮窗?没有吧,那么View总该知道吧(不知道别说你是做android的)

其实说白了悬浮窗就是一个被包裹的view。因为除了一个view他还有很多的属性:长宽深度,类型,证书等等东西,只是属性很多而且属性之间的依赖关系有一些复杂而已。简单的来说可以这么理解。

二、WindowManager介绍

上面简单介绍了悬浮窗的概念,而WindowManager是对悬浮窗进行操作的一个媒介。

WindowManager是一个接口,他是继承了ViewManager接口中的三个方法:

  1. public interface ViewManager
  2. {
  3.   public void addView(View view, ViewGroup.LayoutParams params);
  4.   public void updateViewLayout(View view, ViewGroup.LayoutParams params);
  5.   public void removeView(View view);
  6. }

windowManage暴露给我们的只是这个三个方法,真的是简单粗暴,但是很实用。

这三个方法看名字就知道含义了,增删改嘛,就不多说啦。

而在上面提到的对于悬浮窗的三种分类,也是WindowManager的内部类:WindowManager.LayoutParams,关于LayoutParams是什么在这里就不多说了。这不是我们的重点。

我们平时想要添加一个悬浮窗,就会使用第一个方法:

  1.   WindowManager windowManager = getWindowManager();
  2.   windowManager.addView(.....);

我们在getWindowManager获取的类,实际上是WindowManager的是WindowManager的实现类:WindowManagerImpl。接下来我们走一下添加悬浮窗的流程。

三、悬浮窗添加流程

入口肯定是从自己的addView中,上面说到了WindowManager的实现类是WindowManagerImpl,来看一下:

  1. @Override
  2.     public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
  3.     applyDefaultToken(params);
  4.     mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
  5.   }

这里有两步:第一步是给layoutparams设置一个默认的令牌(就是token这个属性,至于这个干什么的等会再说)

  1.   private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
  2.     // 设置条件:有默认令牌,而且不是子窗口级别的悬浮窗
  3.     if (mDefaultToken != null && mParentWindow == null) {
  4.       if (!(params instanceof WindowManager.LayoutParams)) {
  5.         throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
  6.       }
  7.       // 如果没有令牌就设置默认令牌
  8.       final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
  9.       if (wparams.token == null) {
  10.         wparams.token = mDefaultToken;
  11.       }
  12.     }
  13.   }

然后调用了mGlobal的addView:

  1.   public void addView(View view, ViewGroup.LayoutParams params,
  2.       Display display, Window parentWindow) {
  3.     /**进行一系列判空操作。。。**/
  4.     if (parentWindow != null) {
  5.       parentWindow.adjustLayoutParamsForSubWindow(wparams);
  6.     } else {
  7.       // If there's no parent, then hardware acceleration for this view is
  8.       // set from the application's hardware acceleration setting.
  9.       final Context context = view.getContext();
  10.       if (context != null
  11.           && (context.getApplicationInfo().flags
  12.               & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
  13.         wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
  14.       }
  15.     }
  16.     ViewRootImpl root;
  17.       root = new ViewRootImpl(view.getContext(), display);
  18.       view.setLayoutParams(wparams);
  19.       mViews.add(view);
  20.       mRoots.add(root);
  21.       mParams.add(wparams);
  22.       // do this last because it fires off messages to start doing things
  23.       try {
  24.         root.setView(view, wparams, panelParentView);
  25.       } catch (RuntimeException e) {
  26.         // BadTokenException or InvalidDisplayException, clean up.
  27.         if (index >= 0) {
  28.           removeViewLocked(index, true);
  29.         }
  30.         throw e;
  31.       }
  32.     }
  33.   }

看到WindowManagerGLobal中有三个属性: mViews、mRoots、mParams,可以大胆猜测这个类中保存了我们进程中的所有视图以及相关属性。在这里主要关注一下ViewRootImpl的这个实例对象root,接下来的会走进root的setView中。

ViewRootImpl的setView方法内容有点多,我这里就截取关键的两部分:

1.

  1.   int res; /** = WindowManagerImpl.ADD_OKAY; **/
  2.   try {
  3.     mOrigWindowType = mWindowAttributes.type;
  4.     mAttachInfo.mRecomputeGlobalAttributes = true;
  5.     collectViewAttributes();
  6.     res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
  7.         getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
  8.         mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
  9.         mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

创建了一个名为res的int类型变量,他要获取到的是悬浮窗添加的结果:成功或者失败。

2.

  1. if (res < WindowManagerGlobal.ADD_OKAY) {
  2.         mAttachInfo.mRootView = null;
  3.         mAdded = false;
  4.         mFallbackEventHandler.setView(null);
  5.         unscheduleTraversals();
  6.         setAccessibilityFocus(null, null);
  7.         switch (res) {
  8.           case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
  9.           case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
  10.             throw new WindowManager.BadTokenException(
  11.                 "Unable to add window -- token " + attrs.token
  12.                 + " is not valid; is your activity running?");
  13.           case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
  14.             throw new WindowManager.BadTokenException(
  15.                 "Unable to add window -- token " + attrs.token
  16.                 + " is not for an application");
  17.           case WindowManagerGlobal.ADD_APP_EXITING:
  18.             throw new WindowManager.BadTokenException(
  19.                 "Unable to add window -- app for token " + attrs.token
  20.                 + " is exiting");
  21.           case WindowManagerGlobal.ADD_DUPLICATE_ADD:
  22.             throw new WindowManager.BadTokenException(
  23.                 "Unable to add window -- window " + mWindow
  24.                 + " has already been added");
  25.           case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
  26.             // Silently ignore -- we would have just removed it
  27.             // right away, anyway.
  28.             return;
  29.           case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
  30.             throw new WindowManager.BadTokenException("Unable to add window "
  31.                 + mWindow + " -- another window of type "
  32.                 + mWindowAttributes.type + " already exists");
  33.           case WindowManagerGlobal.ADD_PERMISSION_DENIED:
  34.             throw new WindowManager.BadTokenException("Unable to add window "
  35.                 + mWindow + " -- permission denied for window typ
  36.                   + mWindowAttributes.type);
  37.           case WindowManagerGlobal.ADD_INVALID_DISPLAY:
  38.               throw new WindowManager.InvalidDisplayException("Unable to add window "
  39.                   + mWindow + " -- the specified display can not be found");
  40.           case WindowManagerGlobal.ADD_INVALID_TYPE:
  41.               throw new WindowManager.InvalidDisplayException("Unable to add window "
  42.                   + mWindow + " -- the specified window type "
  43.                   + mWindowAttributes.type + " is not valid");
  44.         }
  45.         throw new RuntimeException(
  46.               "Unable to add window -- unknown error code " + res);
  47.       }

第二部分是res返回失败的所有情况,在添加成功的时候res为OKAY,而非OKAY的情况就是上述情况。

接下来来看一下添加悬浮窗的操作,就是1中mWindowSession.addToDisplay。mWindowSession类型如下:

  1.   final IWindowSession mWindowSession;

在这里其实用到了aidl跨进程通信,最终执行该方法的类是Session:

  1.   @Override
  2.   public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
  3.       int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
  4.       Rect outStableInsets, Rect outOutsets,
  5.       DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
  6.     return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
  7.         outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
  8.   }

这个mService就是一个关键了系统类——WindowMamagerService(WMS)。到了这里我们简单过一下思路:在addView之后,通过WindowManagerGlobal进行一些相关配置,传入ViewRootImpl,再通过aidl方式发送给WMS系统服务。

可能有小伙伴会疑惑。好端端的为什么要用aidl实现?最开始本人也有这个疑惑,但是后来想了想所有的窗口无论系统窗口还是第三方app,窗口都是要通过一个类去进行添加允许判断,这里使用aidl是在合适不过的了。我们接着看一下WMS的addWindow方法:

这个addWindow方法又是一段超长的代码,所以也就不全粘,说一下他的简单流程吧,主要是分为三步:权限判断、条件筛选、添加窗口

WMS的addWindow方法:

  1.   int res = mPolicy.checkAddPermission(attrs, appOp);
  2.   if (res != WindowManagerGlobal.ADD_OKAY) {
  3.     return res;
  4.   }

首先进行一个权限判断,

  1. final WindowManagerPolicy mPolicy;

WindowManagerPolicy的实现类是PhoneWindowManagerPolicy,看一下他的实现:

又是小一百行的代码,我们拆开来看:

  1.   //排除不属于三种类型悬浮窗范围内的type
  2.   //很明显的三段排除。
  3.   if (!((type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW)
  4.       || (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW)
  5.       || (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW))) {
  6.     return WindowManagerGlobal.ADD_INVALID_TYPE;
  7.   }
  8.   //不是系统级别的悬浮窗直接满足条件
  9.   if (type < FIRST_SYSTEM_WINDOW || type > LAST_SYSTEM_WINDOW) {
  10.     return ADD_OKAY;
  11.   }
  12.     //以下几种不是系统警告类型的系统弹窗,会满足条件,除此之外的使用默认判断的方式
  13.     if (!isSystemAlertWindowType(type)) {
  14.       switch (type) {
  15.         case TYPE_TOAST:
  16.           outAppOp[0] = OP_TOAST_WINDOW;
  17.           return ADD_OKAY;
  18.         case TYPE_DREAM:
  19.         case TYPE_INPUT_METHOD:
  20.         case TYPE_WALLPAPER:
  21.         case TYPE_PRESENTATION:
  22.         case TYPE_PRIVATE_PRESENTATION:
  23.         case TYPE_VOICE_INTERACTION:
  24.         case TYPE_ACCESSIBILITY_OVERLAY:
  25.         case TYPE_QS_DIALOG:
  26.           // The window manager will check these.
  27.           return ADD_OKAY;
  28.       }
  29.       return mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
  30.           == PERMISSION_GRANTED ? ADD_OKAY : ADD_PERMISSION_DENIED;
  31.     }

后面的几段代码会频繁出现最后的这段代码:mContext.checkCallingOrSelfPermission,具体实现的类是ContextFixture:

  1. @Override
  2.     public int checkCallingOrSelfPermission(String permission) {
  3.       if (mPermissionTable.contains(permission)
  4.           || mPermissionTable.contains(PERMISSION_ENABLE_ALL)) {
  5.         logd("checkCallingOrSelfPermission: " + permission + " return GRANTED");
  6.         return PackageManager.PERMISSION_GRANTED;
  7.       } else {
  8.         logd("checkCallingOrSelfPermission: " + permission + " return DENIED");
  9.         return PackageManager.PERMISSION_DENIED;
  10.       }
  11.     }

这里会使用默认权限判断的方式,要么允许对应权限,要么就是拥有全部权限,否则就会返回DENIED。

这个说完接着回到checkPermission方法。

  1. //对于系统进程直接满足允许
  2.     final int callingUid = Binder.getCallingUid();
  3.     if (UserHandle.getAppId(callingUid) == Process.SYSTEM_UID) {
  4.       return ADD_OKAY;
  5.     }

说实话下面这一段代码我看的不是很明白,只是看到了这里对8.0之后做了版本限制,直接使用默认检查方式。

  1. ApplicationInfo appInfo;
  2.     try {
  3.       appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
  4.               attrs.packageName,
  5.               0 /* flags */,
  6.               UserHandle.getUserId(callingUid));
  7.     } catch (PackageManager.NameNotFoundException e) {
  8.       appInfo = null;
  9.     }
  10.     if (appInfo == null || (type != TYPE_APPLICATION_OVERLAY && appInfo.targetSdkVersion >= O)) {
  11.       
  12.       return (mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
  13.           == PERMISSION_GRANTED) ? ADD_OKAY : ADD_PERMISSION_DENIED;
  14.     }

这段是要从PackageManager中获取ApplicationInfo,如果获取失败会抛出NameNotFound异常。所以下面的判断是在异常的时候使用默认权限处理方式。

最后还以一步检查操作,关系不大就不看了。到这里checkPermission方法就结束了。

权限检查的步骤已经结束,接着就是根据上述获取到的结果进行条件筛选。

  1.   if (res != WindowManagerGlobal.ADD_OKAY) {
  2.     return res;
  3.   }

首先在权限检查的步骤获取权限失败,那么会直接返回,不会执行条件筛选的步骤。而真正的条件筛选步骤代码也是很多,我这里直接粘过来然后说了。

  1.   //111111111111111
  2.       if (!mDisplayReady) {
  3.         throw new IllegalStateException("Display has not been initialialized");
  4.       }
  5.       final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
  6.       if (displayContent == null) {
  7.         Slog.w(TAG_WM, "Attempted to add window to a display that does not exist: "
  8.             + displayId + ". Aborting.");
  9.         return WindowManagerGlobal.ADD_INVALID_DISPLAY;
  10.       }
  11.       if (!displayContent.hasAccess(session.mUid)
  12.           && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
  13.         Slog.w(TAG_WM, "Attempted to add window to a display for which the application "
  14.             + "does not have access: " + displayId + ". Aborting.");
  15.         return WindowManagerGlobal.ADD_INVALID_DISPLAY;
  16.       }
  17.       if (mWindowMap.containsKey(client.asBinder())) {
  18.         Slog.w(TAG_WM, "Window " + client + " is already added");
  19.         return WindowManagerGlobal.ADD_DUPLICATE_ADD;
  20.       }
  21.       //22222222222222
  22.       if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
  23.         parentWindow = windowForClientLocked(null, attrs.token, false);
  24.         if (parentWindow == null) {
  25.           Slog.w(TAG_WM, "Attempted to add window with token that is not a window: "
  26.              + attrs.token + ". Aborting.");
  27.           return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
  28.         }
  29.         if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
  30.             && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
  31.           Slog.w(TAG_WM, "Attempted to add window with token that is a sub-window: "
  32.               + attrs.token + ". Aborting.");
  33.           return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
  34.         }
  35.       }
  36.       //333333333333333
  37.       if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
  38.         Slog.w(TAG_WM, "Attempted to add private presentation window to a non-private display. Aborting.");
  39.         return WindowManagerGlobal.ADD_PERMISSION_DENIED;
  40.       }
  41.       //444444444444444
  42.       AppWindowToken atoken = null;
  43.       final boolean hasParent = parentWindow != null;
  44.       // Use existing parent window token for child windows since they go in the same token
  45.       // as there parent window so we can apply the same policy on them.
  46.       WindowToken token = displayContent.getWindowToken(
  47.           hasParent ? parentWindow.mAttrs.token : attrs.token);
  48.       // If this is a child window, we want to apply the same type checking rules as the
  49.       // parent window type.
  50.       final int rootType = hasParent ? parentWindow.mAttrs.type : type;
  51.       boolean addToastWindowRequiresToken = false;
  52.       if (token == null) {
  53.         if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
  54.           Slog.w(TAG_WM, "Attempted to add application window with unknown token "
  55.              + attrs.token + ". Aborting.");
  56.           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  57.         }
  58.         if (rootType == TYPE_INPUT_METHOD) {
  59.           Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
  60.              + attrs.token + ". Aborting.");
  61.           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  62.         }
  63.         if (rootType == TYPE_VOICE_INTERACTION) {
  64.           Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
  65.              + attrs.token + ". Aborting.");
  66.           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  67.         }
  68.         if (rootType == TYPE_WALLPAPER) {
  69.           Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
  70.              + attrs.token + ". Aborting.");
  71.           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  72.         }
  73.         if (rootType == TYPE_DREAM) {
  74.           Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
  75.              + attrs.token + ". Aborting.");
  76.           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  77.         }
  78.         if (rootType == TYPE_QS_DIALOG) {
  79.           Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
  80.              + attrs.token + ". Aborting.");
  81.           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  82.         }
  83.         if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
  84.           Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
  85.               + attrs.token + ". Aborting.");
  86.           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  87.         }
  88.         if (type == TYPE_TOAST) {
  89.           // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
  90.           if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
  91.               parentWindow)) {
  92.             Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
  93.                 + attrs.token + ". Aborting.");
  94.             return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  95.           }
  96.         }
  97.         final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
  98.         final boolean isRoundedCornerOverlay =
  99.             (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
  100.         token = new WindowToken(this, binder, type, false, displayContent,
  101.             session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
  102.       } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
  103.         atoken = token.asAppWindowToken();
  104.         if (atoken == null) {
  105.           Slog.w(TAG_WM, "Attempted to add window with non-application token "
  106.              + token + ". Aborting.");
  107.           return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
  108.         } else if (atoken.removed) {
  109.           Slog.w(TAG_WM, "Attempted to add window with exiting application token "
  110.              + token + ". Aborting.");
  111.           return WindowManagerGlobal.ADD_APP_EXITING;
  112.         } else if (type == TYPE_APPLICATION_STARTING && atoken.startingWindow != null) {
  113.           Slog.w(TAG_WM, "Attempted to add starting window to token with already existing"
  114.               + " starting window");
  115.           return WindowManagerGlobal.ADD_DUPLICATE_ADD;
  116.         }
  117.       } else if (rootType == TYPE_INPUT_METHOD) {
  118.         if (token.windowType != TYPE_INPUT_METHOD) {
  119.           Slog.w(TAG_WM, "Attempted to add input method window with bad token "
  120.               + attrs.token + ". Aborting.");
  121.            return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  122.         }
  123.       } else if (rootType == TYPE_VOICE_INTERACTION) {
  124.         if (token.windowType != TYPE_VOICE_INTERACTION) {
  125.           Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "
  126.               + attrs.token + ". Aborting.");
  127.            return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  128.         }
  129.       } else if (rootType == TYPE_WALLPAPER) {
  130.         if (token.windowType != TYPE_WALLPAPER) {
  131.           Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "
  132.               + attrs.token + ". Aborting.");
  133.            return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  134.         }
  135.       } else if (rootType == TYPE_DREAM) {
  136.         if (token.windowType != TYPE_DREAM) {
  137.           Slog.w(TAG_WM, "Attempted to add Dream window with bad token "
  138.               + attrs.token + ". Aborting.");
  139.            return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  140.         }
  141.       } else if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
  142.         if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
  143.           Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "
  144.               + attrs.token + ". Aborting.");
  145.           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  146.         }
  147.       } else if (type == TYPE_TOAST) {
  148.         // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
  149.         addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
  150.             callingUid, parentWindow);
  151.         if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
  152.           Slog.w(TAG_WM, "Attempted to add a toast window with bad token "
  153.               + attrs.token + ". Aborting.");
  154.           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  155.         }
  156.       } else if (type == TYPE_QS_DIALOG) {
  157.         if (token.windowType != TYPE_QS_DIALOG) {
  158.           Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
  159.               + attrs.token + ". Aborting.");
  160.           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
  161.         }
  162.       } else if (token.asAppWindowToken() != null) {
  163.         Slog.w(TAG_WM, "Non-null appWindowToken for system window of rootType=" + rootType);
  164.         // It is not valid to use an app token with other system types; we will
  165.         // instead make a new token for it (as if null had been passed in for the token).
  166.         attrs.token = null;
  167.         token = new WindowToken(this, client.asBinder(), type, false, displayContent,
  168.             session.mCanAddInternalSystemWindow);
  169.       }
  170.       //5555555555555
  171.       final WindowState win = new WindowState(this, session, client, token, parentWindow,
  172.           appOp[0], seq, attrs, viewVisibility, session.mUid,
  173.           session.mCanAddInternalSystemWindow);
  174.       if (win.mDeathRecipient == null) {
  175.         // Client has apparently died, so there is no reason to
  176.         // continue.
  177.         Slog.w(TAG_WM, "Adding window client " + client.asBinder()
  178.             + " that is dead, aborting.");
  179.         return WindowManagerGlobal.ADD_APP_EXITING;
  180.       }
  181.       if (win.getDisplayContent() == null) {
  182.         Slog.w(TAG_WM, "Adding window to Display that has been removed.");
  183.         return WindowManagerGlobal.ADD_INVALID_DISPLAY;
  184.       }
  185.       final boolean hasStatusBarServicePermission =
  186.           mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
  187.               == PackageManager.PERMISSION_GRANTED;
  188.       mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
  189.       win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
  190.       res = mPolicy.prepareAddWindowLw(win, attrs);
  191.       if (res != WindowManagerGlobal.ADD_OKAY) {
  192.         return res;
  193.       }
  194.       final boolean openInputChannels = (outInputChannel != null
  195.           && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
  196.       if (openInputChannels) {
  197.         win.openInputChannel(outInputChannel);
  198.       }
  199.       //666666666666666
  200.       if (type == TYPE_TOAST) {
  201.         if (!getDefaultDisplayContentLocked().canAddToastWindowForUid(callingUid)) {
  202.           Slog.w(TAG_WM, "Adding more than one toast window for UID at a time.");
  203.           return WindowManagerGlobal.ADD_DUPLICATE_ADD;
  204.         }
  205.         
  206.         if (addToastWindowRequiresToken
  207.             || (attrs.flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0
  208.             || mCurrentFocus == null
  209.             || mCurrentFocus.mOwnerUid != callingUid) {
  210.           mH.sendMessageDelayed(
  211.               mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
  212.               win.mAttrs.hideTimeoutMilliseconds);
  213.         }
  214.       }

这里讲筛选部分大体分成这么几个步骤:

  1. 系统以及初始化的一些判断:就像最开始的四个判断。

  2. 子窗口类型时候的对父窗口的相关筛选(父是否为空,以及父亲的类型判断)

  3. 一种特殊的私有类型条件筛选,该类型属于系统类型

  4. 涉及证书(token)的窗口类型条件筛选。

  5. 状态栏权限条件筛选

  6. 吐司类型的条件筛选

在代码中对应的步骤有明确的标注,而具体的代码大多只是一些判断,所以在感觉没有细说的必要了。

在条件筛选完成之后,剩下的类型都是符合添加的类型,从现在开始就开始对不同的type进行不同的添加。经过多到加工后,将OKAY返回。

如果能从添加窗口的步骤返回,就说明一定是OKAY的。那么我们可以一步步跳回层层调用的代码,最终在ViewRootImpl中,对没有添加成功的抛出异常。

  1.   if (res < WindowManagerGlobal.ADD_OKAY) {
  2.           mAttachInfo.mRootView = null;
  3.           mAdded = false;
  4.           mFallbackEventHandler.setView(null);
  5.           unscheduleTraversals();
  6.           setAccessibilityFocus(null, null);
  7.           switch (res) {
  8.             case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
  9.             case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
  10.               throw new WindowManager.BadTokenException(
  11.                   "Unable to add window -- token " + attrs.token
  12.                   + " is not valid; is your activity running?");
  13.             case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
  14.               throw new WindowManager.BadTokenException(
  15.                   "Unable to add window -- token " + attrs.token
  16.                   + " is not for an application");
  17.             case WindowManagerGlobal.ADD_APP_EXITING:
  18.               throw new WindowManager.BadTokenException(
  19.                   "Unable to add window -- app for token " + attrs.token
  20.                   + " is exiting");
  21.             case WindowManagerGlobal.ADD_DUPLICATE_ADD:
  22.               throw new WindowManager.BadTokenException(
  23.                   "Unable to add window -- window " + mWindow
  24.                   + " has already been added");
  25.             case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
  26.               // Silently ignore -- we would have just removed it
  27.               // right away, anyway.
  28.               return;
  29.             case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
  30.               throw new WindowManager.BadTokenException("Unable to add window "
  31.                   + mWindow + " -- another window of type "
  32.                   + mWindowAttributes.type + " already exists");
  33.             case WindowManagerGlobal.ADD_PERMISSION_DENIED:
  34.               throw new WindowManager.BadTokenException("Unable to add window "
  35.                   + mWindow + " -- permission denied for window type "
  36.                   + mWindowAttributes.type);
  37.             case WindowManagerGlobal.ADD_INVALID_DISPLAY:
  38.               throw new WindowManager.InvalidDisplayException("Unable to add window "
  39.                   + mWindow + " -- the specified display can not be found");
  40.             case WindowManagerGlobal.ADD_INVALID_TYPE:
  41.               throw new WindowManager.InvalidDisplayException("Unable to add window "
  42.                   + mWindow + " -- the specified window type "
  43.                   + mWindowAttributes.type + " is not valid");
  44.           }
  45.           throw new RuntimeException(
  46.               "Unable to add window -- unknown error code " + res);
  47.         }

对于OKAY的,在ViewRootImpl中会做一些其他的操作,反正我是没看懂-。=、

四、小结

到这里WMS的添加悬浮窗口的流程差不多就过了一遍了。可能有些地方说的不是很细,大家下来可以关注一下个别几个点。整个过程有这么几个需要强调的地方。

  • 函数循环嵌套,共同消费返回值。

  • 异常循环嵌套

  • 个别地方对M和O以上的系统进行了限制

如果在添加悬浮窗的时候使用了不同的type,可能会发生异常:本人拿了一个8.0的手机,分别对窗口type设置为OVERLAY和ERROR。因为ERROR类型是被弃用的,我发现使用ERROR会抛出异常,而OVERLAY不会。同样的拿了一个6.0的手机添加ERROR类型就没有异常抛出,肯定是上述的问题导致的,但是具体在哪一块我还没有找到,因为整个流程的出口太多了-。=。

此外在WindowManagerGlobal.addView方法中,有一个地方:

  1.   if (parentWindow != null) {
  2.     parentWindow.adjustLayoutParamsForSubWindow(wparams);
  3.   } else {

这个方法是对于有子窗口类型的证书处理,网上查了一下该方法在四点几、六点几和8.0是不同的,也就是说对证书的处理方式变化了,这里本人还没有细看,有兴趣的盆友可以研究一下然后评论交流一番。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对w3xue的支持。如果你想了解更多相关内容请查看下面相关链接

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号