Android Interview Progression Guide directory

Computer network

  1. HTTP quick

Android

  1. Interviewer: Task stack? Return stack? Boot mode? Can’t you tell the difference?
  2. Interviewer: the life cycle of the chatter Activity
  3. Interviewer: Tell me about Context
  4. Interviewer: Why can’t you display a Dialog using Application Context?
  5. Interviewer: Can outofMemoryErrors be try caught?
  6. Interviewer: Why did activity.Finish () wait 10 seconds before onDestroy?
  7. Interviewer: How do you monitor the FPS of your application?
  8. Interviewer: Why can view. post get the width and height of the View?

directory

  • Quiz: Where can I get the width and height of a View?
  • At what point in time is the View measured?
  • Explaining the post ()
  • How else can I get the view width and height?
  • The last

Quiz: Where can I get the width and height of a View?

Today’s article will be more relaxed, compared to the previous few not so large section of the source code to gnaw. To get the width and height of the View, let’s test the code:

class MainActivity : BaseLifecycleActivity(a){

    private val binding by lazy { ActivityMainBinding.inflate(layoutInflater) }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(binding.root)

        // Get the width and height in onCreate()
        Log.e("measure"."measure in onCreate: width=${window.decorView.width}, height=${window.decorView.height}")

        // Get the width in the view.post () callback
        binding.activity.post {
            Log.e("measure"."measure in View.post: width=${window.decorView.width}, height=${window.decorView.height}")}}override fun onResume(a) {
        super.onResume()
        // Get the width in the onResume() callback
        Log.e("measure"."measure in onResume: width=${window.decorView.width}, height=${window.decorView.height}")}}Copy the code

Most people can give a straightforward answer:

E/measure: measure in onCreate: width=0, height=0
E/measure: measure in onResume: width=0, height=0
E/measure: measure in View.post: width=1080, height=2340
Copy the code

The width and height are not available in onCreate() and onResume(), but in the view.post () callback. As you can see from the log print order, the print statement in the view.post () callback is executed last.

Let’s think about this for a moment. When can I get the width and height of a View? Of course, at least after the View is measured. From the results above, onCreate() and onResume() occur before this point, and the callback to viet.post () occurs after this point. All we have to do is figure out the timing, and the problem will be solved.

At what point in time is the View measured?

Everyone knows that the View drawing process takes place in the onResume process (not the activity.onResume () callback), but I decided to start at the beginning as a refresher.

When an app is launched in cold (the application process does not exist), it first establishes a socket connection with Zygote and sends the parameters needed to create the process to Zygote. Zygote server receives a parameter called after ZygoteConnection. ProcessOneCommand () processing parameters, and the fork out of the application process. Finally, findStaticMain() finds the Main() method of the ActivityThread class and executes, and the application process starts.

ActivityThread is not a thread class, but it runs on the main thread, so it doesn’t matter if you consider it the main thread. In the main() method, you create an ActivityThread object, call its Attach () method, and start the main thread message loop, and the event-based message queuing mechanism comes into play.

In the ActivityThread.attach() method, Binder calls ams.attachapplication (), which does two things:

  1. Binds the current process to AMS. Binder then calls back to the application processApplicationThread.bindApplication()Method, prepare the client, create the Context, create the Application, and so on
  2. mStackSupervisor.attachApplicationLocked(app), and finally called torealStartActivityLocked()Start the Activity

The startup process of the Activity will not be described. I have written an article about paoding ding ox Activity startup process before, if you are interested, you can take a look. Here jump straight to ActivityThread. PerformLaunchActivity () method.

> ActivityThread.java

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {.../ / get the ComponentNameComponentName component = r.intent.getComponent(); .// Create ContextImpl objectContextImpl appContext = createBaseContextForActivity(r); .Reflection creates an Activity objectactivity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent); .// Create an Application object
    Application app = r.packageInfo.makeApplication(false, mInstrumentation); .The PhoneWindow object is created in the attach method
    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);

             
    / / execution onCreate ()
    if (r.isPersistable()) {
        mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
    } else{ mInstrumentation.callActivityOnCreate(activity, r.state); }...return activity;
}
Copy the code

MInstrumentation. CallActivityOnCreate () method will eventually callback Activity. The onCreate () method. At this point, the setContentView() method is executed. The setContentView() logic is complicated, but what it does is straightforward. Create the DecorView, then parse the XML based on the layout file ID we passed in, and stuff the resulting view into the DecorView. Notice that so far all we have is an empty shell View tree, which is not added to the screen, and can’t be. So, getting the view width in the onCreate() callback is obviously not desirable.

After onCreate(), we skip onStart(), there’s nothing too important going on and go straight to onResume().

Note: The Activity lifecycle is scheduled by the ClientLifecycleManager class. See the source code for the Activity lifecycle.

