Android中WindowManager与WMS的解析

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

一、悬浮窗的概念

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

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

  1. 系统的是老大,是最高级别,你没见过你下载的什么app把你的下拉菜单盖住了吧-。=
  2. 其次是每一个应用,都有自己的一个应用级别窗口。
  3. 在应用之内能创建好多的界面,所以还有一种是应用内的窗口。

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

    /**
     * Start of system-specific window types. These are not normally
     * created by applications.
     **/
    public static final int FIRST_SYSTEM_WINDOW   = 2000;
    /**
     * End of types of system windows.
     **/
    public static final int LAST_SYSTEM_WINDOW   = 2999;

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

    public static final int FIRST_SUB_WINDOW = 1000;
    /**
     * End of types of sub-windows.
     **/
    public static final int LAST_SUB_WINDOW = 1999;

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

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

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

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

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

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

二、WindowManager介绍

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

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

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

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

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

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

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

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

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

三、悬浮窗添加流程

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

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

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

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

然后调用了mGlobal的addView:

  public void addView(View view, ViewGroup.LayoutParams params,
      Display display, Window parentWindow) {
    /**进行一系列判空操作。。。**/
    if (parentWindow != null) {
      parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
      // If there's no parent, then hardware acceleration for this view is
      // set from the application's hardware acceleration setting.
      final Context context = view.getContext();
      if (context != null
          && (context.getApplicationInfo().flags
              & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
        wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
      }
    }
    ViewRootImpl root;
      root = new ViewRootImpl(view.getContext(), display);
      view.setLayoutParams(wparams);
      mViews.add(view);
      mRoots.add(root);
      mParams.add(wparams);
      // do this last because it fires off messages to start doing things
      try {
        root.setView(view, wparams, panelParentView);
      } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
          removeViewLocked(index, true);
        }
        throw e;
      }
    }
  }

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

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

1.

  int res; /** = WindowManagerImpl.ADD_OKAY; **/
  try {
    mOrigWindowType = mWindowAttributes.type;
    mAttachInfo.mRecomputeGlobalAttributes = true;
    collectViewAttributes();
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
        getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

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

2.

    if (res < WindowManagerGlobal.ADD_OKAY) {
        mAttachInfo.mRootView = null;
        mAdded = false;
        mFallbackEventHandler.setView(null);
        unscheduleTraversals();
        setAccessibilityFocus(null, null);
        switch (res) {
          case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
          case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
            throw new WindowManager.BadTokenException(
                "Unable to add window -- token " + attrs.token
                + " is not valid; is your activity running?");
          case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
            throw new WindowManager.BadTokenException(
                "Unable to add window -- token " + attrs.token
                + " is not for an application");
          case WindowManagerGlobal.ADD_APP_EXITING:
            throw new WindowManager.BadTokenException(
                "Unable to add window -- app for token " + attrs.token
                + " is exiting");
          case WindowManagerGlobal.ADD_DUPLICATE_ADD:
            throw new WindowManager.BadTokenException(
                "Unable to add window -- window " + mWindow
                + " has already been added");
          case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
            // Silently ignore -- we would have just removed it
            // right away, anyway.
            return;
          case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
            throw new WindowManager.BadTokenException("Unable to add window "
                + mWindow + " -- another window of type "
                + mWindowAttributes.type + " already exists");
          case WindowManagerGlobal.ADD_PERMISSION_DENIED:
            throw new WindowManager.BadTokenException("Unable to add window "
                + mWindow + " -- permission denied for window typ
                  + mWindowAttributes.type);
          case WindowManagerGlobal.ADD_INVALID_DISPLAY:
              throw new WindowManager.InvalidDisplayException("Unable to add window "
                  + mWindow + " -- the specified display can not be found");
          case WindowManagerGlobal.ADD_INVALID_TYPE:
              throw new WindowManager.InvalidDisplayException("Unable to add window "
                  + mWindow + " -- the specified window type "
                  + mWindowAttributes.type + " is not valid");
        }
        throw new RuntimeException(
              "Unable to add window -- unknown error code " + res);
      }

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

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

  final IWindowSession mWindowSession;

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

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

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

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

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

WMS的addWindow方法:

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

首先进行一个权限判断,

final WindowManagerPolicy mPolicy;

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

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

  //排除不属于三种类型悬浮窗范围内的type
  //很明显的三段排除。
  if (!((type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW)
      || (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW)
      || (type >= FIRST_SYSTEM_WINDOW && type <= LAST_SYSTEM_WINDOW))) {
    return WindowManagerGlobal.ADD_INVALID_TYPE;
  }
  //不是系统级别的悬浮窗直接满足条件
  if (type < FIRST_SYSTEM_WINDOW || type > LAST_SYSTEM_WINDOW) {
    return ADD_OKAY;
  }
    //以下几种不是系统警告类型的系统弹窗,会满足条件,除此之外的使用默认判断的方式
    if (!isSystemAlertWindowType(type)) {
      switch (type) {
        case TYPE_TOAST:
          outAppOp[0] = OP_TOAST_WINDOW;
          return ADD_OKAY;
        case TYPE_DREAM:
        case TYPE_INPUT_METHOD:
        case TYPE_WALLPAPER:
        case TYPE_PRESENTATION:
        case TYPE_PRIVATE_PRESENTATION:
        case TYPE_VOICE_INTERACTION:
        case TYPE_ACCESSIBILITY_OVERLAY:
        case TYPE_QS_DIALOG:
          // The window manager will check these.
          return ADD_OKAY;
      }
      return mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
          == PERMISSION_GRANTED ? ADD_OKAY : ADD_PERMISSION_DENIED;
    }

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

    @Override
    public int checkCallingOrSelfPermission(String permission) {
      if (mPermissionTable.contains(permission)
          || mPermissionTable.contains(PERMISSION_ENABLE_ALL)) {
        logd("checkCallingOrSelfPermission: " + permission + " return GRANTED");
        return PackageManager.PERMISSION_GRANTED;
      } else {
        logd("checkCallingOrSelfPermission: " + permission + " return DENIED");
        return PackageManager.PERMISSION_DENIED;
      }
    }

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

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

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

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

    ApplicationInfo appInfo;
    try {
      appInfo = mContext.getPackageManager().getApplicationInfoAsUser(
              attrs.packageName,
              0 /* flags */,
              UserHandle.getUserId(callingUid));
    } catch (PackageManager.NameNotFoundException e) {
      appInfo = null;
    }
    if (appInfo == null || (type != TYPE_APPLICATION_OVERLAY && appInfo.targetSdkVersion >= O)) {

      return (mContext.checkCallingOrSelfPermission(INTERNAL_SYSTEM_WINDOW)
          == PERMISSION_GRANTED) ? ADD_OKAY : ADD_PERMISSION_DENIED;
    }

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

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

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

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

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

      //111111111111111
      if (!mDisplayReady) {
        throw new IllegalStateException("Display has not been initialialized");
      }
      final DisplayContent displayContent = getDisplayContentOrCreate(displayId);
      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;
      }
      if (!displayContent.hasAccess(session.mUid)
          && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
        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;
      }
      if (mWindowMap.containsKey(client.asBinder())) {
        Slog.w(TAG_WM, "Window " + client + " is already added");
        return WindowManagerGlobal.ADD_DUPLICATE_ADD;
      }
      //22222222222222
      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;
        }
      }
      //333333333333333
      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;
      }
      //444444444444444
      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;
        }
        if (rootType == TYPE_INPUT_METHOD) {
          Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
             + attrs.token + ". Aborting.");
          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
        if (rootType == TYPE_VOICE_INTERACTION) {
          Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
             + attrs.token + ". Aborting.");
          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
        if (rootType == TYPE_WALLPAPER) {
          Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
             + attrs.token + ". Aborting.");
          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
        if (rootType == TYPE_DREAM) {
          Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
             + attrs.token + ". Aborting.");
          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
        if (rootType == TYPE_QS_DIALOG) {
          Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
             + attrs.token + ". Aborting.");
          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
        if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
          Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
              + attrs.token + ". Aborting.");
          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
        if (type == TYPE_TOAST) {
          // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
          if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
              parentWindow)) {
            Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                + attrs.token + ". Aborting.");
            return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
          }
        }
        final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
        final boolean isRoundedCornerOverlay =
            (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
        token = new WindowToken(this, binder, type, false, displayContent,
            session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
      } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
        atoken = token.asAppWindowToken();
        if (atoken == null) {
          Slog.w(TAG_WM, "Attempted to add window with non-application token "
             + token + ". Aborting.");
          return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
        } else if (atoken.removed) {
          Slog.w(TAG_WM, "Attempted to add window with exiting application token "
             + token + ". Aborting.");
          return WindowManagerGlobal.ADD_APP_EXITING;
        } else if (type == TYPE_APPLICATION_STARTING && atoken.startingWindow != null) {
          Slog.w(TAG_WM, "Attempted to add starting window to token with already existing"
              + " starting window");
          return WindowManagerGlobal.ADD_DUPLICATE_ADD;
        }
      } else if (rootType == TYPE_INPUT_METHOD) {
        if (token.windowType != TYPE_INPUT_METHOD) {
          Slog.w(TAG_WM, "Attempted to add input method window with bad token "
              + attrs.token + ". Aborting.");
           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
      } else if (rootType == TYPE_VOICE_INTERACTION) {
        if (token.windowType != TYPE_VOICE_INTERACTION) {
          Slog.w(TAG_WM, "Attempted to add voice interaction window with bad token "
              + attrs.token + ". Aborting.");
           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
      } else if (rootType == TYPE_WALLPAPER) {
        if (token.windowType != TYPE_WALLPAPER) {
          Slog.w(TAG_WM, "Attempted to add wallpaper window with bad token "
              + attrs.token + ". Aborting.");
           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
      } else if (rootType == TYPE_DREAM) {
        if (token.windowType != TYPE_DREAM) {
          Slog.w(TAG_WM, "Attempted to add Dream window with bad token "
              + attrs.token + ". Aborting.");
           return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
      } else if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
        if (token.windowType != TYPE_ACCESSIBILITY_OVERLAY) {
          Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with bad token "
              + attrs.token + ". Aborting.");
          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
      } else if (type == TYPE_TOAST) {
        // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
        addToastWindowRequiresToken = doesAddToastWindowRequireToken(attrs.packageName,
            callingUid, parentWindow);
        if (addToastWindowRequiresToken && token.windowType != TYPE_TOAST) {
          Slog.w(TAG_WM, "Attempted to add a toast window with bad token "
              + attrs.token + ". Aborting.");
          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
      } else if (type == TYPE_QS_DIALOG) {
        if (token.windowType != TYPE_QS_DIALOG) {
          Slog.w(TAG_WM, "Attempted to add QS dialog window with bad token "
              + attrs.token + ". Aborting.");
          return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
        }
      } else if (token.asAppWindowToken() != null) {
        Slog.w(TAG_WM, "Non-null appWindowToken for system window of rootType=" + rootType);
        // It is not valid to use an app token with other system types; we will
        // instead make a new token for it (as if null had been passed in for the token).
        attrs.token = null;
        token = new WindowToken(this, client.asBinder(), type, false, displayContent,
            session.mCanAddInternalSystemWindow);
      }
      //5555555555555
      final WindowState win = new WindowState(this, session, client, token, parentWindow,
          appOp[0], seq, attrs, viewVisibility, session.mUid,
          session.mCanAddInternalSystemWindow);
      if (win.mDeathRecipient == null) {
        // Client has apparently died, so there is no reason to
        // continue.
        Slog.w(TAG_WM, "Adding window client " + client.asBinder()
            + " that is dead, aborting.");
        return WindowManagerGlobal.ADD_APP_EXITING;
      }
      if (win.getDisplayContent() == null) {
        Slog.w(TAG_WM, "Adding window to Display that has been removed.");
        return WindowManagerGlobal.ADD_INVALID_DISPLAY;
      }
      final boolean hasStatusBarServicePermission =
          mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
              == PackageManager.PERMISSION_GRANTED;
      mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
      win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));
      res = mPolicy.prepareAddWindowLw(win, attrs);
      if (res != WindowManagerGlobal.ADD_OKAY) {
        return res;
      }
      final boolean openInputChannels = (outInputChannel != null
          && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
      if (openInputChannels) {
        win.openInputChannel(outInputChannel);
      }
      //666666666666666
      if (type == TYPE_TOAST) {
        if (!getDefaultDisplayContentLocked().canAddToastWindowForUid(callingUid)) {
          Slog.w(TAG_WM, "Adding more than one toast window for UID at a time.");
          return WindowManagerGlobal.ADD_DUPLICATE_ADD;
        }

        if (addToastWindowRequiresToken
            || (attrs.flags & LayoutParams.FLAG_NOT_FOCUSABLE) == 0
            || mCurrentFocus == null
            || mCurrentFocus.mOwnerUid != callingUid) {
          mH.sendMessageDelayed(
              mH.obtainMessage(H.WINDOW_HIDE_TIMEOUT, win),
              win.mAttrs.hideTimeoutMilliseconds);
        }
      }

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

  1. 系统以及初始化的一些判断:就像最开始的四个判断。
  2. 子窗口类型时候的对父窗口的相关筛选(父是否为空,以及父亲的类型判断)
  3. 一种特殊的私有类型条件筛选,该类型属于系统类型
  4. 涉及证书(token)的窗口类型条件筛选。
  5. 状态栏权限条件筛选
  6. 吐司类型的条件筛选

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

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

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

      if (res < WindowManagerGlobal.ADD_OKAY) {
          mAttachInfo.mRootView = null;
          mAdded = false;
          mFallbackEventHandler.setView(null);
          unscheduleTraversals();
          setAccessibilityFocus(null, null);
          switch (res) {
            case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
            case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
              throw new WindowManager.BadTokenException(
                  "Unable to add window -- token " + attrs.token
                  + " is not valid; is your activity running?");
            case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
              throw new WindowManager.BadTokenException(
                  "Unable to add window -- token " + attrs.token
                  + " is not for an application");
            case WindowManagerGlobal.ADD_APP_EXITING:
              throw new WindowManager.BadTokenException(
                  "Unable to add window -- app for token " + attrs.token
                  + " is exiting");
            case WindowManagerGlobal.ADD_DUPLICATE_ADD:
              throw new WindowManager.BadTokenException(
                  "Unable to add window -- window " + mWindow
                  + " has already been added");
            case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
              // Silently ignore -- we would have just removed it
              // right away, anyway.
              return;
            case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
              throw new WindowManager.BadTokenException("Unable to add window "
                  + mWindow + " -- another window of type "
                  + mWindowAttributes.type + " already exists");
            case WindowManagerGlobal.ADD_PERMISSION_DENIED:
              throw new WindowManager.BadTokenException("Unable to add window "
                  + mWindow + " -- permission denied for window type "
                  + mWindowAttributes.type);
            case WindowManagerGlobal.ADD_INVALID_DISPLAY:
              throw new WindowManager.InvalidDisplayException("Unable to add window "
                  + mWindow + " -- the specified display can not be found");
            case WindowManagerGlobal.ADD_INVALID_TYPE:
              throw new WindowManager.InvalidDisplayException("Unable to add window "
                  + mWindow + " -- the specified window type "
                  + mWindowAttributes.type + " is not valid");
          }
          throw new RuntimeException(
              "Unable to add window -- unknown error code " + res);
        }

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

四、小结

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

  • 函数循环嵌套,共同消费返回值。
  • 异常循环嵌套
  • 个别地方对M和O以上的系统进行了限制

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

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

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

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

总结

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

(0)

相关推荐

  • Android获取其他应用中的assets资源

    最近有这样一个需求:A应用在一定条件下出发某个逻辑后,需要从B应用中获取一些资源(assets下的mp4视频.还有drawable下的一些图片用作背景),具体需求就不说啦哈哈,用一张图来表示应该更明白: A和B应用其实是1对多的关系,不同的B应用需要从他们自己的地方获取到资源给A. 一般我们获取app内的资源肯定是要获取到Resource这个类,而Resource是通过Context类的getResource获取到了,所以我们只需要获取到B应用的Context类就可以了. 可是其他App的Con

  • Android实现百度地图两点画弧线

    本文实例为大家分享了Android实现百度地图两点画弧线的具体代码,供大家参考,具体内容如下 import android.support.annotation.NonNull; import com.baidu.mapapi.map.ArcOptions; import com.baidu.mapapi.map.OverlayOptions; import com.baidu.mapapi.model.LatLng; /** * * http://lbsyun.baidu.com/index.

  • Android利用ObjectAnimator实现ArcMenu

    本文介绍利用ObjectAnimator简单地实现ArcMenu,直接使用本文的ArcMenu类即可快捷地实现菜单功能. 最终使用效果: 先看下最终的使用效果: private int[] imageRes = {R.id.img_menu, R.id.img_menu1, R.id.img_menu2, R.id.img_menu3, R.id.img_menu4, R.id.img_menu5}; private ArcMenu arcMenu; ... //初始化,参数为资源图片id ar

  • Android自定义View仿腾讯TIM下拉刷新View

    一 概述 自定义 View 是 Android 开发里面的一个大学问.偶然间看到 TIM 邮箱界面的刷新 View 还挺好玩的,于是就自己动手实现了一个,先看看 TIM 里边的效果图: 二 需求分析 看到上面的动图,大概也知道我们需要实现的功能: 根据拖动的进度来移动小球的位置 小球移动过程的动画 三 功能实现 新建一个 RefreshView 类继承自 View ,然后我们再在 RefreshView 里面新建一个内部实体类: Circle 来看一下 Circle类的代码 #Cirlce.ja

  • ObjectAnimator属性动画源码分析篇

    又和大家见面了,这几天一直在忙大创项目,所以没有更新博客,而且我发现看源码这个东西必须写个博客或者笔记啊,这之前一段时机笔者已经看了ValueAnimator和ObjectAnimator的源码了,但是这才过了几天,搞了会别的事情就忘得几乎一干二净了.现在又要重头看一遍很痛苦额-.+. 另外,笔者已经在简书写了关于属性动画的比较系统的详细的文章,之后会陆续在CSDN上重新写的(是重新写,不是复制过去哦,因为第一次写的实在是太烂了-.=) 好了不继续扯皮了,我们看来一下今天想要讲的东西--Obje

  • Android动态修改应用图标与名称的方法实例

    遇到的坑 这里我把做这个功能中遇到的一些问题写在前面,是为了大家能先了解有什么问题存在,遇到这些问题的时候就不慌了,这里我把应用图标和名称先统一使用icon代替进行说明. 1.动态替换icon,只能替换内置的icon,无法从服务器端获取来更新icon: 2.动态替换icon以后,应用内更新的时候必须要切换到原始icon),否则可能导致更新安装失败(AS上表现为adb运行会失败),或者升级后应用图标出现多个甚至应用图标都不显示的情况(这些问题都可以通过下面我推荐的开发规则解决掉,所以这是一个坑点,

  • Android自定义View实现简单炫酷的球体进度球实例代码

    前言 最近一直在研究自定义view,正好项目中有一个根据下载进度来实现球体进度的需求,所以自己写了个进度球,代码非常简单.先看下效果: 效果还是非常不错的. 准备知识 要实现上面的效果我们只要掌握两个知识点就好了,一个是Handler机制,用于发消息刷新我们的进度球,一个是clipDrawable.网上关于Handler的教程很多,这里重点介绍一下clipDrawable,进度球的实现全靠clipDrawable. clipDrawable 如下图所示:ClipDrawable和InsertDr

  • Android自定义动态壁纸开发(时钟)

    看到有些手机酷炫的动态壁纸,有没有好奇过他们是如何实现的,其实我们自己也可以实现. 先看效果 上图是动态壁纸钟的一个时钟. 我们先来看看 Livewallpaper(即动态墙纸)的实现,Android的动态墙纸并不是GIF图片,而是一个标准的Android应用程序,也就是APK.既然是应用程序,当然意味着天生具有GIF图片不具备的功能--能与用户发生交互,而且动态的背景变化绝不仅仅局限于GIF图片那般只能是固定的几张图片的循环播放.但是我们在这里没有加入与用户交互的动作,只是加入一个时钟(当然时

  • Android百度地图定位、显示用户当前位置

    本文实例为大家分享了Android百度地图定位.显示用户当前位置的工具类,供大家参考,具体内容如下 1.构建定位Option的工具类 import com.baidu.location.LocationClientOption; /** * 建造 LocationClientOption 项 * * @author peter 2018-12-21 10:58 */ public class LocationClientOptionBuilder { private LocationClient

  • Android高性能日志写入方案的实现

    前言 公司目前在做一款企业级智能客服系统,对于系统稳定性要求很高,不过难保用户在使用中不会出现问题,而 Android SDK 集成在客户的 APP 中,同时由于 Android 碎片化的问题,对于 SDK 的问题排查就显得尤为困难,因此记录下用户的操作日志就显得极为重要. 初始方案 一开始,SDK 记录日志的方式是直接通过写文件,当有一条日志要写入的时候,首先,打开文件,然后写入日志,最后关闭文件.这样做的问题就在于频繁的IO操作,影响程序的性能,而且 SDK 为了保证消息的及时性,还维护了一

随机推荐