Note: The source version analyzed in this article is Android 27
If reproduced, please indicate the source
(Update at 2021-6-11)
First, application startup
First, we need to know:
The Main method of ActivityThread is the entry point for Android application startup.
public final class ActivityThread {
// Omit some code
public static void main(String[] args) {
// Omit some code
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited"); }}Copy the code
The main method does the following:
- 1, called first
Looper.prepareMainLooper()
Method, which creates a Looper object associated with the current thread (main thread). - 2. Then create one
ActivityThread
Object and call itattach()
Methods. - 3. Last call
Looper.loop()
Method to loop the newly created Looper object from its MessageQueue and execute it.
Now, what does the Attach method of the ActivityThread do?
public final class ActivityThread {
final ApplicationThread mAppThread = new ApplicationThread();
// Omit some code
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
if(! system) {// Omit some code
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
// omit subsequent code}}}Copy the code
In the attach approach, called ActivityManager. GetService () method, get to the remote IActivityManager object, and a ApplicationThread instance into the.
ApplicationThread inherits from iApplicationThread. Stub, the native implementation class for communication between Binder processes. It has a number of Activity lifecycle related methods, most of which are named scheduleXXX:
At this point, we can summarize:
ActivityThread
themain
Method, which is the entry point to the application launch.- The Activity lifecycle is controlled by the underlying system, through
Binder
Mechanism, callback toApplicationThread
thescheduleXXX
Methods. ActivityThread
andApplicationThread
For each application, there is only one instance.
Here’s a question:
Why is there no scheduleStartActivity method? Isn’t the Activity’s onStart() lifecycle callback controlled by ActivityManager sending messages?
With that in mind, we started reading the source code for these scheduleXXX methods.
2. Activity lifecycle
We start our analysis with the scheduleLaunchActivity method in ApplicationThread, because the name implies that it should perform Activity creation and startup.
public final class ActivityThread {
// Omit some code
private class ApplicationThread extends IApplicationThread.Stub {
// Omit some code
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
// Assign an ActivityClientRecord value
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
// Omit some code}}Copy the code
In the scheduleLaunchActivity method, an ActivityClientRecord object is first created and assigned.
ActivityClientRecord: Records activity-related data.
The thread is then switched from the Binder thread to the main thread by a Handler. Finally, the handleLaunchActivity method of the ActivityThread is called.
public final class ActivityThread {
// Omit some code
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
// Omit some code
Activity a = performLaunchActivity(r, customIntent);
if(a ! =null) {
// Omit some code
handleResumeActivity(r.token, false, r.isForward, ! r.activity.mFinished && ! r.startsNotResumed, r.lastProcessedSeq, reason);if(! r.activity.mFinished && r.startsNotResumed) {// The activity manager actually wants this one to start out paused, because it
// needs to be visible but isn't in the foreground. We accomplish this by going
// through the normal startup (because activities expect to go through onResume()
// the first time they run, before their window is displayed), and then pausing it.
// However, in this case we do -not- need to do the full pause cycle (of freezing
// and such) because the activity manager assumes it can just retain the current
// state it has.
performPauseActivityIfNeeded(r, reason);
// Omit some code}}}}Copy the code
For the moment, let’s ignore what’s done in the FormLaunchActivity method and just look at the subsequent code. The next code calls handleResumeActivity, assuming it should call the Activity’s onResume method.
Based on the Activity lifecycle:
In the performLaunchActivity method, the onCreate and onStart methods of the Activity must be called in sequence.
With that in mind, start analyzing the FormLaunchActivity method.
public final class ActivityThread {
// Omit some code
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// Omit some code
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// Omit some code
} catch (Exception e) { /* omit some code */ }
try {
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// Omit some code
if(activity ! =null) {
// Omit some code
Window window = null;
if(r.mPendingRemoveWindow ! =null && r.mPreserveWindow) {
window = r.mPendingRemoveWindow;
r.mPendingRemoveWindow = null;
r.mPendingRemoveWindowManager = null;
}
appContext.setOuterContext(activity);
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);
// Omit some code
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
// Omit some code
if(! r.activity.mFinished) { activity.performStart(); r.stopped =false;
}
if(! r.activity.mFinished) { activity.mCalled =false;
if (r.isPersistable()) {
mInstrumentation.callActivityOnPostCreate(activity, r.state,
r.persistentState);
} else {
mInstrumentation.callActivityOnPostCreate(activity, r.state);
}
// Omit some code
}
}
r.paused = true;
/ / save ActivityClientRecord
mActivities.put(r.token, r);
} catch { /* omit catch code */ }
return activity;
}
Copy the code
The above code does the following:
- Creating an Activity object
Call the newActivity method of Instrumentation to create an Activity object through reflection.
- Initialize the Activity
The attach method of the Activity object is called, which initializes some of the Activity’s data and sets the Window object for the Activity. Note: The Window object of the Activity is not the same object as the Window object passed in. This also means that each Activity has its own Window object.
public class Activity extends.{
// Omit some code
private Window mWindow;
private WindowManager mWindowManager;
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*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// Omit some codemWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) ! =0);
// Omit some code
mWindowManager = mWindow.getWindowManager();
// Omit some code
}
// Omit some code
}
Copy the code
- Call three lifecycle methods
1, Instrumentation. CallActivityOnCreate method, this method is invoked in the activity. The performCreate () method. 2, activity. PerformStart () method. 3, Instrumentation. CallActivityOnPostCreate method, this method will be called in the activity. The onPostCreate () method.
Call onCreate, onStart, and onPostCreate in order to verify performLaunchActivity.
To sum up:
In the handleLaunchActivity method, the following lifecycle is called back:
onCreate()
->onStart()
->onPostCreate()
->onResume()
Note: if ActivityClientRecord startsNotResumed = true, life cycle process will become:
onCreate()
->onStart()
->onPostCreate()
->onResume()
->onPause()
Two, interface loading
From the previous section, we learned that the handleLaunchActivity method calls back to the Activity’s onCreate() lifecycle method.
Normally, when creating an Activity, we call setContentView in onCreate() to set the layout file.
Let’s start by analyzing how our custom layout file is loaded.
2.1. Set the layout
First analyze the source code of the setContentView method in the Activity.
public class Activity extends.{
// Omit some code
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
// Omit some code
}
Copy the code
As you can see from the previous analysis, the getWindow() method here returns the Activity’s unique Window object, which is assigned in the Attach method.
Window is an abstract class, and as you can see from the annotations on the class, it only has a subclass called PhoneWindow, so let’s look directly at PhoneWindow’s setContentView method.
public class PhoneWindow extends Window implements MenuBuilder.Callback {
// Omit some code
ViewGroup mContentParent;
@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.
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;
}
// Omit some code
}
Copy the code
If mContentParent == null, call the installDecor() method and then load the layout resources passed in to mContentParent.
So it’s safe to assume that the installDecor() method is used to create mContentParent objects.
public class PhoneWindow extends Window implements MenuBuilder.Callback {
// Omit some code
private DecorView mDecor;
ViewGroup mContentParent;
private void installDecor(a) {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
// Omit some code
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// omit the code for setting icon, title, etc}}}Copy the code
Well, the mContentParent object is created using the generateLayout method, but before that, a DecorView object is created and passed in as a parameter to the generateLayout method.
In the DecorView, all we need to know is that it inherits to FrameLayout, because it’s not very helpful to analyze its details at this point.
So let’s analyze what generateLayout did:
public class PhoneWindow extends Window implements MenuBuilder.Callback {
// Omit some code
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
// Omit some code: read the content from the topic file and set the corresponding Flag
// Inflate the window decor.
int layoutResource;
int features = getLocalFeatures();
// omit some code: use features to set different layoutResource ids for layoutResource
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// Omit some code: Set background, elevation, title, titleColor data for mDecor
mDecor.finishChanging();
returncontentParent; }}Copy the code
The main contents are as follows:
- Gets data from the topic and applies it to the current
Window
In the. - According to Features, let
mDecor
Load different layout files. - To obtain
mContentParent
Object (id iscom.android.internal.R.id.content
).
Here we can get the following information:
- Different themes can make
Window
Load different layouts intoDecorView
In the. setContentView
Method is actually to load a custom layout file intomContentParent
In the.
At this point, we can briefly summarize the flow of setContentView:
1. First, the Window object in the Activity creates a DecorView. 2. Then let the DecorView load different layout resources based on the theme. MContentParent in 3, obtain the layout resource that id for the com. Android. Internal. R.i, dc ontent. 4. Finally load the custom layout into mContentParent.
2.2. Render layout
In the setContentView process, all the layout resources have been loaded, and the loading of the layout involves the addView method.
In general, the addView method calls requestLayout() and invalidate(true) indirectly, causing the interface to be reconfigured and refreshed.
And we know this:
When the Activity is onCreate, the interface is not loaded.
There seems to be a contradiction here, so let’s analyze it and see why the interface is not loaded when the addView method is called.
public abstract class ViewGroup extends View implements ViewParent.ViewManager {
// Omit some code
public void addView(View child, int index, LayoutParams params) {
// Omit some code
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
// Omit some code
}
Copy the code
public class View implements.{
// Omit some code
public void requestLayout(a) {
// Omit some code
if(mParent ! =null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
// Omit some code
}
public void invalidate(boolean invalidateCache) {
invalidateInternal(0.0, mRight - mLeft, mBottom - mTop, invalidateCache, true);
}
void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
boolean fullInvalidate) {
// Omit some code
if (skipInvalidate()) {
return;
}
// omit subsequent code
}
private boolean skipInvalidate(a) {
return(mViewFlags & VISIBILITY_MASK) ! = VISIBLE && mCurrentAnimation ==null&& (! (mParentinstanceofViewGroup) || ! ((ViewGroup) mParent).isViewTransitioning(this));
}
// Omit some code
}
Copy the code
Oh, it turns out that the DecorView doesn’t have a parent at this point, so instead of rearranging and refreshing, only add operations are performed.
When will the interface be loaded?
As anyone who has studied the Android life cycle knows:
When the Activity is in onCreate, it is not visible. When the Activity is onStart, it is visible but not interactive. An Activity is visible and interactive only when it is in onResume.
Let’s look at the implementation of the Activity’s performStart method:
public class Activity extends.{
// Omit some code
final void performStart(a) {
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
mFragments.noteStateNotSaved();
mCalled = false;
mFragments.execPendingActions();
mInstrumentation.callActivityOnStart(this);
if(! mCalled) {throw new SuperNotCalledException(
"Activity " + mComponent.toShortString() +
" did not call through to super.onStart()");
}
mFragments.dispatchStart();
mFragments.reportLoaderStart();
// Omit some code
mActivityTransitionState.enterReady(this);
}
Copy the code
Here as long as the call To the Instrumentation callActivityOnStart method, and the internal implementation of the callActivityOnStart method, simply call the onStart() method of the incoming Activity object.
emmmmm…… There is something wrong with the performStart method. The render interface is in onResume().
With that in mind, let’s take a look at the handleResumeActivity method:
public final class ActivityThread {
// Omit some code
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
ActivityClientRecord r = mActivities.get(token);
// Omit some code
// TODO Push resumeArgs into the activity for consideration
r = performResumeActivity(token, clearHide, reason);
if(r ! =null) {
final Activity a = r.activity;
// Omit some code
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;
// Omit some code
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); }}// omit subsequent code}}}}Copy the code
PerformResumeActivity does nothing but call the Activity’s performResume() method, which calls onResume indirectly.
What’s really important here is adding a DecorView to the ViewManager, which indirectly calls the Windows ManagerGlobal addView method, which is a singleton object. Notice the criteria here, as you can see, An ActivityClientRecord object, then executed once.
public final class WindowManagerGlobal {
// Omit some code
private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
// Omit some code
final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// Omit some code
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);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throwe; }}}// Omit some code
}
Copy the code
The ViewRootImpl object is created and cached in an array. DecorView into ViewRootImpl and execute requestLayout() -> scheduleTraversals() -> doTraversal() on ViewRootImpl. This is eventually executed in the performTraversals() method.
PerformTraversals () will perform the View rendering process, including measurement, placement and rendering, which will be discussed separately in the View rendering section later. We just need to understand that the interface has already been rendered.
2.3, extending
Now that we know that the Activity renders to the screen after onResume(), why is onStart() visible but not interactive? I won’t keep you in suspense. In fact, the official term “visible” has some ambiguity. I think:
“Visible” only applies to the rendered Activity, not the Activity being created.
Take a look at this picture to illustrate:
- 1. For the Activity created, it simply goes through its life cycle and renders in
Resumed
Finished in time. - 2. For rendered activities:
- When it is composed of
Resumed
State switch toStarted
State, where the interface is partially overwritten and out of focus, i.e. unable to interact. - When it is composed of
Started
State switch toCreated
State, the interface is completely overwritten, that is, invisible. - When it is composed of
Created
State switch toStarted
State, the interface is partially covered again, still can not get focus, can not interact. - When it is composed of
Started
State switch toResumed
Status. The interface is fully displayed.
It is this that causes the following problem:
During Activity creation, the onCreate(), onStart(), and onResume() methods cannot get the width and height of the control.
Third, summary
This article explains the life cycle of the call and interface loading process when the Activity starts. In summary, the whole process is as follows: