A brief analysis of Android drawing principles

A brief analysis of Android drawing principles

background

For Android development, during the interview, I am often asked, "Can you talk about the drawing process of View?" I also often ask the interviewer, "The drawing process of View".

For developers with more than 3 years of experience, knowing the basics of onMeasure/onLayout/onDraw and what they do is enough?

If you come to our company and I am your interviewer, I may examine what you have done in the past three years, what you know about View, and ask more detailed questions, such as LinearLayout's onMeasure and onLayout processes? When are they initiated and what is the execution order?

If you know all the above questions, you may be ready to join our company (if you need an internal recommendation, you can contact me, both Android and IOS positions are needed). I may check where your draw canvas comes from, how it is created and displayed on the screen? See how deep you are.

As the current mobile development market is gradually becoming mature and saturated, many companies that are not short of people need senior programmers. Besides, as we all know, the interview requires you to build airplanes and cannons, and then screw in the screws. For an Android developer with more than 3 or 5 years of experience, it is not easy to get along without knowing a little bit about Android. After talking about so many useless things, let's get back to today's topic, a brief analysis of Android's drawing principles.

This article introduces the idea

From a few relatively easy questions in the interview, we go deeper and deeper, until we get to the drawing principle of the screen.

Before talking about the drawing principles of Android, let's first introduce the basic working principles of View in Android. This article will not introduce the event transmission process.

How View Drawing Works

Let's first understand a few important classes, which are also often asked in interviews.

The relationship between Activity, Window (PhoneWindow) and DecorView

To understand the relationship between the three, let's look at the code directly, starting with setContentView at the beginning of Activity (Note: the code deletes some codes that are not part of this analysis process to avoid being too long)

  1. //Activity
  2. /**
  3. * Set the activity content from a layout resource. The resource will be
  4. * inflated, adding all   top - level views to the activity.
  5. *
  6. * @param layoutResID Resource ID to be inflated.
  7. *
  8. * @see #setContentView(android. view . View )
  9. * @see #setContentView(android. view . View , android. view .ViewGroup.LayoutParams)
  10. */
  11. public void setContentView(@LayoutRes int layoutResID) {
  12. getWindow().setContentView(layoutResID);
  13. initWindowDecorActionBar();
  14. }
  15.   
  16. public Window getWindow() {
  17. return mWindow;
  18. }

The setContentView of getWindow called inside is discussed next. So when is this mWindow created?

  1. //Activity
  2. private Window mWindow;
  3. final void attach(Context context, ActivityThread aThread,····) {
  4. attachBaseContext(context);
  5. mFragments.attachHost( null /*parent*/);
  6. mWindow = new PhoneWindow(this, window, activityConfigCallback);
  7. }

PhoneWindow is created in Activity's attach, and PhoneWindow is the implementation class of Window.

Continue with the previous setContentView

  1. //PhoneWindow
  2. @Override
  3. public void setContentView( int layoutResID) {
  4. if (mContentParent == null ) {
  5. installDecor();
  6. } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  7. mContentParent.removeAllViews();
  8. }
  9. if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
  10. final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
  11. getContext());
  12. transitionTo(newScene);
  13. } else {
  14. mLayoutInflater.inflate(layoutResID, mContentParent);
  15. }
  16. }

In setContentView, if mContentParent is empty, installDecor will be called, and finally the layout will be infalte to mContentParent. Let's take a look at installDecor

  1. //PhoneWindow
  2. // This is the view   in which the window contents are placed. It is either
  3. // mDecor itself, or a child of mDecor where the contents go.
  4. ViewGroup mContentParent;
  5.   
  6. private DecorView mDecor;
  7.   
  8. private void installDecor() {
  9. mForceDecorInstall = false ;
  10. if (mDecor == null ) {
  11. mDecor = generateDecor(-1);
  12. } else {
  13. mDecor.setWindow(this);
  14. }
  15. if (mContentParent == null ) {
  16. mContentParent = generateLayout(mDecor);
  17. }
  18. }
  19. protected DecorView generateDecor( int featureId) {
  20. return new DecorView(context, featureId, this, getAttributes());
  21. }

In installDecor, a DecorView is created. Looking at the comment of mContentParent, we can know that it is mDecor itself or the contents part of mDecor.

In summary, we roughly know the relationship between the three.

  • Activity contains a PhoneWindow.
  • PhoneWindow is inherited from Window
  • Activity sets the View to PhoneWindow via setContentView
  • PhoneWindow contains DecorView, and the final layout is added to Decorview.

Understand the relationship between ViewRootImpl, WindowManager, and WindowManagerService (WMS)

After looking at the relationship between the above three, we know that the layout is finally added to DecorView. So how is DecorView added to the system's Framework layer?

When the Activity is ready, makeVisible in the Activity will be called and View will be added through WindowManager. The code is as follows

  1. //Activity
  2. void makeVisible() {
  3. if (!mWindowAdded) {
  4. ViewManager wm = getWindowManager();
  5. wm.addView(mDecor, getWindow().getAttributes());
  6. mWindowAdded = true ;
  7. }
  8. mDecor.setVisibility( View .VISIBLE);
  9. }

