解析Activity启动-窗口篇

news2024/12/27 3:18:48

解析Activity启动-窗口篇

  • 在 解析Activity启动 前两篇文章中,我们分别专注于 堆栈 和 生命周期角度大致的过了一遍启动流程,而本篇会着重窗口的创建和显示流程,继续梳理Activity的启动流程

  • 顺着前两篇文章的分析流程,我们知道和 Window 相关的代码是从 Activity.attch() 开始的,那么就从该函数调用处开始梳理

1. ActivityThread.performLaunchActivity()

frameworks\base\core\java\android\app\ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...

    //在这里创建出了该Activity对应的 ContextImpl 对象,而在 ContextImpl 对象创建时,会同步创建出属于该Activity的 SystemServiceRegistry 对象
    //该context是ActivityContext
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    try {
        //拿到上面获取到的classloader
        java.lang.ClassLoader cl = appContext.getClassLoader();
        //借助 Instrumentation创建出 Activity 对象
        activity = mInstrumentation.newActivity(
            cl, component.getClassName(), r.intent);
       
    }

    try {
        //使用loadedApk创建出application对象
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        if (activity != null) {
            ...
            Window window = null;
            //如果启动的Activity有复用的遗留窗口,那么此时就会把遗留窗口赋值给window,传入启动的activity中
            //当前流程下window是为空的
            if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
                window = r.mPendingRemoveWindow;
                r.mPendingRemoveWindow = null;
                r.mPendingRemoveWindowManager = null;
            }
            appContext.setOuterContext(activity);
            //调用activity进行attach()
            activity.attach(appContext, this, getInstrumentation(), r.token,
                            r.ident, app, r.intent, r.activityInfo, title, r.parent,
                            r.embeddedID, r.lastNonConfigurationInstances, config,
                            r.referrer, r.voiceInteractor, window, r.configCallback);

            ...

            //未调用Activity.onCreate()之前,mCalled为false
            activity.mCalled = false;
            //根据是否存在persistable数据调用不同参数的函数
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            //如果没有调用过Activity.onCreate(),会命中if,直接抛出异常;代表必须调用到Activity.onCreate()
            if (!activity.mCalled) {
                throw new SuperNotCalledException(
                    "Activity " + r.intent.getComponent().toShortString() +
                    " did not call through to super.onCreate()");
            }
            //将创建的Activity保存到ActivityClientRecord中
            r.activity = activity;
        }
        //设置activity的生命周期为 ON_CREATE
        r.setState(ON_CREATE);

        //将该ActivityClientRecord保存到ActivityThread的mActivities列表中
        mActivities.put(r.token, r);

    }
    ...

    return activity;
}
  • ActivityThread.performLaunchActivity() 函数中,完成了如下几件事:

    1. 创建出 ActivitycontextImpl 对象
    2. 创建出 Activity 对象,并调用 Activity.attach() 函数完成初始化以及窗口的创建
    3. 调用 Activity.onCreate() 函数

    在这其中和窗口相关的,主要就是 Activity.attach()Activity.onCreate() 两个函数,我们接下来逐一来进行分析

2. Activity.attach()

frameworks\base\core\java\android\app\Activity.java

//attach()函数中其实就完成了activity相关参数的保存,以及完成了window的创建和与当前activity的绑定
final void attach(Context context, ActivityThread aThread,
                  Instrumentation instr, IBinder token, int ident,
                  Application application, Intent intent, ActivityInfo info,
                  CharSequence title, Activity parent, String id,
                  NonConfigurationInstances lastNonConfigurationInstances,
                  Configuration config, String referrer, IVoiceInteractor voiceInteractor,
                  Window window, ActivityConfigCallback activityConfigCallback) {
    //存下传入的context对象,是个 ContextImpl 类,也就是在 ActivityThread.performLaunchActivity() 中创建的ActivityContext对象
    这里也就是存到了 ContextWrapper.mBase 参数上
    attachBaseContext(context);

    mFragments.attachHost(null /*parent*/);

    //创建出 PhoneWindow 对象,这个就是Activity所持有的 Window ,调用三个参数的phonewindow构造方法,构造出的是main window
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    //设置window.mWindowControllerCallback、window.mCallback、window.mOnWindowDismissedCallback、
    //window.LayoutInflater.mPrivateFactory为当前的Activity
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    //如果定义了键盘模式,且不是unspecified,那么需要将传入的键盘模式,设置到当前window中去
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    //如果当前package设置了uiOptions参数,既不为0,那么需要将该值设置到当前window.mUiOptions中
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    //记下当前的Thread,为 UiThread
    mUiThread = Thread.currentThread();

    //传入的 ActivityThread 为当前Activity的 mMainThread
    mMainThread = aThread;
    //将一系列属于当前Activity的参数,记录下来,保存到Activity的成员变量中
    mInstrumentation = instr;
    mToken = token;
    mIdent = ident;
    mApplication = application;
    mIntent = intent;
    mReferrer = referrer;
    mComponent = intent.getComponent();
    mActivityInfo = info;
    mTitle = title;
    mParent = parent;
    mEmbeddedID = id;
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    //语音相关的
    if (voiceInteractor != null) {
        if (lastNonConfigurationInstances != null) {
            mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
        } else {
            mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                                                   Looper.myLooper());
        }
    }

    //将Phonewindow和Activity相互绑定起来,并为PhoneWindow构建一个WindowManagerImpl对象
    mWindow.setWindowManager(
        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),  //getSystemService()拿出来的是 WindowManagerImpl对象
        mToken, mComponent.flattenToString(),
        (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    //如果当前Activity是个childActivity,那么也就意味着该Activity的窗口是个子window,那么需要把其父window赋值给该子window,
    //在这其中还会对当前的子window做一些处理,比如设置其no_title,因为子窗口都不能有标题
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    //在Activity中保存下对应window的WindowManager,也就是刚刚创建出来的WindowManagerImpl对象
    mWindowManager = mWindow.getWindowManager();
    //保存下config
    mCurrentConfig = config;

    //存下activity的colorMode
    mWindow.setColorMode(info.colorMode);

    //自动填充相关的内容
    setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
    enableAutofillCompatibilityIfNeeded();
}
  • 可以看到在attach()函数中最重要的就是完成了 window 的创建,以及相关的初始化;那么接下来就详细解析一下相关的函数

2.1 PhoneWindow 的构建

frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java

//该方法构建的phonewindow是main window
public PhoneWindow(Context context, Window preservedWindow,
                   ActivityConfigCallback activityConfigCallback) {
    //在重载函数中,存下了传入的 Context 对象,并获取到 layout_inflater_service 服务,存在 mLayoutInflater 变量中
    this(context);
    //在构建main window时,需要使用DecorContext,将 mUseDecorContext置为true
    mUseDecorContext = true;
    //如果传入的preservedWindow不为空,代表存在遗留窗口,命中if
    //在这里就会把遗留窗口相关的数据赋值给当前的窗口,以使得遗留窗口参数生效
    if (preservedWindow != null) {
        //拿到遗留窗口的 DecorView,作为当前phoneWindow的 DecorView
        mDecor = (DecorView) preservedWindow.getDecorView();
        //同样设置遗留窗口的 Elevation 作为当前窗口的 mElevation 
        mElevation = preservedWindow.getElevation();
        //在存在遗留窗口的情况下,就无需再去加载Elevation了,将mLoadElevation置为false,这个也就是高程(Z轴)
        mLoadElevation = false;
        //在此种场景下,需要强制做DecorInstall,将mForceDecorInstall置true
        mForceDecorInstall = true;
        //当前phoneWindow的LayoutParams中的token也需要被赋值为 遗留窗口的
        //该token就是窗口标识
        getAttributes().token = preservedWindow.getAttributes().token;
    }
    //判断当前的PhoneWindow是否可以调整窗口大小,决定条件满足以下其一即可:
    //1. DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES 不为0; 2. package声明了 FEATURE_PICTURE_IN_PICTURE 标识
    boolean forceResizable = Settings.Global.getInt(context.getContentResolver(),
                                                    DEVELOPMENT_FORCE_RESIZABLE_ACTIVITIES, 0) != 0;
    mSupportsPictureInPicture = forceResizable || context.getPackageManager().hasSystemFeature(
        PackageManager.FEATURE_PICTURE_IN_PICTURE);
    //记下config update 的回调
    mActivityConfigCallback = activityConfigCallback;
}
  • 可以看到,在三个参数的 phoneWindow 构造函数中,完成了 PhoneWindow 的创建,而如果存在遗留窗口的情况下,那么会直接拿遗留窗口中的 DecorViewElevation 等内容赋值给当前创建出来的 PhoneWindow,当下我们不关注遗留窗口,还是从整体流程完整的进行梳理,也就是默认没有遗留窗口传入的情况;
  • 有一点可以看到在三个参数的构造函数中,会将 mUseDecorContext 参数置为true,这一点需要注意,在后面的解析中,会有涉及
//单个参数的phoneWindow构造方法,构造出来的是other window,此时mUseDecorContext为默认值,即false
public PhoneWindow(Context context) {
    //将传入的context存到了PhoneWindow类中的 mContext 成员变量中,当前流程下context就是Activity
    super(context);
    //获取到了layout_inflater_service 对象,保存在 mLayoutInflater 变量中,也就是 LayoutInflater 对象
    mLayoutInflater = LayoutInflater.from(context);
}
  • 经过构建,此时Activity就持有了 phoneWindow 对象,那么回到 Activity.attach() 函数中继续向下解析,接下来就是调用 mWindow.setWindowManager() 函数

2.2 WindowManagerImpl的构建和初始化

frameworks\base\core\java\android\view\Window.java

public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
                             boolean hardwareAccelerated) {
    //将Activity相关的appToken、appName都存到当前Window中来,并且存下是否启动硬件加速
    mAppToken = appToken;
    mAppName = appName;
    //可以看到是否硬件加速有两个条件可以控制:1. 传入的hardwareAccelerated,也就是package中定义的; 2. PROPERTY_HARDWARE_UI属性来确定; 满足其一即可
    mHardwareAccelerated = hardwareAccelerated
        || SystemProperties.getBoolean(PROPERTY_HARDWARE_UI, false);
    //如果传入的wm为空,那么此处会自己再去获取一次wm;通过 getSystemService(Context.WINDOW_SERVICE)拿出来
    //的是 windowmanagerImpl 对象,可参见 SystemServieRegistry 类
    if (wm == null) {
        //此处的mContext是Activity对象
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    //创建出WindowManagerImpl对象,其中WindowManagerImpl.mParentWindow为当前的phoneWindow,而WindowManagerImpl.mContext就是该window归属的Activity的ContextImpl对象
    //之所以这里还需要调用create去获取wmi,而不是直接赋值,原因是此时通过contextImpl获取的wmi,是没有归属window的,需要将该wmi附上归属窗口
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
  • 在该函数中,可以看到除了完成了 PhoneWindowActivity 的关联,最主要就是保存了 WindowManager,那么这个 WindowManager 到底是什么?为什么在最后还要调用其 createLocalWindowManager()
  • 首先在 Window.setWindowManager() 函数调用时,参数 wm 是通过 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE) 来获取的,而根据 ActivityThread.performLaunchActivity() 函数中的内容,我们知道此处的 context 就是通过 ContextImpl.createActivityContext() 构建出来的 ActivityContext ,那么我们就去 ContextImpl 类中进行查阅

2.2.1 ContextImpl.getSystemService()

frameworks\base\core\java\android\app\ContextImpl.java

@Override
public Object getSystemService(String name) {
    //通过ContextImpl和name去 SystemServiceRegistry 中查找对象
    return SystemServiceRegistry.getSystemService(this, name);
}
  • 这里就是去 SystemServiceRegistry 对象中去尝试获取,此处的获取方式比较有意思,大致梳理一下:

    1. 静态块注册:

      • SystemServiceRegistry 类中存在一个静态块:

        static {
         	...
                registerService(Context.WINDOW_SERVICE, WindowManager.class,
                        new CachedServiceFetcher<WindowManager>() {
                    @Override
                    public WindowManager createService(ContextImpl ctx) {
                        return new WindowManagerImpl(ctx);
                    }});
            ...
        }
        
        • 结合当前的Window分析流程,我们关注 WINDOW_SERVICE,可以看到在静态块中调用 registerService() 进行了注册:
        private static <T> void registerService(String serviceName, Class<T> serviceClass,
                                                ServiceFetcher<T> serviceFetcher) {
            //以ServiceClass为key,保存下ServiceName
            SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
            //以ServiceName为key,保存下ServiceFetcher
            SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
        }
        
        • 而在 registerService 函数中,其实就是将传入的 classnamefetcher按照一定的关系保存到了 SYSTEM_SERVICE_NAMESSYSTEM_SERVICE_FETCHERS 列表中
    2. SystemService 获取

      public static Object getSystemService(ContextImpl ctx, String name) {
          //通过传入的 ServiceName,去SYSTEM_SERVICE_FETCHERS列表中拿到对应得ServiceFetcher
          ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
          //如果拿到的ServiceFetcher不为空,那么调用其getService()函数获取Service
          return fetcher != null ? fetcher.getService(ctx) : null;
      }
      
      • 根据上面的注册,我们可以知道根据 ServiceName 就可以拿到 ServiceFetcher,而在静态块中注册的 ServiceFetcherCachedServiceFetcher 类型,那么此时调用的就是 CachedServiceFetcher.getService()
      @Override
      @SuppressWarnings("unchecked")
      public final T getService(ContextImpl ctx) {
          //先拿到传入的ContextImpl中的缓存 serviceCache
          final Object[] cache = ctx.mServiceCache;
          //拿到传入的ContextImpl中对ServiceCache中每个Service的状态记录
          final int[] gates = ctx.mServiceInitializationStateArray;
      
          for (;;) {
              boolean doInitialize = false;
              //加锁处理ServiceCache
              synchronized (cache) {
                  //拿到cache中的对应的该Service对象
                  T service = (T) cache[mCacheIndex];
                  //如果能够从缓存中拿到Service对象,代表该Service对象已存在,直接返回缓存中的Service对象
                  //还有一种情况就是从系统中无法找到该Service,也就是该Service对应的缓存状态为STATE_NOT_FOUND,那么此时返回的其实是个null
                  if (service != null || gates[mCacheIndex] == ContextImpl.STATE_NOT_FOUND) {
                      return service;
                  }
      
                  //而如果缓存中对应Service的状态记录为 STATE_READY,那么先给他赋予新的状态STATE_UNINITIALIZED,在下面做初始化
                  if (gates[mCacheIndex] == ContextImpl.STATE_READY) {
                      gates[mCacheIndex] = ContextImpl.STATE_UNINITIALIZED;
                  }
      
                  //如果当前Service缓存状态是STATE_UNINITIALIZED,那么此时就需要进行初始化,置位 doInitialize,并赋值新状态STATE_INITIALIZING
                  if (gates[mCacheIndex] == ContextImpl.STATE_UNINITIALIZED) {
                      doInitialize = true;
                      gates[mCacheIndex] = ContextImpl.STATE_INITIALIZING;
                  }
              }
      
              //如果当前是需要进行初始化的,那么命中if,进行初始化
              if (doInitialize) {
      
                  T service = null;
                  //默认状态是not_found,只有在 createService() 抛异常的时候,缓存service对象状态才会为 NOT_FOUND
                  @ServiceInitializationState int newState = ContextImpl.STATE_NOT_FOUND;
                  try {
                      //创建Service,传入的参数是该Service归属的 ContextImpl
                      service = createService(ctx);
                      //此时的缓存对象状态是为 READY
                      newState = ContextImpl.STATE_READY;
      
                  } catch (ServiceNotFoundException e) {
                      onServiceNotFound(e);
      
                  } finally {
                      //最后将创建出来的Service和最终的状态保存到ContextImpl的mCache和gates中
                      synchronized (cache) {
                          cache[mCacheIndex] = service;
                          gates[mCacheIndex] = newState;
                          cache.notifyAll();
                      }
                  }
                  //返回创建的Service
                  return service;
              }
              //如果没有完成初始化,那么此处就调用 wait() 进行阻塞,直到初始化完成,notifyAll() 时再向下执行
              synchronized (cache) {
                  while (gates[mCacheIndex] < ContextImpl.STATE_READY) {
                      try {
                          cache.wait();
                      } catch (InterruptedException e) {
                          Log.w(TAG, "getService() interrupted");
                          Thread.currentThread().interrupt();
                          return null;
                      }
                  }
              }
          }
      }
      
      • 可以看到首先会尝试从缓存中去拿,如果拿不到,并且符合相应的状态,那么就会调用 createService() 去进行创建
    3. 创建 Service

      • 我们还是以 WINDOW_SERVICE 为例,在上面无法从缓存中获取到 Service 的情况下,就会去调用 createService() 进行创建
      @Override
      public WindowManager createService(ContextImpl ctx) {
          return new WindowManagerImpl(ctx);
      }
      
      • 至此,我们终于得到了 WINDOW_SERVICE 对应的 Service 了,也就是 WindowManagerImpl 对象
  • 经过这一圈的兜兜转转,我们终于得到了 SystemServiceRegistry.getSystemService(this, name) 返回的对象,也就是 WindowManagerImpl 对象;那么接下来回到 setWindowManager() 从上面的分析可知,此时传入的 wm 参数不为空,那么接下来就直接调用到了 createLocalWindowManager() 函数,看一下该函数中完成了什么工作?

2.2.2 WindowManagerImpl.createLocalWindowManager()

frameworks\base\core\java\android\view\WindowManagerImpl.java

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    //构建了一个 WindowManagerImpl 对象返回
    return new WindowManagerImpl(mContext, parentWindow);
}
  • 代码非常简单,就是构建出了一个 WindowManagerImpl 对象进行了返回;但是刚刚不是已经创建出了 WindowManagerImpl 对象了吗?为什么此处又要创建一次呢?我们再返回去看一下 首次创建的代码:return new WindowManagerImpl(ctx) 可以看到是调用了一个参数的构造函数:
public WindowManagerImpl(Context context) {
    //一个参数的构造函数,构建时parentWindow传的是 null
    this(context, null);
}
  • 翻阅代码可知,之所以还要创建一次,是因为首次创建时,调用的是单个参数的构造函数,而在该构造函数中,传入的 parentWindow 是为 null, 也就是说没有完成 WindowManagerImplWindow 的关联,而在第二次创建时,传入了 parentWindow ,至此才完成了 WindowManagerImplPhoneWindow 的关联关系

2.3 总结:

  • 至此,我们的Activity完成了 attach(),创建了 PhoneWindow 并且构建出了和 PhoneWindow 绑定的 WindowManagerImpl 对象,并完成了保存;回到 Acitivity启动 流程中,接下来就是调用 Activity.onCreate() 完成接下来的启动

3. Activity的 onCreate()

  • 再度回到 Activity的Launch 中,前面我们已经完成了 Activity.attach() 的梳理,接下来就会借助 Instrumentation 完成后续的Launch动作,这里我们就看三个参数的 callActivityOnCreate() 函数,因为两个参数的函数最终的调用函数也是一致的,只是传参不同

3.1 Instrumentation.callActivityOnCreate()

frameworks\base\core\java\android\app\Instrumentation.java

public void callActivityOnCreate(Activity activity, Bundle icicle,
                                 PersistableBundle persistentState) {
    //构建 idleHandle,用来处理异步启动的Activity
    prePerformCreate(activity);
    //调用到Activity中
    activity.performCreate(icicle, persistentState);
    //如果有在等待的ActivityMonitor,通知继续执行
    postPerformCreate(activity);
}
  • 前置后置操作我们先不关心,主要关注当前ActivityCreate流程,那么也就是接下来会调用到 Activity.performCreate() 函数中

3.2 Activity.performCreate()

frameworks\base\core\java\android\app\Activity.java

final void performCreate(Bundle icicle, PersistableBundle persistentState) {
    //允许进入画中画模式
    mCanEnterPictureInPicture = true;
    //如果传入的Bundle不为空,那么将其中的HAS_CURENT_PERMISSIONS_REQUEST_KEY获取出来保存到mHasCurrentPermissionsRequest中,默认值是false
    restoreHasCurrentPermissionRequest(icicle);
    //由persistentState的值确定调用几个参数的onCreate()函数
    if (persistentState != null) {
        onCreate(icicle, persistentState);
    } else {
        onCreate(icicle);
    }
    writeEventLog(LOG_AM_ON_CREATE_CALLED, "performCreate");
    mActivityTransitionState.readState(icicle);

    //判断是不是可见的
    mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
        com.android.internal.R.styleable.Window_windowNoDisplay, false);
    mFragments.dispatchActivityCreated();
    mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
}
  • 该函数很简单,最主要的就是去调用了 onCreate() 函数,也就是首先调用到了我们自定义实现的Activity.onCreate()

3.3 Activity.onCreate()

  • 通常来说我们实现的Activity都是会有如下内容:

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
    }
    

    也就是首先调用父类 ActivityonCreate() 函数,接着再调用 setContentView() 加载布局,我们继续顺着往下分析,首先就是父类的 onCreate() 函数

frameworks\base\core\java\android\app\Activity.java

@MainThread
@CallSuper
protected void onCreate(@Nullable Bundle savedInstanceState) {
    ...
    //拿到activity所属的application中所有的callback,调用其onActivityCreated()函数
    getApplication().dispatchActivityCreated(this, savedInstanceState);
    //如果当前的activity支持语音,那么将其添加到mVoiceInteractor中
    if (mVoiceInteractor != null) {
        mVoiceInteractor.attachActivity(this);
    }
    mRestoredFromBundle = savedInstanceState != null;
    //最终将mCalled置为true,代表已完成父类onCreate()的调用
    mCalled = true;
}
  • 该函数也比较简单,主要就是做了一些内容的保存以及当前状态改变的通知,需要关注的主要是如下两个点:
    1. getApplication().dispatchActivityCreated() 在这其中就会去调用所有注册在Application中的 ActivityLifecycleCallbacks 回调,这也就意味着:只要我们注册了 ActivityLifecycleCallbacks,那么就可以监听到当前Activity的生命周期的改变
    2. mCalled = true 在父类的onCreate()函数中,会将 mCalled 变量置为 true,这也就意味着必须调用父类的 onCreate()

4. 布局加载

  • 回到 自定义onCreate() 在完成 super.onCreate() 之后,通常情况下会调用 setContentView() 函数进行自定义布局的加载,接下来我们就查阅一下自定义布局的加载流程

4.1 Activity.setContentView()

  • Activity中存在多个 setCOntentView() 的重载函数,其实每个重载函数执行的流程都类似,此处我们挑选 setContentView(@LayoutRes int layoutResID) 进行解析

frameworks\base\core\java\android\app\Activity.java

public void setContentView(@LayoutRes int layoutResID) {
    //此处getWindow()拿到的就是mWindow变量,也就是attach()函数中创建的 phoneWindow 对象
    //调用到 phoneWindow.setContentView() 函数
    getWindow().setContentView(layoutResID);
    //初始化ActionBar
    initWindowDecorActionBar();
}

  • 在该函数中就是直接调用到了 PhoneWindow.setContentView() 函数中

4.2 PhoneWindow.setContentView()

frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java

@Override
public void setContentView(int layoutResID) {
    //当 mContentParent 不存在时,需要先去进行创建
    if (mContentParent == null) {
        //在这其中会进行DecorView的创建和初始化,并且完成mContentParent的创建
        installDecor();
        //如果当前已经存在mContentParent对象,但是没有转场功能,那么命中if,直接去除mContentParent中所有的view
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    //如果当前的窗口有转场功能,那么命中if
    //在这其中就会去做转场
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        //构建出Scene对象,其中mSceneRoot = mContentParent,mContext = Activity, mLayoutId = layoutResID
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                                                       getContext());
        //用Scene完成mLayoutId添加到mContentParent上的工作
        transitionTo(newScene);
    //否则,要么当前的mContentParent是新建的,要么其上所有的view都已被移除,直接添加布局即可
    } else {
        //这里就是开始将layoutResID代表的布局添加到mContentParent View上,至此客制化自定义的布局就完成了添加
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    //这个就是向上调用parent的requestFitSystemWindows()函数,进行告知完成添加
    mContentParent.requestApplyInsets();
    //phoneWindow中拿到的callback就是Activity
    final Callback cb = getCallback();
    //activity不为空且不是销毁状态下,回调 onContentChanged()函数告知activity此时已完成content布局添加
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    //标识当前content已经完成了设置
    mContentParentExplicitlySet = true;
}
  • 在该函数中,主要就是完成了如下几件工作:

    1. installDecor()中完成了DecorView的创建和初始化,并且完成mContentParent 的创建
    2. mLayoutInflater.inflate()函数中将 layoutResID代表的布局添加到mContentParent 这个 View

    而由此就有那么几个问题:

    1. DecorView是如何进行的创建?
    2. mContentParent 又是个什么样的 View
    3. 传入的 layoutResID 又是如何添加到 mContentParent 上的?

    针对这些问题,我们接下来逐一来分析

4.2.1 DecorView 对象的创建

  • 首先就是 DecorView是如何创建的?该动作就是在 installDecor() 函数中完成的, 该函数比较长,我们拆分开进行解析

frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java

//加载DecorView,具体的动作包括:创建DecorView,加载相应布局和添加对应View,完成 contentView的创建和初始化
private void installDecor() {
    //是否需要重新加载DecorView的标志,当前已经在加载函数中,所以将mForceDecorInstall复位
    mForceDecorInstall = false;
    //如果当前Decor还未创建,那么需要先去创建出DecorView
    if (mDecor == null) {
        //通过该函数创建出了DecorView对象
        mDecor = generateDecor(-1);
        //设置从当前DecorView开始的view Group的焦点获取规则,当前的DecorView只有在其上的子窗口都不需要焦点时才会被允许获取焦点
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        //设置当前的DecorView是根节点
        mDecor.setIsRootNamespace(true);
        //如果当前面板已失效,那么需要让DecorView去触发调用mInvalidatePanelMenuRunnable任务
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    //如果已存在DecorView,那么就复用这个已存在的DecorView,只需要将当前的phonewindow设置到其中,建立window和Decorview的关联即可
    } else {
        mDecor.setWindow(this);
    }
    ...
}
  • 首先第一部分是在创建 DecorView,通过函数 generateDecor() 实现
4.2.1.1 PhoneWindow.generateDecor()
protected DecorView generateDecor(int featureId) {
    Context context;
    //如果当前的phoneWindow是作为main window构建出来的,也就是调用的是三个参数的构造方法构建的,那么mUseDecorContext
    //为true,命中if;在这其中会判断是否需要使用DecorContext对象,如果能够获取到Application,或者说当前window是activity的,
    //那么就创建DecorContext作为传入DecorView的context使用
    if (mUseDecorContext) {
        //在当前的流程中,getContext()拿到的就是所属的Activity,最终拿到的就是 Application 对象
        Context applicationContext = getContext().getApplicationContext();
        //如果拿到的 Application 对象为空,那么就将getContext()函数返回值作为context,后续传入DecorView中
        //也就是拿到的是Activity对象
        if (applicationContext == null) {
            context = getContext();
            //如果获取的Application不为空,那么命中else,构建出DecorContext对象
        } else {
            //构建DecorContext对象,传入的就是application和Activity
            //在DecorContext中,其内部的mBase其实是Application对象
            context = new DecorContext(applicationContext, getContext());
            //如果当前window的mTheme不是无效值,那么将mTheme设置到DecorContext中
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
        //如果当前的phonewindow不是main window,那么直接将该window的mContext传入DecorView中
    } else {
        context = getContext();
    }
    //构建出DecorView对象
    //在当前的流程下,context是DecorContext对象,featureId为-1
    return new DecorView(context, featureId, this, getAttributes());
}
  • 可以看到在该函数中,主要完成了如下工作:

    1. 根据 mUseDecorContext 变量确定 Context 类型;在当前的流程中,创建出来的是 DecorContext
    2. 创建出 DecorView

    我们来看一下 DecorView 的创建都完成了哪些工作?

4.2.1.2 DecorView()

frameworks\base\core\java\com\android\internal\policy\DecorView.java

DecorView(Context context, int featureId, PhoneWindow window,
          WindowManager.LayoutParams params) {
    //将传入的DecorContext存入mContext
    super(context);
    //记下传入的featureID
    mFeatureId = featureId;

    ...

    //更新mAvailableWidth
    updateAvailableWidth();

    //在这其中,会将传入的phoneWindow存到当前DecorView的mWindow中,
    //并且如果当前decorView的Context是DecorContext,那么还需要将phoneWindow存到context中
    setWindow(window);

    //更新Log Tag
    updateLogTag(params);

    //获取到默认的shadow size
    mResizeShadowSize = context.getResources().getDimensionPixelSize(
        R.dimen.resize_shadow_size);
    //着色器初始化
    initResizingPaints();
}
  • DecorView 的创建比较单纯,除了进行了一些必要的初始化之外,就是将传入的 PhoneWindow 保存到了当前 DecorView 中来,换言之,DecorView 就有了归属的 PhoneWindow
4.2.1.3 DecorView.setWindow()

frameworks\base\core\java\com\android\internal\policy\DecorView.java

void setWindow(PhoneWindow phoneWindow) {
    //将phoneWindow保存到DecorView的mWindow参数中,也就是将DecorView和PhoneWindow相互关联起来
    mWindow = phoneWindow;
    //拿到当前DecorView保存的mContext对象
    Context context = getContext();
    //如果当前decorView中的context是DecorContext类时,命中if
    //在这其中会将当前DecorView所属的window保存到DecorContext中
    if (context instanceof DecorContext) {
        DecorContext decorContext = (DecorContext) context;
        decorContext.setPhoneWindow(mWindow);
    }
}
  • 就是将 PhoneWindow 对象保存到了 DecorView.mWindow 变量中,而且如果当前的 DecorView 拥有的是 DecorContext 那么还需要将 PhoneWindow 保存到 DecorContext

  • 至此,DecorView 已经完成了创建,我们回到 installDecor 函数继续往下分析:

//加载DecorView,具体的动作包括:创建DecorView,加载相应布局和添加对应View,完成 contentView的创建和初始化
private void installDecor() {
    ...
    //判断mContentParent是否已存在,如果不存在命中if
    if (mContentParent == null) {
        //构建出ContentLayout,或者说是在往DecorView上添加布局
        mContentParent = generateLayout(mDecor);

        mDecor.makeOptionalFitsSystemWindows();

        //尝试去获取DecorView中是否有id为decor_content_parent的View
        final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
            R.id.decor_content_parent);

        //如果decorView中存在decor_content_parent View,那么命中if
        //处理decorContentParent布局
        if (decorContentParent != null) {
            ...
        //如果不存在DecorContentParent View,那么命中else
        //设置title
        } else {
            //查找当前PhoneWindow中是否有id为title的textView,这个就会遍历找所有子View
            mTitleView = findViewById(R.id.title);
            //当存在id为title的textView时,命中if
            if (mTitleView != null) {
                //如果window设置了no title,那么需要将title相关的View都设置为不可见,且设置前景为空
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    //如果有title_container View,设置其不可见,否则设置titleView不可见
                    final View titleContainer = findViewById(R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    //设置contentView的前景为空
                    mContentParent.setForeground(null);
                    //否则,设置title显示
                } else {
                    mTitleView.setText(mTitle);
                }
            }
        }
		//decor是否定义了背景
        if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) {
            mDecor.setBackgroundFallback(mBackgroundFallbackResource);
        }

        //处理转场动画相关的内容
        if (hasFeature(FEATURE_ACTIVITY_TRANSITIONS)) {
            ...
        }
    }
}
  • installDecor() 函数的剩余部分,代码量很多,但其实归纳起来只是完成了如下几件事:
  1. 通过 generateLayout() 函数往 DecorView 上添加布局,并且返回了View保存到了 mContentParent
  2. 完成 DecorView 的布局添加后,判断添加上的布局中是否存在 R.id.decor_content_parent 元素,有的话对 R.id.decor_content_parent 元素进行一下布局,如果没有那么再看看是否需要设置title
  3. 设置一下背景,以及处理一下转场动画相关的内容

而在这其中,究竟是如何进行的DecorView的布局添加,又往上添加了怎么样的布局,返回的作为 mContentParentView 究竟是什么显然是最重要的,那么接下来就来看一下 generateLayout() 函数

4.2.2 mContentParent 的创建

  • 接下来我们来看一下 mContentParent 这个View 究竟是什么?又是怎么来的?
4.2.2.1 PhoneWindow.generateLayout()

frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java

//生成布局,向decorView中添加view,函数返回的是匹配的系统布局中id为content的view对象
protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.

    //从当前Activity的Resource中获取相关数据
    TypedArray a = getWindowStyle();

    //当前窗口是否是悬浮窗,对应的就是 android:windowIsFloating
    mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
    //确定当前的窗口是否需要改变FLAG_LAYOUT_IN_SCREEN和FLAG_LAYOUT_INSET_DECOR
    int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
        & (~getForcedWindowFlags());
    //如果当前的窗口是悬浮窗,命中if
    if (mIsFloating) {
        //设置窗口长宽由内容决定
        setLayout(WRAP_CONTENT, WRAP_CONTENT);
        //此时只关注flagsToUpdate
        setFlags(0, flagsToUpdate);
        //如果当前窗口不是悬浮窗,命中else,考虑设置FLAG_LAYOUT_IN_SCREEN和FLAG_LAYOUT_INSET_DECOR
    } else {
        setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
    }

    ...

    // Inflate the window decor.

    //在这之上都是在获取当前窗口的属性,如flag,feature等

    int layoutResource;
    //拿到当前窗口的features,当前拿到的localFeature,如果当前的窗口是子窗口,那么当前的localFeatures是去除了父窗口拥有的feature的
    int features = getLocalFeatures();
    //根据设置的feature匹配相应的系统布局文件,即layoutResource
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        layoutResource = R.layout.screen_swipe_dismiss;
        setCloseOnSwipeEnabled(true);
    }...
    //经过上面的匹配流程,我们就拿到当前DecorView feature所对应需要加载的系统布局,也就是 layoutResource 有了值

    //开始处理 DecorView,置位 DecorView.mChanging 标志
    mDecor.startChanging();
    //开始给decorView加载布局,在这其中的layoutResource是在上面根据feature匹配上的系统布局文件
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

    //找到phoneWindow中id为content的View,后续客制化的布局都是显示在该content的view上的
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    //如果从当前的DecorView中找不到content的View,直接抛出异常结束
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    //是否需要进度条
    if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
        ProgressBar progress = getCircularProgressBar(false);
        if (progress != null) {
            progress.setIndeterminate(true);
        }
    }

    //是否需要注册左侧轻扫关闭标志,如果有,则将contentView注册到swipe回调
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        registerSwipeCallbacks(contentParent);
    }

    //如果当前窗口不存在parentWindow,命中if
    if (getContainer() == null) {
        //设置背景
        final Drawable background;
        if (mBackgroundResource != 0) {
            background = getContext().getDrawable(mBackgroundResource);
        } else {
            background = mBackgroundDrawable;
        }
        mDecor.setWindowBackground(background);

        //设置前景
        final Drawable frame;
        if (mFrameResource != 0) {
            frame = getContext().getDrawable(mFrameResource);
        } else {
            frame = null;
        }
        mDecor.setWindowFrame(frame);

        //设置高程
        mDecor.setElevation(mElevation);
        //是否进行显示裁剪,按照大小
        mDecor.setClipToOutline(mClipToOutline);

        //设置title 和 titleColor
        if (mTitle != null) {
            setTitle(mTitle);
        }

        if (mTitleColor == 0) {
            mTitleColor = mTextColor;
        }
        setTitleColor(mTitleColor);
    }

    //结束changing,做了一下大小配置,以及不透明度设置
    mDecor.finishChanging();

    //返回contentView
    return contentParent;
}
  • 该函数代码量还是很大的,但是其实可以按照完成的工作分成四个部分:

    1. 根据当前ActivityStyledAttributes 设置当前Activity对应的Windowfeatureflag
    2. 再根据第一步中获取的当前窗口所需的 feature 匹配需要加载的系统布局 XML 文件
    3. 再将第二步中匹配到的系统布局 XML 加载到 DecorView 上,借助函数 DecorView.onResourcesLoaded() 函数完成
    4. 最后,在完成 DecorView 上的系统布局加载后,找到 idcom.android.internal.R.id.contentView 将其返回,而这个返回的View最后就会保存到 PhoneWindow.mContentParent 变量上

    而在这四个部分中,第三步布局的添加相对比较复杂,我们来展开看一下如何完成的布局添加:

4.2.2.2 DecorView.onResourcesLoaded()

frameworks\base\core\java\com\android\internal\policy\DecorView.java

//在这其中是在添加DecorCaptionView和layoutResourceView,此处的layoutResource是系统内置的layoutId
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
    //如果mbackdropframeRenderer不为空,即当前的窗口是进行过窗口大小调整的,那么需要在此处进行
    //对应大小窗口的加载,这种情况针对的应该是在父窗口中启动的场景
    if (mBackdropFrameRenderer != null) {
        loadBackgroundDrawablesIfNeeded();
        mBackdropFrameRenderer.onResourcesLoaded(
            this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
            mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
            getCurrentColor(mNavigationColorViewState));
    }

    //创建decorCaptionView,只有当窗口为自由窗口的时候才会不为空
    mDecorCaptionView = createDecorCaptionView(inflater);
    //根据传入的layoutResource创建出view
    final View root = inflater.inflate(layoutResource, null);
    //如果当前的窗口存在decorCaptionView,命中if
    //层级就是:decorView->DecorCaptionView->layoutResourceView
    if (mDecorCaptionView != null) {
        //如果decorCaptionView不存在父级,命中if,需要先将mDecorCaptionView添加到decorView中
        if (mDecorCaptionView.getParent() == null) {
            addView(mDecorCaptionView,
                    new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        //然后再将有传入的layoutResource构建的view作为childView添加到mDecorCaptionView中
        mDecorCaptionView.addView(root,
                                  new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        //而如果不存在DecorCaptionView,那么就将layoutResourceView添加到DecorView上,
        //层级为:decorView->layoutResourceView
    } else {

        // Put it below the color views.
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    //将layoutResourceView赋值到mContentRoot变量上,之后再添加view都是在layoutResourceView上进行添加
    mContentRoot = (ViewGroup) root;
    //初始化elevation,也就是高程Z轴
    initializeElevation();
}
  • 在该函数中,主要完成了如下几件事:

    1. 根据当前窗口是否是悬浮窗口,决定是否创建出 decorCaptionView ,所谓的 decorCaptionView 其实就是带 关闭 和 最大化 两个button 的布局
    2. 借助 LayoutInflater.inflate() 函数根据传入的 系统内置的 布局文件ID layoutResource 构建出 View 对象,并将其保存到 DecorView.mContentRoot 变量上
    3. 根据是否存在 mDecorCaptionView ,决定是将 第二步创建出来的 rootView 添加到 mDecorCaptionView 上还是将其添加到 DecorView
    4. 初始化 DecorView 的高程

    在这四步中,RootView 的构建以及添加,我们放到后面去梳理,在当前的步骤中我们重点看一下第一步和第四步的执行流程:

4.2.2.3 DecorView.createDecorCaptionView()

frameworks\base\core\java\com\android\internal\policy\DecorView.java

//返回获取的或者新建的decorCaptionView; decorCaptionView其实就是 最大化和关闭两个button,应用在自由窗口上的
private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) {
    DecorCaptionView decorCaptionView = null;
    //从后到前遍历当前window的所有view,寻找当前window中是否已存在 decorCaptionView
    for (int i = getChildCount() - 1; i >= 0 && decorCaptionView == null; i--) {
        View view = getChildAt(i);
        //如果找到了DecorCaptionView,命中if,取出该View,并将其从DecorView的childView列表中删除
        if (view instanceof DecorCaptionView) {
            //拿到该DecorCaptionView
            decorCaptionView = (DecorCaptionView) view;
            //将其从childView列表中删除
            removeViewAt(i);
        }
    }
    //拿到DecorView归属的Window的layoutParams
    final WindowManager.LayoutParams attrs = mWindow.getAttributes();
    //判断当前窗口是否为应用程序窗口
    final boolean isApplication = attrs.type == TYPE_BASE_APPLICATION ||
        attrs.type == TYPE_APPLICATION || attrs.type == TYPE_DRAWN_APPLICATION;
    //拿到当前窗口的配置
    final WindowConfiguration winConfig = getResources().getConfiguration().windowConfiguration;
    //只有同时满足以下条件,窗口才能够存在标题view,否则不能存在decorCaptionView
    //1. 当前窗口不为悬浮窗口
    //2. 当前窗口为应用程序窗口
    //3. 窗口配置中存在windowDecorCaption(其实判断的窗口模式是否为FREEFORM)
    if (!mWindow.isFloating() && isApplication && winConfig.hasWindowDecorCaption()) {
        //如果当前window中不存在decorCaptionView,此时会命中if
        //构建出DecorCaptionView对象,解析的是系统内的decor_caption.xml布局文件
        if (decorCaptionView == null) {
            decorCaptionView = inflateDecorCaptionView(inflater);
        }
        //建立从属,将该新建的decorCaptionView加到当前的window上来
        decorCaptionView.setPhoneWindow(mWindow, true /*showDecor*/);
        //如果不满足上面的条件,那么当前的Window就不需要 DecorCaptionView,直接将 decorCaptionView置空,后面返回的就是null
    } else {
        decorCaptionView = null;
    }

    //使能decorCaptionView,也就是绘制decorCaptionView,不一定是在此时进行绘制的
    enableCaption(decorCaptionView != null);
    //返回获取或者新建的decorCaptionView
    return decorCaptionView;
}
  • 在该函数中,逻辑也比较清晰,此处我们就稍微总结一下:

    1. 首先从当前DecorViewchildView 中去查找,是否已经存在 decorCaptionView 对象,如果找到就拿出来,然后再将其先从 DecorViewchildView 列表中删掉
    2. 再根据当前窗口的配置,来决定是否需要 decorCaptionView ,如果需要 decorCaptionView但在第一步中没有找到 已存在的decorCaptionView,那么就会调用 DecorView.inflateDecorCaptionView() 函数进行创建
    3. 尝试去绘制 decorCaptionView,返回获取到的或者是新建的 decorCaptionView 对象

    稍微展开看一下 decorCaptionView 的创建:

//根据decor_caption.xml构建出DecorCaptionView对象
private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) {
    final Context context = getContext();
    // We make a copy of the inflater, so it has the right context associated with it.
    //拿到PhoneLayoutInflater对象
    inflater = inflater.from(context);
    //借助PhoneLayoutInflater对象构建出布局为decor_caption的decorCaptionView对象
    final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption,
                                                                      null);
    //设置阴影颜色,light或者dark
    setDecorCaptionShade(context, view);
    //返回构建出来的decorCaptionView对象
    return view;
}
  • 同样是借助 LayoutInflater.inflate() 函数根据 R.layout.decor_caption 布局文件创建出了 DecorCaptionView
4.2.2.4 DecorView.initializeElevation()

frameworks\base\core\java\com\android\internal\policy\DecorView.java

//初始化高度,也就是Z轴
private void initializeElevation() {
    //初始化之后,onLayout()之前不再允许更新高度,故将mAllowUpdateElevation置为false
    mAllowUpdateElevation = false;
    //更新高度
    updateElevation();
}

private void updateElevation() {
    float elevation = 0;
    //记录当前高程是否取决于stack的初始值
    final boolean wasAdjustedForStack = mElevationAdjustedForStack;
    //获取窗口模式
    final int windowingMode =
        getResources().getConfiguration().windowConfiguration.getWindowingMode();
    //如果当前是自由窗口且没有处在调整大小的动作中,命中if
    //获取高程,并且设置标识
    if ((windowingMode == WINDOWING_MODE_FREEFORM) && !isResizing()) {
        //根据当前view所处的window是否具有焦点,选择高程是20还是5
        elevation = hasWindowFocus() ?
            DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP : DECOR_SHADOW_UNFOCUSED_HEIGHT_IN_DIP;
        //如果当前处于不可更新elevation状态,命中if,设置elevation为20
        if (!mAllowUpdateElevation) {
            elevation = DECOR_SHADOW_FOCUSED_HEIGHT_IN_DIP;
        }
        // Convert the DP elevation into physical pixels.
        //数值换算,将单位转换为物理像素
        elevation = dipToPx(elevation);
        //窗口模式为自由窗口时,高程需要根据stack来调整,将相应标志位置为true
        mElevationAdjustedForStack = true;
        //如果当前的窗口模式为pinned,即始终可见的,那么elevation就是5,且也需要根据stack进行调整
    } else if (windowingMode == WINDOWING_MODE_PINNED) {
        elevation = dipToPx(PINNED_WINDOWING_MODE_ELEVATION_IN_DIP);
        mElevationAdjustedForStack = true;
        //如果为其他情况,那么就不需要根据stack来进行调整
    } else {
        mElevationAdjustedForStack = false;
    }

    //只有在高程有更新且需要根据stack调整高程的情况下,才将上面计算出来的高程进行设置
    if ((wasAdjustedForStack || mElevationAdjustedForStack)
        && getElevation() != elevation) {
        mWindow.setElevation(elevation);
    }
}
  • 至此,经过 installDecor() 函数的执行,PhoneWindow 中已经完成了 DecorView 的创建和添加,并且我们得到了 mContentParent 那么接下来就是将 传入的自定义的 layoutResID 添加到 mContentParent 上的过程了,我们继续进行分析

4.2.3 根据布局文件创建 View 对象

  • 回到 setContentView 章节,我们看到其实还会判断当前的 PhoneWindow 是否需要 FEATURE_CONTENT_TRANSITIONS ,如果有的话,是会构建 Scene 对象的,但是其实深入其中查阅代码,我们其实可以看到,它最终的流程也是走到 LayoutInflater.inflate() 进行布局的添加,这一块比较简单,就不做展开叙述,所以我们直接看 LayoutInflater.inflate() 函数的流程。
  • 而在深入分析之前,我们先大致过一下 mLayoutInflater 的创建:
4.2.3.1 mLayoutInflater 的创建
  • 查阅PhoneWindow我们可以看到,mLayoutInflater 是在单个参数的构造函数中进行创建的:

frameworks\base\core\java\com\android\internal\policy\PhoneWindow.java

public PhoneWindow(Context context) {
    //将传入的context存到了PhoneWindow类中的 mContext 成员变量中,当前流程下context就是Activity
    super(context);
    //获取到了layout_inflater_service 对象,保存在 mLayoutInflater 变量中,也就是 LayoutInflater 对象
    mLayoutInflater = LayoutInflater.from(context);
}
  • 传入的 context 其实就是 Activity,如果这一块还不清楚,可以返回去看一下 PhoneWindow的构建 章节;我们继续向下跟踪:

frameworks\base\core\java\android\view\LayoutInflater.java

public static LayoutInflater from(Context context) {
    //获取 layout_inflater_service 对象,也就是PhoneLayoutInflater对象
    LayoutInflater LayoutInflater =
        (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    //如果没有拿到,那么直接抛出异常
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    //返回拿到的 LayoutInflater 对象
    return LayoutInflater;
}
  • 很眼熟吧,最终是通过 getSystemService() 函数去进行获取的,此处我们也不做展开了,如果有不清楚的,可以返回去看 getSystemService()解析 章节,当前我们最后就是调用到了:

frameworks\base\core\java\android\app\SystemServiceRegistry.java

@Override
public LayoutInflater createService(ContextImpl ctx) {
    //构建出了PhoneLayoutInflater对象,当前流程中ctx为Activity对象,通过getOuterContext()拿到的是Activity对象
    return new PhoneLayoutInflater(ctx.getOuterContext());
}});
  • 那么至此,我们就知道了,mLayoutInflater 其实就是 PhoneLayoutInflater 对象,且其内部的 mContext 对象保存的就是对应的 Activity 对象
4.2.3.2 创建View
  • 我们跳过一些简单的重载调用流程,直接看有内容的函数:

frameworks\base\core\java\android\view\LayoutInflater.java

//当前流程中,resource是客制化布局ID,root就是PhoneWindow.mContentParent对象,且因为mContentParent不为空,所以attachToRoot为true
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    //拿到Resources对象
    final Resources res = getContext().getResources();

    //借助Resources对象,将传入的resource xml解析成 XmlResourceParser对象
    final XmlResourceParser parser = res.getLayout(resource);
    try {
        //调用重载函数
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
}
  • 该函数比较简单,将传入的 layoutResId 转换成 XmlResourceParser 对象后,调用另一个重载函数进行进一步的操作
//当前流程中,parser是根据传入的客制化布局Id转换来的 XmlPullParser, root就是PhoneWindow.mContentParent, attachToRoot为true
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    synchronized (mConstructorArgs) {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

        //拿到mContext,当前流程中就是Activity对象
        final Context inflaterContext = mContext;
        //将传入的parser转换成 AttributeSet 对象
        final AttributeSet attrs = Xml.asAttributeSet(parser);
        //先把原来存着的mConstructorArgs[0]记录下来,后面操作完成后,需要还原成原来的值
        Context lastContext = (Context) mConstructorArgs[0];
        //将当前的context设置到mConstructorArgs[0]中,此时也就是把对应的Activity设置到了mConstructorArgs[0]中
        mConstructorArgs[0] = inflaterContext;
        //再把传入的 mContentParent 设置到 result 变量上
        View result = root;

        try {
            // Look for the root node.
            int type;
            //开始找xml中的元素,直到找到 START_TAG 或者 END_DOCUMENT 标签就跳出循环,即找到有用信息了
            while ((type = parser.next()) != XmlPullParser.START_TAG &&
                   type != XmlPullParser.END_DOCUMENT) {
                // Empty
            }

            //如果找到的第一个标签不是START_TAG,说明格式有问题,直接抛出异常
            if (type != XmlPullParser.START_TAG) {
                throw new InflateException(parser.getPositionDescription()
                                           + ": No start tag found!");
            }

            //拿到name
            final String name = parser.getName();

            //如果当前的name是merge,命中if
            if (TAG_MERGE.equals(name)) {
                //在merge时,传入的root不能为空,否则直接抛出异常
                if (root == null || !attachToRoot) {
                    throw new InflateException("<merge /> can be used only with a valid "
                                               + "ViewGroup root and attachToRoot=true");
                }

                //调用 rInflate() 函数,在这其中也就是去构造View
                rInflate(parser, root, inflaterContext, attrs, false);
            //如果name不是 merge,命中else
            } else {
                // Temp is the root view that was found in the xml
                //根据传入的attrs创建出对应的View
                final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                ViewGroup.LayoutParams params = null;

                //如果传入的 root不为空,命中if,当前流程下root就是
                //PhoneWindow.mContentParent,会命中if
                if (root != null) {
                    //根据传入的attrs创建出params对象,其实就是拿到了attrs里面的宽高,
                    //保存到了params对象中
                    params = root.generateLayoutParams(attrs);
                    //如果attachToRoot为false,命中if,将构建出来的params设置到新构建出来的View上
                    //在当前流程中,attachToRoot为true,不会命中if
                    if (!attachToRoot) {
                        temp.setLayoutParams(params);
                    }
                }

                //开始根据attrs创建出所有的View,并且添加到 temp 上
                rInflateChildren(parser, temp, attrs, true);

                //如果传入的root不为空,且attachToRoot为true,代表需要将当前创建出来的View,作为childView添加到root上,命中if
                //当前流程下,会命中if
                if (root != null && attachToRoot) {
                    //将创建出来的temp View添加到 root 上
                    root.addView(temp, params);
                }

                //如果传入的root为空,或者attachToRoot为false,那么就将创建出来的 temp View赋值给 result,在这种情况下,返回的就是创建出来的 temp View
                //在当前的流程下,不会命中该if
                if (root == null || !attachToRoot) {
                    result = temp;
                }
            }

        }...

        //最后返回result,result的值有两种情况:
        //1. result就是传入的root View
        //2. result是创建出来的temp View
        //当前流程下,返回的是root View
        return result;
    }
}
  • 该函数完成的内容还是比较多的,我们来总结一下:

    1. 通过传入的布局文件 parser 借助 createViewFromTag() 函数完成了 parser布局中的根元素 tempView 创建
    2. 根据传入的 root 参数决定是否构建出 parsertempViewViewGroup.LayoutParams ,再根据 attachToRoot 参数决定是否将 ViewGroup.LayoutParams 设置到tempView
    3. 借助 rInflateChildren() 函数完成 parser 中所有的View的创建和添加
    4. 根据传入的 rootattachToRoot 参数决定是否将创建出来的 tempView 添加到 root
    5. 返回 resultresult 有可能是 root 或者 temp,视传入的 rootattachToRoot 参数决定

    总结出来的5个步骤,其中第1、3、4步还涉及比较多的细节,接下来我们逐条来进行简单的解析:

1. tempView 的创建
  • 根据前述的分析,我们知道View的创建是借助 createViewFromTag() 函数实现的,那么接下来就简单解析一下该函数,这里我们跳过重载函数的调用,直接看最后的真正实现:

frameworks\base\core\java\android\view\LayoutInflater.java

//当前流程中,parent就是PhoneWindow.mContentParent,context就是LayoutInflater.mContext,ignoreThemeAttr为false
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                       boolean ignoreThemeAttr) {
    //如果当前传入的name是View,那么需要重新取值 name
    if (name.equals("view")) {
        name = attrs.getAttributeValue(null, "class");
    }

    // Apply a theme wrapper, if allowed and one is specified.
    //如果不忽略ThemeAttr,命中if,尝试构建 ContextThemeWrapper 对象
    if (!ignoreThemeAttr) {
        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
        final int themeResId = ta.getResourceId(0, 0);
        if (themeResId != 0) {
            context = new ContextThemeWrapper(context, themeResId);
        }
        ta.recycle();
    }

    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

    try {
        View view;
        //如果 mFactory2 不为空,那么就借助 mFactory2 来创建出View
        if (mFactory2 != null) {
            view = mFactory2.onCreateView(parent, name, context, attrs);
            //如果mFactory2为空,但是mFactory不为空,那么就用mFactory来创建View
        } else if (mFactory != null) {
            view = mFactory.onCreateView(name, context, attrs);
            //如果mFactory2和mFactory都为空,那么先设置view为空,到下面去创建
        } else {
            view = null;
        }

        //如果mFactory2和mFactory都为空,但是 mPrivateFactory 不为空,那么借助 mPrivateFactory 来创建View
        if (view == null && mPrivateFactory != null) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        //如果mFactory2、mFactory、mPrivateFactory三个都为空,那么此时借助 LayoutInflater 来创建View
        if (view == null) {
            //再将context设置到 mConstructorArgs[0]中,并将原来的值保存起来,后面要还原的
            final Object lastContext = mConstructorArgs[0];
            mConstructorArgs[0] = context;
            try {
                //如果name中没有 . 代表是系统自带的View(类似Button),命中if
                //在这其中会做一次拼接,把View的路径带上,即如果像Button,最后会拼成 android.widget.Button
                if (-1 == name.indexOf('.')) {
                    //其实在onCreateView()函数里只是做了路径拼接,最终也还是调用到 createView()函数
                    view = onCreateView(parent, name, attrs);
                    //否则,就是自定义的View
                } else {
                    view = createView(name, null, attrs);
                }
            } finally {
                //还原 mConstructorArgs[0]
                mConstructorArgs[0] = lastContext;
            }
        }

        //返回创建出来的View
        return view;
    }...
}
  • 该函数中整体的逻辑还是比较简单的,但是有一点需要特别注意,我们可以看到 View 的创建在通过 LayoutInflater 进行创建之前,显示尝试通过 mFactory2mFactorymPrivateFactory 进行创建的,只有在这三个都没有定义或者都没有创建出 View 的前提下,才会走到 LayoutInflater 去进行创建,这也就意味着我们可以通过实现mFactory2mFactorymPrivateFactory 这三者其中之一,进行View创建的客制化,也就是拦截系统的View创建流程! 当前流程下,这三者都是没定义的,那么就直接走到 LayoutInflater 进行View的创建
static final Class<?>[] mConstructorSignature = new Class[] {
    Context.class, AttributeSet.class};

public final View createView(String name, String prefix, AttributeSet attrs)
    throws ClassNotFoundException, InflateException {
    //先尝试从缓存中去拿对应name的构造函数 Constructor
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    //如果能够拿到缓存的 Constructor,但是判断是不合法的,那么命中if,将缓存中的清除掉
    if (constructor != null && !verifyClassLoader(constructor)) {
        constructor = null;
        sConstructorMap.remove(name);
    }
    Class<? extends View> clazz = null;

    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

        //如果上面没拿到 constructor,命中if,在这其中就是去尝试拿constructor
        if (constructor == null) {
            // Class not found in the cache, see if it's real, and try to add it
            //反射拿到对应name的Class
            clazz = mContext.getClassLoader().loadClass(
                prefix != null ? (prefix + name) : name).asSubclass(View.class);

            //如果拿到了对应name的class,但是过滤器Filter不为空,那么需要判断该class是否需要过滤,如果需要过滤,那么就直接抛出异常
            if (mFilter != null && clazz != null) {
                //用过滤器 mFilter 判断该class是否允许获取的
                boolean allowed = mFilter.onLoadClass(clazz);
                //如果不允许获取,那么直接抛出异常
                if (!allowed) {
                    //抛出异常
                    failNotAllowed(name, prefix, attrs);
                }
            }
            //拿到对应class的两个参数的构造函数
            constructor = clazz.getConstructor(mConstructorSignature);
            constructor.setAccessible(true);
            //将该获取到的constructor以name为key保存到缓存 sConstructorMap中
            sConstructorMap.put(name, constructor);
            //如果从缓存中就拿到了构造函数constructor,命中else
        } else {
            // If we have a filter, apply it to cached constructor
            //如果过滤器 mFilter 不为空,还是通过过滤器去判断,是否可以获取
            if (mFilter != null) {
                ...
            }
        }

        Object lastContext = mConstructorArgs[0];
        if (mConstructorArgs[0] == null) {
            // Fill in the context if not already within inflation.
            mConstructorArgs[0] = mContext;
        }
        //将mConstructorArgs赋值给args,那么args其实就是 Object[2]
        Object[] args = mConstructorArgs;
        //将传入的View的attrs保存到 args[1] 中
        args[1] = attrs;

        //调用构造函数生成 View,调用的是两个参数的构造函数,并且传入了context和attrs
        final View view = constructor.newInstance(args);
        //如果是ViewStub,做一些特殊处理
        if (view instanceof ViewStub) {
            // Use the same context when inflating ViewStub later.
            final ViewStub viewStub = (ViewStub) view;
            viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
        }
        //复原mConstructorArgs[0]的值
        mConstructorArgs[0] = lastContext;
        //返回创建出来的View
        return view;

    }...
}
  • 该函数也比较清晰,就是通过反射去获取构造函数,此处获取的是两个参数的构造函数,基于此我们也可以知道,如果是自定义的View,两个参数的构造函数必须实现,否则系统就无法通过反射去进行View的创建,最后通过构造函数进行View的创建
2. 递归创建parser中的所有View
  • 查阅 rInflateChildren() 函数,其就是直接去调用了 rInflate() 函数,我们直接来看一下该函数

frameworks\base\core\java\android\view\LayoutInflater.java

//递归,完成parser中所有的View的创建,并完成addView()
void rInflate(XmlPullParser parser, View parent, Context context,
              AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

    final int depth = parser.getDepth();
    int type;
    boolean pendingRequestFocus = false;

    //遍历parser,完成所有的View创建,并添加到对应的ViewGroup上
    while (((type = parser.next()) != XmlPullParser.END_TAG ||
            parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

        if (type != XmlPullParser.START_TAG) {
            continue;
        }

        final String name = parser.getName();

        if (TAG_REQUEST_FOCUS.equals(name)) {
            pendingRequestFocus = true;
            consumeChildElements(parser);
        } else if (TAG_TAG.equals(name)) {
            parseViewTag(parser, parent, attrs);
        } else if (TAG_INCLUDE.equals(name)) {
            if (parser.getDepth() == 0) {
                throw new InflateException("<include /> cannot be the root element");
            }
            parseInclude(parser, context, parent, attrs);
        } else if (TAG_MERGE.equals(name)) {
            throw new InflateException("<merge /> must be the root element");
        } else {
            //创建出View
            final View view = createViewFromTag(parent, name, context, attrs);
            final ViewGroup viewGroup = (ViewGroup) parent;
            final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
            //如果创建出来的View还有子View,那么递归,先将属于该View的childView都创建出来,并完成添加
            rInflateChildren(parser, view, attrs, true);
            //将创建出来的View,添加到传入的parent上
            viewGroup.addView(view, params);
        }
    }
	...
}
  • 可以看到该函数就是在遍历 parser 中的内容,进行递归创建 View ,并将创建出来的View添加到对应的 parentView 上,至于如何进行的 addView() 我们在下面进行分析
3. childView的添加
  • 我们同样跳过多个重载函数,只看最终的实现

frameworks\base\core\java\android\view\ViewGroup.java

public void addView(View child, int index, LayoutParams params) {
    //传入的child不能为空
    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }

    //先触发一次layout
    requestLayout();
    //将当前view的drawCache置无效,也就是清理掉
    invalidate(true);
    //进行childView的添加
    addViewInner(child, index, params, false);
}
  • childView 真正的添加流程是在 addViewInner() 函数中进行的,需要明确一点,当前的 addView()parentView 进行调用的,也就是 this 就是 parentView
private void addViewInner(View child, int index, LayoutParams params,
                          boolean preventRequestLayout) {

    //如果有动画,那么此时去取消disappering动画显示
    if (mTransition != null) {
        mTransition.cancel(LayoutTransition.DISAPPEARING);
    }

    //一个view只能有一个parent,当view已有归属时,不能二次添加
    if (child.getParent() != null) {
        throw new IllegalStateException("The specified child already has a parent. " +
                                        "You must call removeView() on the child's parent first.");
    }

    //如果存在过渡动画,那么将child和当前view添加到过渡动画中
    if (mTransition != null) {
        mTransition.addChild(this, child);
    }

    //传入的params如果为null,命中if,构建出一个params
    if (!checkLayoutParams(params)) {
        params = generateLayoutParams(params);
    }

    //是否需要阻止调用布局,如果需要,命中if,直接将params保存到child中;
    //否则命中else,保存到child后,还需要触发一次layout
    //当前的流程下,命中else
    if (preventRequestLayout) {
        child.mLayoutParams = params;
    } else {
        child.setLayoutParams(params);
    }

    //如果传入的index是-1,代表child需要放到父viewGroup的最后面
    if (index < 0) {
        index = mChildrenCount;
    }

    //将传入的child按照index下标加到mChildren中
    addInArray(child, index);

    //将当前的view保存到child.mParent中去,至此之后,操作的child View有了mParent值,为当前的view
    if (preventRequestLayout) {
        child.assignParent(this);
    } else {
        child.mParent = this;
    }
    ...

    //回调通知有child View被添加了,通过回调函数可以实现View Add的监听
    dispatchViewAdded(child);

    ...
}
  • 我们先忽略一些和当前流程关联不大的代码,只关注 addView 相关的流程,可以看到主要就是如下步骤:
    1. 将传入的 LayoutParams 设置到了 传入的 childView
    2. childView 按照 index 添加到了 parentView.mChildren 数组的特定位置上,这一步之后,parentView就持有了 childView
    3. parentView 保存到了 childView.mParent 变量上,这一步之后,childView就持有了 parentView

4.3 总结:

  • 至此我们就完成了布局的加载流程,在这其中我们主要完成了:

    1. 创建出 DecorView ,然后将 DecorViewPhoneWindow 相互绑定了起来
    2. 拿到第一步中创建出来的 DecorView ,为 DecorView 添加了系统布局,然后拿到其中 id = R.id.contentViewGroup,将其保存到 PhoneWindow.mContentParent 变量上
    3. 根据自定义的布局创建出 View 对象,并将该 View 作为 childView 添加到 PhoneWindow.mContentParent

    经过上面的流程,属于ActivityPhoneWindow就完成了所有的布局加载,完成了正确显示的前提条件,而该PhoneWindow 的最终显示,是在 ActivityThread.handleResumeActivity() 函数开始触发的,那么接下来我们就来看一下 Window 的显示流程

5. Activity的Resume

  • 接下来我们就来跟着流程梳理一下,phoneWindow 是如何显示出来的。顺着Activity的启动流程,走到了 ActivityThread.handleResumeActivity() 函数,如果不了解如何走入的该函数,可以回顾 解析Activity启动-生命周期角度 篇章

frameworks\base\core\java\android\app\ActivityThread.java

@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
                                 String reason) {
    ...
    //如果当前ActivityClientRecord中的window为空,且其所代表的activity不为空且是可见的,那么命中if
    //先置条件必须是当前的window为空的情况下,也就是还未进行window的add的前提下,会走入该if中,去进行window的添加
    if (r.window == null && !a.mFinished && willBeVisible) {
        //将之前在CreateActivity时构建出来的PhoneWindow保存到ActivityClientRecord.window变量中
        r.window = r.activity.getWindow();
        //拿到同样是在CreateActivity时构建出来的DecorView
        View decor = r.window.getDecorView();
        //先将DecorView设置为不可见状态
        decor.setVisibility(View.INVISIBLE);
        //拿到在Activity中保存的WindowManagerImpl对象
        ViewManager wm = a.getWindowManager();
        //拿到phoneWindow的参数
        WindowManager.LayoutParams l = r.window.getAttributes();
        //将CreateActivity时构建出来的DecorView保存到对应的Activity中
        a.mDecor = decor;
        //当前的窗口类型是 TYPE_BASE_APPLICATION
        l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
        //保存键盘模式
        l.softInputMode |= forwardBit;
        //如果mPreserveWindow为true,代表有preserve窗口,命中if
        //如果存在遗留窗口,在CreateActivity创建窗口时其实就已经完成复用了
        //当前流程下,不会命中if
        if (r.mPreserveWindow) {
            //存在preserveWindow的情况下,对应的window是已经完成add了,后续无需再次add,所以将mWindowAdded置true,代表已经完成添加
            a.mWindowAdded = true;
            //将mPreserveWindow复位
            r.mPreserveWindow = false;
            //如果是复用窗口窗口的情况,那么我们需要通知ViewRootImpl回调将会更改
            ViewRootImpl impl = decor.getViewRootImpl();
            if (impl != null) {
                impl.notifyChildRebuilt();
            }
        }
        //如果当前的activity可见,命中if
        //进行窗口添加
        if (a.mVisibleFromClient) {
            //如果mWindowAdded为false,代表窗口还未完成添加,那么命中if,进行窗口添加
            if (!a.mWindowAdded) {
                //从此后窗口就完成了添加,那么将mWindowAdded置true
                a.mWindowAdded = true;
                //调用windowManagerImpl进行窗口的添加
                wm.addView(decor, l);
            //如果mWindowAdded已经为true,代表已经完成了窗口的添加,那么只需要更新窗口参数即可
            } else {
                //进行窗口参数的更新
                a.onWindowAttributesChanged(l);
            }
        }

    } else if (!willBeVisible) {
        if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
        r.hideForNow = true;
    }
	...
}
  • 我们先忽略和Window 流程无关的代码,归纳一下完成了如下的事项:

    1. 构建出 WindowManager.LayoutParams 对象,也就是当前 PhoneWindow 的窗口参数,需要特别注意的,在默认的当前流程中,LayoutParams.type 是固定的,是为 WindowManager.LayoutParams.TYPE_BASE_APPLICATION,这也就代表着如果让系统进行的DecorView添加,那么window的类型是确定的,就是TYPE_BASE_APPLICATION
    2. 在不存在遗留窗口且Activity可见的前提下,如果Activity还未进行窗口的添加,那么就调用 WindowManagerImpl.addView() 进行窗口的添加

    那么接下来就展开看一下 WindowManagerImpl.addView() 是如何完成的窗口添加

5.1 WMG中的addView

  • 查阅 WindowManagerImpl.addView() 其实并没有做任何工作,直接调用到了 WindowManagerGlobal.addView() 函数,我们直接从该函数看起

frameworks\base\core\java\android\view\WindowManagerGlobal.java

//当前流程中,view是DecorView,parentWindow是PhoneWindow
public void addView(View view, ViewGroup.LayoutParams params,
                    Display display, Window parentWindow) {
    //首先是判断传参的有效性
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    //做一层强转,传入的params其实就是 WindowManager.LayoutParams
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    //如果传入的parentWindow不为空,那么需要针对传入的Window参数做一些处理
    if (parentWindow != null) {
        //对传入的窗口参数做一些调整,可能包含wparams.title和wparams.token以及硬件加速标识等的调整
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    //如果传入的parentWindow为空,那么根据传入的View来决定是否存在硬件加速需求
    } else {
        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;
    View panelParentView = null;

    //加锁
    synchronized (mLock) {
        //构建一个runable任务来监控属性变化
        if (mSystemPropertyUpdater == null) {
            mSystemPropertyUpdater = new Runnable() {
                @Override public void run() {
                    synchronized (mLock) {
                        for (int i = mRoots.size() - 1; i >= 0; --i) {
                            mRoots.get(i).loadSystemProperties();
                        }
                    }
                }
            };
            //添加监听
            SystemProperties.addChangeCallback(mSystemPropertyUpdater);
        }

        //从mViews列表中去查找是否已存在当前传入的View,返回下标
        int index = findViewLocked(view, false);
        //如果找到了view,那么命中if,已经存在的View是不能再次进行添加的
        if (index >= 0) {
            //如果正在销毁的view列表中包含传入的view,那么就调用doDie()销毁它
            if (mDyingViews.contains(view)) {
                mRoots.get(index).doDie();
            //否则直接抛出异常,已经存在的View不能再次添加
            } else {
                throw new IllegalStateException("View " + view
                                                + " has already been added to the window manager.");
            }
        }

        //如果当前传入的窗口类型是子窗口,那么去查找他的父窗口的View
        if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
            wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            final int count = mViews.size();
            for (int i = 0; i < count; i++) {
                //根据RootViewImpl去找当前已保存的父窗口的view对象
                if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                    panelParentView = mViews.get(i);
                }
            }
        }

        //构建ViewRootImpl对象,在构造时,会创建出如Windowsession、AttachInfo等参数
        root = new ViewRootImpl(view.getContext(), display);

        //将更新后的窗口参数设置回传入的View中
        view.setLayoutParams(wparams);

        //将传入的View、wparams,创建的ViewRootImpl存到WMG的对应列表中
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        try {
            //将传入的View和创建出来的ViewRootImpl进行绑定
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}
  • 纵观该函数,主要就是完成了如下几件事:

    1. 根据传入的 parentWindow 参数是否为空,对传入的窗口参数 params 做一些处理;当前流程下,parentWindowPhoneWindow,会调用 PhoneWindow.adjustLayoutParamsForSubWindow()params 做处理
    2. 判断需要添加的 View是否已经完成了添加,如果已经添加过,那么是无法二次添加的
    3. 构建出 ViewRootImpl 对象,回设处理过的窗口参数 params 到传入的 View 中,将传入的View、构建出来的 ViewRootImpl、传入的窗口参数params 分别存到 WindowManagerGlobalmViewsmRootsmParams三个列表中
    4. 最终将传入的 Viewparams 设置到创建出来的 ViewRootImpl

    在这其中,我们展开看一下相关细节

1. 传入窗口参数的微调

  • 针对传入的 params 内容的调整,是借助 adjustLayoutParamsForSubWindow() 函数完成的,大致展开看一下:

frameworks\base\core\java\android\view\Window.java

//根据传入的wp中定义的窗口类型,考虑是否给传入的wp赋值token和title
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
    //拿到传入的wp中的title
    CharSequence curTitle = wp.getTitle();
    //如果传入的wp中保存的窗口类型是subWindow,那么命中if
    //在这其中会考虑是否给wp中设置token和title
    if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
        wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
        //如果传入的wp中没有windowToken,且当前窗口存在Decorview,那么将DecorView的windowToken赋值给wp.token
        if (wp.token == null) {
            //拿到当前Window中的DecorView
            View decor = peekDecorView();
            //如果当前窗口存在DecorView,那么就将DecorView的mWindowToken保存到wp.token参数中
            if (decor != null) {
                wp.token = decor.getWindowToken();
            }
        }
        //如果传入的wp中没有title,那么根据子窗口类型,添加对应的title
        if (curTitle == null || curTitle.length() == 0) {
            final StringBuilder title = new StringBuilder(32);
            if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA) {
                title.append("Media");
            } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY) {
                title.append("MediaOvr");
            } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
                title.append("Panel");
            } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL) {
                title.append("SubPanel");
            } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ABOVE_SUB_PANEL) {
                title.append("AboveSubPanel");
            } else if (wp.type == WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG) {
                title.append("AtchDlg");
            } else {
                title.append(wp.type);
            }
            if (mAppName != null) {
                title.append(":").append(mAppName);
            }
            wp.setTitle(title);
        }
    //而如果传入wp的窗口类型是systemWindow,那么考虑是否将传入wp的title加上Sys字段
    } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
               wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
        //如果传入的wp中没有title,那么加上Sys字段的title
        if (curTitle == null || curTitle.length() == 0) {
            final StringBuilder title = new StringBuilder(32);
            title.append("Sys").append(wp.type);
            if (mAppName != null) {
                title.append(":").append(mAppName);
            }
            wp.setTitle(title);
        }
    //如果传入的wp既不是子窗口也不是系统窗口,那么考虑赋值token和title
    } else {
        //如果传入的wp中没有windowToken,将当前窗口或者当前窗口的父窗口的AppToken赋值给他
        if (wp.token == null) {
            wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
        }
        //没有title的情况下,将当前的appName设置为title
        if ((curTitle == null || curTitle.length() == 0)
            && mAppName != null) {
            wp.setTitle(mAppName);
        }
    }
    //考虑是否将当前窗口的packageName设置给传入wp
    if (wp.packageName == null) {
        wp.packageName = mContext.getPackageName();
    }
    //是否硬件加速的标识
    if (mHardwareAccelerated ||
        (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
        wp.flags |= FLAG_HARDWARE_ACCELERATED;
    }
}
  • 该函数还是比较清晰的,有一点需要说明的就是该函数在当前的流程中是在 PhoneWindow 中执行的

