Android UI System workflow (I) :

In this article we take a look at what the Framework does in the three-step process from Activity creation to view from the Java layer.

During the Activity launch process, the handleLaunchActivity of the ActivityThread is called. The key call is the performLaunchActivity method that executes the ActivityThread. Internally complete the Activity object creation:

try {
    // Create an instance internally through Class's newInstance method
    java.lang.ClassLoader cl = appContext.getClassLoader();
    activity = mInstrumentation.newActivity(
        cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if(r.state ! =null) { r.state.setClassLoader(cl); }}catch (Exception e) {
        // ...
}
// ...
// Call the attach method to attach the Activity:
activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
// Then, after creating the Activity, call the onCreate callback
mInstrumentation.callActivityOnCreate(activity, r.state);
// Instrumentation's callActivityOnCreate method calls the activity's performCreate method
Copy the code

The Activity’s performCreate callback to the Activity’s onCreate completes the creation and loading of the DecorView.

In the Attach method of the activity:

    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        // Completes the creation of the Activity's PhoneWindow object, which holds the WMS
        // The window token given.
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        // The current thread, the one started by activityThread. main, is the UI thread
        mUiThread = Thread.currentThread();
        // Complete some member field assignments
        // ...
    }
Copy the code

When onCreate is created, the Activity is not visible to the user. The user really sees what the interface of the Activity looks like when onResume is started. The handleResumeActivity method corresponding to ActivityThread first calls the performResumeActivity method to inform the Activity of the onResume lifecycle:

public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
            String reason) {
        final ActivityClientRecord r = mActivities.get(token);
        // ...
        try {
            // ...
            r.activity.performResume(r.startsNotResumed, reason);
            r.state = null;
            r.persistentState = null;
            r.setState(ON_RESUME);
            // ...
        } catch (Exception e) {
            // ...
        }
        return r;
    }
Copy the code

Similar to Create, performResume calls the Activity’s onResume callback inside to inform the Activity that it is already visible.

Then in ActivityThread. HandleResumeActivity in:

if (r.window == null && !a.mFinished && willBeVisible) {
    r.window = r.activity.getWindow();
    // Get a DecorView loaded in the Activity's onCreate and place it in an invisible state
    View decor = r.window.getDecorView();
    decor.setVisibility(View.INVISIBLE);
    // Here WindowManager is an interface inherited from ViewManager
    // ViewManager defines interfaces for adding, updating, and removing views from activities
    // The actual implementation of WM is Windows ManagerImpl
    ViewManager 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) {
        // If the decorView is not already added to WM, perform the add process
        if(! a.mWindowAdded) { a.mWindowAdded =true;
                wm.addView(decor, l);
        } else {
        // ...
        }
    // ...
}
Copy the code

WindowManagerImpl’s addView method is called to WindowManagerGlobal’s addView, SetView creates the ViewRootImpl and adds a decorView to the ViewRootImpl.

About what ViewRootImpl does:

  • One is to act as a link between Window and View communication (such as distributing touch events to the DecorView).

  • Second, as the butler of the drawing process, I deal with the process of View measure, layout and draw.

In the ViewRootImpl setView, requestLayout is called to complete the first layout scheduling.

The requestLayout implementation is as follows:

public void requestLayout(a) {
    // If the layout request is already being processed, do not repeat the execution
    if(! mHandlingLayoutInLayoutRequest) {// Make sure the call is in the main thread
        checkThread();
        mLayoutRequested = true;
        / / scheduleTraversals. The core API of the three processes of the Android View systemscheduleTraversals(); }}Copy the code

