In my last article in the series: Android App/Activity startup process analysis has analyzed an App from clicking its icon to the Activity’s onCreate(), onStart() and onResume(). The entire process whose lifecycle is invoked. As we all know, the contents displayed on the screen of an ordinary App are loaded by the system through self-designed interfaces, and how are the elements in these interfaces rendered? This article will continue to analyze the entire process from a source code perspective based on Android Nougat.
Before we begin, review the sequence diagram from ActivityThread to Activity that was analyzed in the previous article:
Step 1: Initialize PhoneWindow and WindowManager
As shown in the figure above, the Attach () method is called before the Activity’s onCreate(), onStart(), and onResume() lifecycles are called, so we’ll start this article with attach() :
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) { attachBaseContext(context); .// mWindow is an instance of PhoneWindow object
mWindow = new PhoneWindow(this, window);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this); .// Call the Window setWindowManager methodmWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) ! =0);
if(mParent ! =null) {
mWindow.setContainer(mParent.getWindow());
}
// Get WindowManager from Window
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
Copy the code
MWindow is a variable of type Window. In the attach() method, an instance of a PhoneWindow object is created and assigned to mWindow. PhoneWindow directly inherits from the Window class. Then we call the Window’s setWindowManager() method:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {...// mWindowManager is an instance of the WindowManagerImpl object
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
Copy the code
Therefore, the mWindow variable in Acitvity is an instance of the PhoneWindow class, and mWindowManager is an instance of the WindowManagerImpl class, and the main job of the Attach () method is to initialize both variables.
Step 2: Initialize the DecorView
Track code execution
Then we go to the onCreate method, and as we all know, if you want to display your layout file or View in your Activity, We must call setContentView() in the Activity’s onCreate() method to pass in our layout ID or View. Look at one of the setContentView() methods:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Copy the code
Continue looking at the PhoneWindow class setContentView() method:
public void setContentView(int layoutResID) {
if (mContentParent == null) {// This is the first call
// Initialize Decor
installDecor();
} else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// Transition animation, default false
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
// Parse the layout filemLayoutInflater.inflate(layoutResID, mContentParent); }... }Copy the code
If this method is called for the first time, mContentParent is null, otherwise remove all mContentParent’s child views if there is no transition animation and continue tracing the installDecor() method:
private void installDecor(a) {
mForceDecorInstall = false;
if (mDecor == null) {
// Generate a DecorView object
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) {
// Call the generateLayout methodmContentParent = generateLayout(mDecor); . }}}Copy the code
When mDecor is null, the generateDecor() method is called to create an instance of the DecorView class, which inherits from FrameLayout. If so, call generateLayout(), which creates the mContentParent object and traces it in:
protected ViewGroup generateLayout(DecorView decor) { TypedArray a = getWindowStyle(); .// Initialize Windows with various properties set in WindowStyle
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); }...int layoutResource;
int features = getLocalFeatures();
// Get the corresponding layout file according to the set features value and assign it to layoutResource
if ((features & (1<< FEATURE_SWIPE_TO_DISMISS)) ! =0) {
layoutResource = R.layout.screen_swipe_dismiss;
} 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;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & ((1 << FEATURE_PROGRESS) | (1<< FEATURE_INDETERMINATE_PROGRESS))) ! =0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1<< FEATURE_CUSTOM_TITLE)) ! =0) {
if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
removeFeature(FEATURE_ACTION_BAR);
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
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; }}else if ((features & (1<< FEATURE_ACTION_MODE_OVERLAY)) ! =0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
// Call onResourcesLoaded
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
/ / in layoutResource according to the id: com. Android. The internal, R.i, dc ontent retain and assigned to a ViewGroup contentParent object
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view"); }... mDecor.finishChanging();/ / return contentParent
return contentParent;
}
Copy the code
DecorView onResourcesLoaded() method:
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {... mDecorCaptionView = createDecorCaptionView(inflater);// Parse the layoutResource file
final View root = inflater.inflate(layoutResource, null);
if(mDecorCaptionView ! =null) {... }else {
// Added as a root layout to mDecor
addView(root, 0.new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
Copy the code
As you can see, the generateLayout method is divided into four main parts:
- Will first through the com. Android. Internal. R.s tyleable. Window set in the various attributes for each requestFeature or setFlags operation Window.
- The next step is to select different window decoration layout files based on the set features values, resulting in layoutResource values. Therefore, methods such as requestFeature() that set the full screen in your Activity need to be called before setContentView so that you can choose a different root layout depending on your Settings.
- Pass the layoutResource value to the DecorView’s onResourcesLoaded() method and use LayoutInflater to convert the layout into a View as the root View and add it to mDecor.
- To look for in the mDecor id for the com. Android. Internal, R.i, dc ontent of ViewGroup and return as a return value, normally this ViewGroup FrameLayout.
About the fourth, could have some question, why according to id for com. Android. Internal. R.i, dc ontent we will be able to find the corresponding ViewGroup? The answer is in the generateLayout() method we analyzed earlier, which gets the layout file based on the features values and assigns it to layoutResource, All of these layout files have a FrameLayout with an ID of Content, but some layout files may also have ActiionBar, Title, etc. These layout files are stored in this directory. Take r.layout.screen_simple, which reads as follows:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="? attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="? android:attr/windowContentOverlay" />
</LinearLayout>
Copy the code
Back to the setContentView() method of PhoneWindow, after installDecor(), mDecor is initialized and mContentParent is assigned a value. Back to setContentView(), The last important step is to use mLayoutInflater. Inflater to push our layout files into the FrameLayout id content in mDecor.
public void setContentView(int layoutResID) {
if (mContentParent == null) {// This is the first call
// Initialize Decor
installDecor();
} else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// Transition animation, default false
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
// Parse the layout filemLayoutInflater.inflate(layoutResID, mContentParent); }... }Copy the code
summary
At this point, the setContentView() method is complete, and generally divided into three steps:
- Initialize mDecor, which is an instance of the DecorView class that inherits from FrameLayout;
- Based on the property values in theme, select the layout file and load it with infalter.inflater() and add it to mDecor. Each of these layout files has a FrameLayout with an ID of Content;
- The layout file set in the Activity’s setContentView() method, MLayoutInflater. Inflater () is pushed into the FrameLayout content in mDecor.
The sequence diagram of this process is as follows:
The relationship between Activity, PhoneWindow, DecorView and ContentView is shown below:
However, our layout is not shown at this point, so keep looking.
Step 3: Create the ViewRootImpl and associate the DecorView
Track code execution
As you can see in the opening sequence diagram, the ActivityThread indirectly calls attach() and onCreate() of the Activity in the handleLaunchActivity() method:
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {... WindowManagerGlobal.initialize();// Execute the performLaunchActivity method
Activity a = performLaunchActivity(r, customIntent);
if(a ! =null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
// Execute the handleResumeActivity method and finally call the onStart and onResume methods
handleResumeActivity(r.token, false, r.isForward, ! r.activity.mFinished && ! r.startsNotResumed);if(! r.activity.mFinished && r.startsNotResumed) { r.activity.mCalled =false;
mInstrumentation.callActivityOnPause(r.activity);
r.paused = true; }}else {
// Stop the Activity
ActivityManagerNative.getDefault()
.finishActivity(r.token, Activity.RESULT_CANCELED, null.false); }}Copy the code
The handleResumeActivity() method is then called:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); .// Methods such as onStart() and onResume() are eventually called
r = performResumeActivity(token, clearHide, reason);
if(r ! =null) {
finalActivity a = r.activity; .if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
/ / get DecorView
View decor = r.window.getDecorView();
// Make the DecorView invisible
decor.setVisibility(View.INVISIBLE);
// Get ViewManager, here is WindowManagerImpl instanceViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; .if(a.mVisibleFromClient && ! a.mWindowAdded) {// Flag set to true
a.mWindowAdded = true;
// Call WindowManagerImpl's addView methodwm.addView(decor, l); }}else if (!willBeVisible) {
...
}
...
if(! r.activity.mFinished && willBeVisible && r.activity.mDecor ! =null && !r.hideForNow) {
...
if (r.activity.mVisibleFromClient) {
// Call the makeVisible method to make the DecorView visibler.activity.makeVisible(); }}... }else {
try {
// If an exception occurs during this process, the Activity is killed
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throwex.rethrowFromSystemServer(); }}}Copy the code
After executing the performResumeActivity() method, the Activity object in ActivityClientRecord is then retrieved and the DecorView object initialized earlier in the setContentView process is returned. It’s then passed as a parameter to an object of type ViewManager wm’s addView method, ViewManager is an interface, so who implements it? Here’s a quick twist: Look back at the attach() method of the Activity:
final void attach(...). {...// mWindow is a PhoneWindow object
mWindow = new PhoneWindow(this, window); .// Call the Window setWindowManager methodmWindow.setWindowManager(...) ; . }Copy the code
Tracing the Window’s setWindowManager() method:
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
boolean hardwareAccelerated) {...// mWindowManager is an instance of the WindowManagerImpl object
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
Copy the code
So, back to the main thread, the Activity’s getWindowManager() gets an instance of the WindowManagerImpl object, and look at its addView() method:
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
// mGlobal is an instance of the WindowManagerGlobal object
mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}
Copy the code
MGlobal is an instance of the WindowManagerGlobal object. Look at its addView() method:
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {... ViewRootImpl root; View panelParentView =null;
synchronized (mLock) {
...
root = newViewRootImpl(view.getContext(), display); . mRoots.add(root); . }try {
// Add the passed DecorView to the ViewRootImpl
root.setView(view, wparams, panelParentView);
} catch(RuntimeException e) { ... }...// Set the parent property of view to ViewRootImpl
view.assignParent(this); . }Copy the code
We first create a ViewRootImpl object, then pass the DecorView object as a parameter to the ViewRootImpl setView() method, The DecorView adds it and sets its parent property to ViewRootImpl, thus establishing a connection between them.
summary
As you can see, ViewRootImpl is the DecorView manager and is responsible for the measurement, layout, and drawing of the View Tree, as well as controlling the View Tree refresh operation through Choreographer, which we will discuss later.
Step 4: Establish a connection between PhoneWindow and WindowManagerService
As the administrator of all Windows, WMS is responsible for adding and deleting Windows, managing Surface and distributing events, etc. Therefore, if the PhoneWindow object in each Activity needs to be displayed, it must interact with WMS.
Track code execution
To continue the flow of the previous step, look at the setView method of the View wrootimpl:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) { mView = view; .// 1
// 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(); .try{...// 2. MWindowSession invokes WMS remotely with Binder
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
...
} finally{... }... view.assignParent(this); . }}}Copy the code
Ignore the requestLayout method for a moment. In note 2, IWindowSession is the proxy object in THE WMS used to receive the Session object called by ViewRootImpl. That is, ViewRootImpl calls WMS methods remotely via IWindowSession:
Session.java
@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
Rect outOutsets, InputChannel outInputChannel) {
// mService is the WMS object
return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
outContentInsets, outStableInsets, outOutsets, outInputChannel);
}
Copy the code
WindowManagerService.java
public int addWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
InputChannel outInputChannel) {...// Create a WindowState object
WindowState win = new WindowState(this, session, client, token,
attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent); . win.attach();// Create a SurfaceSession
mWindowMap.put(client.asBinder(), win); // mWindowMap is a set of Windows that WindowManagerService uses to hold all the current Windows. win.mToken.addWindow(win);// There can be multiple WindowStates under one token. In fact, token corresponds to PhoneWindow one by one.. }Copy the code
This is a typical Binder bidirectional communication model. Binder mechanisms can be found with AIDL. This process is shown below:
summary
Summarize the relationship between activities, Windows, and WMS with a diagram:
Step 5: Establish a connection to SurfaceFlinger
SurfaceFlinger is one of Android’s most important system services. It is responsible for Layer composition and rendering (Surface in Native name).
Track code execution
Following the steps above, the WindowState attach() method will call the Session windowAddedLocked() method:
WindowState.java
void attach(a) {
if (WindowManagerService.localLOGV) Slog.v(
TAG, "Attaching " + this + " token=" + mToken
+ ", list=" + mToken.windows);
mSession.windowAddedLocked();
}
Copy the code
Session.java
void windowAddedLocked(String packageName) {...if (mSurfaceSession == null) {... mSurfaceSession =newSurfaceSession(); . }}public final class SurfaceSession {
private long mNativeClient; // SurfaceComposerClient*
private static native long nativeCreate(a); .public SurfaceSession(a) { mNativeClient = nativeCreate(); }... }Copy the code
The nativeCreate() method is a native method:
android_view_SurfaceSession.cpp
static jlong nativeCreate(JNIEnv* env, jclass clazz) {
SurfaceComposerClient* client = new SurfaceComposerClient();
client->incStrong((void*)nativeCreate);
return reinterpret_cast<jlong>(client);
}
Copy the code
The nativeCreate method constructs a SurfaceComposerClient object, which acts as a bridge between the application and the SurfaceFlinger. The SurfaceComposerClient pointer calls the following methods when used for the first time:
void SurfaceComposerClient::onFirstRef() {
....
sp<ISurfaceComposerClient> conn;
// sf is the SurfaceFlinger object pointerconn = (rootProducer ! =nullptr)? sf->createScopedConnection(rootProducer) : sf->createConnection(); . }Copy the code
It creates an ISurfaceComposerClient object via SurfaceFlinger’s createScopedConnection method:
SurfaceFlinger.cpp
sp<ISurfaceComposerClient> SurfaceFlinger::createConnection() {
return initClient(new Client(this));
}
static sp<ISurfaceComposerClient> initClient(const sp<Client>& client) {
status_t err = client->initCheck();
// Just check for errors and return to the client itself
if (err == NO_ERROR) {
return client;
}
return nullptr;
}
Copy the code
The Client class implements the ISurfaceComposerClient(inheriting from IInterface) interface so that it can communicate across processes. SurfaceComposerClient uses it to communicate with SurfaceFlinger. It also creates surfaces and maintains all layers of an application.
The initClicent method does some error checking and returns to the Client itself.
summary
The process is shown below:
Step 6: Apply for a Surface
After steps 4 and 5, the ViewRootImpl is connected to WMS and SurfaceFlinger, but the View is still not displayed. We all know that the UI is ultimately going to be displayed on the Surface, so when was the Surface created? Return to the setView method of ViewRootImpl at the beginning of Step 4:
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) { mView = view; .// 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(); .try{... res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mInputChannel); }... }}}Copy the code
WMS handles event distribution as well as window management, so before associating with WMS, make sure that the View Tree has performed layout operations to receive events from the WMS.
Trace the requestLayout() method of ViewRootImpl:
public void requestLayout(a) {
if(! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested =true; scheduleTraversals(); }}Copy the code
Trace the scheduleTraversals() method of ViewRootImpl:
void scheduleTraversals(a) {
if(! mTraversalScheduled) { mTraversalScheduled =true;
// Set synchronization barrier to suspend processing of subsequent synchronization messages
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// Execute mTraversalRunnable on the next frame
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); . }}Copy the code
First, set synchronization barriers, suspend processing of subsequent synchronization messages, and then use the Choreographer class to execute mTraversalRunnable objects when the next frame is drawn (about the Choreographer principle, See Choreographer mechanism implementation for Android). Briefly, Choreographer will receive inside VSync signals from the SurfaceFlinger for a VSync cycle of about 16ms.
SurfaceFlinger is initiated by the init process running on the underlying process, a system of its main responsibility is to synthesize and apply colours to a drawing Surface (Layer), vertical sync signal and sent to the target process VSync. Therefore, if you want to receive Vsync signals, you must first establish a connection with SurfaceFlinger, which is why you go to Step 5 first.
MTraversalRunnable is a Runnable object:
final class TraversalRunnable implements Runnable {
@Override
public void run(a) { doTraversal(); }}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
Copy the code
The run() method has only one line of code, and the doTraversal() method looks like this:
void doTraversal(a) {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// Remove synchronization barriersmHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier); .// Enter the View drawing processperformTraversals(); . }}Copy the code
Once the synchronization barrier has been removed, all the preparatory work for rendering has been done, and the performTraversals() method is called to enter the View’s rendering process.
private void performTraversals(a) {
finalView host = mView; // mView is just a DecorView. relayoutWindow(params, viewVisibility, insetsPending); .// Execute the Measure processperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec); .// Execute the Layout processperformLayout(lp, desiredWindowWidth, desiredWindowHeight); .// Execute Draw processperformLayout(); . }Copy the code
Trace relayoutWindow method:
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {...int relayoutResult = mWindowSession.relayout(
mWindow, mSeq, params,
(int) (mView.getMeasuredWidth() * appScale + 0.5 f),
(int) (mView.getMeasuredHeight() * appScale + 0.5 f),
viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets, mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingConfiguration, mSurface); . }Copy the code
Binder call again (highly recommended), which calls the RELayout method of WMS. Note that the last argument is the SurfaceView object, which was initialized when the ViewRootImpl was defined:
final Surface mSurface = new Surface();
Again, relayoutWindow at WMS:
public int relayoutWindow(Session session, IWindow client, int seq,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, int flags,
Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
Configuration outConfig, Surface outSurface){... result = createSurfaceControl(outSurface, result, win, winAnimator); . }private int createSurfaceControl(Surface outSurface, int result, WindowState win,WindowStateAnimator winAnimator) {... surfaceController = winAnimator.createSurfaceLocked(win.mAttrs.type, win.mOwnerUid); . surfaceController.getSurface(outSurface); }Copy the code
This first calls WindowStateAnimator createSurfaceLocked to generate a truly valid Surface object in the Native layer, The getSurface method then associates the Surface object in the Java layer with it.
Step 7: Formally draw the View
After the Surface has been applied, the View otimpl performTraversals() method will continue to execute. This method is quite verbose, ignoring conditional judgment, and is condensed as follows:
private void performTraversals(a) {... relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); . WindowManager.LayoutParams lp = mWindowAttributes; .// Get the DecorView width and height MeasureSpec
int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
intchildHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); .// Execute the Measure processperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec); .// Execute the Layout processperformLayout(lp, desiredWindowWidth, desiredWindowHeight); .// Execute Draw processperformLayout(); . }Copy the code
First, the values of childWidthMeasureSpec and childHeightMeasureSpec are retrieved from the getRootMeasureSpec() method for drawing the DecorView. Because the DecorView is the root of all the child elements, the layout of the child elements is layered, so all the child elements are measured, laid out, and drawn layer by layer, starting from the DecorView, Corresponding to performMeasure(), performLayout() and performLayout() methods, the whole process is shown as follows:
Understand the MeasureSpec
MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = MeasureSpec = Measure; MeasureSpec is defined as follows to avoid creating too many objects to reduce memory allocation:
public static class MeasureSpec {
private static final int MODE_SHIFT = 30;
private static final int MODE_MASK = 0x3 << MODE_SHIFT;
// Unrestricted measurement mode: the parent container does not impose any restrictions on the View, the View can be as large as it wants,
// This pattern is usually used within systems.
public static final int UNSPECIFIED = 0 << MODE_SHIFT;
SpecSize: The parent container has determined the size required by the View.
// When the layout parameter is match_parent or a specific value
public static final int EXACTLY = 1 << MODE_SHIFT;
The parent container specifies that the View can only be SpecSize.
// The layout argument is wrap_content
public static final int AT_MOST = 2 << MODE_SHIFT;
// Create a MeasureSpec based on SpecMode and SpecSize
public static int makeMeasureSpec(int size, int mode) {
if (sUseBrokenMakeMeasureSpec) {
return size + mode;
} else {
return(size & ~MODE_MASK) | (mode & MODE_MASK); }}/ / get SpecMode
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
/ / get SpecSize
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
}
/ / adjust the MeasureSpec
static int adjust(int measureSpec, int delta) {
final int mode = getMode(measureSpec);
if (mode == UNSPECIFIED) {
return make MeasureSpec(0, UNSPECIFIED);
}
int size = getSize(measureSpec) + delta;
if (size < 0) {
size = 0;
}
returnmakeMeasureSpec(size, mode); }}Copy the code
Looking at the getRootMeasureSpec() method, the first argument passed is the width or height of the entire screen, and the second argument is the Window’s LayoutParams:
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
int measureSpec;
switch (rootDimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
break;
case ViewGroup.LayoutParams.WRAP_CONTENT:
measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
break;
default:
measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
break;
}
return measureSpec;
}
Copy the code
From the code logic, the DecorView’s MeasureSpec is generated as follows:
- LayoutParams = MATCH_PARENT: Exact mode, size is the width or height of the screen;
- LayoutParams = WRAP_CONTENT: Cannot exceed the width or height of the screen;
- Fixed size: Exact mode, size is the value specified in LayoutParams.
But for ordinary views, the View’s measure() method is called by the parent ViewGroup. Look at the measureChildWithMargins() method of the ViewGroup:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// Gets the layout parameters of the child elements
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// MeasureSpec produces the child element
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code
As you can see, before calling the measure() method of the child element, the getChildMeasureSpec() method produces the child element’s MeasureSpec. In addition to the parent’s MeasureSpec and the child’s LayoutParams, the child’s Margin and the parent’s Padding are also related to the size of the child. The getChildMeasureSpec() method does this:
public static int getChildMeasureSpec(int spec, int padding, int childDimesion) {
int specMode = MeasureSpec.getMode(spec);
int specSize = MeasureSpec.getSize(spec);
// The maximum available space of the child element is the size of the parent container minus the size of the space already occupied by the parent
int size = Math.max(0, specSize - padding);
int resultSize = 0;
int resultMode = 0;
switch (sepcMode) {
// Parent has imposed an exact size on us
case MeasureSpec.EXACTLY:
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 (childDimesion == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size. It can't be
// bigger than us.
resultSize = size;
resultMode = MeasureSpec.AT_MOST;
}
break;
// Parent has imposed a maximum size on us
case MeasureSpec.AT_MOST:
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;
// Parent asked to see how big we want to be
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 = 0;
resultMode = MeasureSpec.UNSPECIFIED;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
// Child wants to determine its own size....
// find out how big it should be
resultSize = 0;
resultMode == MeasureSpec.UNSPECIFIED;
}
break;
}
return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
Copy the code
The whole process is a bit more complicated and can be seen in the following table:
Measure process analysis
Back in the performTraversals() method, the DecorView’s MeasureSpec is retrieved and the performMeasure() method is called:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {... mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); . }Copy the code
The mView is a DecorView instance passed in via the setView() method that inherits from FrameLayout, which in turn is a ViewGroup that also inherits from the View. The View’s measure() method is final and cannot be overridden by subclasses, so it is actually the View’s measure method that is called:
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {... onMeasure(widthMeasureSpec, heightMeasureSpec); . }Copy the code
View onMeasure()
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
Copy the code
As you can see, the View’s default onMeasure() method first calls getDefaultSize() to get the default width and height values, and then setMeasuredDimension() to set the values. Look at the code for getDefaultSize() :
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
Copy the code
Either AT_MOST or EXACTLY returns a measureSpec specSize, which is the final result of the measurement. In the case of UNSPECIFIED, it returns a recommended minimum value that is dependent on the background size of the minimum set by the child element.
As you can see from the default implementation of onMeasure(), if we define a control that inherits directly from the View and do not override the onMeasure() method, When you use this control and set layout_width or layout_height to wrap_content, it will look like match_parent! When wrap_content is used in the layout, the specMode is AT_MOST and the specSize is parentSize. GetDefaultSize () returns specSize, so the width or height of the child element is set to the size of the remaining space of the parent container. A common solution is to override the onMeasure() method as follows:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
// Default width/height
int mWidth = default_width;
int mHeight = default_height;
// When the layout parameter is set to wrap_content, use the default value
if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT && getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, mHeight);
// Width/height The default values are used when any of the layout parameters are wrap_content
} else if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
setMeasuredDimension(mWidth, heightSize);
} else if(getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) { setMeasuredDimension(widthSize, mHeight); }}Copy the code
Because DecorView inherits from FrameLayout, which is a ViewGroup, the ViewGroup is an abstract class that does not define a specific measurement process and uses the View’s onMeasure() by default. DecorView rewrites the onMeasure() method to override the onMeasure() method. Each subclass has different layout properties and therefore needs different measurement logic.
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {...final int widthMode = getMode(widthMeasureSpec);
final intheightMode = getMode(heightMeasureSpec); .if(widthMode == AT_MOST) { ... }...if(heightMode == AT_MOST) { ... }...super.onMeasure(widthMeasureSpec, heightMeasureSpec); . }Copy the code
The onMeasure() method of the parent FrameLayout class will be called as follows:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
intcount = getChildCount(); .for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if(mMeasureAllChildren || child.getVisibility() ! = GONE) { measureChildWithMargins(child, widthMeasureSpec,0, heightMeasureSpec, 0); . }}... setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); . }Copy the code
As you can see, the measureChildWithMargins() method is iterated over each of its children, and the measureChildWithMargins() method is already there to calculate the child’s MeasureSpec and then call the child’s measure() method:
protected void measureChildWithMargins(View child,
int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
// Gets the layout parameters of the child elements
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
// MeasureSpec produces the child element
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
+ heightUsed, lp.height);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
Copy the code
At this time, the measure() method of the View is actually called. As can be seen from the above content, the onMeasure() method of the child element will be called again, so that the onMeasure() method of each child element is recursively called to measure.
Layout Process Analysis
Back to the performLayout() method, after performMeasure() traverses all child elements, the performLayout() method is called:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {...// Call the layout() method of the DecorView
host.layout(0.0, host.getMeasuredWidth(), host.getMeasuredHeight()); . }Copy the code
GetMeasuredWidth () and getMeasuredHeight() are the measurements calculated by the DecorView in the previous Measure process, and are passed as arguments to the Layout () method. The View layout() method is called:
public void layout(int l, int t, int r, int b) {...// Whether the View state has changed
boolean changed = isLayoutModeOptical(mParent) ?
setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
// If the View state changes, the layout is rearranged
if(changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) { onLayout(changed, l, t, r, b); . }... }Copy the code
SetOpticalFrame () also calls the setFrame() method directly inside setOpticalFrame().
protected boolean setFrame(int left, int top, int right, int bottom) {
boolean changed = false;
if(mLeft ! = left || mRight ! = right || mTop ! = top || mBottom ! = bottom) { changed =true; .int oldWidth = mRight - mLeft;
int oldHeight = mBottom - mTop;
int newWidth = right - left;
int newHeight = bottom - top;
booleansizeChanged = (newWidth ! = oldWidth) || (newHeight ! = oldHeight);// Invalidate our old position
invalidate(sizeChanged);
// Initialize the variable
mLeft = left;
mTop = top;
mRight = right;
mBottom = bottom;
// Update the display list for renderingmRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom); .if (sizeChanged) {
// If the View size changes, the onSizeChanged method is called insidesizeChange(newWidth, newHeight, oldWidth, oldHeight); }... }// Returns whether the change has occurred
return changed;
}
Copy the code
The setFrame() method does the following:
- Judge whether the View position changes, and according to the change of the corresponding processing;
- Initialize the mLeft, mBottom, mRight, and mTop variables. The first time this method is called, the return value istrue;
- Call the native methods in RenderNode to update the display list for rendering.
Go back to the layout() method and use the state returned by setFrame() to determine whether onLayout() needs to be called for relayout. Check the onLayout() method:
/** * Assign a size and position to a view and all of its * descendants * * This is the second phase of the layout mechanism. * (The first is measuring). In this phase, each parent calls * layout on all of its children to position them. * This is typically done using the child measurements * that were stored in the measure pass().
* * Derived classes should not override this method. * Derived classes with children should override * onLayout. In that method, they should * call layout on each of their children.
*/
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {}Copy the code
The onLayout() method is an empty method. As you can see from the last paragraph of the comment, a single View does not need to override this method. When a View subclass has child elements (i.e. viewgroups), you should override this method and call layout() for each child element. So as a ViewGroup, we look at the DecorView’s onLayout() method:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom); . }Copy the code
The main logic here is to call the parent’s onLayout() method and continue tracing FrameLayout’s onLayout() method:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
final int count = getChildCount();
final int parentLeft = getPaddingLeftWithForeground();
final int parentRight = right - left - getPaddingRightWithForeground();
final int parentTop = getPaddingTopWithForeground();
final int parentBottom = bottom - top - getPaddingBottomWithForeground();
for (int i = 0; i < count; i++) {
final View child = getChildAt(i);
if(child.getVisibility() ! = GONE) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final int width = child.getMeasuredWidth();
final int height = child.getMeasuredHeight();
int childLeft;
int childTop;
int gravity = lp.gravity;
if (gravity == -1) {
gravity = DEFAULT_CHILD_GRAVITY;
}
final int layoutDirection = getLayoutDirection();
final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
lp.leftMargin - lp.rightMargin;
break;
case Gravity.RIGHT:
if(! forceLeftGravity) { childLeft = parentRight - width - lp.rightMargin;break;
}
case Gravity.LEFT:
default:
childLeft = parentLeft + lp.leftMargin;
}
switch (verticalGravity) {
case Gravity.TOP:
childTop = parentTop + lp.topMargin;
break;
case Gravity.CENTER_VERTICAL:
childTop = parentTop + (parentBottom - parentTop - height) / 2 +
lp.topMargin - lp.bottomMargin;
break;
case Gravity.BOTTOM:
childTop = parentBottom - height - lp.bottomMargin;
break;
default:
childTop = parentTop + lp.topMargin;
}
// Call the layout method for each child elementchild.layout(childLeft, childTop, childLeft + width, childTop + height); }}}Copy the code
In the layoutChildren() method, you calculate the left, top, right, and bottom values for each child element based on your layout logic and call their Layout () method.
The Layout process is used by the ViewGroup to determine the position of its child elements. Once the position of the ViewGroup is determined, the onLayout() method iterates through and calls the Layout() method of all child elements. The onLayout() method of the child element is called when the layout() method of the child element is called, thus implementing layers of recursion.
Draw process analysis
Finally, back to the main performTraversals() method, where the size of each View is determined by the Measure process and the position of each View is determined by the Layout process. Now we move on to the next flow to determine the exact drawing details of each View. View the performDraw() method contents:
private void performDraw(a) {...final boolean fullRedrawNeeded = mFullRedrawNeeded;
mFullRedrawNeeded = false;
mIsDrawing = true;
try {
draw(fullRedrawNeeded);
} finally {
mIsDrawing = false; }... }Copy the code
Trace draw() method:
private void draw(boolean fullRedrawNeeded) {...// "dirty" areas, i.e. areas that need to be redrawn
finalRect dirty = mDirty; .if (fullRedrawNeeded) {
mAttachInfo.mIgnoreDirtyState = true;
dirty.set(0.0, (int) (mWidth * appScale + 0.5 f), (int) (mHeight * appScale + 0.5 f)); }...if(! dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {if(mAttachInfo.mHardwareRenderer ! =null && mAttachInfo.mHardwareRenderer.isEnabled()) {
...
// Call drawSoftware
if(! drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {return; }}}... }Copy the code
Check the drawSoftware() method implementation:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo,...) {
finalCanvas canvas; . canvas = mSurface.lockCanvas(dirty); . mView.draw(canvas); . mSurface.unlockCanvasAndPost(canvas); }Copy the code
The mView is a DecorView, and the mSurface is a Surface object in the Java layer. The actual operation is mapped to the Surface object in the Native layer. Let’s see how the Canvas object is pulled out:
public Canvas lockCanvas(Rect inOutDirty){... mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);return mCanvas;
}
Copy the code
MNativeObject is the pointer of the Surface object of the Native layer. Here, a Canvas object is obtained from the Surface object of the Native layer through the Native method nativeLockCanvas method. The Canvas object is then passed to the DecorView.
Continue looking at the draw() method in DecorView:
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
if(mMenuBackground ! =null) { mMenuBackground.draw(canvas); }}Copy the code
Draw () {FrameLayout (); ViewGroup () {draw();
public void draw(Canvas canvas) {
final int privateFlags = mPrivateFlags;
final boolean dirtyOpaque = (privateFlags & PFLAG_DIRTY_MASK) == PFLAG_DIRTY_OPAQUE &&
(mAttachInfo == null| |! mAttachInfo.mIgnoreDirtyState); mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;/* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) */
// Step 1, draw the background, if needed
int saveCount;
if(! dirtyOpaque) { drawBackground(canvas); }// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
booleanhorizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) ! =0;
booleanverticalEdges = (viewFlags & FADING_EDGE_VERTICAL) ! =0;
if(! verticalEdges && ! horizontalEdges) {// Step 3, draw the content
if(! dirtyOpaque) onDraw(canvas);// Step 4, draw the children
dispatchDraw(canvas);
// Overlay is part of the content and draws beneath Foreground
if(mOverlay ! =null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// we're done...
return;
}
/* * Here we do the full fledged routine... * (this is an uncommon case where speed matters less, * this is why we repeat some of the tests that have been * done above) */
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0 f;
float bottomFadeStrength = 0.0 f;
float leftFadeStrength = 0.0 f;
float rightFadeStrength = 0.0 f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0 f;
bottomFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0 f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0 f;
rightFadeStrength = Math.max(0.0 f, Math.min(1.0 f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0 f;
}
saveCount = canvas.getSaveCount();
int solidColor = getSolidColor();
if (solidColor == 0) {
final int flags = Canvas.HAS_ALPHA_LAYER_SAVE_FLAG;
if (drawTop) {
canvas.saveLayer(left, top, right, top + length, null, flags);
}
if (drawBottom) {
canvas.saveLayer(left, bottom - length, right, bottom, null, flags);
}
if (drawLeft) {
canvas.saveLayer(left, top, left + length, bottom, null, flags);
}
if (drawRight) {
canvas.saveLayer(right - length, top, right, bottom, null, flags); }}else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
if(! dirtyOpaque) onDraw(canvas);// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, right, top + length, p);
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, bottom - length, right, bottom, p);
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(left, top, left + length, bottom, p);
}
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
canvas.drawRect(right - length, top, right, bottom, p);
}
canvas.restoreToCount(saveCount);
// Overlay is part of the content and draws beneath Foreground
if(mOverlay ! =null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
}
Copy the code
As you can see from the code comments, the draw() process is divided into six steps:
- Draw the View background;
- Save the current canvas layer;
- Draw the contents of the View;
- If there are child elements, the draw() method of the child element is called;
- Draw the fade-in effect and restore the layer;
- Draw the View’s decorations (scroll bars, etc.).
Step 2 and step 5 are not normally used, so let’s move on to step 3 and see how it allocates the drawing of child elements:
protected void dispatchDraw(Canvas canvas) {}Copy the code
Obviously, a single View has no child elements, so let’s see how a ViewGroup does this:
@Override
protected void dispatchDraw(Canvas canvas) {...for (int i = 0; i < childrenCount; i++) {
while (transientIndex >= 0 && mTransientIndices.get(transientIndex) == i) {
final View transientChild = mTransientViews.get(transientIndex);
if((transientChild.mViewFlags & VISIBILITY_MASK) == VISIBLE || transientChild.getAnimation() ! =null) { more |= drawChild(canvas, transientChild, drawingTime); }... }... }... }protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
return child.draw(canvas, this, drawingTime);
}
Copy the code
In the ViewGroup dispatchDraw(), each child element is iterated over and their draw() method is called, thus implementing the layers of recursive calls to finish the drawing.
summary
Throughout the whole process of Measure, Layout and Draw, the flow chart is shown as follows:
Step 8: Display the View
If you remember, the View Measure, Layout, and Draw processes performed in the previous step are all derived from the wm.addView() method in the previous handleResumeActivity(). Review the handleResumeActivity() method:
final void handleResumeActivity(IBinder token,
boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) { ActivityClientRecord r = mActivities.get(token); .// Methods such as onStart() and onResume() are eventually called
r = performResumeActivity(token, clearHide, reason);
if(r ! =null) {
finalActivity a = r.activity; .if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
/ / get DecorView
View decor = r.window.getDecorView();
// Make the DecorView invisible
decor.setVisibility(View.INVISIBLE);
// Get ViewManager, here is WindowManagerImpl instanceViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; .if(a.mVisibleFromClient && ! a.mWindowAdded) {// Flag set to true
a.mWindowAdded = true;
// Call WindowManagerImpl's addView methodwm.addView(decor, l); }}else if (!willBeVisible) {
...
}
...
if(! r.activity.mFinished && willBeVisible && r.activity.mDecor ! =null && !r.hideForNow) {
...
if (r.activity.mVisibleFromClient) {
// Call the makeVisible method to make the DecorView visibler.activity.makeVisible(); }}... }else {
try {
// If an exception occurs during this process, the Activity is killed
ActivityManagerNative.getDefault()
.finishActivity(token, Activity.RESULT_CANCELED, null,
Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
} catch (RemoteException ex) {
throwex.rethrowFromSystemServer(); }}}Copy the code
You can see that the DecorView is invisible until the wm.AddView () method is called, so even after the Measure, Layout, and Draw processes, our View is still not displayed on the screen. Take a look at the Activity’s makeVisible() method:
void makeVisible(a) {
if(! mWindowAdded) { ViewManager wm = getWindowManager(); wm.addView(mDecor, getWindow().getAttributes()); mWindowAdded =true;
}
mDecor.setVisibility(View.VISIBLE);
}
Copy the code
After executing DecorView setVisibility(), our View will officially appear on the screen!
conclusion
- Window is an abstract class that provides methods for various Window operations;
- PhoneWindow is the only implementation of Window, and there is an instance of PhoneWindw in each Acitvity;
- DecorView — Top level view that inherits from FrameLayout. When setContentView(), PhoneWindow creates and associates a DecorView with the DecorView;
- PhoneWindow parses and adds the layout file to the DecorView based on Theme, Feature, etc. These layouts contain a FrameLayout with an ID of Content.
- The layout file set in the setContentView() method is parsed by PhoneWindow and pressed into the FrameLayout id of Content in the DecorView;
- The formal rendering of the View begins with the performTraversals() method of the ViewRootImpl;
- Before a DecorView associates with the WMS, a requestLayout operation is performed to prepare the DecorView for receiving events.
- RequestLayout calls WMS’s relayoutWindow method with Binder to map Surface objects in the Java layer to Native layer.
- A single View generally needs to rewrite the onMeasure() method to calculate its own width and height according to the layout parameters and the parent View’s measurement specifications and save;
- The ViewGroup needs to override the onMeasure() method to calculate the size of all its children and then calculate its own size and save it;
- A single View generally does not need to override the onLayout() method;
- The ViewGroup needs to override the onLayot() method to determine the positions of all child elements based on the measured values;
- The Canvas argument of the View’s onDraw method is returned from the Surface object in the Native layer;
- A single View needs to override the onDraw() method to draw itself;
- The ViewGroup needs to override the onDraw() method to draw itself and traverse the child elements to draw them.
- The ActivityThread will call activit.makevisible () to make the DecorView visible after the Activity’s onResume() life cycle has been called.
This whole process can be roughly represented as follows:
series
This is what happens when you press the power button — Analysis of the Startup process of the Android system
Android App/Activity startup process analysis
How to draw the content on the screen — How Android View works
Refer to the article
Android application layer View drawing process and source code analysis
(3) custom View Layout process – the most understandable custom View principle series
Android UI display principle summary
An article to understand the relationship between The Android graphics system Surface and SurfaceFlinger
Book an In-depth Understanding of the Android kernel (Volume 1)
If you have any questions or different opinions about the content of this article, please leave a comment and we will discuss it together.