2. ViewRootImpl的构建

  • 接下来看一下 ViewRootImpl 的创建:

frameworks\base\core\java\android\view\ViewRootImpl.java

public ViewRootImpl(Context context, Display display) {
    //当前流程下,mContext存的就是DecorContext对象
    mContext = context;
    //通过WMG去获取Session对象,这样ViewRootImpl就具有了和WMS通讯的基础
    //通过 WMG 去获取的Session是单例
    mWindowSession = WindowManagerGlobal.getWindowSession();
    mDisplay = display;
    mBasePackageName = context.getBasePackageName();
    mThread = Thread.currentThread();
    mLocation = new WindowLeaked(null);
    mLocation.fillInStackTrace();
    mWidth = -1;
    mHeight = -1;
    mDirty = new Rect();
    mTempRect = new Rect();
    mVisRect = new Rect();
    mWinFrame = new Rect();
    //ViewRootImpl中的mWindow是内部类W,保存有当前的ViewRootImpl对象
    mWindow = new W(this);
    mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
    mViewVisibility = View.GONE;
    mTransparentRegion = new Region();
    mPreviousTransparentRegion = new Region();
    //是否是首次创建的标识
    mFirst = true; // true for the first time the view is added
    //是否完成添加的标识
    mAdded = false;
    //构建出AttachInfo对象,在这其中保存有session、ViewRootImpl.W对象等
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                                      context);
    mAccessibilityManager = AccessibilityManager.getInstance(context);
    mAccessibilityManager.addAccessibilityStateChangeListener(
        mAccessibilityInteractionConnectionManager, mHandler);
    mHighContrastTextManager = new HighContrastTextManager();
    mAccessibilityManager.addHighTextContrastStateChangeListener(
        mHighContrastTextManager, mHandler);
    mViewConfiguration = ViewConfiguration.get(context);
    mDensity = context.getResources().getDisplayMetrics().densityDpi;
    mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
    mFallbackEventHandler = new PhoneFallbackEventHandler(context);
    //拿到Choreographer对象
    mChoreographer = Choreographer.getInstance();
    mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);

    if (!sCompatibilityDone) {
        sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;

        sCompatibilityDone = true;
    }

    //加载属性
    loadSystemProperties();
}
  • 纵观 ViewRootImpl 对象的创建,主要是内部元素的构建,而在这其中,有几个变量我们需要特别留意,分别是:

    1. mWindowSession对象,是 ViewRootImplWMS 通讯的桥梁
    2. ViewRootImpl 内部的 mWindow 对象
    3. mAttachInfo 对象
    4. mChoreographer 对象

    此处我们再深入看一下 WindowSession 对象的创建细节:

frameworks\base\core\java\android\view\WindowManagerGlobal.java

//获取IWindowSession对象
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        //如果当前没有sWindowSession,那么就会先进行创建
        if (sWindowSession == null) {
            try {
                //获取imm
                InputMethodManager imm = InputMethodManager.getInstance();
                //获取wms
                IWindowManager windowManager = getWindowManagerService();
                //通过WMS去open一个 Session 对象
                sWindowSession = windowManager.openSession(
                    new IWindowSessionCallback.Stub() {
                        @Override
                        public void onAnimatorScaleChanged(float scale) {
                            ValueAnimator.setDurationScale(scale);
                        }
                    },
                    imm.getClient(), imm.getInputContext());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        //返回 sWindowSession 对象
        return sWindowSession;
    }
}
  • 借助 WindowManagerGlobal 拿到 WindowManagerService 对象,在 WindowManagerService 中完成了 WindowSession 的获取

frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java

@Override
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
                                  IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");
    //构建出 Session 对象返回,可以看到session中保存了WMS对象
    Session session = new Session(this, callback, client, inputContext);
    return session;
}
  • 最终就是在 WMS 中创建出了 Session 对象,并返回;而在 Session 对象中是持有 WMS

3. DecorView和ViewRootImpl的绑定

  • 接下来再看看一下 DecorViewViewRootImpl 的绑定流程,是通过 ViewRootImpl.setView() 函数实现的

frameworks\base\core\java\android\view\ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    //加锁
    synchronized (this) {
        //如果当前ViewRootImpl中还没有记录view,那么就去完成相关绑定的一系列动作
        //而如果当前的ViewRootImpl中已经存在mView的记录,那么当前就不会做任何其他的动作
        if (mView == null) {
            //将传入的View保存到mView中
            mView = view;

            //将传入的display的state状态保存到mAttachInfo中
            mAttachInfo.mDisplayState = mDisplay.getState();
            //将当前的ViewRoot的mDisplayListener和mHandler注册到Displaymanager中,监听屏幕变化
            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

            //记录布局方向
            mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
            mFallbackEventHandler.setView(view);
            //保存一份当前传入的View的布局参数
            mWindowAttributes.copyFrom(attrs);
            //判断是否需要赋值包名,为空就赋值
            if (mWindowAttributes.packageName == null) {
                mWindowAttributes.packageName = mBasePackageName;
            }
            //更新传入的布局参数,此时只是有可能更新了一下packagename
            attrs = mWindowAttributes;
            setTag();
            
            //记录下传入的View的flags
            mClientWindowLayoutFlags = attrs.flags;

            setAccessibilityFocus(null, null);

            //如果当前传入的View是继承自RootViewSurfaceTaker,取出其中的callback记录下来
            if (view instanceof RootViewSurfaceTaker) {
                mSurfaceHolderCallback =
                    ((RootViewSurfaceTaker)view).willYouTakeTheSurface();
                if (mSurfaceHolderCallback != null) {
                    mSurfaceHolder = new TakenSurfaceHolder();
                    mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
                    mSurfaceHolder.addCallback(mSurfaceHolderCallback);
                }
            }


            //如果当前的View没有声明需要手动设置Z轴位置,那么命中if,进行系统自动决定
            if (!attrs.hasManualSurfaceInsets) {
                attrs.setSurfaceInsets(view, false /*manual*/, true /*preservePrevious*/);
            }

            CompatibilityInfo compatibilityInfo =
                mDisplay.getDisplayAdjustments().getCompatibilityInfo();
            mTranslator = compatibilityInfo.getTranslator();

            //如果传入的View不是RootViewSurfaceTaker的子类,那么此时mSurfaceHolder为空,命中if
            //针对硬件加速做处理
            if (mSurfaceHolder == null) {
                enableHardwareAcceleration(attrs);
                final boolean useMTRenderer = MT_RENDERER_AVAILABLE
                    && mAttachInfo.mThreadedRenderer != null;
                if (mUseMTRenderer != useMTRenderer) {
                    endDragResizing();
                    mUseMTRenderer = useMTRenderer;
                }
            }

            boolean restore = false;
            if (mTranslator != null) {
                mSurface.setCompatibilityTranslator(mTranslator);
                restore = true;
                attrs.backup();
                mTranslator.translateWindowLayout(attrs);
            }
            if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);

            if (!compatibilityInfo.supportsScreen()) {
                attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
                mLastInCompatMode = true;
            }

            //记录下键盘模式
            mSoftInputMode = attrs.softInputMode;
            //当前窗口属性有变动
            mWindowAttributesChanged = true;
            //窗口属性变化标识,标识的是变化的内容
            mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
            //将记录在ViewRootImpl中的View保存到mAttachInfo中,作为mRootView,当前流程下就是 DecorView
            mAttachInfo.mRootView = view;
            mAttachInfo.mScalingRequired = mTranslator != null;
            mAttachInfo.mApplicationScale =
                mTranslator == null ? 1.0f : mTranslator.applicationScale;
            //panelParentView不为空代表当前添加的是子窗口,保存下父窗口的token
            if (panelParentView != null) {
                mAttachInfo.mPanelParentWindowToken
                    = panelParentView.getApplicationWindowToken();
            }
            //在这之后,ViewRootImpl就拥有了mView,也就完成了View的添加
            mAdded = true;
            int res; /* = WindowManagerImpl.ADD_OKAY; */

            //进行一次布局
            requestLayout();
            //如果该窗口没有要求不能有Input通道,那么就创建出 InputChannel 
            if ((mWindowAttributes.inputFeatures
                 & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                mInputChannel = new InputChannel();
            }
            //判断是否设置PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY标识
            mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                                         & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
            try {
                //记录下窗口类型
                mOrigWindowType = mWindowAttributes.type;
                //重新计算窗口全局属性
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                //借助windowSession将当前的ViewRootImpl以及新创建的InputChannel等数据保存到Display中去
                //这里最后就调用到了 WMS.addWindow() 函数中
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                                  getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                                                  mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                                  mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
            } catch (RemoteException e) {
                mAdded = false;
                mView = null;
                mAttachInfo.mRootView = null;
                mInputChannel = null;
                mFallbackEventHandler.setView(null);
                unscheduleTraversals();
                setAccessibilityFocus(null, null);
                throw new RuntimeException("Adding window failed", e);
            } finally {
                if (restore) {
                    attrs.restore();
                }
            }

            if (mTranslator != null) {
                mTranslator.translateRectInScreenToAppWindow(mAttachInfo.mContentInsets);
            }
            mPendingOverscanInsets.set(0, 0, 0, 0);
            mPendingContentInsets.set(mAttachInfo.mContentInsets);
            mPendingStableInsets.set(mAttachInfo.mStableInsets);
            mPendingDisplayCutout.set(mAttachInfo.mDisplayCutout);
            mPendingVisibleInsets.set(0, 0, 0, 0);
            mAttachInfo.mAlwaysConsumeNavBar =
                (res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR) != 0;
            mPendingAlwaysConsumeNavBar = mAttachInfo.mAlwaysConsumeNavBar;
            if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
            //如果addToDisplay失败,会命中if,抛出异常
            if (res < WindowManagerGlobal.ADD_OKAY) {
                ...
            }

            if (view instanceof RootViewSurfaceTaker) {
                mInputQueueCallback =
                    ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
            }
            //如果前面构建了 InputChannel,那么此处就会构建出mInputEventReceiver
            if (mInputChannel != null) {
                if (mInputQueueCallback != null) {
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
                mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                                                                   Looper.myLooper());
            }

            //至此,将ViewRootImpl作为ParentView添加到了传入的View中
            //也就是 ViewRootImpl 是 RootView 的parentView,在当前流程下,就是DecorView拥有了parentView,即ViewRootImpl
            view.assignParent(this);
            //是否有添加touchMode标志
            mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
            //是否可见标志
            mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;

            //辅助manager是否开启,如果开启的话,确定是否完成连接
            if (mAccessibilityManager.isEnabled()) {
                mAccessibilityInteractionConnectionManager.ensureConnection();
            }

            //是否让系统自动确认传入的view是否对辅助窗口很重要
            if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
            }

            //获取到title
            CharSequence counterSuffix = attrs.getTitle();
            //构建输入事件拦截器
            mSyntheticInputStage = new SyntheticInputStage();
            InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
            InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                                                                        "aq:native-post-ime:" + counterSuffix);
            InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
            InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                                                    "aq:ime:" + counterSuffix);
            InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
            InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                                                                      "aq:native-pre-ime:" + counterSuffix);

            //将相关的输入事件拦截器保存到ViewRootImpl对应变量上
            mFirstInputStage = nativePreImeStage;
            mFirstPostImeInputStage = earlyPostImeStage;
            mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
        }
    }
}
  • 该函数相对还是比较复杂的,我们总结一下完成的内容:

    1. 存下传入的View,当前流程下存下的就是 DecorView, 并且转存了传入的窗口参数 attrs
    2. ViewRootImpl.mAdded 标志置为 true,代表当前 ViewRootImpl 已添加了关联的 View, 并且调用 requestLayout() 完成一次布局
    3. 借助 WindowSession 将相关数据存到 WindowManagerService 中去,通过函数 WindowSession.addToDisplay() 实现
    4. 将当前的 RootViewImpl 保存到传入的 View.mParent 变量上,在当前流程中,也就是保存到了 DecorView.mParent 变量上
    5. 构建和保存输入事件拦截器

    在这五步中,相对复杂的就是第三步,WindowSession 其实就是 Session 对象,而 Session.addDisplay() 最后就是直接调用到了 WindowManagerService.addWindow() 函数中,也就是将相关数据添加到了 WMS 中,那么接下来就来看一下是如何添加和保存的:

frameworks\base\services\core\java\com\android\server\wm\WindowManagerService.java