ScheduleTravsersals, as its name indicates, completes the scheduling of ViewTree traversals, which are measures, layouts, and draws of the ViewTree.

    void scheduleTraversals(a) {
        // If you are already in the scheduling phase, there is no point in repeating the call
        // Repeated calls to requestLayout and invalidate will not trigger repeated redrawing of the View
        if(! mTraversalScheduled) { mTraversalScheduled =true;
            // Send a synchronization barrier message to the main thread to mask the processing of synchronization messages
            // Because UI update event processing must take priority over the normal tasks of the main thread.
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            // Send a mTraversalRunnable to Choreographer
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code

Choreographer is the heavyweight guest on the Android view system and, as the name suggests, Choreographer is the director who choreographers Android’s actions of animation, interaction, and drawing.

It was added after version 4.1 to synchronize Vsync signals, and works with Vsync to unify the timing of animation, input, and drawing in Android.

And the function of the postCallback is to execute our incoming callback on the next frame, either postCallback or postCallbackDelayed, Are postCallbackDelayedInternal encapsulation:

    // The following actions are actually Runnable objects
    private void postCallbackDelayedInternal(int callbackType,
            Object action, Object token, long delayMillis) {
        // ...
        synchronized (mLock) {
            final long now = SystemClock.uptimeMillis();
            final long dueTime = now + delayMillis;
            // Add callback to mCallbackQueues
            mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
            PostCallbackDelay If the delay parameter is 0
            // scheduleFrameLocked
            // otherwise, MSG_DO_SCHEDULE_CALLBACK is sent to the main thread message queue
            // asynchronous messages, and we can also see the Handler mechanism
            if (dueTime <= now) {
                scheduleFrameLocked(now);
            } else {
                Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
                msg.arg1 = callbackType;
                msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, dueTime); }}}Copy the code

Take a look at mCallbackQueues. The callbackType passed in when scheduleTraversals are called is choreback. CALLBACK_TRAVERSAL, which is itself an array. The element of the array is the CallbackQueue (that is, the queue of callbacks).

There are four callbacktypes:

// Callback of the highest priority input event type
public static final int CALLBACK_INPUT = 0;
// Animation update type Callback
public static final int CALLBACK_INSETS_ANIMATION = 2;
// The view iterates through the update type Callback
public static final int CALLBACK_TRAVERSAL = 3; 
Callback after the view traverses the Callback process
public static final int CALLBACK_COMMIT = 4;
Copy the code

In scheduleFrameLocked:

    private void scheduleFrameLocked(long now) {
        // If the current frame is already scheduled, do not schedule it again to avoid wasteful rework
        if(! mFrameScheduled) {// This flag bit will be set back to false in doFrame
            mFrameScheduled = true;
            // Whether the current system uses vsync, of course true
            if (USE_VSYNC) {
                // scheduleVsyncLocked if the scheduled thread is the main thread
                // If not, insert into the queue head of the main thread's message queue
                // an asynchronous message with what as MSG_DO_SCHEDULE_VSYNC
                if (isRunningOnLooperThreadLocked()) {
                    scheduleVsyncLocked();
                } else {
                    Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
                    msg.setAsynchronous(true); mHandler.sendMessageAtFrontOfQueue(msg); }}else {
                // If vSYNC is not used, calculate the time of the next frame by yourself
                final long nextFrameTime = Math.max(
                        mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
                if (DEBUG_FRAMES) {
                    Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
                }
                Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
                msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, nextFrameTime); }}}Copy the code

And scheduleVsyncLocked is called mDisplayEventReceiver. ScheduleVsync () :

    public void scheduleVsync(a) {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
                    + "receiver has already been disposed.");
        } else{ nativeScheduleVsync(mReceiverPtr); }}Copy the code

The nativeScheduleVsync base registers with the SurfaceFlinger service and registers with the DisplayEventReceiver itself. The next time a Vsync signal comes in, The dispatchVsync method to DisplayEventReceiver is called to call back its onVsync callback (but note that it is registered once, callback once, not callback repeatedly), while DisplayEventReceiver is an abstract class, Front mDisplayEventReceiver actually FrameDisplayEventReceiver object, its onVsync is as follows:

        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
            // Still sending messages to the main thread, mHandler is FrameHanlder
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            / / take care of this here, FrameDisplayEventReceiver is achieved
            // Runnable interface
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }
Copy the code

And sent the message, because is with the callback, so in the Handler to dispatchMessage, implementing callback run method, FrameDisplayEventReceiver run method is as follows:

public void run(a) {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }
Copy the code

