StartingWindow 启动添加显示

一、简介

应用冷启动时候,Activity组件在它的窗口显示出来之前,可以显示一个启动窗口。等应用加载好第一帧之后, 启动窗口消失,显示应用主界面。这个启动窗口可以看作是Activity组件的预览窗口,是由WindowManagerService服务统一管理的,WindowManagerService服务负责启动和结束。


demo_gaitubao_364x619_gaitubao_338x603.gif

如上图,在activity启动的过程中,加了一个默认图作为启动窗口。
其有三种类型startingwindow。
1、系统默认的 StartingWindow
用户点了应用图标启动应用,马上弹出系统默认的 StartingWindow,等应用加载好第一帧之后,StartingWindow 消失,显示应用第一帧
2、自定义 StartingWindow
用户点了应用图标启动应用,弹出应用自己定制的StartingWindow,等应用加载好第一帧之后,定制的 StartingWindow 消失,显示应用主界面
3、StartingWindow 禁掉或者设置透明
用户点了应用图标启动应用,由于 StartingWindow 被禁掉或者被设置透明,所以会出现点击图标后,会出现偏黑色半透明界面,再等应用第一帧加载出来后,显示出应用主界面。

二、启动窗口的显示过程

startingwindow3.png

Activity启动窗口是由AMS来决定是否要显示的。AMS通知WMS为正在启动的Activity组件显示一个启动窗口,WMS再通过窗口管理类PhoneWindowManager来创建这个启动窗口。
看一下ams到调用ActivityRecord中的showStartingWindow时序图。


3.png

1、ActivityStack

    frameworks/base/services/core/java/com/android/server/wm/ActivityStack.java
//r就是正在启动的Activity组件,newTask是否要将该Activity组件放在一个新的任务中启动
    void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
                             boolean newTask, boolean keepCurTransition, ActivityOptions options) { //@1
......
        if (r.mAppWindowToken == null) {
            //创建要启动的ActivityRecord的mAppWindowToken。并添加到Task顶部中。并将AppWindowToken添加到了displayContent
            r.createAppWindowToken();  //@3
        }
......
        boolean doShow = true; //@4 doShow初始化默认true
        if (newTask) {
     //r.intent.getFlags() 可以获得正在启动的Activity组件的标志值,当这个标志值的FLAG_ACTIVITY_RESET_TASK_IF_NEEDED位运算不等于0的时候。
            if ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { //@5
     //任务重置,正在启动的Activity组件通知ActivityManagerService服务对它运行在的任务进行重置
                resetTaskIfNeededLocked(r, r); //@6
//调用topRunningNonDelayedActivityLocked来检查位于系统Activity组件堆栈顶端的Activity组件是否就是正在启动的Activity组件,
//就可以知道正在启动的Activity组件的窗口接下来是否是需要显示的。如果需要显示的话,那么变量doShow的值就等于true。
                doShow = topRunningNonDelayedActivityLocked(null) == r;  //@7
            }
        } else if (options != null && options.getAnimationType()
                == ActivityOptions.ANIM_SCENE_TRANSITION) {
            doShow = false;
        }
        if (r.mLaunchTaskBehind) {
            // Don't do a starting window for mLaunchTaskBehind. More importantly make sure we
            // tell WindowManager that r is visible even though it is at the back of the stack.
            r.setVisibility(true);
            ensureActivitiesVisibleLocked(null, 0, !PRESERVE_WINDOWS);
        } else if (SHOW_APP_STARTING_PREVIEW && doShow) {  //@8
  //@8 ActivityStack类的静态成员变量SHOW_APP_STARTING_PREVIEW是用描述系统是否可以为正在启动的Activity组件显示启动窗口,只有在它的值等于true,
  //以及正在启动的Activity组件的窗口接下来是要显示出来的情况下,即变量doShow的值等于true,
  //ActivityManagerService服务才会请求WindowManagerService服务为正在启动的Activity组件设置启动窗口。

  //当前要启动的 Activity所在的task
            TaskRecord prevTask = r.getTaskRecord(); //9
//寻找当前task中设置了STARTING_WINDOW_SHOWN,并且不是正在finishing中并且可以显示的Activity,如果有则接下来会置为null。
//如果是第一次启动的应用,则它的task中是没有设置了STARTING_WINDOW_SHOWN的Activity的,所以的prev为null。
//STARTING_WINDOW_SHOWN是在下面的showStartingWindow方法中设置的。
            ActivityRecord prev = prevTask.topRunningActivityWithStartingWindowLocked(); //10
            if (prev != null) { 
                // We don't want to reuse the previous starting preview if:
                // (1) The current activity is in a different task.
                if (prev.getTaskRecord() != prevTask) {
                    prev = null;
                }
                // (2) The current activity is already displayed.
                else if (prev.nowVisible) {
                    prev = null;
                }
            }
//调用要启动的ActivityRecord的showStartingWindow
            r.showStartingWindow(prev, newTask, isTaskSwitch(r, focusedTopActivity));//@11
           }
      } else{
        // If this is the first activity, don't do any fancy animations,
        // because there is nothing for it to animate on top of.
        ActivityOptions.abort(options);
      }
}

      ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
          for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
              final TaskRecord task = mTaskHistory.get(taskNdx);
              final ArrayList activities = task.mActivities;
              for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                  ActivityRecord r = activities.get(activityNdx);
                  if (!r.finishing && !r.delayedResume && r != notTop && r.okToShowLocked()) {
                      return r;
                  }
              }
          }
          return null;
      }

/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
   void createAppWindowToken() {
......
//TaskRecord task。addActivitytoTop最终会放入到一个集合 
         task.addActivityToTop(this);   ArrayList mActivities
......
      }

/frameworks/base/services/core/java/com/android/server/wm/TaskRecord.java
      ActivityRecord topRunningActivityWithStartingWindowLocked() {
          if (mStack != null) {
              for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) {
                  ActivityRecord r = mActivities.get(activityNdx);
                  if (r.mStartingWindowState != STARTING_WINDOW_SHOWN
                          || r.finishing || !r.okToShowLocked()) {
                      continue;
                  }
                  return r;
              }
          }
          return null;
      }

2.png

@5 r.intent.getFlags() 可以获得正在启动的Activity组件的标志值,当这个标志值的FLAG_ACTIVITY_RESET_TASK_IF_NEEDED位运算不等于0的时候。

(FLAG_ACTIVITY_RESET_TASK_IF_NEEDED:如果设置该属性,并且这个activity在一个新的task中正在被启动或者被带
到一个已经存在的task的顶部,这时这个activity将会被作为这个task的首个页面加载。这将会导致拥有这个应用的affinities的task处于一个合适的状态(移动activity到这个task或者activity从中移出),或者简单的重置这个task到它的初始状态")

2、ActivityRecord

下面试ActivityRecord到PhoneWIndowManager的时序图

4.png
/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch) {
          showStartingWindow(prev, newTask, taskSwitch, false /* fromRecents */);
      }
  
//第一次启动应用prev = null,newTask = true,taskSwitch = true
      void showStartingWindow(ActivityRecord prev, boolean newTask, boolean taskSwitch,
              boolean fromRecents) {  //@1
//mAppWindowToken 在 ActivityStack的startActivityLocked创建过了,
          if (mAppWindowToken == null) { //@2
              return;
          }
//overlay 不展示startingwindow
          if (mTaskOverlay) { //@3
              // We don't show starting window for overlay activities.
              return;
          }
//如果是在做activity在做anim 动画,不展示startingwindow
          if (pendingOptions != null
                  && pendingOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
              // Don't show starting window when using shared element transition.
              return;
          } //@4
 
          final CompatibilityInfo compatInfo =
                  mAtmService.compatibilityInfoForPackageLocked(info.applicationInfo);
//添加启动窗口,参数theme是在创建ActivityRecord的时候获取应用AndroidManifest中activity中设置的theme。
          final boolean shown = addStartingWindow(packageName, theme, //@5
                  compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                  prev != null ? prev.appToken : null, newTask, taskSwitch, isProcessRunning(),
                  allowTaskSnapshot(),
                  mState.ordinal() >= RESUMED.ordinal() && mState.ordinal() <= STOPPED.ordinal(),
                  fromRecents);
//如果添加启动窗口成功,则mStartingWindowState 置为 STARTING_WINDOW_SHOWN,表示此Activity 是启动窗口状态。
          if (shown) { //@6
              mStartingWindowState = STARTING_WINDOW_SHOWN;
          }
      }

addStartingWindow方法

/frameworks/base/services/core/java/com/android/server/wm/ActivityRecord.java
      boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
              CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
              IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
              boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) {
          if (DEBUG_STARTING_WINDOW) {
              Slog.v(TAG, "setAppStartingWindow: token=" + appToken
                      + " pkg=" + pkg + " transferFrom=" + transferFrom + " newTask=" + newTask
                      + " taskSwitch=" + taskSwitch + " processRunning=" + processRunning
                      + " allowTaskSnapshot=" + allowTaskSnapshot);
          }
          if (mAppWindowToken == null) {
              Slog.w(TAG_WM, "Attempted to set icon of non-existing app token: " + appToken);
              return false;
          }
          if (mAppWindowToken.getTask() == null) {
              // Can be removed after unification of Task and TaskRecord.
              Slog.w(TAG_WM, "Attempted to start a window to an app token not having attached to any"
                      + " task: " + appToken);
              return false;
          }
          return mAppWindowToken.addStartingWindow(pkg, theme, compatInfo, nonLocalizedLabel,
                  labelRes, icon, logo, windowFlags, transferFrom, newTask, taskSwitch,
                  processRunning, allowTaskSnapshot, activityCreated, fromRecents);
      }

3、AppWindowToken

/frameworks/base/services/core/java/com/android/server/wm/AppWindowToken.java
boolean addStartingWindow(String pkg, int theme, CompatibilityInfo compatInfo,
              CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags,
              IBinder transferFrom, boolean newTask, boolean taskSwitch, boolean processRunning,
              boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents) {
          // If the display is frozen, we won't do anything until the actual window is
          // displayed so there is no reason to put in the starting window.

          if (!okToDisplay()) { //@1
              return false;
          }
  // 如果 mStartingData 不等于null,Activity组件的启动窗口已经创建出来了,不需要startingwindow了。
          if (mStartingData != null) {  //@2
              return false;
          }
  //找主窗口   findMainWindow 目前还没有创建,应该为null
          final WindowState mainWin = findMainWindow();  //@3
//主窗口不等于null,或者是以展示状态,说明已存在,不用添加startingwindow,则返回false,添加startringwindow失败。
          if (mainWin != null && mainWin.mWinAnimator.getShown()) { //@4
              // App already has a visible window...why would you want a starting window?
              return false;
          }
  //创建快照类型启动窗口
          final ActivityManager.TaskSnapshot snapshot =
                  mWmService.mTaskSnapshotController.getSnapshot(
                          getTask().mTaskId, getTask().mUserId,
                          false /* restoreFromDisk */, false /* reducedResolution */);  //@5
//获取启动窗口类型以判断是否添加启动窗口 getStartingWindowType
          final int type = getStartingWindowType(newTask, taskSwitch, processRunning,
                  allowTaskSnapshot, activityCreated, fromRecents, snapshot); //@6
  
          if (type == STARTING_WINDOW_TYPE_SNAPSHOT) {
//如果是任务快照类型窗口则走createSnapshot,主要分析启动窗口,此略过。
              return createSnapshot(snapshot);   //@7
          }
  
          // If this is a translucent window, then don't show a starting window -- the current
          // effect (a full-screen opaque starting window that fades away to the real contents
          // when it is ready) does not work for this.
          if (DEBUG_STARTING_WINDOW) {
              Slog.v(TAG, "Checking theme of starting window: 0x" + Integer.toHexString(theme));
          }
/*参数theme的值不等于0,那么该窗口就有可能是:
背景是半透明的;
浮动窗口,即是一个壁纸窗口或者一个输入法窗口;
需要显示壁纸(背景也是半透明的)。
由于浮动窗口和背景半透明的窗口是不可以显示启动窗口的,因此,在上述三种情况下,
WindowManagerService类的成员函数setAppStartingWindow也是直接返回而不往下处理了。*/
          if (theme != 0) {  //@8
              AttributeCache.Entry ent = AttributeCache.instance().get(pkg, theme,
                      com.android.internal.R.styleable.Window,
                      mWmService.mCurrentUserId);
              if (ent == null) {
                  // Whoops!  App doesn't exist. Um. Okay. We'll just pretend like we didn't
                  // see that.
                  return false;
              }
              final boolean windowIsTranslucent = ent.array.getBoolean(
                      com.android.internal.R.styleable.Window_windowIsTranslucent, false);
              final boolean windowIsFloating = ent.array.getBoolean(
                      com.android.internal.R.styleable.Window_windowIsFloating, false);
              final boolean windowShowWallpaper = ent.array.getBoolean(
                      com.android.internal.R.styleable.Window_windowShowWallpaper, false);
              final boolean windowDisableStarting = ent.array.getBoolean(
                      com.android.internal.R.styleable.Window_windowDisablePreview, false);
              if (DEBUG_STARTING_WINDOW) {
                  Slog.v(TAG, "Translucent=" + windowIsTranslucent
                          + " Floating=" + windowIsFloating
                          + " ShowWallpaper=" + windowShowWallpaper);
              }
              if (windowIsTranslucent) {
                  return false;
              }
              if (windowIsFloating || windowDisableStarting) {
                  return false;
              }
              if (windowShowWallpaper) {
                  if (getDisplayContent().mWallpaperController
                          .getWallpaperTarget() == null) {
                      // If this theme is requesting a wallpaper, and the wallpaper
                      // is not currently visible, then this effectively serves as
                      // an opaque window and our starting window transition animation
                      // can still work.  We just need to make sure the starting window
                      // is also showing the wallpaper.
                      windowFlags |= FLAG_SHOW_WALLPAPER;
                  } else {
                      return false;
                  }
              }
          }
        /*跟踪代码发现最初传下来的transferFrom应该为null,也就是此从此transferFrom获取的 AppWindowToken 对象为null,
transferStartingWindow方法返回false。 transforFrom为 pervtask.appToken (IApplicationToken.Stub appToken)*/
          if (transferStartingWindow(transferFrom)) { //@9
              return true;
          }
  
          // There is no existing starting window, and we don't want to create a splash screen, so
          // that's it!
//如果获取的不是STARTING_WINDOW_TYPE_SPLASH_SCREEN则不会添加
          if (type != STARTING_WINDOW_TYPE_SPLASH_SCREEN) {
              return false;
          } //10
 
          if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Creating SplashScreenStartingData");
//构造SplashScreenStartingData 对象mStartingData 。
          mStartingData = new SplashScreenStartingData(mWmService, pkg,
                  theme, compatInfo, nonLocalizedLabel, labelRes, icon, logo, windowFlags,
                  getMergedOverrideConfiguration()); //@11
//准备添加启动窗口
          scheduleAddStartingWindow();  //@12
          return true;
      }

