directory
One, foreword
Second, what is our goal
Third, where to start the drawing process
4. Where does the interface structure of the Activity begin to form
How does the drawing process work
Six, in actual combat
Write at the end
One, foreword
Drawing process is an essential part of Android advancement and one of the most frequently asked interview questions. This aspect of the excellent article has been very much, but the children today or to their own attitude to fry a fry this cold rice, perhaps is the egg fried rice π. So, without further ado, the old rule is to start with the map, and then start sharing.
Label layout
Second, what is our goal
In fact, this article, the children tangled for a long time, because the drawing process involves a lot of things, not an article can be written, so this article I want to determine some goals, to prevent because trace source too deep, and lost in the source code, and finally lead to nothing. Our goals are:
- Where to start the drawing process
- Where does the interface structure of an Activity begin to form
- How does the drawing process work
Then we started to conquer one by one.
Third, where to start the drawing process
When we talk about the drawing process, we will think of or hear of onMeasure, onLayout and onDraw, but have you ever thought why we open an App or open an Activity, will trigger this series of processes? To understand where the drawing process starts, it is necessary to explain the App startup process and the Activity startup process.
We all know that ActivityThread’s Main is an App entry point. Let’s go to the main method and see what it does.
The main method of ActivityThread is a reflection call from the ZygoteInit class that ends up using the invokeStaticMain method of the RuntimeInit class. Those who are interested in children’s shoes can check them out by themselves. Limited by space, we will not share them.
/ / ActivityThread class
public static void main(String[] args) {
/ /... Omit irrelevant code
// Prepare Looper for the main thread
Looper.prepareMainLooper();
// Instantiate ActivityThread, which manages the execution of the main thread in the application process
ActivityThread thread = new ActivityThread();
// Enter attach method
thread.attach(false);
/ /... Omit irrelevant code
/ / open stars
Looper.loop();
/ /... Omit irrelevant code
}
Copy the code
Entering the main method, we see the familiar Handler mechanism. In Android, all messages are driven by messages, and this is no exception. We can see that Looper is prepared first, and Looper is started at the end of the loop to fetch messages, which is used to process messages to the main thread.
That’s why we don’t need to start Looper on the main thread. Emmm is a bit of a digression.
Looking back, you can see the interspersed instantiation of the ActivityThread class and the attach method called. Here’s the code. Let’s move on.
/ / ActivityThread class
private void attach(boolean system) {
/ /... Omit irrelevant code
// system is false to enter the branch
if(! system) {/ /... Omit irrelevant code
// Get the Proxy of the system's AMS service to send data to the AMS process
final IActivityManager mgr = ActivityManager.getService();
try {
// We pass our mAppThread to AMS, and AMS controls our App Activity
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
/ /... Omit irrelevant code
} else {
/ /... Omit irrelevant code
}
/ /... Omit irrelevant code
}
/ / ActivityManager class
public static IActivityManager getService(a) {
return IActivityManagerSingleton.get();
}
/ / ActivityManager class
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create(a) {
// Get the AMS binder here
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
// The AMS proxy can send data
final IActivityManager am = IActivityManager.Stub.asInterface(b);
returnam; }};Copy the code
We go to attach method, which basically gets the Proxy of ActivityManagerService (AMS) via ActivityManager getService. Achieve cross-process communication with AMS.
The Proxy and Stub mentioned in this article are used by analogy with the class name when the system automatically generates AIDL for us, which is convenient for explanation. The Proxy sends information, and the Stub receives information.
In the MGR. AttachApplication (mAppThread); The code sends a message to the AMS process, carrying an mAppThread parameter of type ApplicationThread. What this code does is hand over our application’s “controller” to AMS, allowing AMS to control the Activity lifecycle in our application. Why do you say that? It’s important to understand the structure of the ApplicationThread class, which looks like this:
/ / ActivityThread $ApplicationThread class
private class ApplicationThread extends IApplicationThread.Stub {
// Omit a lot of 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);
// Messages from AMS are encapsulated in ActivityClientRecord and sent to Handler
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
r.intent = intent;
r.referrer = referrer;
r.voiceInteractor = voiceInteractor;
r.activityInfo = info;
r.compatInfo = compatInfo;
r.state = state;
r.persistentState = persistentState;
r.pendingResults = pendingResults;
r.pendingIntents = pendingNewIntents;
r.startsNotResumed = notResumed;
r.isForward = isForward;
r.profilerInfo = profilerInfo;
r.overrideConfig = overrideConfig;
updatePendingConfiguration(curConfig);
sendMessage(H.LAUNCH_ACTIVITY, r);
}
// Omit a lot of code
}
Copy the code
Given the method names of ApplicationThread, it may be surprising to see that most of the method names are scheduleXxxYyyy, which is similar to the familiar lifecycle. The code above leaves the methods we need to scheduleLaunchActivity, which contain our Activity’s onCreate, onStart, and onResume.
The scheduleLaunchActivity method wraps the information from AMS in the ActivityClientRecord class and ends with sendMessage(h.launch_activity, r); This line of code sends the information to the Handler in our main thread as the h.launch_activity information flag. Let’s go to the main thread Handler implementation class H. The specific code is as follows:
/ / ActivityThread $H class
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
// Omit a lot of code
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null."LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
// Omit a lot of code}}// Omit a lot of code
}
Copy the code
We know from the code above that the message type is LAUNCH_ACTIVITY, which leads to the handleLaunchActivity method, which we follow down to the code below
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, 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;
if(r.profilerInfo ! =null) {
mProfiler.setProfiler(r.profilerInfo);
mProfiler.startProfiling();
}
// Make sure we are running with the most recent config.
handleConfigurationChanged(null.null);
if (localLOGV) Slog.v(
TAG, "Handling launch of " + r);
// Initialize before creating the activity
WindowManagerGlobal.initialize();
// Get an Activity object that calls the Activity's onCreate and onStart life cycles
Activity a = performLaunchActivity(r, customIntent);
// The Activity does not enter empty
if(a ! =null) {
r.createdConfig = new Configuration(mConfiguration);
reportSizeConfigurations(r);
Bundle oldState = r.state;
// This method eventually calls back to the Activity's onResume
handleResumeActivity(r.token, false, r.isForward, ! r.activity.mFinished && ! r.startsNotResumed, r.lastProcessedSeq, reason);if(! r.activity.mFinished && r.startsNotResumed) { performPauseActivityIfNeeded(r, reason);if(r.isPreHoneycomb()) { r.state = oldState; }}}else {
// If there was an error, for any reason, tell the activity manager to stop us.
try {
ActivityManager.getService()
.finishActivity(r.token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throwex.rethrowFromSystemServer(); }}}Copy the code
Let’s start with performLaunchActivity(R, customIntent); The onCreate and onStart methods are eventually called. Seeing is believing and hearing is believing, so let’s move on. Let’s go to this code
/ / ActivityThread class
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// omit irrelevant code
// Create the Activity Context
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// ClassLoader loads the Activity class and creates the Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// omit irrelevant code
} catch (Exception e) {
// omit irrelevant code
}
try {
/ / create the Application
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
// omit irrelevant code
if(activity ! =null) {
// omit irrelevant code
// The attach of the Activity was called
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);
// This intent is obtained by getIntent
if(customIntent ! =null) {
activity.mIntent = customIntent;
}
// omit irrelevant code
// Call the Activity's onCreate
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
// omit irrelevant code
if(! r.activity.mFinished) {// zincPower calls the Activity's onStart
activity.performStart();
r.stopped = false;
}
if(! r.activity.mFinished) {// zincPower calls the Activity's onRestoreInstanceState method to restore the data
if (r.isPersistable()) {
if(r.state ! =null|| r.persistentState ! =null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state, r.persistentState); }}else if(r.state ! =null) { mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state); }}// omit irrelevant code
}
// omit irrelevant code
}
// omit irrelevant code
return activity;
}
/ / Instrumentation class
public void callActivityOnCreate(Activity activity, Bundle icicle, PersistableBundle persistentState) {
prePerformCreate(activity);
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}
/ / the Activity class
final void performCreate(Bundle icicle) {
restoreHasCurrentPermissionRequest(icicle);
// call onCreate
onCreate(icicle);
mActivityTransitionState.readState(icicle);
performCreateCommon();
}
/ / the Activity class
final void performStart(a) {
// omit irrelevant code
// Call the Activity onStart
mInstrumentation.callActivityOnStart(this);
// omit irrelevant code
}
/ / Instrumentation class
public void callActivityOnStart(Activity activity) {
// Calls the Activity's onStart
activity.onStart();
}
Copy the code
After entering the performLaunchActivity method, we will find many things that we are familiar with. The children have annotated the key points, because they are not the focus of the article, I will not go into detail, otherwise it will be too long.
We locate directly to mInstrumentation callActivityOnCreate this line of code. Entering this method calls the activity’s performCreate method, which in turn calls the onCreate method of the activity lifecycle that we often override. π now that I’ve found the onCreate call, I need to FLAG1 here, because this is where target 2 needs to be turned on. I’ll share it in the next section, but don’t worry.
Come back to continue performLaunchActivity method of execution, will call to the activity performStart method, and this method will be called to mInstrumentation. CallActivityOnStart method, Finally, the onStart method of our oft-rewritten Activity lifecycle is called within this method. π At this point, find where onStart is called.
Having found two life cycle calls, we need to go back to the handleLaunchActivity method and continue running to the handleResumeActivity method as follows:
/ / ActivityThread class
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
// Omit some code
r = performResumeActivity(token, clearHide, reason);
// Omit some code
if (r.window == null && !a.mFinished && willBeVisible) {
// Assign the Window in the Activity to the Window in ActivityClientRecord
r.window = r.activity.getWindow();
// Get a DecorView that is initialized in the Activity's setContentView
View decor = r.window.getDecorView();
// Not visible at this time
decor.setVisibility(View.INVISIBLE);
// WindowManagerImpl is the implementation class of ViewManager
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;
/ / to add DecorView WindowManager, and bring a WindowManager. LayoutParams
// This triggers the actual drawing process
wm.addView(decor, l);
} else{ a.onWindowAttributesChanged(l); }}}// omit irrelevant code
}
Copy the code
The performResumeActivity method ends up calling the Activity’s onResume method. Since that is not the goal of this section, we won’t go further. Kids can do that on their own, and the code is simpler. At this point we’ve rounded up the three Acitivity lifecycle functions onCreate, onStart, and onResume that we’ve been rewriting. If you look at ApplicationThread’s other methods, you’ll find traces of the Activity lifecycle in them, confirming our initial statement that we’re handing the application “remote control” to AMS. It is worth noting that this operation is in a cross-process scenario.
Keep running down to wm. AddView (decor, L); This line of code, whose concrete implementation class of WM is WindowManagerImpl, follows further down to the following series of calls
/ / WindowManagerImpl class
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// tag: enter this line
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
/ / WindowManagerGlobal class
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
// omit irrelevant code
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// omit irrelevant code
// Initialize ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// Pass view and param to root
// ViewRootImpl starts drawing the view
// tag: enter this line
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
if (index >= 0) {
removeViewLocked(index, true);
}
throwe; }}}/ / ViewRootImpl class
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// omit irrelevant code
// Enter the drawing process
// tag: enter this line
requestLayout();
// omit irrelevant code}}}/ / ViewRootImpl class
@Override
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true;
// tag: enter this linescheduleTraversals(); }}/ / ViewRootImpl class
void scheduleTraversals(a) {
if(! mTraversalScheduled) { mTraversalScheduled =true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// Submit to the choreographer, which will call mTraversalRunnable on the next frame to run
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
Copy the code
There are a number of intermediate jump methods, all of which are tagged: After entering this line of comments, children can follow themselves and will find that the postCallback method of the Choreographer class will be called. Choreographer is a class that will receive a vSYNC signal, so when the next frame arrives, it will invoke the task we just submitted, mTraversalRunnable here, and execute its Run method.
It is worth noting that tasks submitted through Choreographer’s postCallback method will not be called every frame, but only when the next frame arrives, at which point the task will be removed. In short, commit once and call once in the next frame.
Let’s move on to the mTraversalRunnable and see what is done in each frame.
/ / ViewRootImpl class
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
/ / ViewRootImpl class
final class TraversalRunnable implements Runnable {
@Override
public void run(a) { doTraversal(); }}/ / ViewRootImpl class
void doTraversal(a) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// enter here
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false; }}}/ / ViewRootImpl class
private void performTraversals(a) {
// omit irrelevant code
if(! mStopped || mReportNextDraw) {// omit irrelevant code
// FLAG2
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// omit irrelevant code
// Make a measurement
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
// omit irrelevant code
// display
performLayout(lp, mWidth, mHeight);
// omit irrelevant code
// The layout completes the callback
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
// omit irrelevant code
// Draw
performDraw();
}
Copy the code
Once the run method of mTraversalRunnable is called, there will be a series of method calls, and then performTraversals, where we’ve been talking about the origins of the three draw process methods. These three sources are the three methods we see above performMeasure, performLayout, performDraw.
These three methods will carry out a call chain as shown in the following figure (π or hand-drawn, do not spray). As we know from the code, they will be called in the order of performMeasure, performLayout and performDraw.
PerformMeasure will trigger our measurement process. As shown in the figure, the ViewGroup entering the first layer will call measure and onMeasure, and call the next layer in onMeasure. Then the View or ViewGroup at the next level will repeat the action, measuring all views. (This process can be understood as depth traversal of a book.)
The performLayout and performMeasure processes are similar except for the method names.
performDraw
Slightly different, when the current control is ViewGroup, only the background needs to be drawn or we need to passsetWillNotDraw(false)
When we set up our ViewGroup that we need to draw, it goes inonDraw
Method, and then throughdispatchDraw
Draw the child View, and so on. And if it is a View, naturally there is no need to draw the child View, just its own content can be drawn.Now that we know the origin of the drawing process,onMeasure
γ onLayout
,onDraw
These are three methods that we’ll go into more detail and use in the field.
4. Where does the interface structure of the Activity begin to form
The diagram above shows the structure of the Activity. Let’s start with a general description and then go through the process in the source code.
We can clearly know that an Activity corresponds to a Window, and the only implementation class of the Window is PhoneWindow, which is initialized in the Attach method of the Activity. We also mentioned attach method before, interested children’s shoes can go deep by themselves.
One layer down is a DecorView held by the PhoneWindow. The DecorView initialization is in the setContentView, which we’ll examine in more detail later. DecorView is our top-level View, and the layout we set up is just its child View.
DecorView is a FrameLayout. But in the setContentView, we’re going to give it a LinearLayout. The child views of this linear layout are typically composed of TitleBar and ContentView. TitleBar we can use requestWindowFeature(window.feature_no_title); And the ContentView is the ViewGroup that loads the layout file that we set up.
Now that we have a general impression, let’s break it down. In the previous section (FLAG1), the first life cycle we went to was onCreate, and in that method we would say setContentView(r.layout.xxxx) to set the layout. As we saw in the previous section, the actual drawing process is after onResume, so what does setContentView do? Let me go into the source code to find out.
Enter the setContentView method of your Activity and you will see the following code. GetWindow returns an object of type Window, and Window’s only implementation class is PhoneWindow, according to the official annotation, so we go into PhoneWindow and look at its setContentView method. There are two lines of code worth noting here. Let’s go one by one. Let’s start with the installDecor method.
/ / the Activity class
public void setContentView(@LayoutRes int layoutResID) {
// getWindow returns PhoneWindow
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
/ / the Activity class
public Window getWindow(a) {
return mWindow;
}
/ / PhoneWindow class
@Override
public void setContentView(int layoutResID) {
// mContentParent is empty. MContentParent is the container that holds our layout
if (mContentParent == null) {
// Initialize the top-level View -- DecorView and the load container for the layout we set -- ViewGroup(mContentParent)
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 layout file we set into mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if(cb ! =null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
Copy the code
The installDecor method initializes our top-level View (i.e., DecorView) and the container that initially decorates our layout (i.e., the mContentParent property). The specific code is as follows
private void installDecor(a) {
mForceDecorInstall = false;
if (mDecor == null) {
// A mDecor will be instantiated
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) {
// Initialize mContentParent
mContentParent = generateLayout(mDecor);
// omit irrelevant code
}
Copy the code
DecorView is created in General Decor. The code is simple as follows
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if(mTheme ! = -1) { context.setTheme(mTheme); }}}else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
Copy the code
The generateLayout method is followed by the generateLayout method. The core code is as follows. If we set some features in the requestFeature before the onCreate method, the getLocalFeatures will get. And assign the appropriate layout to the layoutResource property based on its value. Finally, we parse the layout resource, assign it to the DecorView, and then assign the DecorView control with ID content to contentParent, which will load the layout resource we set.
protected ViewGroup generateLayout(DecorView decor) {
// omit irrelevant code
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();
// Load the DecorView layout
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// Here we get the container id of the content to load our Settings
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// omit irrelevant code
return contentParent;
}
Copy the code
We go back to the setContentView method and go to the mLayOutinflater.inflate (…) ; This line of code, layoutResID set the layout file for us, and mContentParent is the control whose ID is Content that we just got, and here is the object tree that parses it from the XML file into a control, And put it in the mContentParent container.
So far, the Activity’s setContentView lets us “translate” the layout file from the XML to the corresponding control object, forming a tree of controls with the DecorView as the root for our later drawing process to traverse.
How does the drawing process work
Finally came to the core section, we continue to analyze the third section of the last said three methods onMeasure, onLayout, onDraw, this is the drawing process to run the last gate, is our custom control can be operated in the part. Let’s go through them one by one
1, onMeasure
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
Copy the code
To explain this method, we need to explain the meaning and composition of the two parameters. Both parameters are of type MeasureSpec
What is the MeasureSpec
MeasureSpec is a 32-bit binary number. The higher two bits are the measurement mode, namely SpecMode; The lower 30 bits are the measured values, known as SpecSize. Let’s take a look at the source code to find out what these two values mean.
Here is the code for the MeasureSpec class (with some irrelevant code removed)
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
// The final result is: 11... (30)
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// The parent View does not impose any constraints on the child View. The child View can be any size it wants.
// Binary: 00... (30)
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
// The parent View determines the exact size of the child View. The size of the child View is what the parent View measures
// binary: 01... (30)
public static final int EXACTLY = 1 << MODE_SHIFT;
// The parent View specifies the maximum size available to a child View. The child View size cannot exceed this value.
// binary: 10... (30)
public static final int AT_MOST = 2 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
@MeasureSpecMode int mode) {
/ / API after 17 sUseBrokenMakeMeasureSpec to false
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return(size & ~MODE_MASK) | (mode & MODE_MASK); }}@MeasureSpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return(measureSpec & ~MODE_MASK); }}Copy the code
(1) Measurement mode
The class has three constants, UNSPECIFIED, EXACTLY, and AT_MOST, which correspond to the three measurement modes. The definitions of these constants are listed in the annotation. The children have compiled the following table for easy reference.
The name of the | meaning | Numerical value (binary) | The specific performance |
---|---|---|---|
UNSPECIFIED | The parent View imposes no constraints on the child View, which can be any size it wants | 00 …(30δΈͺ0) | System internal use |
EXACTLY | The parent View has determined the exact size of the child View, and the child View’s size is measured by the parent View | 01 …(30δΈͺ0) | Value match_parent |
AT_MOST | The parent View specifies the maximum size available to a child View. The View size cannot exceed this value. | 10 …(30δΈͺ0) | wrap_content |
(2) makeMeasureSpec
MakeMeasureSpec method, which is used to combine the measurement mode and measurement size by combining the two values into a 32-bit number with the higher 2 bits for the measurement mode and the lower 30 bits for the size.
This method is very short, mainly thanks to (size & ~ MODE_MASK) | (mode & MODE_MASK) of an operator, but also brought a certain understanding of the difficulty. Let’s break it down
size & ~MODE_MASK
Eliminate the value of measurement mode in size, that is, the height 2 position is 00mode & MODE_MASK
Retain the value of the passed pattern parameter while setting the lower 30 position to 0… (30 0)(size & ~MODE_MASK) | (mode & MODE_MASK)
30 bits lower than size + 2 bits higher than mode
As for &, ~, | why the three operation can do so SAO operation, please click the kid’s another blog post – Android a simple explanation. (The content is very brief, if you are not familiar with this content, it is highly recommended to browse)
(3) the getMode
The getMode method is used to get the higher 2 bits of the measureSpec value we passed, the measurement mode.
GetSize (4)
The getSize method is used to get the lower 30 bits of the measureSpec value we passed, the measured value.
Explain theMeasureSpec
What is it? We still have two questions to ask:
- Where do these two parameter values come from
- How to use these two parameter values
Where do these two parameter values come from
With the help of the diagram below, set the current runningonMeasure
A MeasureSpec of A method in B is computed by its parent (A), and the evaluated rules ViewGroup have corresponding methods, i.egetChildMeasureSpec
. getChildMeasureSpec
The specific code is as follows. We continue with the scenario above, and the value obtained in B is zeroA uses its own MeasureSpecAnd B’s layoutparams. width or layoutparams. height to calculate B’s MeasureSpec.
/ / ViewGroup class
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (specMode) {
// The superview is a mode of definite size
case MeasureSpec.EXACTLY:
/** * The size of the childDimension is greater than 0, indicating that the childDimension is set to {@linkLayoutparams.match_parent}; childDimension = {@linkLayoutparams.wrap_content}, indicating that the child view wants to be its own size, but cannot exceed the size of its parent view. * /
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size. So be it.
resultSize = size;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// The superview already has a maximum size limit
case MeasureSpec.AT_MOST:
/** * The size of the childDimension is greater than 0, indicating that the childDimension is set to {@linkLayoutparams.match_parent}, * -----@linkLayoutparams.wrap_content}, * ----- indicates that the child view wants to be its own size, but not larger than its parent view. * /
if (childDimension >= 0) {
// Child wants a specific size... so be it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size, but our size is not fixed.
// Constrain child to not be bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
case MeasureSpec.UNSPECIFIED:
if (childDimension >= 0) {
// Child wants a specific size... let him have it
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.MATCH_PARENT) {
// Child wants to be our size... find out how big it should
// be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size.... find out how
// big it should be
resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
resultMode = MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Copy the code
Let’s put this code into a table
Child LayoutParams(vertical) \ Parent class SpecMode(horizontal) | EXACTLY | AT_MOST | UNSPECIFIED |
---|---|---|---|
Dp /px (determined value) | EXACTLY ChildSize |
EXACTLY ChildSize |
EXACTLY ChildSize |
MATCH_PARENT | EXACTLY ParentSize |
AT_MOST ParentSize |
UNSPECIFIED 0 |
WRAP_CONTENT | AT_MOST ParentSize |
AT_MOST ParentSize |
UNSPECIFIED 0 |
Therefore, in the end, the two values obtained by B’s onMeasure method are the constraint suggestions made by superview A to B.
You might wonder where the top-level DecorView constraint comes from. We cut back to FLAG2, and when we enter the performMeasure method, Carrying two MeasureSpec is a WindowManager passed the Window of the Rect high and wide Window WindowManager. LayoutParam decision together. In short, the DecorView’s constraints are derived from the Window’s parameters.
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
// Make a measurement
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
Copy the code
How to use these two parameter values
The word we’ve been referring to above is called “suggestion” because a MeasureSpec that reaches both dimensions of B already determines the width and height of control B.
Here we can use the analogy of parents always telling their children how to do it (i.e. calculate the child View’s MeasureSpec). Sensible children will know that listening to their parents’ advice can save them from missteps (i.e. follow the inherited MeasureSpec constraints), while naughty children, Find it more fun to break the rules (i.e., regardless of MeasureSpec’s rules).
By convention, we’re going to follow the constraints given by the parent View. And the B control calculates its own child View MeasureSpec (if there is a child View), the child View will measure the sun View, such a layer of measurement (here can feel the tree structure of the magic π).
B controls to complete the measurement of the View, call setMeasuredDimension will own the ultimate measuring wide high Settings, this completes B controls the measuring process was completed.
2, onLayout
protected void onLayout(boolean changed, int l, int t, int r, int b)
Copy the code
OnLayout is for placing, this process is relatively simple, because we have obtained the width and height of each sub-view from onMeasure. The parent View is responsible for providing the upper left and lower right coordinates of each child View according to its own logic.
3, ontouch
protected void onDraw(Canvas canvas)
Copy the code
In the drawing process, onDraw should be said to be the most familiar to children, as long as you can draw what you need to draw on canvas.
Six, in actual combat
To sum up, the last section is the sentence we always say in the interview, onMeasure is responsible for measurement, onLayout is responsible for placing, onDraw is responsible for drawing, but the theory is always too empty, we now integrate the theory into the operation. We use the streaming layout of the tag to illustrate this further.
1. Effect drawing
Making the entry:portal
2. Coding ideas
In this TagFlowLayout scenario, we add the label TextView to the control TagFlowLayout (or more complex layouts, for clarity). We put in four tabs: “Big Android,” “Big Kids,” “JAVA,” and “PHP is the Best Language.”Let’s use the flow chart of children’s hand drawing to clarify the drawing process.
(1) onMeasure
Initially, the control is empty, which is the first small diagram.
Then place the first TAB “Big Android” within the width of the TagFlowLayout, as shown in the second small image.
Then put the second tag “Fierce children” in. As shown in the third image, it is too wide for the TagFlowLayout, so we wrap it and put “fierce children” in the second line.
Then add the third tag “JAVA” within the width of the TagFlowLayout, as shown in the fourth small figure.
Finally, put the remaining “PHP is the best language” in there. There is a problem that you can’t fit one in a row because the width of “PHP is the best language” has exceeded the width of TagFlowLayout. So when WE measure a MeasureSpec for “PHP is the best language”, we need to “correct” it so that it is as wide as TagFlowLayout, resulting in a MeasureSpec that looks like the sixth diagram.
Finally, we need to use our measurements to set the width and height of our own TagFlowLayout control using setMeasuredDimension.
(2) onLayout
After onMeasure, TagFlowLayout knows the width and height of each child and which line each child should “stand on”, but the specific coordinates still need to be calculated.
(l1, T1) is (0,0), while (r1,b1) is (0+ width, 0+height).
(l2, T2) is (0, the Height of the first row),(r2,b2) is (Width, the Height of the first row + its Height).
The coordinates of “JAVA” depend on “Fierce Little Friends” and “Big Android,” (l3,t3); (r3,b3); (l3,t3); (r3,b3);
“PHP is the best language” depends on the total height of the first two lines, depending on the calculation of coordinates. (l4, T4) is (0, the first row Height + the second row Height), (r4,b4) is (its Width, the first row Height + the second row Height + its Height).
(3) onDraw
This method is not needed in our control because the task of drawing is taken care of by the child Views. OnDraw will not be called in our TagFlowLayout for the exact reason that we have said in the previous section.
3, summary
Although matting a lot, but the TagFlowLayout code is not much, here is no longer pasted out, need to enter the portal. We only need to measure in onMeasure, then store the measured value, and finally place it in onLayout depending on the measurement result.
Write at the end
It has been nearly three weeks since the release of the last blog post. This time, it takes a long time for many reasons. There are many knowledge points involved in the drawing process, and what is described here is only the part that is close to our developers. There is another reason is some personal things of children, need some time to calm down, but eventually also insist on finishing writing. If you find something wrong, please discuss it with me in the comments section and we will make progress together. Give me a thumbs up if you think this bowl of “egg fried rice” tastes different.
Advanced UI series Github address: please enter the portal, if you like to give me a star bar π