So what is the relationship between them? (The client and server mentioned below are the client and server concepts in Binder communication.)

The following content is the key part that needs to be understood

  • ViewRootImpl (client): View holds mAttachInfo linked to WMS, and mAttachInfo holds ViewRootImpl. ViewRootImpl is the implementation of ViewRoot. When WMS manages the window, it needs to notify the client to perform some operations, such as event response. ViewRootImpl has an internal class W, which inherits IWindow.Stub, which is actually a Binder, and is used to interact with WMS IPC. ViewRootHandler is also its internal class that inherits Handler, and is used to make asynchronous calls with the data returned by the remote IPC.
  • WindowManger (client): The client needs to create a window, and the specific task of creating a window is completed by WMS. WindowManger is like a department manager. Whoever has any needs tells it. It interacts with WMS. The client cannot interact directly with WMS.
  • WindowManagerService (WMS) (server): responsible for window creation, display, etc.

Redrawing of View

From the above relationship, ViewRootImpl is used to receive messages from WMS. Then let's take a look at some View drawing codes in ViewRootImpl.

Here I would like to emphasize that ViewRootImpl has two important internal classes

  • The W class inherits Binder and is used to receive messages from WMS.
  • The ViewRootHandler class inherits Handler and receives asynchronous messages from the W class

Let's take a look at the ViewRootHandler class. (Take View's setVisible as an example.)

  1. // ViewRootHandler (an inner class of ViewRootImpl, used for asynchronous message processing, very similar to the startup of Acitivity)
  2. //The first step Handler receives the message passed by W (Binder)
  3. @Override
  4. public void handleMessage(Message msg) {
  5. switch (msg.what) {
  6. case MSG_INVALIDATE:
  7. (( View ) msg.obj).invalidate();
  8. break;
  9. case MSG_INVALIDATE_RECT:
  10. final View .AttachInfo.InvalidateInfo info = ( View .AttachInfo.InvalidateInfo) msg.obj;
  11. info.target.invalidate(info. left , info. top , info. right , info.bottom);
  12. info.recycle();
  13. break;
  14. case MSG_DISPATCH_APP_VISIBILITY://Handling Visible
  15. handleAppVisibility(msg.arg1 != 0);
  16. break;
  17. }
  18. }
  19.   
  20. void handleAppVisibility(boolean visible) {
  21. if (mAppVisible != visible) {
  22. mAppVisible = visible;
  23. scheduleTraversals();
  24. if (!mAppVisible) {
  25. WindowManagerGlobal.trimForeground();
  26. }
  27. }
  28. }
  29.   
  30. void scheduleTraversals() {
  31. if (!mTraversalScheduled) {
  32. mTraversalScheduled = true ;
  33. mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
  34. //Start the next refresh and traverse the View tree
  35. mChoreographer.postCallback(
  36. Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null );
  37. if (!mUnbufferedInputDispatch) {
  38. scheduleConsumeBatchedInput();
  39. }
  40. notifyRendererOfFramePending();
  41. pokeDrawLockIfNeeded();
  42. }
  43. }

Take a look at mTraversalRunnable

  1. final class TraversalRunnable implements Runnable {
  2. @Override
  3. public void run() {
  4. doTraversal();
  5. }
  6. }
  7. final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
  8.   
  9. void doTraversal() {
  10. if (mTraversalScheduled) {
  11. mTraversalScheduled = false ;
  12. mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
  13. performTraversals();
  14. }
  15. }

In TraversalRunnable, execute doTraversal. And execute performTraversals() in doTraversal. Do you see the familiar performTraversals()? Yes, the drawing of View starts here.

In ViewRootImpl's performTraversals(), this method code is very long (about 800 lines of code), the general process is

  1. Determine whether the view size needs to be recalculated, and if necessary, execute performMeasure()
  2. Do you need to relocate the location, performLayout()
  3. Do you need to redraw performDraw()?

So what causes the View to be redrawn? Here are three main reasons:

  1. Changes in the internal state of the view itself (enable, pressed, etc.) may cause redrawing
  2. View is added or deleted inside View
  3. The size and visibility of the View itself has changed

View drawing process

In the previous section, performTraversals() is called by WMS IPC. The drawing process of View is generally

From performTraversals -> performMeasure() -> performLayout() -> performDraw().

Let's take a look at performMeasure()

  1. //ViewRootImpl
  2. private void performMeasure( int childWidthMeasureSpec, int childHeightMeasureSpec) {
  3. if (mView == null ) {
  4. return ;
  5. }
  6. Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure" );
  7. try {
  8. mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
  9. finally
  10. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  11. }
  12. }
  13.   
  14. public final void measure( int widthMeasureSpec, int heightMeasureSpec) {
  15. MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
  16. && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
  17. final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
  18. && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
  19. final boolean needsLayout = specChanged
  20. && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
  21. if (forceLayout || needsLayout) {
  22. mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
  23. resolveRtlPropertiesIfNeeded();
  24. int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey( key );
  25. if (cacheIndex < 0 || sIgnoreMeasureCache) {
  26. //The onMeasure method is called here
  27. onMeasure(widthMeasureSpec, heightMeasureSpec);
  28. mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
  29. }
  30. }
  31. }

