preface
This article is mainly from the Activity setContentView() set our View ContentView pointcut to analyze how our View is loaded into the process, to learn the View loading process.
Activiy’s startup initialization
First let’s take a look at what the Activity does when it starts. Here’s a picture of what it does when it starts.
The ActivityThread#handleLaunchActivity() method is called when an Activity is started. The core method is ActivityThread#performLaunchActivity(). If you are familiar with the ASM Activity startup process, you will know it. If you are not familiar with it, it will not affect the following process.
public Activity handleLaunchActivity(ActivityClientRecord r, PendingTransactionActions pendingActions, Intent customIntent) {...// performLaunchActivity is called
finalActivity a = performLaunchActivity(r, customIntent); .return a;
}
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {... ContextImpl appContext = createBaseContextForActivity(r); Activity activity =null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 1. Create the Activity by mInstrumentation
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) {
.....
}
try{...if(activity ! =null) {
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if(r.overrideConfig ! =null) {
config.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
Window window = null;
if(r.mPendingRemoveWindow ! =null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
//2. Call the Attach method of the activity and create a PhoneWindow
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);
if(customIntent ! =null) {
activity.mIntent = customIntent;
}
r.lastNonConfigurationInstances = null;
checkAndBlockForNetworkAccess();
activity.mStartedActivity = false;
int theme = r.activityInfo.getThemeResource();
if(theme ! =0) {
activity.setTheme(theme);
}
activity.mCalled = false;
//5. Invoke the Activity onCreate lifecycle with the mInstrumentation
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
if(! activity.mCalled) {throw new SuperNotCalledException(
"Activity " + r.intent.getComponent().toShortString() +
" did not call through to super.onCreate()");
}
r.activity = activity;
}
r.setState(ON_CREATE);
mActivities.put(r.token, r);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
......
}
return 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) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
//3. Create the PhoneWindow member variable in Activiy
mWindow = new PhoneWindow(this, window, activityConfigCallback); .//4. Set WindowManagerImpl instance to PhoneWindowmWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) ! =0);
if(mParent ! =null) {
mWindow.setContainer(mParent.getWindow());
}
//WindowManagerImpl instances assign values to mWindowManager local variables
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
setAutofillCompatibilityEnabled(application.isAutofillCompatibilityEnabled());
enableAutofillCompatibilityIfNeeded();
}
Copy the code
There are five core steps:
- Create an Activity instance with the mInstrumentation
- Execute the Attach method of the activity
- Create the PhoneWindow and assign it to the activity variable mWindow
- Pass the WindowManangerImpl instance to PhoneWindow
- Call the Activity through the mInstrumentation to walk the onCreate lifecycle
This is where the initialization of the Activity is done, so let’s look at the initialization of the setContentView in onCreate
View load initialization
Let’s go straight to the flow after calling the onCreate method, starting with the figure above
setContentView
public void setContentView(@LayoutRes int layoutResID) {
// Get PhoneWindow setting contentView
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Copy the code
So inside the onCreate method we’re going to call the setContentView method, which is actually calling the PhoneWindow setContentView
@Override
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.
// mContentParent is the parent View of our View, if it doesn't exist we need to load it out
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 {
// Load the contentView into mContentParentmLayoutInflater.inflate(layoutResID, mContentParent); }... }Copy the code
The above code has two core steps
- InstallDecor initializes DecorView and mContentParent
- Load the contentView into the mContentParent
InstallDecor initializes DecorView and mContentParent
private void installDecor(a) {
mForceDecorInstall = false;
// create a DecorView
if (mDecor == null) {
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if(! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! =0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }}else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 2. Initialize mContentParent
mContentParent = generateLayout(mDecor);
// Set up decor part of UI to ignore fitsSystemWindows if appropriate.
mDecor.makeOptionalFitsSystemWindows();
finalDecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById( R.id.decor_content_parent); . }}// Create our decorView
protected DecorView generateDecor(int featureId) {
// System process doesn't have application context and in that case we need to directly use
// the context we have. Otherwise we want the application context, so we don't cling to the
// activity.
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext());
if(mTheme ! = -1) { context.setTheme(mTheme); }}}else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme..// The rest are only done if this window is not embedded; otherwise,
// the values are inherited from our container..// Inflate the window decor.
// 3. This code gives us the decor layout resource logic. The resources here are all internal resources of the system
int layoutResource;
int features = getLocalFeatures();
// System.out.println("Features: 0x" + Integer.toHexString(features));
if ((features & (1<< FEATURE_SWIPE_TO_DISMISS)) ! =0) {
layoutResource = R.layout.screen_swipe_dismiss;
setCloseOnSwipeEnabled(true);
} else 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();
// 4. Load the layout into the decorView
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 5. Get contentParentViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); .return contentParent;
}
Copy the code
This is the View initialization process. Views create views by parsing XML using LayoutInflaters. If you’re interested in how LayoutInflaters parse XML, you can read layoutInflaters separately. DecorView > rootView > rootView > rootView > rootView > rootView > rootView > rootView > rootView
What does the DecorView onResourcesLoaded do?
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {... mDecorCaptionView = createDecorCaptionView(inflater);1. Initialize our root View layout
final View root = inflater.inflate(layoutResource, null);
if(mDecorCaptionView ! =null) {
if (mDecorCaptionView.getParent() == null) {
addView(mDecorCaptionView,
new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mDecorCaptionView.addView(root,
new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
} else {
// Put it below the color views.
// 2. Add root view to DecorView
addView(root, 0.new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
public void addView(View child, int index, LayoutParams params) {
if (DBG) {
System.out.println(this + " addView");
}
if (child == null) {
throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
}
// addViewInner() will call child.requestLayout() when setting the new LayoutParams
// therefore, we call requestLayout() on ourselves before, so that the child's request
// will be blocked at our level
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
Copy the code
First we initialize the rootView with LayoutInfater. Second we add the rootView to our DecorView because our DecorView is a subclass of FramLayout. Ok, so our View is added to the DecorView through this process.
Do you think the whole process is over?
Note: There are only associations between the Activity, PhoneWindow, decorView, and contentView. Our View (decorView) is not actually added to the Window. The following figure
So the next step is for our decorView to actually load into the Window
The DecorView loads into the Window
So let’s go back to the ActivityThread’s handleResumeActivity method, which executes after the handleLaunchActivity. Why go back to this method is a matter of designing the ASM startup process.
Let’s move on to the handleResumeActivity method below
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {...if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
// 1. Get our WindowManagerImpl
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;
ViewRootImpl impl = decor.getViewRootImpl();
if(impl ! =null) { impl.notifyChildRebuilt(); }}if (a.mVisibleFromClient) {
if(! a.mWindowAdded) { a.mWindowAdded =true;
// 2. Add decorView to our WindowManagerImpl
wm.addView(decor, l);
} else{ a.onWindowAttributesChanged(l); }}}else if(! willBeVisible) {if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
r.hideForNow = true; }... }Copy the code
The WindowManagerImpl in the first step is created by calling (WindowManager) Context.getSystemService (context.window_service) when the activity attach is attached. Set to PhoneWindow.
The second step is to add the decorView to the Windows ManagerImpl
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
Copy the code
MGlobal is a singleton of the Windows ManagerGlobal class, so let’s move on
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {... ViewRootImpl root; View panelParentView =null;
synchronized (mLock) {
// Start watching for system property changes..//1. Here we start creating our ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
//2. Set LayoutParams in decorView. The parent is not set in decorView so the drawing process is not triggered
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 {
//3. The critical step is to actually add the DecorView to the ViewRootImpl
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throwe; }... }}Copy the code
The three core steps above:
- The first step is to create our ViewRootImpl
- The second step is to set the LayoutParams to the decorView. At this point, the decorView has not been parent so the drawing process will not be triggered
- The third critical step is to actually add the DecorView to the ViewRootImpl
Let’s move on to the setView method of view Rule PL
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
// Omit large chunks of code here. 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.
//1. Call requestLayout to trigger the drawing processrequestLayout(); .//3. Set ViewRootImpl as the parent of the DecorView, after which the View's requestLayout and InitialDate trigger the drawing process
view.assignParent(this); . }}}@Override
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true;
//2. Post a queue task to draw a viewscheduleTraversals(); }}void scheduleTraversals(a) {
if(! mTraversalScheduled) { mTraversalScheduled =true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run(a) { doTraversal(); }}Copy the code
- Call requestLayout to post a queue task in the main thread to trigger the drawing process. Note that when the Activity onCreate calls the View, measureHeight is 0, because the drawing process has not yet started
- Set ViewRootImpl as the parent of the DecorView, after which the View’s requestLayout and InitialDate trigger the drawing process