//当前流程下,client 就是 ViewRootImpl.W 对象,attrs就是待add的Window的窗口参数
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) {
    int[] appOp = new int[1];
    //根据窗口类型去判断是否具备add权限,如果不具备,那么就直接返回
    int res = mPolicy.checkAddPermission(attrs, appOp);
    if (res != WindowManagerGlobal.ADD_OKAY) {
        return res;
    }

    boolean reportNewConfig = false;
    WindowState parentWindow = null;
    long origId;
    final int callingUid = Binder.getCallingUid();
    final int type = attrs.type;

    //对windowMap加锁
    synchronized(mWindowMap) {
        //如果当前display还未初始化完成,抛出异常
        if (!mDisplayReady) {
            throw new IllegalStateException("Display has not been initialialized");
        }

        //获取或者创建传入的displayID所对应的 DisplayContent 对象
        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;
        }
        //如果当前的session在对应的displayContent上没有许可,或者当前显示的不是session对应的UID(多用户状态吧),直接返回
        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;
        }

        //如果当前mWindowMap中已经包含传入的ViewRootImpl.w 对象,那么直接返回,无需再进行添加
        if (mWindowMap.containsKey(client.asBinder())) {
            Slog.w(TAG_WM, "Window " + client + " is already added");
            return WindowManagerGlobal.ADD_DUPLICATE_ADD;
        }

        //如果当前待添加的窗口是子窗口类型,那么需要判断其父窗口是否存在,且要求其父窗口不能也为子窗口类型,否则直接返回
        //如果是符合要求的子窗口,那么经过此处之后,parentWindow变量保存的就是该待添加子窗口的父窗口
        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;
            }
        }

        //如果待添加的窗口类型是私有窗口,但是对应的display不是私有的,直接返回
        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;
        }

        AppWindowToken atoken = null;
        //判断当前待添加的窗口是否具有父窗口,也就是判断当前待添加的窗口是否是子窗口
        final boolean hasParent = parentWindow != null;
        //尝试从对应的DisplayContent中匹配windowToken,这里如果当前待添加的窗口是子窗口,那么拿的就是其父窗口的windowToken
        //这里是有可能返回为空的,即未做windowToken的创建
        WindowToken token = displayContent.getWindowToken(
            hasParent ? parentWindow.mAttrs.token : attrs.token);
        //拿到rootwindow的类型,所谓的rootType也就是有parentWindow就用parentWindow的type,否则就是自身的type
        final int rootType = hasParent ? parentWindow.mAttrs.type : type;

        //添加Toast是否需要令牌
        boolean addToastWindowRequiresToken = false;

        //如果当前窗口还没有创建保存windowToken对象,在判断当前的窗口类型是可以进行隐式创建之后,进行windowToken的创建和保存
        if (token == null) {
            //如果当前的窗口类型是 applicationWindow、inputMethodWindow、VioceWindow、wallpaper、DreamWindow、QSDialog、overlayWindow、toast
            //那么windowToken必须显式创建,而不能是在WMS中隐式创建(如果是applicationWindow,那么windowToken的创建和添加是在 ActivityStack.startActivityLocked()时完成的)
            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;
            }
            ...
            //如果满足要求,那么可以在WMS中进行windowToken的隐式创建
            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);
        //如果windowtoken已存在,且当前的window类型是 application,那么需要判断当前application的状态
        } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
            //通过windowToken拿到关联的appToken,这时候的token其实是 appWindowToken对象,返回的就是AppWindowToken本身
            atoken = token.asAppWindowToken();
            //根据apptoken判断状态,是否属于有效的添加场景
            //如果上面拿到的atoken为空,代表token不是AppWindowToken对象,那么和窗口类型对不上,直接返回
            if (atoken == null) {
                Slog.w(TAG_WM, "Attempted to add window with non-application token "
                       + token + ".  Aborting.");
                return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
            //如果appWindowToken是已经被移除的,也就是其removed标识为true,那么同样直接返回
            } 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;
            }
        //而如果窗口类型是输入法,那么需要判断windowToken的类型是否是输入法
        } 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;
            }
        //下面的判断类似,都是判断传入的窗口类型和当前获取的windowToken类型是否一致
        } else if (rootType == TYPE_VOICE_INTERACTION) {
            ...
        //如果当前传入的窗口类型不是application,但是获取的windowToken是appWindowToken,那么需要重新赋值token,将其赋值为windowToken类型
        } else if (token.asAppWindowToken() != null) {
            Slog.w(TAG_WM, "Non-null appWindowToken for system window of rootType=" + rootType);
            attrs.token = null;
            token = new WindowToken(this, client.asBinder(), type, false, displayContent,
                                    session.mCanAddInternalSystemWindow);
        }

        //创建出 WindowState 对象,关注一下传入参数,clients是该窗口关联的 ViewRootImpl.W 对象,token就是上面拿到的WindowToken对象
        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                                                appOp[0], seq, attrs, viewVisibility, session.mUid,
                                                session.mCanAddInternalSystemWindow);
        if (win.mDeathRecipient == null) {
            Slog.w(TAG_WM, "Adding window client " + client.asBinder()
                   + " that is dead, aborting.");
            return WindowManagerGlobal.ADD_APP_EXITING;
        }

        //如果创建的WindowState没有归属的display,直接返回
        //这个其实是通过windowState中的WindowToken去获取的
        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;
        //这里会去调整一下窗口参数,比如如果当前是toast,那么在此处就会设置其的消失时间
        mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
        //设置windowState.mShowToOwnerOnly变量
        win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

        //只有当该窗口是statusBar或者NavigationBar时,而系统中已存在这两种窗口,返回的不为ok
        res = mPolicy.prepareAddWindowLw(win, attrs);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        //判断是否可以打开inputChannel
        final boolean openInputChannels = (outInputChannel != null
                                           && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        //允许接收输入事件的前提下,创建出InputChannel,这其中的细节可以结合输入事件去看
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);
        }

        //如果窗口类型是 toast,需要考虑是否设置超时,超时后移除显示
        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);
            }
        }

        // From now on, no exceptions or errors allowed!

        res = WindowManagerGlobal.ADD_OKAY;
        //如果当前还不存在获取焦点的Window,那么将当前的Window添加到mWinAddedSinceNullFocus列表中
        //记录一下哪些window是在失去焦点后进行添加的
        if (mCurrentFocus == null) {
            mWinAddedSinceNullFocus.add(win);
        }

        //如果窗口类型是输入法dialog\statusBar\navigation,需要将当前Window添加到display.mTapExcludedWindow列表中
        if (excludeWindowTypeFromTapOutTask(type)) {
            displayContent.mTapExcludedWindows.add(win);
        }

        origId = Binder.clearCallingIdentity();

        //将Session中window计数+1
        win.attach();
        //至此,将创建出来的WindowState以 ViewRootImpl.W 为key存到了WMS.mWindowMap中
        mWindowMap.put(client.asBinder(), win);

        win.initAppOpsState();

        final boolean suspended = mPmInternal.isPackageSuspended(win.getOwningPackage(),
                                                                 UserHandle.getUserId(win.getOwningUid()));
        win.setHiddenWhileSuspended(suspended);

        //是否需要隐藏系统报警窗口,当mHidingNonSystemOverlayWindows不为空时需要去隐藏
        final boolean hideSystemAlertWindows = !mHidingNonSystemOverlayWindows.isEmpty();
        win.setForceHideNonSystemOverlayWindowIfNeeded(hideSystemAlertWindows);

        //如果匹配的窗口是applicationWindow,并且传入的进行添加的window类型是TYPE_APPLICATION_STARTING
        //那么将正在进行添加的WindowState记录到 aToken.startingWindow中
        final AppWindowToken aToken = token.asAppWindowToken();
        if (type == TYPE_APPLICATION_STARTING && aToken != null) {
            aToken.startingWindow = win;
            if (DEBUG_STARTING_WINDOW) Slog.v (TAG_WM, "addWindow: " + aToken
                                               + " startingWindow=" + win);
        }

        boolean imMayMove = true;

        //将该windowState添加到其对应的WindowToken.mChildren列表中,并且将WindowToken保存到 WindowState.mParent 变量中
        //也就是由此之后,windowToken和WindowState就建立了关联,相关保存了对方,且一个 WindowToken 可以包含多个 WindowState
        win.mToken.addWindow(win);
        //如果当前进行添加的窗口是输入法窗口,那么需要将其保存到WMS.mInputMethodWindow中,并重新计算一次ime的目标
        //将imMayMove置为false,代表不能移除输入法窗口
        if (type == TYPE_INPUT_METHOD) {
            win.mGivenInsetsPending = true;
            setInputMethodWindowLocked(win);
            imMayMove = false;
            //如果是输入法dialog,只需要计算一下ime的目标,同样imMayMove置false
        } else if (type == TYPE_INPUT_METHOD_DIALOG) {
            displayContent.computeImeTarget(true /* updateImeTarget */);
            imMayMove = false;
            //如果是其他类型的窗口,另作处理
        } else {
            //针对壁纸窗口或者相关窗口,配置相应的标识
            if (type == TYPE_WALLPAPER) {
                displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
                displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
            } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
            } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {
                displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
            }
        }

        //如果需要为了ime的变化而调整窗口
        win.applyAdjustForImeIfNeeded();

        //如果当前window是分屏的DIVIDER,那么调用DividerController去进行设置
        if (type == TYPE_DOCK_DIVIDER) {
            mRoot.getDisplayContent(displayId).getDockedDividerController().setWindow(win);
        }

        final WindowStateAnimator winAnimator = win.mWinAnimator;
        winAnimator.mEnterAnimationPending = true;
        winAnimator.mEnteringAnimation = true;
        // Check if we need to prepare a transition for replacing window first.
        //检查我们是否需要先准备替换窗口的过渡
        if (atoken != null && atoken.isVisible()
            && !prepareWindowReplacementTransition(atoken)) {
            //命中其中,代表不需要进行窗口的过渡
            prepareNoneTransitionForRelaunching(atoken);
        }

        final DisplayFrames displayFrames = displayContent.mDisplayFrames;
        // TODO: Not sure if onDisplayInfoUpdated() call is needed.
        final DisplayInfo displayInfo = displayContent.getDisplayInfo();
        //更新displayInfo
        displayFrames.onDisplayInfoUpdated(displayInfo,
                                           displayContent.calculateDisplayCutoutForRotation(displayInfo.rotation));
        final Rect taskBounds;
        //如果当前的AppWindowToken是有归属的Task的,那么需要拿到这个Task所设定的尺寸
        if (atoken != null && atoken.getTask() != null) {
            taskBounds = mTmpRect;
            atoken.getTask().getBounds(mTmpRect);
            //如果没有归属的task,那么直接将taskBounds置为null即可
        } else {
            taskBounds = null;
        }
        //判断是否需要显示navigation bar
        if (mPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, outFrame,
                                    outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) {
            res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_NAV_BAR;
        }

        //是否在触摸模式下启动的标识
        if (mInTouchMode) {
            res |= WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE;
        }
        //如果mApptoken为空(即当前窗口不是applicationWindow),或者mClientHidden为false(代表当前窗口可见),
        //添加 ADD_FLAG_APP_VISIBLE 标志,这会决定当前窗口是否可见
        if (win.mAppToken == null || !win.mAppToken.isClientHidden()) {
            res |= WindowManagerGlobal.ADD_FLAG_APP_VISIBLE;
        }

        mInputMonitor.setUpdateInputWindowsNeededLw();

        //判断是否需要进行焦点的更新
        boolean focusChanged = false;
        //如果当前添加的窗口可以接收输入事件,并且需要获取焦点,那么设置imMaymove为false,代表输入法目标不需要更新
        //就是当前添加的窗口
        if (win.canReceiveKeys()) {
            focusChanged = updateFocusedWindowLocked(UPDATE_FOCUS_WILL_ASSIGN_LAYERS,
                                                     false /*updateInputWindows*/);
            if (focusChanged) {
                imMayMove = false;
            }
        }

        //如果im需要移动,那么重新计算im的目标
        if (imMayMove) {
            displayContent.computeImeTarget(true /* updateImeTarget */);
        }

        // Don't do layout here, the window must call
        // relayout to be displayed, so we'll do it there.
        //windowState.getParent()拿到的就是windowState对应归属的WindowToken对象,也就是开始给WindowToken中存的WidnowState分配layer
        win.getParent().assignChildLayers();

        //如果焦点变化了,那么输入事件的消费者也要跟着变化
        if (focusChanged) {
            mInputMonitor.setInputFocusLw(mCurrentFocus, false /*updateInputWindows*/);
        }
        mInputMonitor.updateInputWindowsLw(false /*force*/);

        if (localLOGV || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addWindow: New client "
                                                  + client.asBinder() + ": window=" + win + " Callers=" + Debug.getCallers(5));

        //判断是否需要更新config
        if (win.isVisibleOrAdding() && updateOrientationFromAppTokensLocked(displayId)) {
            reportNewConfig = true;
        }
    }

    //如果需要更新config,那么发送新的config给对应的display
    if (reportNewConfig) {
        sendNewConfiguration(displayId);
    }

    Binder.restoreCallingIdentity(origId);

    return res;
}
  • 通过该函数的执行,window 就被添加到了 WMS 中,该函数还是比较长的,我们大致对其进行一下总结:

    1. 首先根据传入的 displayID 获取或者创建出对应的 DisplayContent 对象
    2. 再根据 attrs.token (该attrs可能是当前窗口或者父窗口的,视当前窗口类型而定),从第一步中拿到的 DisplayContent 中去尝试获取对应的 WindowToken 对象
    3. 如果第二步中拿到的 windowToken 为空,那么判断当前流程中正在添加的窗口类型,决定是否可以在 WMS 中隐式的创建 WindowToken ;而如果第二步中拿到的 WindowToken 不为空,那么同样需要判断当前流程中正在添加的窗口类型,根据窗口类型去判断拿到的 windowToken 是否合法,是否需要做相应的处理
    4. 创建出 WindowState 对象,在 WindowState 中记录了对应的 ViewRootImpl.W 对象,第三步中创建或者获取到的 WindowToken 对象等
    5. 将第四步中创建出来的 WindowStateViewRootImpl.Wkey, 存到放 WindowManagerService.mWindowMap 列表中
    6. 通过 WindowState.mToken.addWindow() 方法,将 WindowState 保存到 WindowToken.mChildren 列表中,并且将 WindowToken 作为 WindowStateparent 保存到 WindowState.mParent 变量上,至此 WindowTokenWindowState 就互相持有了对方

    略去很多和当前窗口添加关联不大的代码,总结出来的执行逻辑即如以上六步,而通过该 addWindow() 函数的执行,我们的 Window 就完成了在 WindowManagerService 中的存储,但是需要注意的是,在 WMS 中,窗口是以 WindowState 的对象进行保存的,且是和 ViewRootImpl.W 对象建立的关联,而通过 ViewRootImpl.W 就可以获取到 ViewRootImpl,再通过 ViewRootImpl 拿到其中的 ViewRootImpl.mView ,而这个 mView 通常来说就是 DecorView ,而 DecorView 中就会保存有 PhoneWindow 对象,而通过这样一条链路,WMS 也就是可以通过 WindowState 对象访问到 PhoneWindow 对象

  • 在梳理完 WMS.addWindow() 之后,想必还会存在一个疑问,那就是如果我们的窗口类型是 application_window,这也就意味着它的 WindowToken 是无法在 WMS 中进行隐式创建的,那么该窗口的 WindowToken 是在哪里创建的呢?接下来就让我们回到启动流程中,梳理APP的WindowToken的创建

5.2 App的WindowToken创建

  • 翻阅 AMS侧解析Activity启动 流程,我们可以知道在Activity启动过程中,WindowToken是在借由 ActivityStarter.startActivityUnchecked(9参)函数调用到 ActivityStack.startActivityLocked(5参)函数中完成创建的,那么接下来我们就以 ActivityStack.startActivityLocked(5参)为入口进行梳理

1. ActivityStack.startActivityLocked()

frameworks\base\services\core\java\com\android\server\am\ActivityStack.java