Finally, the measure method of View is called, and the measure() method in View is defined as final type to ensure the execution of the entire process. performLayout() and performDraw() are similar processes.

For programmers, customizing View only requires paying attention to the corresponding methods it provides, onMeasure/onLayout/onDraw. There are many online materials introducing this knowledge, and you can also easily see the code in View and ViewGroup. It is recommended to read the source code of LinerLayout to understand this part of knowledge, which will not be elaborated here.

A Brief Analysis of Android's Drawing Principles

Android screen drawing

As for drawing, we have to start with performDraw(). Let's take a look at how this process is drawn.

  1. //ViewRootImpl
  2. //1
  3. private void performDraw() {
  4. try {
  5. draw(fullRedrawNeeded);
  6. finally
  7. mIsDrawing = false ;
  8. Trace.traceEnd(Trace.TRACE_TAG_VIEW);
  9. }
  10. }
  11.   
  12. //2
  13. private void draw(boolean fullRedrawNeeded) {
  14. Surface surface = mSurface;
  15. if (!surface.isValid()) {
  16. return ;
  17. }
  18.   
  19. if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
  20. return ;
  21. }
  22. }
  23.   
  24. //3
  25. private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
  26. boolean scalingRequired, Rect dirty) {
  27. Canvas canvas = mSurface.lockCanvas(dirty);
  28. }

Look at the code execution flow, 1—>2->3, finally get the Java layer canvas, and then perform a series of drawing operations. The canvas is obtained through Suface.lockCanvas().

So what is Surface? Here Surface is just an abstraction. When the APP creates a window, it will call WindowManager to initiate a request to the WMS service, carrying the surface object. Only when it is allocated a screen buffer can it actually correspond to a window on the screen.

Let's take a look at the drawing architecture in the Framework. Better understanding of Surface

Surface essentially represents only a plane. Drawing different patterns is obviously an operation, not a piece of data. Android uses the Skia drawing driver library to draw on the plane, and uses canvas to represent this function in the program.

Introduction to Double Buffering Technology

In ViewRootImpl, we see that after receiving the drawing message, it does not draw immediately but calls scheduleTraversals, and calls Choreographer.postCallback() in scheduleTraversals. Why is this? This actually involves the principle of screen drawing (other platforms are similar except Android).

We all know that the display refreshes at a fixed frequency, such as 60Hz for iPhone and 120Hz for iPad Pro. When one frame of image is drawn and ready to draw the next frame, the display will send a vertical synchronization signal (VSync), so a 60Hz screen will send such a signal 60 times in one second.

And generally speaking, in a computer system, the CPU, GPU, and display work together in a specific way: the CPU submits the calculated display content to the GPU, the GPU renders it and puts it into the frame buffer, and then the video controller takes the frame data from the frame buffer according to the VSync signal and passes it to the display for display.

However, if the screen has only one buffer, when the VSync signal is sent, the screen starts to refresh, and what you see on the screen is data changing line by line. In order to make the screen look like frame by frame data, there are usually two buffers (also called double buffers). When the data is to be refreshed, the data in the other buffer is directly replaced.

In double buffering technology, if the data in the buffer cannot be refreshed within a specific time (if it is 60HZ, it is within 16ms), the screen sends a VSync synchronization signal, and the switching between the two buffers cannot be completed, then it will cause a freeze.

Back to scheduleTraversals(), this is where double buffering technology (or triple buffering technology) is used. Choreographer receives the synchronization signal of VSync, and when the screen is refreshed, it starts the screen refresh operation.

<<:  Talking about 5G mobile phones again: Excessive publicity under information asymmetry has made users enthusiastic and also coerced manufacturers

>>:  Comprehensive understanding of B-side product design: basic literacy

Recommend

New tricks for space processing: turning spacecraft debris into treasure

Recently, a Falcon 9 rocket from the United State...

6 tips to quickly improve UI design effects

Editor's note: When designing a UI, there are...

How to plan a successful and beautiful event?

As an operator, event promotion (event operation)...

Why do large education companies operate private domain traffic in QQ groups?

Since WeChat has taken over every aspect of our l...

Artificial intelligence is getting smarter, should we give AI human rights?

A few years ago, the topic of legal personhood an...

Channel promotion: How to bring better quality volume with less money?

The concept of the "second half of the Inter...

What is the purpose of the phone prompting system upgrade? Should I upgrade?

[[436573]] Some people think that mobile phones n...

How I Doubled My Pickup Rates as a Coder

Image @pinterest Providing value to customers usi...

Meituan Operations: The Marketing Logic of Meituan Takeaway Monthly Card

Buy monthly pass at super low price First, let’s ...

How to design a live broadcast room? Live broadcast room design guide!

Live streaming has long been the fastest and most...

Jack Trading Academy JTA: Principles of Institutional Orders

Jack Trading Academy JTA: Introduction to the res...

How to do high-quality marketing?

2014 was a boom year for mobile healthcare. Accor...

Why does my voice sound so bad in the audio?

Since the introduction of voice services on socia...

JD product analysis report!

This article will analyze JD.com from the perspec...