> ActivityThread.java

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
        String reason) {...// 1. Callback onResume
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
    ···
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams l = r.window.getAttributes();
    // 2. Add decorView to WindowManagerwm.addView(decor, l); . }Copy the code

Two things, call back onResume and add DecorView to WindowManager. So, getting the width and height of the view in the onResume() callback is no different from getting the width and height of the view in the onCreate() callback.

Wm. AddView (decor, l) final call to WindowManagerGlobal. The addView ().

public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {...// 1. Key, initialize ViewRootImpl
    root = new ViewRootImpl(view.getContext(), display);
    // 2. Key, initiate drawing and display on screen
    root.setView(view, wparams, panelParentView);
Copy the code

Both lines of code are important here. Take a look at the ViewRootImpl constructor in comment 1.

public ViewRootImpl(Context context, Display display) {...// 1. IWindowSession proxy object, which communicates with WMS with BindermWindowSession = WindowManagerGlobal.getWindowSession(); ./ / 2.
    mWidth = -1;
    mHeight = -1; .// 3. Initialize AttachInfo
    // Remember that mAttachInfo is initialized here
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context); .// 4. Initialize Choreographer with Threadlocal storage
    mChoreographer = Choreographer.getInstance();
}
Copy the code
  1. Initializes mWindowSession, which can communicate with Binder WMS
  2. Here you can see that the width and height have not been assigned
  3. Initializing AttachInfo is important here and will be covered later
  4. Starting Choreographer, Previous Article Interviewer: How do I monitor my application’s FPS? In detail

Look again at the viewrootimpl.setView () method in comment 2.

> ViewRootImpl.java

// The view argument is a DecorView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        if (mView == null) {
            mView = view;

            // 1. Initiate the first drawing
            requestLayout();

            // 2. Binder calls session.addtodisplay () to add window to the screen
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);

            // 3. Assign the parent of decorView to ViewRootImpl
            view.assignParent(this); }}}Copy the code

The requestLayout() method initiates the first drawing.

> ViewRootImpl.java

public void requestLayout(a) {
    if(! mHandlingLayoutInLayoutRequest) {// Check the thread
        checkThread();
        mLayoutRequested = true;
        / / the keyscheduleTraversals(); }}Copy the code

ViewRootImpl. ScheduleTraversals () method is introduced in detail in the article, here to summarize:

  1. ViewRootImpl.scheduleTraversals()Method creates a synchronization barrier to prioritize asynchronous messages. throughChoreographer.postCallback()Method submits the taskmTraversalRunnable, this task is responsible for View measurement, layout, drawing.
  2. Choreographer.postCallback()Methods byDisplayEventReceiver.nativeScheduleVsync()Method registers the next time with the underlying systemvsyncSignal monitoring. The next timevsyncWhen it comes, the system will call it backdispatchVsync()Method, the final callbackFrameDisplayEventReceiver.onVsync()Methods.
  3. FrameDisplayEventReceiver.onVsync()Method to retrieve the previously committedmTraversalRunnableAnd executed. This completes the drawing process.

The doTraversal() method is performed in mTraversalRunnable.

> ViewRootImpl.java

void doTraversal(a) {
if (mTraversalScheduled) {
    // 1. MTraversalScheduled set to false
    mTraversalScheduled = false;
    // 2. Remove the synchronization barrier
    mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

    // 3. Start the layout, measurement and drawing processperformTraversals(); . }Copy the code
> ViewRootImpl.java

private void performTraversals(a) {...// 1. Bind Window
    host.dispatchAttachedToWindow(mAttachInfo, 0);
    mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);

    getRunQueue().executeActions(mAttachInfo.mHandler);

    // 2. Request WMS to calculate window size
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

    / / 3. Measurement
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    4 / / layout
    performLayout(lp, mWidth, mHeight);

    / / 5. Paint
    performDraw();
}
Copy the code

The logic of the performTraversals() method is quite complex, so a few important method calls are condensed here. At this point, the overall drawing process of the View is complete, and there is no doubt that the width and height can be obtained at this point.

The time for the View to be measured has been found. Now let’s verify that view.post () executes the callback at this point.

Explaining the post ()

> View.java

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if(attachInfo ! =null) {
        // 1. AttachInfo is not empty and is sent through mHandler
        return attachInfo.mHandler.post(action);
    }
    // 2. If attachInfo is empty, add it to the queue
    getRunQueue().post(action);
    return true;
}
Copy the code

The key here is whether attachInfo is empty. As mentioned in the previous section, let’s review:

  • attachInfoIs in theViewRootImplConstructor of the,
  • ViewRootImplIs in theWindowManagerGlobal.addView()To create the
  • WindowManagerGlobal.addView()ActivityThreadhandleResumeActivity()Is called in, but inActivity.onResume()After the callback

So, if attachInfo is not empty, you are at least in the middle of the message processing for the view drawing. Send the Runnable to the post() Handler. When the Message containing the Runnable is executed, it must be able to get the View’s width and height.

In the onCreate() and onResume() callbacks, attachInfo must be empty and getRunQueue().post(action) is relied on. The principle is simple: store the Runnable to be executed by post() in a queue and pull it out at the appropriate time (the View has been measured). Let’s first see what queue getRunQueue() gets.