接下来稍微看一下其中子功能方法@3 findMainWindow, @6 getStartingWindowType

/frameworks/base/services/core/java/com/android/server/wm/AppWindowToken.java
 WindowState findMainWindow() {
          return findMainWindow(true);
      }
  
      /**
       * Finds the main window that either has type base application or application starting if
       * requested.
       *
       * @param includeStartingApp Allow to search application-starting windows to also be returned.
       * @return The main window of type base application or application starting if requested.
       */
      WindowState findMainWindow(boolean includeStartingApp) {
          WindowState candidate = null;
          for (int j = mChildren.size() - 1; j >= 0; --j) {  //@1
              final WindowState win = mChildren.get(j);
              final int type = win.mAttrs.type;
              // No need to loop through child window as base application and starting types can't be
              // child windows.
              if (type == TYPE_BASE_APPLICATION
                      || (includeStartingApp && type == TYPE_APPLICATION_STARTING)) {
                  // In cases where there are multiple windows, we prefer the non-exiting window. This
                  // happens for example when replacing windows during an activity relaunch. When
                  // constructing the animation, we want the new window, not the exiting one.
                  if (win.mAnimatingExit) {
                      candidate = win;
                  } else {
                      return win;
                  }
              }
          }
          return candidate;
     }

@1 遍历mChildren,从层级最高的窗口开始,寻找类型为TYPE_BASE_APPLICATION或者TYPE_APPLICATION_STARTING的窗口,返回一个WindowState 对象。

/frameworks/base/services/core/java/com/android/server/wm/AppWindowToken.java
private int getStartingWindowType(boolean newTask, boolean taskSwitch, boolean processRunning,
              boolean allowTaskSnapshot, boolean activityCreated, boolean fromRecents,
              ActivityManager.TaskSnapshot snapshot) {
          if (getDisplayContent().mAppTransition.getAppTransition()
                  == TRANSIT_DOCK_TASK_FROM_RECENTS) {  //@1
              // TODO(b/34099271): Remove this statement to add back the starting window and figure
              // out why it causes flickering, the starting window appears over the thumbnail while
              // the docked from recents transition occurs
              return STARTING_WINDOW_TYPE_NONE;
          } else if (newTask || !processRunning || (taskSwitch && !activityCreated)) {  //@2
              return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
          } else if (taskSwitch && allowTaskSnapshot) {  // @3
              if (mWmService.mLowRamTaskSnapshotsAndRecents) { //@4
                  // For low RAM devices, we use the splash screen starting window instead of the
                  // task snapshot starting window.
                  return STARTING_WINDOW_TYPE_SPLASH_SCREEN;
              }
              return snapshot == null ? STARTING_WINDOW_TYPE_NONE
                      : snapshotOrientationSameAsTask(snapshot) || fromRecents
                              ? STARTING_WINDOW_TYPE_SNAPSHOT : STARTING_WINDOW_TYPE_SPLASH_SCREEN; //@5
          } else {
              return STARTING_WINDOW_TYPE_NONE; //@6
          }
      }

@1 当前正在进行最近任务栏相关的过渡动画,
@2 如果在一个新task启动,或者此Activity所在的进程还未运行,或者(此Activity的task和启动它的Activity不在同一个task并且此Activity还为创建),满足其中一个条件说明需要启动窗口
@3 此Activity的task和启动它的Activity不在同一个task 并且允许创建任务快照窗口。
@4 在低RAM设备上返回使用启动窗口
@5 返回STARTING_WINDOW_TYPE_NONE、STARTING_WINDOW_TYPE_SNAPSHOT 、STARTING_WINDOW_TYPE_SPLASH_SCREEN的一些判断
@6 返回不需要启动窗口

接着看主线方法scheduleAddStartingWindow
/frameworks/base/services/core/java/com/android/server/wm/AppWindowToken.java
      void scheduleAddStartingWindow() {
          // Note: we really want to do sendMessageAtFrontOfQueue() because we
          // want to process the message ASAP, before any other queued
          // messages.
          if (!mWmService.mAnimationHandler.hasCallbacks(mAddStartingWindow)) {
              if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Enqueueing ADD_STARTING");
              mWmService.mAnimationHandler.postAtFrontOfQueue(mAddStartingWindow);
          }
      }

AMS调用其mAnimationHandler(子线程,可对照systrace看) post了一个Runnable,使用postAtFrontOfQueue的方式,插入队列头部以加快启动窗口添加速度。