void startActivityLocked(ActivityRecord r, ActivityRecord focusedTopActivity,
                         boolean newTask, boolean keepCurTransition, ActivityOptions options) {
    //拿到所属的TaskRecord以及对应的taskId
    TaskRecord rTask = r.getTask();
    final int taskId = rTask.taskId;
   
    //activity不是由ActivityOptions.setLaunchTaskBehind 主动启动,且 rTask在
    //该Stack中不存在 或者是 明确要新建Task,命中if,那么将该rTask放置到Stack的最顶层
    if (!r.mLaunchTaskBehind && (taskForIdLocked(taskId) == null || newTask)) {
        insertTaskAtTop(rTask, r);
    }
    TaskRecord task = null;
    //如果是不需要新建Task的,那么就命中if,即复用task的情况
    if (!newTask) {
       
        boolean startIt = true;
        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
            ...
            if (task == rTask) {
                if (!startIt) { 
                    //满足条件会在此处就进行AppWindowToken的创建
                    r.createWindowContainer();
                    ActivityOptions.abort(options);
                    return;
                }
                break;
            } else if (task.numFullscreen > 0) {
                startIt = false;
            }
        }
    }

    ...
    
    //如果直到此处,对应的ActivityRecord还不存在关联的AppWindowToken,那么就在此处进行创建
    if (r.getWindowContainerController() == null) {
        r.createWindowContainer();
    }
    ...
}
  • 这里我们着重看和窗口相关的代码,可以看到,不管是复用task还是新建task,都是调用 ActivityRecord.createWindowContainer() 去进行的 Window 的创建

2. ActivityRecord.createWindowContainer()

frameworks\base\services\core\java\com\android\server\am\ActivityRecord.java

void createWindowContainer() {
    //如果当前ActivityRecord中controller不为空,直接抛异常
    if (mWindowContainerController != null) {
        throw new IllegalArgumentException("Window container=" + mWindowContainerController
                                           + " already created for r=" + this);
    }

    //将inHistory标志置位
    inHistory = true;

    //去获取 TaskRecord 的 TaskWindowContainerController 对象
    final TaskWindowContainerController taskController = task.getWindowContainerController();

    //更新当前ActivityRecord所属的task的bounds,同时也更新当前ActivityRecord的bounds
    task.updateOverrideConfigurationFromLaunchBounds();
    updateOverrideConfiguration();

    //构建出AppWindowContainerController对象,以mWindowContainerController变量进行保存
    mWindowContainerController = new AppWindowContainerController(taskController, appToken,
                                                                  this, Integer.MAX_VALUE /* add on top */, info.screenOrientation, fullscreen,
                                                                  (info.flags & FLAG_SHOW_FOR_ALL_USERS) != 0, info.configChanges,
                                                                  task.voiceSession != null, mLaunchTaskBehind, isAlwaysFocusable(),
                                                                  appInfo.targetSdkVersion, mRotationAnimationHint,
                                                                  ActivityManagerService.getInputDispatchingTimeoutLocked(this) * 1000000L);

    //将当前的ActivityRecord放到所属task的顶部
    task.addActivityToTop(this);

    //记录下当前窗口的窗口类型状态
    //之所谓标志命名是last,是因为一个ActivityRecord中的windowContainer是可以移除重新添加的
    mLastReportedMultiWindowMode = inMultiWindowMode();
    mLastReportedPictureInPictureMode = inPinnedWindowingMode();
}
  • 在创建 WindowContainer 的时候,首先要确保当前ActivityRecord是不存在 WindowContainerController 的,否则就会直接抛出异常;而在这之后,我们就会去创建 AppWindowContainerController 对象

3. AppWindowContainerController.AppWindowContainerController()

frameworks\base\services\core\java\com\android\server\wm\AppWindowContainerController.java

public AppWindowContainerController(TaskWindowContainerController taskController,
                                    IApplicationToken token, AppWindowContainerListener listener, int index,
                                    int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
                                    boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
                                    int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
                                    WindowManagerService service) {
    super(listener, service);
    mHandler = new H(service.mH.getLooper());
    //mToken就是传入的 ActivityRecord.Token 内部类对象
    mToken = token;
    synchronized(mWindowMap) {
        //先根据传入的 ActivityRecord.Token 对象去 RootWindowContainer 中去查找是否存在对应的 AppWindowToken 对象
        AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
        //如果能够拿到,即缓存中已经存在该Activity所属的AppWindowToken,那么就直接返回即可
        if (atoken != null) {
            // TODO: Should this throw an exception instead?
            Slog.w(TAG_WM, "Attempted to add existing app token: " + mToken);
            return;
        }

        //而如果没有现成的已存在的AppWindowToken,那么接下来就需要去新建

        //先拿到传入的taskController中记录的Task对象,该task中记录着当前Activity所属的DisplayContent对象
        final Task task = taskController.mContainer;
        //task对象如果为空,直接抛出异常
        if (task == null) {
            throw new IllegalArgumentException("AppWindowContainerController: invalid "
                                               + " controller=" + taskController);
        }

        //创建出AppWindowToken对象
        atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
                                 inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
                                 requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
                                 alwaysFocusable, this);
        if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
                                                             + " controller=" + taskController + " at " + index);
        //将创建出来的AppWindowToken按照特定的下标添加到task.mChildren列表中,
        //并且在这其中会给AppWindowToken.mParent赋值为task,并且调用mParent.assignChildLayers()函数
        //给添加进去的AppWindowToken分配高程
        task.addChild(atoken, index);
    }
}
  • 在该函数中,最终就会去创建 AppWindowToken 对象,并将其按照给定的序号保存到 Task.mChildren 列表中
@VisibleForTesting
AppWindowToken createAppWindow(WindowManagerService service, IApplicationToken token,
                               boolean voiceInteraction, DisplayContent dc, long inputDispatchingTimeoutNanos,
                               boolean fullscreen, boolean showForAllUsers, int targetSdk, int orientation,
                               int rotationAnimationHint, int configChanges, boolean launchTaskBehind,
                               boolean alwaysFocusable, AppWindowContainerController controller) {
    //直接调用构造函数进行创建
    return new AppWindowToken(service, token, voiceInteraction, dc,
                              inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdk, orientation,
                              rotationAnimationHint, configChanges, launchTaskBehind, alwaysFocusable,
                              controller);
}

frameworks\base\services\core\java\com\android\server\wm\AppWindowToken.java

AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
               DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
               boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
               int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
               AppWindowContainerController controller) {
    this(service, token, voiceInteraction, dc, fullscreen);
    //在这里就会将当前的 AppWindowToken 和 传入的 controller 相互绑定,即:
    //this.mController = controller, controller.mContainer = this
    setController(controller);
    mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
    mShowForAllUsers = showForAllUsers;
    mTargetSdk = targetSdk;
    mOrientation = orientation;
    layoutConfigChanges = (configChanges & (CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION)) != 0;
    mLaunchTaskBehind = launchTaskBehind;
    mAlwaysFocusable = alwaysFocusable;
    mRotationAnimationHint = rotationAnimationHint;

    // Application tokens start out hidden.
    setHidden(true);
    hiddenRequested = true;
}
  • 至此,我们就完成了 ActivityRecord 对应的 WindowContainer 的创建,但是在创建过程中我们屡次看到 诸如 Taskcontroller 等对象,这些对象代表什么?相互之间有什么关联?所以基于此,我们还是需要梳理一下这些和Window有关的类的相互关系。

5.3 Window中的container和controller

  • 在这里我们使用一张图来进行相关关系的展示,且结合AMS中的相关类一起进行统计:在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/170809.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

DBCO高分子PEG_DBCO-PEG-Lipoic COOH_二苯并环辛炔-聚乙二醇-硫辛酸

DBCO-PEG-Lipoic acid“点击化学"一般由叠氮化物&#xff08;azide&#xff09;和炔烃&#xff08;alkyne&#xff09;作用形共价键&#xff0c;具有高效稳定&#xff0c;高特异性等优点。反应不受PH影响&#xff0c;能在常温条件下的水中进行,甚至能在活细胞中进行。DBCO…

第十三届蓝桥杯省赛 JAVA A组 - 矩形拼接

✍个人博客&#xff1a;https://blog.csdn.net/Newin2020?spm1011.2415.3001.5343 &#x1f4da;专栏地址&#xff1a;蓝桥杯题解集合 &#x1f4dd;原题地址&#xff1a;付账问题 &#x1f4e3;专栏定位&#xff1a;为想参加蓝桥别的小伙伴整理常考算法题解&#xff0c;祝大家…

Python学习中的六个技巧小结

1. 引言 “Beautiful is better than ugly.” 上述为著名的The Zen of Python的第一句话&#xff0c;也是有追求的python开发人员的信条之一。 所以我们的问题来了&#xff1a; 如何编写漂亮的Python代码? 本文重点通过九个示例向大家展示Python中的六个小技巧&#xff0c;以帮…

java后端-servlet超详细入门

java后端介绍今天我正式开始了一个新话题&#xff0c;那就是 Web。目前我主要会介绍后端。作为后端的老大哥 java&#xff0c;也有很多后端框架&#xff0c;比如大家耳熟能详的 spring 等。今天来带大家入门 servlet&#xff0c;不管是学生&#xff0c;刚毕业或是已经工作自学编…

【倍增】魔力小球

今天最后一篇&#xff0c;该睡了&#xff0c;怕猝死QwQ学校OJ上的一道模板题&#xff0c;去年不会做&#xff0c;今年还是不会做嘻嘻&#xff0c;还好最后调出来了&#xff0c;错的原因竟然是题目有歧义这个小球i的i是他喵的小球编号&#xff0c;不是id&#xff01;出题人是懂出…

Win11的两个实用技巧系列之电脑system占用高的解决办法

Win11 system占用cpu过高是什么原因? Win11电脑system占用高的解决办法Win11 system占用cpu过高是什么原因&#xff1f;Win11系统遇到system占用cpu很高&#xff0c;该怎么解决呢&#xff1f;下面我们就来看看Win11电脑system占用高的解决办法System占用cpu过高导致电脑卡顿&a…

2023年中职网络安全技能竞赛网页渗透(注入版)

竞赛任务书内容 (一)拓扑图 网页渗透 任务环境说明: 服务器场景:Server2121 服务器场景操作系统:未知(封闭靶机) 用户名:未知 密码:未知 1.访问服务器网站目录1,根据页面信息完成条件,将获取到的flag提交; 2.访问服务器网站目录2,根据页面信息完成条件,将获…

学生写字台灯用什么牌子的好?高品质学生台灯品牌推荐

学生写字台灯&#xff0c;很明显就是为学生而设计的&#xff0c;针对学生长时间学习&#xff0c;用眼强度大的特点&#xff0c;这种学生台灯在设计上对灯光的亮度、样式、护眼技术都是很有讲究的&#xff0c;为的就是保护学生眼睛&#xff0c;在一定程度上缓解眼部疲劳的作用。…

进程概念理解

既然要了解计算机的进程&#xff0c;那么就需要先了解一下计算机的底层结构 目录 冯洛伊曼体系结构 操作系统 系统调用接口 进程 PCB task_struct 内容 操作系统如何组织进程 冯洛伊曼体系结构 想了解计算机的底层结构&#xff0c;那么必定绕不开冯洛伊曼体系结构&…

19/365 java 多线程

1. 基础概念 程序&#xff1a;指令集和数据的集合。&#xff08;静态&#xff09; 进程&#xff1a;对程序的一次执行。&#xff08;动态&#xff09; 对同一个程序&#xff0c;执行两次&#xff0c;那就是两个进程。 进程是系统资源分配的基本单位 线程&#xff1a;一个进程…

gma 1.1.2 (2023.01.14) 更新日志(重大更新:开始支持空间绘图)

重大更新&#xff1a;从本版本开始&#xff0c; gma 逐步 支持空间绘图功能&#xff08;依赖 matplotlib&#xff09;&#xff01; 获取 gma 1.1.2 1、百度网盘&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1eT2rJRiUDJuJkWeLJNL-cw?pwdb07n 提取码&#xff1a;b…

基于SIMULINK的动力电池CAN通信仿真教程

在真实的整车开发过程中&#xff0c;整车厂一般会先设计出整车网络架构&#xff0c;并依据此架构及ECU之间的功能交互设计网络总线数据库&#xff08;Database&#xff09;, 作为重要的技术文档&#xff0c;可以根据需 要全部或部分地公开给各个ECU供应商。也存在一些特殊情况&…

【渗透测试】web端姿势-前端利用

目录 前端 存在问题 关于密码重置 jwt攻击 jwt介绍 工具使用 学习来源 前端 存在问题 任意用户注册 未授权访问&#xff0c;直接访问对应链接&#xff0c;可得到系统权限 可爆破用户名 爆破用户名&#xff0c;密码 用户名注入 万能密码 用户名Xss 修改返回包信息&#…

(三)计算机组成原理——总线

文章目录&#xff08;三&#xff09;计算机组成原理——总线总线的基本概念单总线双总线面向CPU以存储器为中心总线的分类片内总线系统总线数据总线地址总线控制总线通信总线总线特性及性能指标总线特性机械特性电气特性功能特性时间特性性能指标总线标准总线结构单总线多总线双…

3.1 python高阶应用

文章目录闭包装饰器设计模式单例模式工厂模式多线程进程、线程和并行执行多线程编程网络编程服务端开发客户端开发正则表达式基础匹配元字符匹配递归闭包 def account_create(inital_account 0) :def atm(num:int,deposite:bool True) :# 声明inital_account是外部声明的init…

【Ajax】服务器的基本概念

一、客户端与服务器上网的目的通过互联网的形式来获取和消费资源。2. 服务器上网过程中&#xff0c;负责存放和对外提供资源的电脑&#xff0c;叫做服务器。3. 客户端上网过程中&#xff0c;负责获取和消费资源的电脑&#xff0c;叫做客户端。二、URL地址URL地址的概念URL&…

工业互联网2022:第一梯队成型、专精玩家突围

文|智能相对论作者|沈浪回顾2022年&#xff0c;市场依旧对工业互联网领域保持着高度的热情与专注。近期&#xff0c;IDC、Gartner等各大国际研究机构接连发布多份工业互联网相关报告&#xff0c;包括《2022年度中国工业互联网平台企业侧市场分析报告》《2022年度工业互联网平台…

唯一杰出级!百度智能云曦灵获信通院权威认证

​中国信通院公布“数字人系统评测结果” 百度智能云曦灵平台 继首批通过48项基础能力评测后 在第二轮性能分级评测中 以超高分获得行业唯一“杰出级”证书 代表了当前数字人的最高标准 该评测标准依托国际电信联盟&#xff08;ITU&#xff09;、中国通信标准化协会&#xff08…

JNPF低代码开发平台 全新版本 使用讨论 多用户商城系统源码 框架源码部署文档

JNPF快速开发平台是一套成熟的快速开发框架&#xff0c; JNPF作为承重墙&#xff0c;强大支撑保障&#xff0c;提供所有操作系统的生长土壤&#xff0c;JNPF快速开发平台采用前后端分离技术、采用B/S架构开发&#xff0c;形成一站式开发多端&#xff08;APPPC&#xff09;使用&…

【手写 Vue2.x 源码】第二十九篇 - diff算法-节点比对

一&#xff0c;前言 上篇&#xff0c;diff 算法问题分析与 patch 方法改造&#xff0c;主要涉及以下几点&#xff1a; 初始化与更新流程分析&#xff1b;问题分析与优化思路&#xff1b;新老虚拟节点比对模拟&#xff1b;patch 方法改造&#xff1b; 下篇&#xff0c;diff 算…