> View.java

private HandlerActionQueue getRunQueue(a) {
    if (mRunQueue == null) {
        mRunQueue = new HandlerActionQueue();
    }
    return mRunQueue;
}
Copy the code
public class HandlerActionQueue {
    private HandlerAction[] mActions;
    private int mCount;

    public void post(Runnable action) {
        postDelayed(action, 0);
    }

    // Send the task
    public void postDelayed(Runnable action, long delayMillis) {
        final HandlerAction handlerAction = new HandlerAction(action, delayMillis);

        synchronized (this) {
            if (mActions == null) {
                mActions = new HandlerAction[4]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; }}// Execute the task
    public void executeActions(Handler handler) {
        synchronized (this) {
            final HandlerAction[] actions = mActions;
            for (int i = 0, count = mCount; i < count; i++) {
                final HandlerAction handlerAction = actions[i];
                handler.postDelayed(handlerAction.action, handlerAction.delay);
            }

            mActions = null;
            mCount = 0; }}...private static class HandlerAction {
        final Runnable action;
        final long delay;

        public HandlerAction(Runnable action, long delay) {
            this.action = action;
            this.delay = delay;
        }

        public boolean matches(Runnable otherAction) {
            return otherAction == null && action == null|| action ! =null&& action.equals(otherAction); }}}Copy the code

The HandlerActionQueue is an array of handlerActions with an initial size of 4. HandlerAction has two member variables, the Runnable to execute and the time to delay execution.

The execution logic of the queue is in the executeActions(Handler) method, where the task is distributed through an incoming handler. Now we just need to find when to call executeActions(). This can be found in view.java, where tasks are distributed in the dispatchAttachedToWindow() method.

void dispatchAttachedToWindow(AttachInfo info, int visibility) {...if(mRunQueue ! =null) {
            // Assign tasks
            mRunQueue.executeActions(info.mHandler);
            mRunQueue = null;
        }
    / / callback onAttachedToWindow ()
    onAttachedToWindow();
}
Copy the code

For dispatchAttachedToWindow(), you can do a global search with Ctrl + F in this article. We saw this in the last video, and I’ll remind you to remember it, in the performTraversals() method.

> ViewRootImpl.java

private void performTraversals(a) {...// 1. Look here
    host.dispatchAttachedToWindow(mAttachInfo, 0);
    mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);

    getRunQueue().executeActions(mAttachInfo.mHandler);

    // 2. Request WMS to calculate window size
    relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);

    / / 3. Measurement
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

    4 / / layout
    performLayout(lp, mWidth, mHeight);

    / / 5. Paint
    performDraw();
}
Copy the code

Note note 1. You might be a little confused by this. The width and height of the View can be obtained from dispatchAttachedToWindow().

First, you need to know that performTraversals() is performed during a message processing in the main thread message queue, Mrunqueue.executeactions (), which is indirectly called by dispatchAttachedToWindow(), also sends tasks to the main thread message queue via the Handler. It must then be executed after the performTraversals() method. So, there is no problem getting the width and height of the View here.

So here, the whole closed loop is formed, just to summarize.

View.post() performs different logic depending on whether the ViewRootImpl is already created. If the ViewRootImpl has been created, that is, mAttachInfo has been initialized, send a message directly to the Handler to perform the task. If the ViewRootImpl is not created, that is, the View hasn’t started drawing yet, it will save the task as a HandlerAction, and it will be stored in the HandlerActionQueue until the View starts drawing, When the performTraversal() method is executed, the task temporarily stored in the HandlerActionQueue is distributed by the Handler in the dispatchAttachedToWindow() method.

Also note that the View drawing takes place in a Meesage process, and the view.post () task takes place in a Message process, so they must be sequential.

How else can I get the view width and height?

In addition to getting the width and height of a View through view.post (), there are two recommended ways to do this.

The first, onWindowFocusChanged().

override fun onWindowFocusChanged(hasFocus: Boolean) {  
    super.onWindowFocusChanged(hasFocus)
    if(hasFocus){ ... }}Copy the code

The second, OnGlobalLayoutListener.

binding.dialog.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener{
    override fun onGlobalLayout(a) {
        binding.dialog.viewTreeObserver.removeOnGlobalLayoutListener(this)... }})Copy the code

Either method can be called multiple times. OnWindowFocusChanged is called both when the Activity gains focus and when it loses focus. OnGlobalLayoutListener is also called multiple times when the state of the View tree changes, and listeners can be removed as needed.

The last

As an aside, the Android Interview Progression Guide is a paid column that I maintain at The Little Column and already has some paying users. This is the ninth article, in order to protect the rights of paying users, there is no way to synchronize all articles. If you’re interested in this column, poke in and take a look.

In recent years, several articles have introduced the relevant knowledge of View display principle and drawing process piecemeal. After that, a whole issue is prepared, which can be expected to be a smelly and long “foot-binding” article.

That’s it for this issue. I mean, I’ll see you next time.

In addition, you can also search my public account Bingxin said TM, follow more of my articles.