/frameworks/base/services/core/java/com/android/server/wm/AppWindowToken.java
      private final Runnable mAddStartingWindow = new Runnable() {
  
          @Override
          public void run() {
              // Can be accessed without holding the global lock
              final StartingData startingData;
              synchronized (mWmService.mGlobalLock) {
                  // There can only be one adding request, silly caller!
                  mWmService.mAnimationHandler.removeCallbacks(this);
  
                  if (mStartingData == null) {
                      // Animation has been canceled... do nothing.
                      if (DEBUG_STARTING_WINDOW) {
                          Slog.v(TAG, "startingData was nulled out before handling"
                                  + " mAddStartingWindow: " + AppWindowToken.this);
                      }
                      return;
                  }
                  startingData = mStartingData;
              }
  
              if (DEBUG_STARTING_WINDOW) {
                  Slog.v(TAG, "Add starting " + this + ": startingData=" + startingData);
              }
  
              WindowManagerPolicy.StartingSurface surface = null;
              try {
// 调用 StartingData对象createStartingSurface 创建一个surface
                  surface = startingData.createStartingSurface(AppWindowToken.this); //@1
              } catch (Exception e) {
                  Slog.w(TAG, "Exception when adding starting window", e);
              }
              if (surface != null) {
                  boolean abort = false;
                  synchronized (mWmService.mGlobalLock) {
                      // If the window was successfully added, then
                      // we need to remove it.
                      if (removed || mStartingData == null) {
                          if (DEBUG_STARTING_WINDOW) {
                              Slog.v(TAG, "Aborted starting " + AppWindowToken.this
                                      + ": removed=" + removed + " startingData=" + mStartingData);
                          }
                          startingWindow = null;
                          mStartingData = null;
                          abort = true;
                      } else {
//把创建的surface赋值给全局变量 startingSurface ,这个变量在removeStartingWindow,transferStartingWindow有用。
                          startingSurface = surface; //@2
                      }
                      if (DEBUG_STARTING_WINDOW && !abort) {
                          Slog.v(TAG,
                                  "Added starting " + AppWindowToken.this + ": startingWindow="
                                          + startingWindow + " startingView=" + startingSurface);
                      }
                  }
                  if (abort) {
                      surface.remove();
                  }
              } else if (DEBUG_STARTING_WINDOW) {
                  Slog.v(TAG, "Surface returned was null: " + AppWindowToken.this);
              }
          }
      };

大概过程是创建了要启动的ActivityRecord,判断ActivityRecord的状态是否符合启动是startingwindow,然后创建splashScreenStaringStata对象,通过splashScreenStaringStata createStartingSurface。

3、StartingData

接下来看一下createStartingSurface 方法内容
SplashScreenStartingData继承StartingData,实现其抽象方法createStartingSurface

/frameworks/base/services/core/java/com/android/server/wm/StartingData.java
/frameworks/base/services/core/java/com/android/server/wm/SplashScreenStartingData.java

StartingSurface createStartingSurface(AppWindowToken atoken) {
          return mService.mPolicy.addSplashScreen(atoken.token, mPkg, mTheme, mCompatInfo,
                  mNonLocalizedLabel, mLabelRes, mIcon, mLogo, mWindowFlags,
                  mMergedOverrideConfiguration, atoken.getDisplayContent().getDisplayId());
      }

4、PhoneWindowManager

startingwindow1.png

调到WindowManagerPolicy的实现类PhoneWindowManager的addSplashScreen方法。
PhoneWindowManager implements WindowManagerPolicy

/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
 @Override
      public StartingSurface addSplashScreen(IBinder appToken, int userId, String packageName,
              int theme, CompatibilityInfo compatInfo, CharSequence nonLocalizedLabel, int labelRes,
              int icon, int logo, int windowFlags, Configuration overrideConfig, int displayId) {
......
          WindowManager wm = null;
          View view = null;
  
          try {
              Context context = mContext;
 
              // Obtain proper context to launch on the right display.
              final Context displayContext = getDisplayContext(context, displayId);
......
//创建PhoneWindow
              final PhoneWindow win = new PhoneWindow(context); //@1
//设置标记此PhoneWindow它是一个启动窗口类型。
              win.setIsStartingWindow(true);  //@2
  
              CharSequence label = context.getResources().getText(labelRes, null);
              // Only change the accessibility title if the label is localized
              if (label != null) {   //@3
// 此phonewindow设置title
                  win.setTitle(label, true);
              } else {
                  win.setTitle(nonLocalizedLabel, false);
              }
  /*设置Type,启动窗口类型
用于应用程序启动时所显示的窗口。应用本身不要使用这种类型。
      它用于让系统显示些信息,直到应用程序可以开启自己的窗口。 */
              win.setType(
                  WindowManager.LayoutParams.TYPE_APPLICATION_STARTING);//@4
  
              synchronized (mWindowManagerFuncs.getWindowManagerLock()) { 
                  // Assumes it's safe to show starting windows of launched apps while
                  // the keyguard is being hidden. This is okay because starting windows never show
                 // secret information.
                  // TODO(b/113840485): Occluded may not only happen on default display
                  if (displayId == DEFAULT_DISPLAY && mKeyguardOccluded) {
                      windowFlags |= FLAG_SHOW_WHEN_LOCKED;
                  }
              }
  

// 设置各种flag,不可接受触摸事件和不可获得焦点,但是可以接受输入法窗口
              win.setFlags(   //@5
                  windowFlags|
                  WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
                  WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
                  WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
                  windowFlags|
                  WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE|
                  WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE|
                  WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);
  
              win.setDefaultIcon(icon);  
//设置图标logo
              win.setDefaultLogo(logo); //@ 6
  //设置宽高全屏的,即与父窗口一样大,但是由于这是一个顶层窗口,因此实际上是指与屏幕一样大;
              win.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
                      WindowManager.LayoutParams.MATCH_PARENT); //@7
  
              final WindowManager.LayoutParams params = win.getAttributes();
//启动窗口用的是当前这个Activity的AppWindowToken (IApplicationToken.Stub appToken)可以知道,
//一个Activity组件的启动窗口和它本身的窗口都是对应同一个窗口令牌的,因此, 它们在窗口堆栈中就属于同一组窗口。
              params.token = appToken;  //@8
              params.packageName = packageName;
              params.windowAnimations = win.getWindowStyle().getResourceId(
                      com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
              params.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
              // Setting as trusted overlay to let touches pass through. This is safe because this
              // window is controlled by the system.
              params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
  
              if (!compatInfo.supportsScreen()) {
                  params.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
              }
  //设置title为Splash Screen+包名
              params.setTitle("Splash Screen " + packageName); //@9
//此方法中会给启动窗口设置资源文件,并调用setContentView设置到PhoneWindow的DecorView中
              addSplashscreenContent(win, context); //@10
  
              wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
 //view 就是phonewindow中的decorview。getDecorView就可以将它的顶层视图创建出来,并且保存在变量view中。
              view = win.getDecorView(); //@11
  
              if (DEBUG_SPLASH_SCREEN) Slog.d(TAG, "Adding splash screen window for "
                  + packageName + " / " + appToken + ": " + (view.getParent() != null ? view : null));
   //添加到wm(windowManagerImpl)中。
              wm.addView(view, params);  //@12
  
              // Only return the view if it was successfully added to the
              // window manager... which we can tell by it having a parent.
              return view.getParent() != null ? new SplashScreenSurface(view, appToken) : null;
          } catch (WindowManager.BadTokenException e) {
              // ignore
              Log.w(TAG, appToken + " already running, starting window not displayed. " +
                      e.getMessage());
          } catch (RuntimeException e) {
              // don't crash if something else bad happens, for example a
              // failure loading resources because we are loading from an app
              // on external storage that has been unmounted.
              Log.w(TAG, appToken + " failed creating starting window", e);
          } finally {
              if (view != null && view.getParent() == null) {
                  Log.w(TAG, "view not successfully added to wm, removing view");
                  wm.removeViewImmediate(view);
              }
          }
  
          return null;
      }


/frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java
      private void addSplashscreenContent(PhoneWindow win, Context ctx) {
          final TypedArray a = ctx.obtainStyledAttributes(R.styleable.Window);
          final int resId = a.getResourceId(R.styleable.Window_windowSplashscreenContent, 0);
          a.recycle();
          if (resId == 0) {
              return;
          }
          final Drawable drawable = ctx.getDrawable(resId);
          if (drawable == null) {
              return;
          }
  
          // We wrap this into a view so the system insets get applied to the drawable.
          final View v = new View(ctx);
          v.setBackground(drawable);
          win.setContentView(v);
      }
5.png

每一个进程都有一个本地窗口管理服务,这个本地窗口管理服务是由WindowManagerImpl类来实现的,负责维护进程内的所有窗口的视图对象。通过调用WindowManagerImpl类的成员函数addView就可以将一个窗口的视图增加到本地窗口管理服务中去,以便这个视图可以接受本地窗口管理服务的管理。
WindowManagerImpl类的成员函数addView还会请求WindowManagerService服务为其所增加的窗口视图创建一个WindowState对象,以便WindowManagerService服务可以维护对应的窗口的运行状态。

5、窗口添加过程,WindowManagerImpl,WindowManagerGlobal

WindowManagerImpl 继承 WindowManager,看WindowManagerImpl 的addView方法

/frameworks/base/core/java/android/view/WindowManagerImpl.java
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
          applyTokens(params);
          mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
                  mContext.getUserId());
      }
/frameworks/base/core/java/android/view/WindowManagerGlobal.java
      public void addView(View view, ViewGroup.LayoutParams params,
              Display display, Window parentWindow) {
......
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        }

          ViewRootImpl root;
          View panelParentView = null;
          synchronized (mLock) {
            ......
              root = new ViewRootImpl(view.getContext(), display);
  
              view.setLayoutParams(wparams);
  
              mViews.add(view);  // ArrayList,view是decorview
              mRoots.add(root);  ArrayList
              mParams.add(wparams);  // ArrayList
  
              // do this last because it fires off messages to start doing things
              try {
                  root.setView(view, wparams, panelParentView);//将DecorView到ViewRootImpl中
              } catch (RuntimeException e) {
......
                  throw e;
              }
          }
      }

6、ViewRootImpl

    /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ......
//requestLayout 会调用scheduleTraversals。
                  // Schedule the first layout -before- adding to the window
                  // manager, to make sure we do the relayout before receiving
                  // any other events from the system.
                requestLayout();  @1
                ......

                try {
                    ......
//mWindow是viewrootImpl的一个内部类,在创建时候会传入当前ViewRootImpl对象,其extends IWindow.Stub 用来跨进程操作
                      res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                              getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                              mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                              mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                             mTempInsets);@2
                    ......
                }
                ......
            }
        }
    }

      @Override
      public void requestLayout() {
 //mHandlingLayoutInLayoutRequest 默认是false,在performlayout() view.requestLayout()前后会操作此值true false,
          if (!mHandlingLayoutInLayoutRequest) { @3
              checkThread();
              mLayoutRequested = true;
              scheduleTraversals();    
          }
      }

      @UnsupportedAppUsage
      void scheduleTraversals() {
          if (!mTraversalScheduled) {
              mTraversalScheduled = true;
//设置同步栅栏,拦截同步消息
              mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();@4
/* Choreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);就是去请求vsync,等下一次vsync到来的时候进行绘制显示界面。
此次消息任务会被设置为异步。msg.setAsynchronous(true); 因为之前已经设置了同步屏障,所以异步消息会优先执行。*/
              mChoreographer.postCallback(
                      Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);  @5
              if (!mUnbufferedInputDispatch) {
                  scheduleConsumeBatchedInput();
              }
              notifyRendererOfFramePending();
              pokeDrawLockIfNeeded();
          }
      }

/frameworks/base/core/java/android/view/Choreographer.java
private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {
......
//省略了从SurfaceFlinger进程下发vsync信号的过程。vsync信号过来后,在Java层会回调到FrameDisplayEventReceiver 的onVsync方法。
        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) { @6
            long now = System.nanoTime();
......
/* 处设置了此message为异步消息,在申请Vsync之前mHandler.getLooper().getQueue().postSyncBarrier(); 
设置了同步栅栏,也就是所申请的vsync信号到来后,优先执行异步消息。而在@7处设置了此消息为异步消息,
此消息就会优先执行,所以一般view的更新消息是优先执行的。执行完了异步消息后就会通过removeSyncBarrier该方法将同步屏障移除。
(在viewRootImpl 的unscheduleTraversals()、doTraversal() 会调用 MessageQueue.java 中的removeSyncBarrier)*/
            Message msg = Message.obtain(mHandler, this);  
            msg.setAsynchronous(true);  @7 如何到 @8
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {  @8
            mHavePendingVsync = false;
/*doframe 我们很熟悉了
其后面会走到 performTraversals()。performTraversals() 会执行performMeasure、performLayout、performDraw,也就是View绘制的三大流程:测量,布局,绘制。View的绘制流程都是由ViewRootImpl发起的。
这个就算是把StartingWindow 显示出来了。*/
            doFrame(mTimestampNanos, mFrame); @9
        }
    }

//下面是 如果从@7 到 @8
/frameworks/base/core/java/android/os/Message.java
    public static Message obtain(Handler h, Runnable callback) {
        Message m = obtain();
        m.target = h;
        m.callback = callback;

        return m;
    }

/frameworks/base/core/java/android/os/Handler.java
    public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {  
            handleCallback(msg); 
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

/frameworks/base/core/java/android/os/Handler.java
private static void handleCallback(Message message) {
        message.callback.run();
    }

下来看一下@ 2 处 mWindowSession.addToDisplay
mWindowSession 是 实现类是session
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient

/frameworks/base/services/core/java/com/android/server/wm/Session.java
      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,
              InsetsState outInsetsState) {
          return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                  outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
                  outInsetsState);
      }

因为WMS和Session都是运行在系统进程,所以不需要跨进程通信,直接调用WMS的方法

