解析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()
函数中,完成了如下几件事:- 创建出
Activity
的contextImpl
对象 - 创建出
Activity
对象,并调用Activity.attach()
函数完成初始化以及窗口的创建 - 调用
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
的创建,而如果存在遗留窗口的情况下,那么会直接拿遗留窗口中的DecorView
、Elevation
等内容赋值给当前创建出来的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);
}
- 在该函数中,可以看到除了完成了
PhoneWindow
和Activity
的关联,最主要就是保存了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
对象中去尝试获取,此处的获取方式比较有意思,大致梳理一下:-
静态块注册:
-
在
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
函数中,其实就是将传入的class
、name
、fetcher
按照一定的关系保存到了SYSTEM_SERVICE_NAMES
和SYSTEM_SERVICE_FETCHERS
列表中
- 结合当前的Window分析流程,我们关注
-
-
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
,而在静态块中注册的ServiceFetcher
是CachedServiceFetcher
类型,那么此时调用的就是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()
去进行创建
- 根据上面的注册,我们可以知道根据
-
创建
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
, 也就是说没有完成WindowManagerImpl
和Window
的关联,而在第二次创建时,传入了parentWindow
,至此才完成了WindowManagerImpl
和PhoneWindow
的关联关系
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);
}
- 前置后置操作我们先不关心,主要关注当前
Activity
的Create
流程,那么也就是接下来会调用到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); ... }
也就是首先调用父类
Activity
的onCreate()
函数,接着再调用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;
}
- 该函数也比较简单,主要就是做了一些内容的保存以及当前状态改变的通知,需要关注的主要是如下两个点:
getApplication().dispatchActivityCreated()
在这其中就会去调用所有注册在Application
中的ActivityLifecycleCallbacks
回调,这也就意味着:只要我们注册了ActivityLifecycleCallbacks
,那么就可以监听到当前Activity
的生命周期的改变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;
}
-
在该函数中,主要就是完成了如下几件工作:
- 在
installDecor()
中完成了DecorView
的创建和初始化,并且完成mContentParent
的创建 - 在
mLayoutInflater.inflate()
函数中将layoutResID
代表的布局添加到mContentParent
这个View
上
而由此就有那么几个问题:
DecorView
是如何进行的创建?mContentParent
又是个什么样的View
?- 传入的
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());
}
-
可以看到在该函数中,主要完成了如下工作:
- 根据
mUseDecorContext
变量确定Context
类型;在当前的流程中,创建出来的是DecorContext
- 创建出
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()
函数的剩余部分,代码量很多,但其实归纳起来只是完成了如下几件事:
- 通过
generateLayout()
函数往DecorView
上添加布局,并且返回了View
保存到了mContentParent
上 - 完成
DecorView
的布局添加后,判断添加上的布局中是否存在R.id.decor_content_parent
元素,有的话对R.id.decor_content_parent
元素进行一下布局,如果没有那么再看看是否需要设置title
- 设置一下背景,以及处理一下转场动画相关的内容
而在这其中,究竟是如何进行的DecorView
的布局添加,又往上添加了怎么样的布局,返回的作为 mContentParent
的 View
究竟是什么显然是最重要的,那么接下来就来看一下 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;
}
-
该函数代码量还是很大的,但是其实可以按照完成的工作分成四个部分:
- 根据当前
Activity
的StyledAttributes
设置当前Activity
对应的Window
的feature
和flag
- 再根据第一步中获取的当前窗口所需的
feature
匹配需要加载的系统布局XML
文件 - 再将第二步中匹配到的系统布局
XML
加载到DecorView
上,借助函数DecorView.onResourcesLoaded()
函数完成 - 最后,在完成
DecorView
上的系统布局加载后,找到id
为com.android.internal.R.id.content
的View
将其返回,而这个返回的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();
}
-
在该函数中,主要完成了如下几件事:
- 根据当前窗口是否是悬浮窗口,决定是否创建出
decorCaptionView
,所谓的decorCaptionView
其实就是带 关闭 和 最大化 两个button
的布局 - 借助
LayoutInflater.inflate()
函数根据传入的 系统内置的 布局文件IDlayoutResource
构建出View
对象,并将其保存到DecorView.mContentRoot
变量上 - 根据是否存在
mDecorCaptionView
,决定是将 第二步创建出来的rootView
添加到mDecorCaptionView
上还是将其添加到DecorView
上 - 初始化
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;
}
-
在该函数中,逻辑也比较清晰,此处我们就稍微总结一下:
- 首先从当前
DecorView
的childView
中去查找,是否已经存在decorCaptionView
对象,如果找到就拿出来,然后再将其先从DecorView
的childView
列表中删掉 - 再根据当前窗口的配置,来决定是否需要
decorCaptionView
,如果需要decorCaptionView
但在第一步中没有找到 已存在的decorCaptionView
,那么就会调用DecorView.inflateDecorCaptionView()
函数进行创建 - 尝试去绘制
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;
}
}
-
该函数完成的内容还是比较多的,我们来总结一下:
- 通过传入的布局文件
parser
借助createViewFromTag()
函数完成了parser
布局中的根元素tempView
创建 - 根据传入的
root
参数决定是否构建出parser
中tempView
的ViewGroup.LayoutParams
,再根据attachToRoot
参数决定是否将ViewGroup.LayoutParams
设置到tempView
中 - 借助
rInflateChildren()
函数完成parser
中所有的View
的创建和添加 - 根据传入的
root
和attachToRoot
参数决定是否将创建出来的tempView
添加到root
上 - 返回
result
,result
有可能是root
或者temp
,视传入的root
和attachToRoot
参数决定
总结出来的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
进行创建之前,显示尝试通过mFactory2
、mFactory
、mPrivateFactory
进行创建的,只有在这三个都没有定义或者都没有创建出View
的前提下,才会走到LayoutInflater
去进行创建,这也就意味着我们可以通过实现mFactory2
、mFactory
、mPrivateFactory
这三者其中之一,进行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
相关的流程,可以看到主要就是如下步骤:- 将传入的
LayoutParams
设置到了 传入的childView
上 - 将
childView
按照index
添加到了parentView.mChildren
数组的特定位置上,这一步之后,parentView
就持有了childView
- 将
parentView
保存到了childView.mParent
变量上,这一步之后,childView
就持有了parentView
- 将传入的
4.3 总结:
-
至此我们就完成了布局的加载流程,在这其中我们主要完成了:
- 创建出
DecorView
,然后将DecorView
和PhoneWindow
相互绑定了起来 - 拿到第一步中创建出来的
DecorView
,为DecorView
添加了系统布局,然后拿到其中id = R.id.content
的ViewGroup
,将其保存到PhoneWindow.mContentParent
变量上 - 根据自定义的布局创建出
View
对象,并将该View
作为childView
添加到PhoneWindow.mContentParent
上
经过上面的流程,属于
Activity
的PhoneWindow
就完成了所有的布局加载,完成了正确显示的前提条件,而该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
流程无关的代码,归纳一下完成了如下的事项:- 构建出
WindowManager.LayoutParams
对象,也就是当前PhoneWindow
的窗口参数,需要特别注意的,在默认的当前流程中,该LayoutParams.type
是固定的,是为WindowManager.LayoutParams.TYPE_BASE_APPLICATION
,这也就代表着如果让系统进行的DecorView
添加,那么window
的类型是确定的,就是TYPE_BASE_APPLICATION
- 在不存在遗留窗口且
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;
}
}
}
-
纵观该函数,主要就是完成了如下几件事:
- 根据传入的
parentWindow
参数是否为空,对传入的窗口参数params
做一些处理;当前流程下,parentWindow
是PhoneWindow
,会调用PhoneWindow.adjustLayoutParamsForSubWindow()
对params
做处理 - 判断需要添加的
View
是否已经完成了添加,如果已经添加过,那么是无法二次添加的 - 构建出
ViewRootImpl
对象,回设处理过的窗口参数params
到传入的View
中,将传入的View
、构建出来的ViewRootImpl
、传入的窗口参数params
分别存到WindowManagerGlobal
的mViews
、mRoots
、mParams
三个列表中 - 最终将传入的
View
和params
设置到创建出来的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
对象的创建,主要是内部元素的构建,而在这其中,有几个变量我们需要特别留意,分别是:mWindowSession
对象,是ViewRootImpl
和WMS
通讯的桥梁ViewRootImpl
内部的mWindow
对象mAttachInfo
对象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的绑定
- 接下来再看看一下
DecorView
和ViewRootImpl
的绑定流程,是通过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;
}
}
}
-
该函数相对还是比较复杂的,我们总结一下完成的内容:
- 存下传入的
View
,当前流程下存下的就是DecorView
, 并且转存了传入的窗口参数attrs
- 将
ViewRootImpl.mAdded
标志置为true
,代表当前ViewRootImpl
已添加了关联的View
, 并且调用requestLayout()
完成一次布局 - 借助
WindowSession
将相关数据存到WindowManagerService
中去,通过函数WindowSession.addToDisplay()
实现 - 将当前的
RootViewImpl
保存到传入的View.mParent
变量上,在当前流程中,也就是保存到了DecorView.mParent
变量上 - 构建和保存输入事件拦截器
在这五步中,相对复杂的就是第三步,
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
中,该函数还是比较长的,我们大致对其进行一下总结:- 首先根据传入的
displayID
获取或者创建出对应的DisplayContent
对象 - 再根据
attrs.token
(该attrs可能是当前窗口或者父窗口的,视当前窗口类型而定),从第一步中拿到的DisplayContent
中去尝试获取对应的WindowToken
对象 - 如果第二步中拿到的
windowToken
为空,那么判断当前流程中正在添加的窗口类型,决定是否可以在WMS
中隐式的创建WindowToken
;而如果第二步中拿到的WindowToken
不为空,那么同样需要判断当前流程中正在添加的窗口类型,根据窗口类型去判断拿到的windowToken
是否合法,是否需要做相应的处理 - 创建出
WindowState
对象,在WindowState
中记录了对应的ViewRootImpl.W
对象,第三步中创建或者获取到的WindowToken
对象等 - 将第四步中创建出来的
WindowState
以ViewRootImpl.W
为 key, 存到放WindowManagerService.mWindowMap
列表中 - 通过
WindowState.mToken.addWindow()
方法,将WindowState
保存到WindowToken.mChildren
列表中,并且将WindowToken
作为WindowState
的parent
保存到WindowState.mParent
变量上,至此WindowToken
和WindowState
就互相持有了对方
略去很多和当前窗口添加关联不大的代码,总结出来的执行逻辑即如以上六步,而通过该
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
的创建,但是在创建过程中我们屡次看到 诸如Task
、controller
等对象,这些对象代表什么?相互之间有什么关联?所以基于此,我们还是需要梳理一下这些和Window
有关的类的相互关系。
5.3 Window中的container和controller
- 在这里我们使用一张图来进行相关关系的展示,且结合AMS中的相关类一起进行统计: