好吧,不喜欢啰里啰嗦,直接进入主题吧。
当我们打开一个页面(Activity)时,在onCreate()
方法中调用setContentView(layoutID)
,就会发现我们的XML
中所写的布局绘制到屏幕上了。
那么有没有想过,setContentView(layoutID)
这个方法是怎样让XML
中的布局显示到屏幕上的?下面我们结合源码来看下这个过程到底是怎样的?
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
我们点进去看下,注意这里需要打开Activity.java
类中的setContentView()
方法,而不是AppCompatActivity.java
类中的方法。
/**
* Set the activity content from a layout resource. The resource will be
* inflated, adding all top-level views to the activity.
*
* @param layoutResID Resource ID to be inflated.
*
* @see #setContentView(android.view.View)
* @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
*/
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
紧接着在点到getWindow().setContentView(layoutResID)
的方法。
额,,,是抽象类Window.java
中的抽象方法。接下里我们回去,看下getWindow()
返回的是什么?
public Window getWindow() {
return mWindow;
}
额,接着往下跟。
@UnsupportedAppUsage
private Window mWindow;
额,,,既然这样,那我们就要找下,这个成员变量mWindow
是在哪被赋值的。
最终我们发现:
@UnsupportedAppUsage
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, IBinder assistToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mAssistToken = assistToken;
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());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
mWindow.setPreferMinimalPostProcessing(
(info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);
setAutofillOptions(application.getAutofillOptions());
setContentCaptureOptions(application.getContentCaptureOptions());
}
mWindow
是在Activity.java
类中的attach()
方法中被赋值的。
mWindow = new PhoneWindow(this, window, activityConfigCallback);
我们看到是将一个PhoneWindow
对象赋值给了mWindow
。废了老半天的功夫才找到。其实还有最简单的一种方式,那就是在Window.java
类描述中已经告诉我们了:
The only existing implementation of this abstract class is android.view.PhoneWindow, which you should instantiate when needing a Window.
意思是只有一个类实现了Widonw
的抽象类就是android.view.PhoneWindow
。
OK,到这里先暂停下,想一想下面的问题:
既然调用setContentView(layoutID)
,可以绘制XML布局中的控件,那么又是在哪里触发Activity中的onCreate()
生命周期方法呢?
因为只有调用了onCreate(...)
方法才会调用setContentView(layoutID)
方法,才会完成布局的渲染显示。
我就不卖关子了,直接说结果了,我们知道打开某一个Activity页面,但是我们又重来没有new过Activity对象,所以一定是某个地方中的某段代码替我们创建了一个Activity对象,那就是ActivityThread.java
中的performLaunchActivity()
方法中创建一个Activity对象的。
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
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,
r.assistToken);
......
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
......
return activity;
}
可以看到是通过activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent)
这句代码来创建一个Activity对象的。我们点进去看下:
public Activity newActivity(ClassLoader cl, String className,
Intent intent)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
String pkg = intent != null && intent.getComponent() != null
? intent.getComponent().getPackageName() : null;
return getFactory(pkg).instantiateActivity(cl, className, intent);
}
接着调用getFactory(pkg).instantiateActivity(cl, className, intent)
来实例化一个Activity对象,我们在跟进去看下:
public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
@Nullable Intent intent)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Activity) cl.loadClass(className).newInstance();
}
哦~~~原来是通过ClassLoader
来创建一个Activity对象的。
那我们在思考下,是在哪里调用Activity对应的生命周期方法呢?
我们还是回到ActivityThread
类中。
我们知道Activity的创建是在ActivityThread
中的performLaunchActivity
方法中的,我们接着往下看会发现一段代码:
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
哦?这里调用了callActivityOnCreate
方法,跟进去看下:
public void callActivityOnCreate(Activity activity, Bundle icicle) {
prePerformCreate(activity);
activity.performCreate(icicle);
postPerformCreate(activity);
}
在Instrumentation
中callActivityOnCreate
方法中调用了activity.performCreate(icicle)
,我们在Activity
中搜一下这个方法:
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
......
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
onCreate(icicle);
}
......
}
原来如此,在performCreate
方法中调用了onCreate
生命周期方法。
同理,我们可以推算出其他生命周期方法调用的方法:
-
performLaunchActivity
方法中调用Activity的onCreate
方法 -
handleStartActivity
方法中调用Activity的onStart
方法 -
performResumeActivity
方法中调用Activity的onResume
方法 -
performPauseActivity
方法中调用Activity的onPause
方法 -
performStopActivity
方法中调用Activity的onStop
方法 -
performDestroyActivity
方法中调用Activity的onDestroy
方法
也就是说Activity中所谓的生命周期方法,都是通过ActivityThread在不同的方法(时机)调用Activity对象中不同的生命周期方法。
好了到这里我们应该明白了Activity生命周期方法的调用。
不知道刚刚你有没有注意到performLaunchActivity
方法中有这样一段代码:
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,
r.assistToken);
我们先不管这个方法是干嘛用的,但是有一点我们清晰的知道,activity.attach()
方法一定是在activity.onCreate()
方法之前调用的。
但是仔细想想这个方法熟悉吗?
就是我们最开始追踪setContentView()
方法在哪调用的时候,然后追踪mWindow
是在哪里被赋值的?有印象了吧,对就是这个attach()
方法中对mWindow
赋值的。所以也就是说attach()
方法一定是在setContentView()
方法之前调用的。
到这里我们应该清楚一点:
Activity和View之间是需要Window(PhoneWindow)来连接的。
我们用一张图来表示下:
好了接下来我们就来看下PhoneWindow
中的setContentView
方法。
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
首先判断mContentParent
是否为空,显然我们第一次进入时,肯定是空的,我们可以通过debug调试手段来看这个属性是否为空。
我们可以看到第一次进来时
mContentParent
确实是空的,之后进入installDecor()
方法,我们接着往下走;Debug-installDecor
首先判断
mDecor
是否为空,第一次进入当然为空,所以下面就是生成一个Decor
,然后赋值给mDecor
。这个mDecor
是个啥?
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
看描述,这是Window中最顶层的View,包含Window装饰部分。我们点进去看下:
DecorView
发现DecorView是继承自FrameLayout的。
我们接着往下走:
看到这里会发现:不管
generateDecor
方法中前面怎么操作,最终都会new一个DecorView并返回。我们接着往下Debug:
Debug-installDecor-1
当创建了DecorView之后,通过
generateLayout(mDecor)
方法将mContentParent
和mDecor
进行了关联,我们看下它的源码:
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
if (false) {
System.out.println("From style:");
String s = "Attrs:";
for (int i = 0; i < R.styleable.Window.length; i++) {
s = s + " " + Integer.toHexString(R.styleable.Window[i]) + "="
+ a.getString(i);
}
System.out.println(s);
}
mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
& (~getForcedWindowFlags());
if (mIsFloating) {
setLayout(WRAP_CONTENT, WRAP_CONTENT);
setFlags(0, flagsToUpdate);
} else {
setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
getAttributes().setFitInsetsSides(0);
getAttributes().setFitInsetsTypes(0);
}
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
requestFeature(FEATURE_ACTION_BAR_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
requestFeature(FEATURE_ACTION_MODE_OVERLAY);
}
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowTranslucentStatus,
false)) {
setFlags(FLAG_TRANSLUCENT_STATUS, FLAG_TRANSLUCENT_STATUS
& (~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowTranslucentNavigation,
false)) {
setFlags(FLAG_TRANSLUCENT_NAVIGATION, FLAG_TRANSLUCENT_NAVIGATION
& (~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowShowWallpaper, false)) {
setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
}
if (a.getBoolean(R.styleable.Window_windowEnableSplitTouch,
getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB)) {
setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
}
a.getValue(R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
a.getValue(R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
if (DEBUG) Log.d(TAG, "Min width minor: " + mMinWidthMinor.coerceToString()
+ ", major: " + mMinWidthMajor.coerceToString());
if (a.hasValue(R.styleable.Window_windowFixedWidthMajor)) {
if (mFixedWidthMajor == null) mFixedWidthMajor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedWidthMajor,
mFixedWidthMajor);
}
if (a.hasValue(R.styleable.Window_windowFixedWidthMinor)) {
if (mFixedWidthMinor == null) mFixedWidthMinor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedWidthMinor,
mFixedWidthMinor);
}
if (a.hasValue(R.styleable.Window_windowFixedHeightMajor)) {
if (mFixedHeightMajor == null) mFixedHeightMajor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedHeightMajor,
mFixedHeightMajor);
}
if (a.hasValue(R.styleable.Window_windowFixedHeightMinor)) {
if (mFixedHeightMinor == null) mFixedHeightMinor = new TypedValue();
a.getValue(R.styleable.Window_windowFixedHeightMinor,
mFixedHeightMinor);
}
if (a.getBoolean(R.styleable.Window_windowContentTransitions, false)) {
requestFeature(FEATURE_CONTENT_TRANSITIONS);
}
if (a.getBoolean(R.styleable.Window_windowActivityTransitions, false)) {
requestFeature(FEATURE_ACTIVITY_TRANSITIONS);
}
mIsTranslucent = a.getBoolean(R.styleable.Window_windowIsTranslucent, false);
final Context context = getContext();
final int targetSdk = context.getApplicationInfo().targetSdkVersion;
final boolean targetPreL = targetSdk < android.os.Build.VERSION_CODES.LOLLIPOP;
final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
if (!mForcedStatusBarColor) {
mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
}
if (!mForcedNavigationBarColor) {
mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
mNavigationBarDividerColor = a.getColor(R.styleable.Window_navigationBarDividerColor,
0x00000000);
}
if (!targetPreQ) {
mEnsureStatusBarContrastWhenTransparent = a.getBoolean(
R.styleable.Window_enforceStatusBarContrast, false);
mEnsureNavigationBarContrastWhenTransparent = a.getBoolean(
R.styleable.Window_enforceNavigationBarContrast, true);
}
WindowManager.LayoutParams params = getAttributes();
// Non-floating windows on high end devices must put up decor beneath the system bars and
// therefore must know about visibility changes of those.
if (!mIsFloating) {
if (!targetPreL && a.getBoolean(
R.styleable.Window_windowDrawsSystemBarBackgrounds,
false)) {
setFlags(FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS & ~getForcedWindowFlags());
}
if (mDecor.mForceWindowDrawsBarBackgrounds) {
params.privateFlags |= PRIVATE_FLAG_FORCE_DRAW_BAR_BACKGROUNDS;
}
}
if (a.getBoolean(R.styleable.Window_windowLightStatusBar, false)) {
decor.setSystemUiVisibility(
decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
if (a.getBoolean(R.styleable.Window_windowLightNavigationBar, false)) {
decor.setSystemUiVisibility(
decor.getSystemUiVisibility() | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
}
if (a.hasValue(R.styleable.Window_windowLayoutInDisplayCutoutMode)) {
int mode = a.getInt(R.styleable.Window_windowLayoutInDisplayCutoutMode, -1);
if (mode < LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
|| mode > LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
throw new UnsupportedOperationException("Unknown windowLayoutInDisplayCutoutMode: "
+ a.getString(R.styleable.Window_windowLayoutInDisplayCutoutMode));
}
params.layoutInDisplayCutoutMode = mode;
}
if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
>= android.os.Build.VERSION_CODES.HONEYCOMB) {
if (a.getBoolean(
R.styleable.Window_windowCloseOnTouchOutside,
false)) {
setCloseOnTouchOutsideIfNotSet(true);
}
}
if (!hasSoftInputMode()) {
params.softInputMode = a.getInt(
R.styleable.Window_windowSoftInputMode,
params.softInputMode);
}
if (a.getBoolean(R.styleable.Window_backgroundDimEnabled,
mIsFloating)) {
/* All dialogs should have the window dimmed */
if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
}
if (!haveDimAmount()) {
params.dimAmount = a.getFloat(
android.R.styleable.Window_backgroundDimAmount, 0.5f);
}
}
if (params.windowAnimations == 0) {
params.windowAnimations = a.getResourceId(
R.styleable.Window_windowAnimationStyle, 0);
}
// The rest are only done if this window is not embedded; otherwise,
// the values are inherited from our container.
if (getContainer() == null) {
if (mBackgroundDrawable == null) {
if (mFrameResource == 0) {
mFrameResource = a.getResourceId(R.styleable.Window_windowFrame, 0);
}
if (a.hasValue(R.styleable.Window_windowBackground)) {
mBackgroundDrawable = a.getDrawable(R.styleable.Window_windowBackground);
}
}
if (a.hasValue(R.styleable.Window_windowBackgroundFallback)) {
mBackgroundFallbackDrawable =
a.getDrawable(R.styleable.Window_windowBackgroundFallback);
}
if (mLoadElevation) {
mElevation = a.getDimension(R.styleable.Window_windowElevation, 0);
}
mClipToOutline = a.getBoolean(R.styleable.Window_windowClipToOutline, false);
mTextColor = a.getColor(R.styleable.Window_textColor, Color.TRANSPARENT);
}
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
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);
}
}
// Remaining setup -- of background and title -- that only applies
// to top-level windows.
if (getContainer() == null) {
mDecor.setWindowBackground(mBackgroundDrawable);
final Drawable frame;
if (mFrameResource != 0) {
frame = getContext().getDrawable(mFrameResource);
} else {
frame = null;
}
mDecor.setWindowFrame(frame);
mDecor.setElevation(mElevation);
mDecor.setClipToOutline(mClipToOutline);
if (mTitle != null) {
setTitle(mTitle);
}
if (mTitleColor == 0) {
mTitleColor = mTextColor;
}
setTitleColor(mTitleColor);
}
mDecor.finishChanging();
return contentParent;
}
我k,这代码着实有点多啊,300多行,但是聪明的你一定会发现,这段代码其实就是取各种属性然后设置给Window已经DecorView;
我们举个例子:
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestFeature(FEATURE_ACTION_BAR);
}
设置是否有标题栏。
if (a.getBoolean(R.styleable.Window_windowFullscreen, false)) {
setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN & (~getForcedWindowFlags()));
}
给窗口设置全屏。
其他的属性就不一一解释了,是不是感觉很熟悉,熟悉的味道,就是我们当年使用的各种requestWindowFeature()
。
这也就是为什么设置窗口属性要在setContentView
方法之前进行,因为在这之前我们要用到这些属性。
接下来我们看下关键的代码:
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
// System.out.println("Title Icons!");
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
// Special case for a window with only a progress bar (and title).
// XXX Need to have a no-title version of embedded windows.
layoutResource = R.layout.screen_progress;
// System.out.println("Progress!");
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
// Special case for a window with a custom title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
// XXX Remove this once action bar supports these features.
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
// If no other features and not embedded, only need a title.
// If the window is floating, we need a dialog layout
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
// System.out.println("Title!");
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
这段代码的作用就是根据getLocalFeatures()
方法的返回值来决定使用哪种控件的xml布局,比如没有标题栏,那就是使用没有标题栏控件的xml布局,哦原来如此,恍然大悟,这也就是我们页面默认加载的xml布局。
getLocalFeatures()
这个方法其实就是前面我们设置的各种属性的集合。
我们通过debug的方式看下默认页面的布局是哪一个:
最终使用的默认布局是
R.layout.screen_simple
。我们查看下这个布局是什么样的。
screen-simple.xml
就是一个
LinearLayout
,里面包含ActionBar布局以及真正用户视图的根容器FrameLayout
,id是content。熟悉吗?????哈哈哈。。。
OK,找到最终要使用默认布局的id之后,就直接被添加进了DecorView中。
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
具体怎样添加的这里就不去仔细看了,就是通过inflater将xml布局创建出来,然后add到DecorView中。也就是说经过这句代码之后,mDecor
中是已经有布局了控件,然后接下来通过ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
这句代码找到我们用户视图的id,然后将其返回。
也就是说generateLayout()
方法执行之后,mContentParent
就有值了,就是id为content
的布局FrameLayout;
我们用一张图来看下当前他们之间的关系:
Activity-Window-View-1
他们之间的关系是不是更清晰了。
目前到这里为止,我们从Activity
中的setContentView()
方法中传进来的XML
布局依旧没有用到,好尴尬~~~
回到setContentView()
中,我们接着往下走。
哇~~终于来了,在这里我们传进来的
XML
的布局ID,终于用到了。
mLayoutInflater.inflate(layoutResID, mContentParent);
这句代码就不用过多解释了吧,就是把我们传进来的布局添加到mContentParent
,mContentParent
是谁?还记得吧,就是我们刚刚看的那个screen-simple.xml
中的ID为@android:id/content
的FrameLayout
,就是把我们的布局添加到这里面。
ok,到这里一个完成的布局树就建立了。
大致是这样的:
到这里我们的布局是不是就会显示到屏幕中了呢?
很显然不是,因为每一个View都需要被调用:
- measure 测量
- layout 布局
- draw 绘制
一个View只有经过这个3个步骤之后才会被显示到屏幕上,仅仅是创建View是不够的。
而且我们知道,每一个子View的测量,布局和绘制都是它的Parent
来完成的,所以这里我们要找到最顶层的View(DecorView)的Parent
,这样我们才能真正了解这个测量,布局和绘制的整个流程。那么DecorView
的Parent
是谁呢?答案是:ViewRootImpl
。为什么是ViewRootImpl
呢?最简单的方式就是我们打印一下DecorView
的Parent
就行了。
ViewRootImpl
看到了吧,DecorView
的Parent
是ViewRootImpl
。
那么现在的Activity,Window,View之间的关系应该是这样的:
Activity-Window-View-2
接下来我们就要先看下ViewRootImpl
,因为它是最终的“父亲
”。在这之前我们要先看下ViewRootImpl
在哪里被创建的,同样的方式,我们打个断点看下就好了。
可以看到
ViewRootImpl
是在WindowManagerGlobal
中的addView()
方法中被创建的。咦?这个
WindowManagerGlobal
又是个什么东西呢?我们看下方法调用栈,接着往下找:WindowManagerImpl-1
哦?很熟悉的味道,在
ActivityThread
中的handleResumeActivity()
方法中调用了WindowManagerImpl
中的addView()
方法,然后在WindowManagerImpl
的addView()
方法又调用了WindowManagerGlobal
中的addView()
方法,从方法的调用找可以看出来这些,下面我们从源码的角度来看下是不是这样的。先看下
ActivityThread
中的handleResumeActivity()
方法:
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
mSomeActivitiesChanged = true;
// TODO Push resumeArgs into the activity for consideration
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
if (r == null) {
// We didn't actually resume the activity, so skipping any follow-up actions.
return;
}
if (mActivitiesToBeDestroyed.containsKey(token)) {
// Although the activity is resumed, it is going to be destroyed. So the following
// UI operations are unnecessary and also prevents exception because its token may
// be gone that window manager cannot recognize it. All necessary cleanup actions
// performed below will be done while handling destruction.
return;
}
final Activity a = r.activity;
......
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (r.mPreserveWindow) {
a.mWindowAdded = true;
r.mPreserveWindow = false;
// Normally the ViewRoot sets up callbacks with the Activity
// in addView->ViewRootImpl#setView. If we are instead reusing
// the decor view we have to notify the view root that the
// callbacks may have changed.
ViewRootImpl impl = decor.getViewRootImpl();
if (impl != null) {
impl.notifyChildRebuilt();
}
}
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
} else {
// The activity will get a callback for this {@link LayoutParams} change
// earlier. However, at that time the decor will not be set (this is set
// in this method), so no action will be taken. This call ensures the
// callback occurs with the decor set.
a.onWindowAttributesChanged(l);
}
}
// If the window has already been added, but during resume
// we started another activity, then don't yet make the
// window visible.
} else if (!willBeVisible) {
if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true;
}
......
}
代码有点多,我们只看重点的代码:
首先我们前面已经了解到了Activity
生命周期方法的调用时机,也就是说ActivithThread
中的handleResumeActivity()
方法中一定会调用Activity
的onResume()
方法,也就是final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
这行代码里面会调用onResume()
方法,具体这里就不在跟进去看了。
其次wm.addView(decor, l);
这行代码是将DecorView
按照设置的窗口属性添加到wm
中,这个wm
就是从当前Activity
中获取的WindowManager
,所以这个WindowManager
对象是什么时候被赋值的呢?还记得我们当时看的Activity
中的attach()
方法吗?我们当时是为了找setContentView()
的调用栈的,其中有这么一句代码:
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
原来WindowManager
也只在attach()
方法中被赋值的。
ok,现在我们也知道WindowManager
在哪被创建了的,接下来还回到ActivithThread
中的handleResumeActivity()
方法中,然后在看WindowManager
中的addView()
方法,结果找到了ViewManager
接口中的addVIew()
方法,很显然这不是我们想要的,还记得我们当时找PhoneWindow
的过程吗,所以我们只能去找WindowManager
的实现类,也就是:WindowManagerImpl
,即使我们不利用这中方式,就只看刚刚DEBUG的方法调用栈,ActivityThread
中的handleResumeActivity()
方法之后,下一个方法就是WindowManagerImpl
中的addView()
方法,所以这里我们要去到WindowManagerImpl
中的addView()
方法:
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
mContext.getUserId());
}
这里有调用了mGlobal
的addView()
方法,这个 mGlobal
是什么呢?
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
是WindowManagerImpl
中的一个成员变量,而且是个单例对象。接下来我们转战到WindowManagerGlobal
中的addView()
方法。
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow, int userId) {
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");
}
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
if (parentWindow != null) {
parentWindow.adjustLayoutParamsForSubWindow(wparams);
} else {
// If there's no parent, then hardware acceleration for this view is
// set from the application's hardware acceleration setting.
final Context context = view.getContext();
if (context != null
&& (context.getApplicationInfo().flags
& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
}
}
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Start watching for system property changes.
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);
}
int index = findViewLocked(view, false);
if (index >= 0) {
if (mDyingViews.contains(view)) {
// Don't wait for MSG_DIE to make it's way through root's queue.
mRoots.get(index).doDie();
} else {
throw new IllegalStateException("View " + view
+ " has already been added to the window manager.");
}
// The previous removeView() had not completed executing. Now it has.
}
// If this is a panel window, then find the window it is being
// attached to for future reference.
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++) {
if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
panelParentView = mViews.get(i);
}
}
}
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
可以看到ViewRootImpl
被创建,然后又被添加到WindowManagerGlobal
中所维护的一个ArrayList
中。
好了到这里我们的关系图又可以更新一波了,如下:
Activity-Window-View-3
但是这个过程是先后顺序的,同样都是通过Activity
中的attach()
方法,但是首先创建的PhoneWindow
对象,其次才是创建了WindowManagerImpl
对象,这也就意味着,通过setContentView
最终到DecorView
中的时,DecorView
中的mParent
即是ViewRootImpl
有一段时间是mParent=null
的。这里这个结论我们先记着。
这个关系现在已经梳理的差不多了,但是到现在为止,我们还没有看到任何有关一个View
相关的measure
,layout
和draw
相关的代码。ok,不着急,接下来我们进入到重点了,在WindowManagerGlobal
中的addView()
方法中有这样一句代码:
try {
root.setView(view, wparams, panelParentView, userId);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
这个root
是ViewRootImpl
,view
是DecorView
,哦,懂了懂了,这句代码就是讲ViewRootImpl
和DecorView
进行关联的。恭喜你,答对了。
我们看下这个setView()
方法。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
mView = view;
mAttachInfo.mDisplayState = mDisplay.getState();
mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
mWindowAttributes.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_USE_BLAST;
attrs = mWindowAttributes;
setTag();
if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
& WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
&& (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
Slog.d(mTag, "setView: FLAG_KEEP_SCREEN_ON changed from true to false!");
}
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
setAccessibilityFocus(null, null);
if (view instanceof RootViewSurfaceTaker) {
mSurfaceHolderCallback =
((RootViewSurfaceTaker)view).willYouTakeTheSurface();
if (mSurfaceHolderCallback != null) {
mSurfaceHolder = new TakenSurfaceHolder();
mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
mSurfaceHolder.addCallback(mSurfaceHolderCallback);
}
}
// Compute surface insets required to draw at specified Z value.
// TODO: Use real shadow insets for a constant max Z.
if (!attrs.hasManualSurfaceInsets) {
attrs.setSurfaceInsets(view, false /*manual*/, true /*preservePrevious*/);
}
CompatibilityInfo compatibilityInfo =
mDisplay.getDisplayAdjustments().getCompatibilityInfo();
mTranslator = compatibilityInfo.getTranslator();
// If the application owns the surface, don't enable hardware acceleration
if (mSurfaceHolder == null) {
// While this is supposed to enable only, it can effectively disable
// the acceleration too.
enableHardwareAcceleration(attrs);
final boolean useMTRenderer = MT_RENDERER_AVAILABLE
&& mAttachInfo.mThreadedRenderer != null;
if (mUseMTRenderer != useMTRenderer) {
// Shouldn't be resizing, as it's done only in window setup,
// but end just in case.
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;
mAttachInfo.mRootView = view;
mAttachInfo.mScalingRequired = mTranslator != null;
mAttachInfo.mApplicationScale =
mTranslator == null ? 1.0f : mTranslator.applicationScale;
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
}
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
InputChannel inputChannel = null;
if ((mWindowAttributes.inputFeatures
& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
inputChannel = new InputChannel();
}
mForceDecorViewVisibility = (mWindowAttributes.privateFlags
& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
try {
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
adjustLayoutParamsForCompatibility(mWindowAttributes);
res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mDisplayCutout, inputChannel,
mTempInsets, mTempControls);
setFrame(mTmpFrame);
} catch (RemoteException e) {
mAdded = false;
mView = null;
mAttachInfo.mRootView = null;
inputChannel = 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);
}
mPendingDisplayCutout.set(mAttachInfo.mDisplayCutout);
mAttachInfo.mAlwaysConsumeSystemBars =
(res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0;
mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
mInsetsController.onStateChanged(mTempInsets);
mInsetsController.onControlsChanged(mTempControls);
if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
mAdded = false;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
case WindowManagerGlobal.ADD_APP_EXITING:
throw new WindowManager.BadTokenException(
"Unable to add window -- app for token " + attrs.token
+ " is exiting");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
"Unable to add window -- window " + mWindow
+ " has already been added");
case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- another window of type "
+ mWindowAttributes.type + " already exists");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- permission denied for window type "
+ mWindowAttributes.type);
case WindowManagerGlobal.ADD_INVALID_DISPLAY:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified display can not be found");
case WindowManagerGlobal.ADD_INVALID_TYPE:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified window type "
+ mWindowAttributes.type + " is not valid");
case WindowManagerGlobal.ADD_INVALID_USER:
throw new WindowManager.BadTokenException("Unable to add Window "
+ mWindow + " -- requested userId is not valid");
}
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
if ((res & WindowManagerGlobal.ADD_FLAG_USE_BLAST) != 0) {
mUseBLASTAdapter = true;
}
if ((res & WindowManagerGlobal.ADD_FLAG_USE_TRIPLE_BUFFERING) != 0) {
mEnableTripleBuffering = true;
}
if (view instanceof RootViewSurfaceTaker) {
mInputQueueCallback =
((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
}
if (inputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
}
view.assignParent(this);
mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
if (mAccessibilityManager.isEnabled()) {
mAccessibilityInteractionConnectionManager.ensureConnection();
}
if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
// Set up the input pipeline.
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);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
if (mView instanceof RootViewSurfaceTaker) {
PendingInsetsController pendingInsetsController =
((RootViewSurfaceTaker) mView).providePendingInsetsController();
if (pendingInsetsController != null) {
pendingInsetsController.replayAndAttach(mInsetsController);
}
}
}
}
}
我k,这代码着实有点多啊,其实我都不想贴出来了。。。。。。
问题不大,我们找关键代码:
mView = view;
这句代码是将DecorView
赋值给ViewRootImpl
中的成员变量mView
。
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();
这里就是我们所有View第一次进行layout
的调用。哦,原来View
的布局是在这里触发的啊。
点进去看下:
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
checkThread();
这句代码是干嘛用的?点进去看下:
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
这个报错信息熟悉吗?我k,这不就是我们在子线程中更新UI报的错误嘛。。。哦,原来是在这里报的错呀。。
接下来我们点到scheduleTraversals()
这个方法中看下是干什么用的:
@UnsupportedAppUsage
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这行代码:
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
编舞者对象向主线程post一个Runnable
,这是一个异步操作,也就是说再下一个Loop循环周期时,会调用post进去的这个Runnable
,也就是传进去的mTraversalRunnable
,是下一个循环周期才会调用,而不是立刻调用。
为了保证我们的异步消息被优先执行,在这只之前插入一条同步屏障的消息:
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
至于同步屏障消息,就是插入一个条target=null
的消息,来过滤同步消息和异步消息,将同步消息屏蔽,优先执行异步消息,在合适的时候移除屏障消息,大致原理就是这样的,这里就不在详细说了。
接下来看这个mTraversalRunnable
做了什么:
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
很简单就是执行doTraversal()
方法,跟进去看下:
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
这行:
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
刚刚已经说过,同步屏障消息会在合适的时候移除,这里就移除了刚刚添加的同步屏障的消息,因为这个异步方法已经执行到了。
接下来我们看下performTraversals();
这个方法,这个方法是ViewRootImpl
的核心代码,但是由于方法太长了,大概有800行左右的代码,这里我们就不全部贴出来了,我们只截取比较重要的部分来说一说:
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth;
int desiredWindowHeight;
这里分别是:窗口属性和所期望的宽和高。窗口属性默认的是MATCH_PARENT
,如果是Activity的话,通过window.getAttributes()
获取的窗口属性和这里的mWindowAttributes
是一样的。
往下看:
Rect frame = mWinFrame;
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
final Configuration config = mContext.getResources().getConfiguration();
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
// For wrap content, we have to remeasure later on anyways. Use size consistent with
// below so we get best use of the measure cache.
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
} else {
// After addToDisplay, the frame contains the frameHint from window manager, which
// for most windows is going to be the same size as the result of relayoutWindow.
// Using this here allows us to avoid remeasuring after relayoutWindow
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
}
// We used to use the following condition to choose 32 bits drawing caches:
// PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
// However, windows are now always 32 bits by default, so choose 32 bits
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mWindowVisibility = viewVisibility;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
// Set the layout direction if it has not been set before (inherit is the default)
if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
host.setLayoutDirection(config.getLayoutDirection());
}
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
如果是第一次调用,会进入到mFirst
条件的代码块中,然后标记mLayoutRequested=true;
,从名字就可以看出来,标记为true
,是需要进行重新布局的。
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
// For wrap content, we have to remeasure later on anyways. Use size consistent with
// below so we get best use of the measure cache.
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
} else {
// After addToDisplay, the frame contains the frameHint from window manager, which
// for most windows is going to be the same size as the result of relayoutWindow.
// Using this here allows us to avoid remeasuring after relayoutWindow
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
}
这里是根据窗口属性来设置我们布局预期的宽和高。
- 类型是包含状态栏,输入法或者含有音量布局,设置实际的宽和高。
- 类型是
WRAP_CONTENT
,设置屏幕的宽和高。 - 否则就是当前窗口的大小。
往下看:
host.dispatchAttachedToWindow(mAttachInfo, 0);
这个host
,就是mView
,也就是DecorView
,即是在这里调用了View
的dispatchAttachedToWindow()
方法,我们去看下View
中的dispatchAttachedToWindow()
方法。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
registerPendingFrameMetricsObservers();
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
// Transfer all pending runnables.
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);
mRunQueue = null;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
// NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to
// perform the dispatching. The iterator is a safe guard against listeners that
// could mutate the list by calling the various add/remove methods. This prevents
// the array from being modified while we iterate it.
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);
if (isShown()) {
// Calling onVisibilityAggregated directly here since the subtree will also
// receive dispatchAttachedToWindow and this same call
onVisibilityAggregated(vis == VISIBLE);
}
}
// Send onVisibilityChanged directly instead of dispatchVisibilityChanged.
// As all views in the subtree will already receive dispatchAttachedToWindow
// traversing the subtree again here is not desired.
onVisibilityChanged(this, visibility);
if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
// If nobody has evaluated the drawable state yet, then do it now.
refreshDrawableState();
}
needGlobalAttributesUpdate(false);
notifyEnterOrExitForAutoFillIfNeeded(true);
notifyAppearedOrDisappearedForContentCaptureIfNeeded(true);
}
我们看到第一行:
mAttachInfo = info;
把ViewRootImpl
中的mAttachInfo
给传进来了,也就是说当前界面中的一组View所使用的AttachInfo
都是同一个,所以说判断一个View有没有依附到Window
上,可以判断AttachInfo
是否为空。而View
中的:
public boolean isAttachedToWindow() {
return mAttachInfo != null;
}
也正是这样做的,哈哈哈哈。。。
接下来:
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
是TreeObserver
证明周期的一个回调,attachToWindow
。
dispatchApplyInsets(host);
这个方法就是系统为当前的窗口添加状态栏和导航栏。
也就是说经过这一步,再从DecorView
中获取child
就不是一个了,它真正的样子应该是这样的:
看下这句代码:
getRunQueue().executeActions(mAttachInfo.mHandler);
使用mAttachInfo
的mHandler
执行Runnable
。
看源码也正是这样做的:
public void executeActions(Handler handler) {
synchronized (this) {
final HandlerAction[] actions = mActions;
for (int i = 0, count = mCount; i < count; i++) {
final HandlerAction handlerAction = actions[i];
handler.postDelayed(handlerAction.action, handlerAction.delay);
}
mActions = null;
mCount = 0;
}
}
这,,,到底是干什么的?
还记得我们在View中切换线程怎么用的吗?
view.post(runnable);
对,就是通过post
方法来切换到主线程执行Runnable
对象。
我们看下源码:
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
如果mAttachInfo
不为空,就立即使用mAttachInfo
的mHandler
来将这个Runnable
对象post
进去。如果为空,则将这个Runnable
对象添加到待执行的一个队列中:mRunQueue
,然后等待合适的时机执行这些Runnable
任务。合适的时机在哪?答案就是我们正在分析的这句代码:
getRunQueue().executeActions(mAttachInfo.mHandler);
ok,那mAttachInfo
中的mHandler
又是什么时候创建的呢?
我们知道mAttachInfo
是在ViewRootImpl
中创建的:
mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,context);
可以看到创建AttachInfo
对象时,传入了一个mHandler
对象,这个对象就是我们在mAttachInfo
中所使用的mHandler
对象,那这个mHandler
又是在哪创建的呢?
final ViewRootHandler mHandler = new ViewRootHandler();
哦,它是ViewRootImpl
的一个成员变量,也就是创建ViewRootImpl
时就创建了该对象,即是在主线程中创建的Handler
对象,也就是说所有Runnable
的执行都会被切换到主线程中来执行。
接着往下看:
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
第一次的时候mLayoutRequested
,mStopped
表示当前窗口被关闭的时候才会为true
,或者执行onStop()
方法时。所以这个layoutRequested
为true
。接下来就会进入到:
if (layoutRequested) {
final Resources res = mView.getContext().getResources();
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
} else {
if (!mPendingDisplayCutout.equals(mAttachInfo.mDisplayCutout)) {
cutoutChanged = true;
}
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
Configuration config = res.getConfiguration();
desiredWindowWidth = dipToPx(config.screenWidthDp);
desiredWindowHeight = dipToPx(config.screenHeightDp);
}
}
}
// Ask host how big it wants to be
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
}
重点看最后一行:
windowSizeMayChange |= measureHierarchy(host, lp, res,
desiredWindowWidth, desiredWindowHeight);
从方法的名字可以看出是对整个视图树的测量。我们点进去看下:
private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
int childWidthMeasureSpec;
int childHeightMeasureSpec;
boolean windowSizeMayChange = false;
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) Log.v(mTag,
"Measuring " + host + " in display " + desiredWindowWidth
+ "x" + desiredWindowHeight + "...");
boolean goodMeasure = false;
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
// On large screens, we don't want to allow dialogs to just
// stretch to fill the entire width of the screen to display
// one line of text. First try doing the layout at a smaller
// size to see if it will fit.
final DisplayMetrics packageMetrics = res.getDisplayMetrics();
res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
int baseSize = 0;
if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
baseSize = (int)mTmpValue.getDimension(packageMetrics);
}
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
+ ", desiredWindowWidth=" + desiredWindowWidth);
if (baseSize != 0 && desiredWindowWidth > baseSize) {
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight()
+ ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
+ " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
goodMeasure = true;
} else {
// Didn't fit in that size... try expanding a bit.
baseSize = (baseSize+desiredWindowWidth)/2;
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
+ baseSize);
childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
+ host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
if (DEBUG_DIALOG) Log.v(mTag, "Good!");
goodMeasure = true;
}
}
}
}
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after measure");
host.debug();
}
return windowSizeMayChange;
}
因为我们的默认的宽和高都是MATCH_PARENT
,所以代码是不会走到if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {}
这个代码块中的,直接到下面:
if (!goodMeasure) {
childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
windowSizeMayChange = true;
}
}
根据期望的宽和高以及窗口的宽高来得出MeasureSpec
,在得到宽和高的MeasureSpec
之后进入测量代码:
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
到这里是不是感觉很熟悉了,就是当我们自定义View的时候重写onMeasure()
方法时,传入的参数。我们点到这个方法去看看,到底是怎样开始测量的。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
哦?原来是直接调用了mView
的measure()
方法,还记得这个mView
是谁吗?对,就是DecorView
,也就是说此时开始最顶层的View
开始进行了测量,所以它的子View就会一层一层的被调用到,然后完成这个视图树的测量工作。
ok,测量的工作我们找到了,接下来我们回到performTraversals()
方法,接着往下看:
if (mFirst || windowShouldResize || viewVisibilityChanged || cutoutChanged || params != null || mForceNextWindowRelayout) {
......
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
......
}
因为是第一次进来,所以这个判断条件是会满足的,所以会进入到代码块中,也就是会执行:
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
点进去看下:
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
float appScale = mAttachInfo.mApplicationScale;
boolean restore = false;
if (params != null && mTranslator != null) {
restore = true;
params.backup();
mTranslator.translateWindowLayout(params);
}
if (params != null) {
if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
if (mOrigWindowType != params.type) {
// For compatibility with old apps, don't crash here.
if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
Slog.w(mTag, "Window type can not be changed after "
+ "the window is added; ignoring change of " + mView);
params.type = mOrigWindowType;
}
}
}
long frameNumber = -1;
if (mSurface.isValid()) {
frameNumber = mSurface.getNextFrameNumber();
}
int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5f),
(int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
mTmpFrame, mTmpRect, mTmpRect, mTmpRect, mPendingBackDropFrame,
mPendingDisplayCutout, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
mTempControls, mSurfaceSize, mBlastSurfaceControl);
if (mSurfaceControl.isValid()) {
if (!useBLAST()) {
mSurface.copyFrom(mSurfaceControl);
} else {
final Surface blastSurface = getOrCreateBLASTSurface(mSurfaceSize.x,
mSurfaceSize.y);
// If blastSurface == null that means it hasn't changed since the last time we
// called. In this situation, avoid calling transferFrom as we would then
// inc the generation ID and cause EGL resources to be recreated.
if (blastSurface != null) {
mSurface.transferFrom(blastSurface);
}
}
} else {
destroySurface();
}
mPendingAlwaysConsumeSystemBars =
(relayoutResult & WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
if (restore) {
params.restore();
}
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWinFrame(mTmpFrame);
}
setFrame(mTmpFrame);
mInsetsController.onStateChanged(mTempInsets);
mInsetsController.onControlsChanged(mTempControls);
return relayoutResult;
}
因为每一个窗口的大小并不是由应用层来决定的,而是由ActivityManagerService(AMS)
来决定的。所以这里就是来告诉AMS
需要重新设置窗口的大小。
接下来往下看:
if (!mStopped || mReportNextDraw) {
boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
(relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
|| mHeight != host.getMeasuredHeight() || dispatchApplyInsets ||
updatedConfiguration) {
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="
+ mWidth + " measuredWidth=" + host.getMeasuredWidth()
+ " mHeight=" + mHeight
+ " measuredHeight=" + host.getMeasuredHeight()
+ " dispatchApplyInsets=" + dispatchApplyInsets);
// Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
前面说过这个mStopped
不出意外的话都是false
,所以会进入到这个代码块中,我们看到这个代码块中又执行了一次测量操作:
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
所以到目前为止已经执行2次测量了。为什么要重复测量呢?其实不然,因为第一次测量其实是用来确定窗口的大小的,而第二次才是真正的测量View
的大小。
所以经过这两次测量之后,窗口的尺寸和视图树的尺寸都已完成确定了值。既然尺寸已经确定了,那么接下来就是视图树的布局和绘制了。
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
performLayout(lp, mWidth, mHeight);
// By this point all views have been sized and positioned
// We can compute the transparent area
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
if (mTranslator != null) {
mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
}
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
// reconfigure window manager
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after setFrame");
host.debug();
}
}
因为前面layoutRequested
是true
,而mStopped
是false
,所以会执行下面的代码:
performLayout(lp, mWidth, mHeight);
而这个方法中会调用:
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
其中host
就是我们的DecorView
,所以到这里就进行了视图树的布局操作,一层一层往下调用,最终完成整个视图树的布局。
接着再往下看:
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
......
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
因为didLayout
为true
,所以triggerGlobalLayoutListener
也为true
,就会执行mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
,这个方法是回调整个视图树布局完成,仔细看下是不是很熟悉,当我们想最快拿到一个View的大小的时候,是不是就是通过监听视图树的回调来完成的?哦原来是这样。这也是拿到一个控件大小最快的最合适的方式,比view.post()
的时机要好。
ok,到这里我们已经找到View
的测量和布局,只剩下绘制了。
接着往下走:
boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
if (!cancelDraw) {
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
performDraw();
} else {
if (isViewVisible) {
// Try again
scheduleTraversals();
} else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
}
可以看到当视图可见时,就会调用performDraw();
。点进去看下:
因为代码有点多,我们就只贴重要的部分:
private void performDraw() {
......
boolean canUseAsync = draw(fullRedrawNeeded);
......
}
可以看到这里和performMeasure()
以及performLayout()
不一样,performDraw()
,会先调用ViewRootImpl
自己的draw()
方法。我们看下这个darw()
方法:
同样代码也是比较多,直贴重要的:
......
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
......
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
......
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
......
可以看到draw
和measure
,layout
不同,会区分是否开启了硬件加速绘制,如果开启了就使用硬件加速绘制,否则使用软件绘制。
在软件绘制方法里,最终是调用了mView.draw()
方法,也就是开始了整个视图树的绘制工作。
我们先更新一下关系图:
ok,到这里基本上整个View的绘制流程就分析完了。基于以上的分析,我们思考下,子线程中可以更新UI吗?或者说在什么条件下,子线程中可以更新UI。
首先答案是:子线程中可更新UI。
什么条件下可以在子线程中更新UI呢?
就是ViewRootImp
还没有成为了DecorView
的Parent
时,在这之前都可以在子线程中更新UI,因为当ViewRootImpl
一旦成为了DecorView
的Parent
时,当执行requestLayout()
会触发线程检查,即checkThread()
,而这个checkThread()
所做的事情就是判断当前线程是否为主线程,如果不是,就我们熟悉的那个错误:
Only the original thread that created a view hierarchy can touch its views.
ok,既然知道是这么回事,那么我们就可以在checkThread
之前搞点事情。
- 子线程中更新
TextView
的文字子线程中更新之前,先在主线程中执行
requestLayout()
,然后在子线程中再更新文字。
原理:主线程中触发requestLayout()
之后,再出发requestLayout()
会被忽略,代码上做了优化。
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
当主线程中执行requestLayout
,会导致mPrivateFlags |= PFLAG_FORCE_LAYOUT;
,然后在接下来的mParent.requestLayout();
其中的mParent.isLayoutRequested()
条件不满足,所以不会执行parent.reqeustLayout()
,也就不会执行checkThread()
。这个标志位mPrivateFlags |= PFLAG_FORCE_LAYOUT;
等到控件layout()
的时候会重置。
同样的道理,我们在TextView
现在主线程设置文字,然后在子线程在更新文字,也不会报错,道理相同。因为TextView
的setText()
方法中会触发requestLayout()
方法。
- 直接使用
windowManager
的addView()
方法。原理:
ViewRootImpl
中的checkThread()
方法只是检测线程是否一致,所以我们只需要保持线程一致就好。我们知道ViewRootImpl
只在WindowManagerGlobal
中的addView()
方法中创建的,所以如果在子线程中addView()
即可,但是这里需要注意一点,因为创建ViewRootImpl
时会初始化Handler
,因为子线程中没有Looper
对象,所以在addView()
之前要先准备Looper.prepare()
,之后要加上Looper.loop()
。
伪代码如下:
thread{
Looper.prepare()
val button = Button(this)
windowManager.addView(button, WindowManager.LayoutParams())
button.setOnClickListener{
button.text = "${Thread.currentThread().name} : ${SystemClock.uptimeMillis()}"
}
Looper.loop()
}
-
固定宽高的
TextView
,在子线程中更新文字,前提开启硬件加速。原理:当
TextView
未固定宽高是,设置文字时会动态监测layout
的高度是否发生变化,如果没有变化,则不执行requestLayout()
,而是直接invalidate()
之后直接返回。但为什么要开启硬件加速呢?因为在执行invalidate()
时,如果开启了硬件加速,会直接进行快捷绘制,直接调用到ViewRootImpl
中的invalidate()
方法,进而触发scheduleTraversals()
,注意这里并没有走checkThread()
方法;如果关闭了硬件加速,则会走正常的布局流程,就会触发checkThread()
方法。 -
SurfaceView
原理因为SurfaceView的绘制流程和常用的View不一样,不会检测线程。
个人能力有限,如有错误之处,还望指出,我会第一时间验证并修改。
网友评论