/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java
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) {
// Activity在启动时就创建了AppWindowToken,并将AppWindowToken添加到了displayContent,
//前面说了启动窗口用的当前Activity的AppWindowToken,所以此处token能够获取到。
            WindowToken token = displayContent.getWindowToken(   @1
                    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;
            if (token == null) {
                        ......  
            }else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {  @2
                //当前Activity的AppWindowToken
                atoken = token.asAppWindowToken();
                if (atoken == null) {
                        .....
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } else if (atoken.removed) {
                        .....
                    return WindowManagerGlobal.ADD_APP_EXITING;
//如果是启动窗口类型,并且当前Activity的AppWindowToken的成员变量startingWindow不为空则说明启动窗口是重复添加。
                } else if (type == TYPE_APPLICATION_STARTING && atoken.startingWindow != null) {  @3
                            .....
                    return WindowManagerGlobal.ADD_DUPLICATE_ADD;
                }
// 创建启动窗口的WindowState,在WindowState构造函数中会获取启动窗口的mBaseLayer用作Z-order排序。
                final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,    @4
                    session.mCanAddInternalSystemWindow);
//添加到mWindowMap
                    mWindowMap.put(client.asBinder(), win);  @5
                    ......
            final AppWindowToken aToken = token.asAppWindowToken();
// 这里才给当前Activity的AppWindowToken的startingWindow赋值,所以当aToken.startingWindow,不为空则一定是添加过了启动窗口。
            if (type == TYPE_APPLICATION_STARTING && aToken != null) {  @6
                aToken.startingWindow = win;
            }
            //将启动窗口添加到当前windowState的AppWindowToken中,并且会按照自定义规则排序。
            win.mToken.addWindow(win); @7
            ......
}

@1 Activity在启动时就创建了AppWindowToken,并将AppWindowToken添加到了displayContent,前面说了启动窗口用的当前Activity的AppWindowToken,所以此处token能够获取到。
@2
@3 如果是启动窗口类型,并且当前Activity的AppWindowToken的成员变量startingWindow不为空则说明启动窗口是重复添加。
@4 创建启动窗口的WindowState,在WindowState构造函数中会获取启动窗口的mBaseLayer用作Z-order排序。
@5 添加到mWindowMap
@6 这里才给当前Activity的AppWindowToken的startingWindow赋值,所以当aToken.startingWindow,不为空则一定是添加过了启动窗口。
@7 将启动窗口添加到当前windowState的AppWindowToken中,并且会按照自定义规则排序。

/frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
void addWindow(final WindowState win) {
//如果是子窗口,不会添加到WindowToken中,子窗口是添加到父窗口的WindowState中。
        if (win.isChildWindow()) {  @1
            return;
        }
//如果没有添加过进行添加。
        if (!mChildren.contains(win)) {  @2
/*处执行的是windowToken的父类WindowContainer中的方法addChild,
windowToken 集成WindowContainer。*/
            addChild(win, mWindowComparator);  @3
            mWmService.mWindowsChanged = true;
        }
    }

@1 如果是子窗口,不会添加到WindowToken中,子窗口是添加到父窗口的WindowState中。
@2 如果没有添加过进行添加。
@3 处执行的是windowToken的父类WindowContainer中的方法addChild,
windowToken 集成WindowContainer。
class WindowToken extends WindowContainer

/frameworks/base/services/core/java/com/android/server/wm/WindowContainer.java
      protected void addChild(E child, Comparator comparator) {
......
          int positionToAdd = -1;
          if (comparator != null) {
              final int count = mChildren.size();
              for (int i = 0; i < count; i++) {
                  if (comparator.compare(child, mChildren.get(i)) < 0) { @1
                      positionToAdd = i;
                      break;
                  }
              }
          }
  
          if (positionToAdd == -1) {
              mChildren.add(child);
          } else {
              mChildren.add(positionToAdd, child);
          }
          onChildAdded(child);
  
          // Set the parent after we've actually added a child in case a subclass depends on this.
          child.setParent(this); 
      }

@1 通过自定义比较器,计算出当前窗口的z-order位置
看下自定义比较器,

/frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
 private final Comparator mWindowComparator =
            (WindowState newWindow, WindowState existingWindow) -> {
        final WindowToken token = WindowToken.this;
        if (newWindow.mToken != token) {
            throw new IllegalArgumentException("newWindow=" + newWindow
                    + " is not a child of token=" + token);
        }

        if (existingWindow.mToken != token) {
            throw new IllegalArgumentException("existingWindow=" + existingWindow
                    + " is not a child of token=" + token);
        }

        return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
    };

比较当前token的两个子窗口,如果新添加的窗口的Z-order小于已存在的窗口,则返回-1,否则返回1

/frameworks/base/services/core/java/com/android/server/wm/WindowToken.java
@Override
    protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
            WindowState existingWindow) {
        final int type1 = newWindow.mAttrs.type;
        final int type2 = existingWindow.mAttrs.type;

        if (type1 == TYPE_BASE_APPLICATION && type2 != TYPE_BASE_APPLICATION) {  @1
            return false;
        } else if (type1 != TYPE_BASE_APPLICATION && type2 == TYPE_BASE_APPLICATION) {  @2
            return true;
        }

        if (type1 == TYPE_APPLICATION_STARTING && type2 != TYPE_APPLICATION_STARTING) {  @3
            return true;
        } else if (type1 != TYPE_APPLICATION_STARTING && type2 == TYPE_APPLICATION_STARTING) {   @4
            return false;
        }
        return true;
    }

@1 @2 TYPE_BASE_APPLICATION类型窗口的Z-order应该在AppWindowToken所有窗口之下。
@3 @4 启动类型窗口排在AppWindowToken所有窗口之上

你可能感兴趣的:(StartingWindow 启动添加显示)