The core of doFrame is to execute the various callbacks registered with Choreographer:

    void doFrame(long frameTimeNanos, int frame) {
        final long startNanos;
        synchronized (mLock) {
            if(! mFrameScheduled) {return; // no work to do
            }
            long intendedFrameTimeNanos = frameTimeNanos;
            startNanos = System.nanoTime();
            // Calculate the time difference between two calls to doFrame
            final long jitterNanos = startNanos - frameTimeNanos;
            // If the time difference exceeds one frame, a frame drop has occurred
            if (jitterNanos >= mFrameIntervalNanos) {
                // Count the number of frames dropped
                final long skippedFrames = jitterNanos / mFrameIntervalNanos;
                // If the number of frames dropped exceeds SKIPPED_FRAME_WARNING_LIMIT
                // (default 30 frames, system properties configurable)
                // Output logs
                if (skippedFrames >= SKIPPED_FRAME_WARNING_LIMIT) {
                    Log.i(TAG, "Skipped " + skippedFrames + " frames! "
                            + "The application may be doing too much work on its main thread.");
                }
                final long lastFrameOffset = jitterNanos % mFrameIntervalNanos;
                if (DEBUG_JANK) {
                    Log.d(TAG, "Missed vsync by " + (jitterNanos * 0.000001 f) + " ms "
                            + "which is more than the frame interval of "
                            + (mFrameIntervalNanos * 0.000001 f) + " ms! "
                            + "Skipping " + skippedFrames + " frames and setting frame "
                            + "time to " + (lastFrameOffset * 0.000001 f) + " ms in the past.");
                }
                frameTimeNanos = startNanos - lastFrameOffset;
            }
            // If this condition is true, there may have been a drop behavior before
            ScheduleVsyncLocked is called again, waiting for the next Vsync signal.
            if (frameTimeNanos < mLastFrameTimeNanos) {
                scheduleVsyncLocked();
                return;
            }
            // This is usually 1, which can be configured via system properties
            // Presumably an optimization to reduce refresh rate to save battery consumption
            if (mFPSDivisor > 1) {
                long timeSinceVsync = frameTimeNanos - mLastFrameTimeNanos;
                if (timeSinceVsync < (mFrameIntervalNanos * mFPSDivisor) && timeSinceVsync > 0) {
                    scheduleVsyncLocked();
                    return;
                }
            }

            mFrameInfo.setVsync(intendedFrameTimeNanos, frameTimeNanos);
            mFrameScheduled = false;
            mLastFrameTimeNanos = frameTimeNanos;
        }

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);
            mFrameInfo.markInputHandlingStart();
            // Start executing all kinds of callback
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);
            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);
            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

        if (DEBUG_FRAMES) {
            final long endNanos = System.nanoTime();
            Log.d(TAG, "Frame " + frame + ": Finished, took "
                    + (endNanos - startNanos) * 0.000001 f + " ms, latency "
                    + (startNanos - frameTimeNanos) * 0.000001 f + " ms."); }}Copy the code

The core work done in doCallbacks is to remove each Callback of type Runnable and execute:

    void doCallbacks(int callbackType, long frameTimeNanos) {
        CallbackRecord callbacks;
        synchronized (mLock) {
            // ...
            final long now = System.nanoTime();
            // Retrieve all callbacks of the current type to be processed in this frame
            callbacks = mCallbackQueues[callbackType].extractDueCallbacksLocked(
                    now / TimeUtils.NANOS_PER_MS);
            if (callbacks == null) {
                return;
            }
            // ...
        }
        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]);
            // Execute one by one along the callback queue
            for(CallbackRecord c = callbacks; c ! =null; c = c.next) {
                // ...c.run(frameTimeNanos); }}finally {
            synchronized (mLock) {
                // Some callback work for callback.} Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

In the corresponding scheduleTraversals, postCallback’s callback to Choregrapher is CALLBACK_TRAVESAL, and the corresponding Runnable is mduleTraversalRunnable, The run method calls doTraversal to start the ViewTree traversal:

    void doTraversal(a) {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            // Remove the synchronization barrier
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
            // performTraversals
            performTraversals();
            // ...}}Copy the code

Here’s a final diagram to summarize the process:

ViewRootImpl will send a mTraversalRunnable message to Choreographer by calling requestLayout (which can also be other apis), and Choreographer will then go through scheduleVsync, A callback is registered to SurficeFlinger via IPC. When the next Vsync signal arrives, SurficeFlinger calls the onVsync callback of the application process via IPC. Finally, the execution of performaTraversals in ViewRootImpl is triggered, followed by the process of recursive measure, layout and draw. In this process, the View can measure, place and draw the View through the corresponding onXXX method.