Android application layer development Framework must know

Author: Takayuki please answer

foreword

I believe that students who do application layer business development, like me, "hate" Framework. Indeed, if we are doing application layer development on weekdays, then basically we rarely touch the knowledge of Framework. But due to the pressure of life, interviews are always inevitable, so as a qualified migrant worker, we must have some basic knowledge of Framework.

There are many articles on the Internet that talk about Framework in a shallow or deep way. There are many source codes that you must have seen more than once or twice, but you will forget them after a while. In this article, I will list the basic knowledge of Framework that I think needs to be mastered from the perspective of application layer development. In order to make it easier for everyone to memorize and consolidate, I will explain more from the process rather than a very in-depth interpretation of the source code. The article will be relatively long. You can use it as a knowledge booklet and carry out targeted reading according to the directory. Browse (based on Android 8.0/9.0).

Not much to say, let's rush!

1. System startup process

Zygote process

The Zygote process is a very important process, which is responsible for the creation of the virtual machine (DVM or ART) in the Android system, the creation of the application process, and the creation of the SystemServer process.

After the system is turned on, the init process will be started, and the init process will fork out the Zygote process. When the Zygote process starts, it mainly does the following things:

  1. Create a virtual machine. When Zygote creates the application process and SystemServer process in its own way, they can get a copy of the virtual machine.
  2. As a Socket server to monitor AMS request creation process
  3. Start the SystemServer process

SystemServer process

The SystemServer process is the most important system-level process in our learning Framework. Many services we are familiar with (AMS, WMS, PMS, etc.) belong to a service provided by it, and it is the process that communicates with our APP process most frequently.

When the SystemServer process starts, it mainly does the following things:

  1. Start the Binder thread pool to prepare for inter-process communication
  2. Start various system services, such as AMS, WMS, PMS

Launcher process

The Launcher process is actually our desktop process. Students who are familiar with it know that a page of desktop is essentially a Grid-type RecylerView, so how is it created?

This is the last step of system startup. After the SystemServer process starts the AMS process, the AMS process will start the Launcher process. The Launcher process communicates with the PMS process, obtains all the installation package information on the machine, and renders the data (APP icon, APP name, etc.) to the desktop.

summary:

The system startup process can be summarized by the following figure:

2. Application process startup process

Earlier we talked about how the SystemServer process and the desktop process are started. After they are started, we can start our application process from the desktop.

After clicking the APP icon on the desktop, the Launcher process will request AMS to create the APP startup page. After AMS recognizes that the APP process does not exist, it will use the Socket method to request the Zygote process to create an APP process. After Zygote creates the APP process by fork, it will reflect the method called android.app.ActivityThreadand main()initialize the ActivityThread.

ActivityThread can be understood as a class that manages APP main thread tasks. When main()initializing in the method, we will initialize the message loop of the main thread, so as to receive all the messages that the main thread needs to process, and ensure the rendering of the UI on the main thread (the message mechanism will be described in detail later).


public static void main(String[] args) {
    ...
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);//注释1

    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler(); //注释2
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    // End of event ActivityThreadMain.
    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();
}

private void attach(boolean system, long startSeq) {
    ...
    if (!system) {
        ...
        final IActivityManager mgr = ActivityManager.getService();
        try {
            mgr.attachApplication(mAppThread, startSeq); // 注释3
        } catch (RemoteException ex) {
            throw ex.rethrowFromSystemServer();
        }
        ...
    } else {
        ...
        try {
            mInstrumentation = new Instrumentation();
            mInstrumentation.basicInit(this);
            ContextImpl context = ContextImpl.createAppContext(
                    this, getSystemContext().mPackageInfo);
            mInitialApplication = context.mPackageInfo.makeApplication(true, null);
            mInitialApplication.onCreate();
        } catch (Exception e) {
            throw new RuntimeException(
                    "Unable to instantiate Application():" + e.toString(), e);
        }
    }
    ...
}

You can see that the main thread Handler will be created in Note 2. The Handler class name here Hbelongs to the inner class of ActivityThread. It will be mentioned several times later.

When calling the method at Note 1 attach, the incoming value system参数is false, so it will go to the code at Note 3, and here it returns to AMS, calling the AMS attachApplicationmethod. attachApplicationThe method will always be called to ApplicationThreadthe bindApplicationmethod, and finally to ActivityThread.handleBindApplicationthe method:

private void handleBindApplication(AppBindData data) {
    ...
    try {
        final ClassLoader cl = instrContext.getClassLoader();
        // 注释1
        mInstrumentation = (Instrumentation)
            cl.loadClass(data.instrumentationName.getClassName()).newInstance();
    } catch (Exception e) {
        throw new RuntimeException(
            "Unable to instantiate instrumentation "
            + data.instrumentationName + ": " + e.toString(), e);
    }

    final ComponentName component = new ComponentName(ii.packageName, ii.name);
    mInstrumentation.init(this, instrContext, appContext, component,
            data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
    ...
    
    try {
        ...
        try {
            // 注释2
            app = data.info.makeApplication(data.restrictedBackupMode, null);
            ...
            if (!data.restrictedBackupMode) {
                if (!ArrayUtils.isEmpty(data.providers)) {
                    // 注释3
                    installContentProviders(app, data.providers);
                    ...
                }
            }
            // 注释4
            mInstrumentation.onCreate(data.instrumentationArgs);
        } catch (Exception e) {
            throw new RuntimeException(
                "Exception thrown in onCreate() of "
                + data.instrumentationName + ": " + e.toString(), e);
        }
        try {
            // 注释5
            mInstrumentation.callApplicationOnCreate(app);
        } catch (Exception e) {
            if (!mInstrumentation.onException(app, e)) {
                throw new RuntimeException(
                  "Unable to create application " + app.getClass().getName()
                  + ": " + e.toString(), e);
            }
        }
    }
    ...
}

Note 1 will be initialized Instrumentation, and the Instrumentation class is very important, which will be introduced later. Here it is mainly used to create Application at Note 2, Note 4, and Note 5 and call the onCreate life cycle of Application. When creating an Application, attachBaseContextthe method of the Application will be called first. In addition, we can see in Note 3 that as the application process starts, ContentProviders will also be started .

summary:

The application process startup process can be summarized by the following figure:

Three, Activity startup process

In the previous section, we have successfully started the APP process, but our page has not yet started. In this section, we will talk about how the Activity is started.

Let us recall that in the previous section, the Launcher process first requested AMS to create the APP startup page, so the desktop of the Launcher process is actually an Activtiy, which starts our startup page and calls the Activity#startActivity() method. After going in layer by layer, we can find that the method is actually Instrumentationcalled execStartActivity():

#Activity

public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
        @Nullable Bundle options) {
    if (mParent == null) { //注释1
        options = transferSpringboardActivityOptions(options);
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode, options);  //注释2
        ……
    } else {
        if (options != null) {
            mParent.startActivityFromChild(this, intent, requestCode, options);
        } else {
            // Note we want to go through this method for compatibility with
            // existing applications that may have overridden it.
            mParent.startActivityFromChild(this, intent, requestCode);
        }
    }
}

As you can see in Note 1, mParent represents the previous page. When the startup page is opened, mParent is null, and the execStartActivity() method of Instrumentation in Note 2 is called. Instrumentation not only executes startActivity, it is also responsible for all Activity life cycle calls:

[External link picture transfer failed, the source site may have an anti-theft link mechanism, it is recommended to save the picture and upload it directly (img-9Oz7klLG-1689227673647)(https://upload-images.jianshu.io/upload_images/23087443-fa3785fb293e8509.png ?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)]

But it's not a real executor, it's just wrapped, why wrap it like this? Personal understanding is because Instrumentation needs to monitor these behaviors, and its member variables mActivityMonitorsare for this purpose.

Let's go in and look at the Instrumentation execStartActivity()method:

public ActivityResult execStartActivity(
        Context who, IBinder contextThread, IBinder token, Activity target,
        Intent intent, int requestCode, Bundle options) {
    ……
    try {
        intent.migrateExtraStreamToClipData();
        intent.prepareToLeaveProcess(who);
        int result = ActivityManager.getService()
            .startActivity(whoThread, who.getBasePackageName(), intent,
                    intent.resolveTypeIfNeeded(who.getContentResolver()),
                    token, target != null ? target.mEmbeddedID : null,
                    requestCode, 0, null, options); //注释1
        checkStartActivityResult(result, intent);
    } catch (RemoteException e) {
        throw new RuntimeException("Failure from system", e);
    }
    return null;
}

Note 1 actually gets ActivityManager.getService()the startActivity to call. So ActivityManager.getService()where is this sacred? go further down

private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);//注释1
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
        };

In Note 1, we can see ActivityManager.getService()what we finally got IActivityManager. This code uses AIDL (students who don’t know it can learn it by themselves), and get the proxy object of AMS in the APP process IActivityManager. Then the startActivity that is finally called is the startActivity of AMS.

AMS will also carry out a relatively long link in the process of startActivity, mainly to verify permissions, process ActivityRecord, Activity task stack, etc. I will not go into details here, and will eventually call the method app.thread.scheduleLaunchActivity. app.threadIn fact IApplicationThread, like the previous IActivityManager, it is also a proxy object, and the proxy is the app process ApplicationThread. ApplicationThreadBelongs to the inner class of ActivityThread, we can see its scheduleLaunchActivitymethod:

@Override

public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,

    ActivityInfo info, Configuration curConfig, Configuration overrideConfig,

    CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,

    int procState, Bundle state, PersistableBundle persistentState,

    List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,

    boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {

    updateProcessState(procState, false);

    ActivityClientRecord r = new ActivityClientRecord(); // 注释1

    r.token = token;

    r.ident = ident;

    ...

    updatePendingConfiguration(curConfig);

    sendMessage(H.LAUNCH_ACTIVITY, r); // 注释2

}

Note 1 encapsulates the parameters for starting the Activity into ActivityClientRecord. Note 2 to send a message to H. The reason for sending it His because ApplicationThread itself is a Binder object, and it scheduleLaunchActivityis in the Binder thread when it is executed, so we need to Hswitch to the main thread.

HWhen a message is received LAUNCH_ACTIVITY, the method is called handleLaunchActivity:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {

    ...

    Activity a = performLaunchActivity(r, customIntent);//注释1

    if (a != null) {

        r.createdConfig = new Configuration(mConfiguration);

        reportSizeConfigurations(r);

        Bundle oldState = r.state;

        handleResumeActivity(r.token, false, r.isForward,//注释2

        !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);

    if (!r.activity.mFinished && r.startsNotResumed) {
        ...

        performPauseActivityIfNeeded(r, reason);//注释3

    } else {

       ...

    }

}

Looking at the code at Note 2 and Note 3, you can imagine that they must be related to the Activity life cycle, and here also explains why the onPause life cycle of the previous page is called after the onResume of the next page. Note 1 performLaunchActivitymethod is very important, let's take a look:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

...

    try {
        // 注释1

        java.lang.ClassLoader cl = appContext.getClassLoader();

        activity = mInstrumentation.newActivity(

        cl, component.getClassName(), r.intent);

        ...

    } catch (Exception e) {

        ...

    }

    try {

        Application app = r.packageInfo.makeApplication(false, mInstrumentation); // 注释2

        ...
        // 注释3

        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);

        ...

        int theme = r.activityInfo.getThemeResource();

        if (theme != 0) {

            activity.setTheme(theme); // 注释4

        }

        activity.mCalled = false;
        
        // 注释5
        if (r.isPersistable()) {

            mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);

        } else {

            mInstrumentation.callActivityOnCreate(activity, r.state);

        }

        ...
        // 注释6

        if (!r.activity.mFinished) {

            activity.performStart();

            r.stopped = false;

        }

        ...
        // 注释7

        if (r.state != null || r.persistentState != null) {

            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,

        r.persistentState);

        }

        } else if (r.state != null) {

            mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);

        }

    }

    ...

}

An Activity instance is created through reflection at Note 1. Note 2 is called to create the Application. If the Application has been created r.packageInfo.makeApplicationin the stage here , it will return directly.ActivityThread#handleBindApplication()

The method of Activity is called at Note 3 attach, and the bound Activity will be created internally PhoneWindow. Note 4 sets the theme of the Activity, Note 5 calls the onCreate() life cycle of the Activity, Note 6 calls the onStart() life cycle, and Note 7 calls the OnRestoreInstanceState() life cycle when the Activity resumes. Combined with the onResume() life cycle mentioned above, the startup process of our Activity has been finished. Of course, here is the link to start the Activity. The logic of the non-starting Activity is actually not bad. If you are interested, you can take a look at the source code yourself.

summary

The interaction relationship of each process during the startup page startup process:

The startup process of the startup page can be summarized by the following diagram:

4. Context

How many Contexts are there in the APP? The answer is the number of Activities + the number of Services + 1, 1 refers to the Application.

As shown in the figure above, ContextImplboth ContextWrapperinherit from Context, and the specific implementation class of Context exists ContextImplas ContextWrappera member variable of Context mBase. Activity, Service, and Application all inherit from and ContextWrapperbelong to Context, but they all rely ContextImplon implementing Context-related functions.

The specific creation time of ContextImpl is to call the method when the Activity, Service, and Application are created. The attachBaseContext()specific code will not be posted here, and interested students can check it out by themselves.

Application#getApplicationContext()What does the method get?

# ContextImpl

@Override
public Context getApplicationContext() {
    return (mPackageInfo != null) ?
            mPackageInfo.getApplication() : mMainThread.getApplication();
}

Finally, the method is called ContextImpl#getApplicationContext(), and you can see that the object is finally returned to the Context Application. The returned fragment#getContext()Context is Activityan object.

Five, the working principle of View

View and Window are two complementary existences. This section introduces View first, and the knowledge points related to Window can be ignored, and will be introduced in detail in the next section. You can first understand Window as a canvas, and View as a painting. Paintings are always rendered on the canvas, which means that View must be displayed with the help of Window.

View's drawing process

Each View will have an ViewRootImplobject. The starting point of View drawing is ViewRootImpl的perfromTraversalsthe method, and these three processes perfromTraversalswill be called internally .measure、layout、draw

measureIn order to measure the size of the View, measure will be called again onMeasure. In onMeasure, the sub-View will be measured first, and finally the size of the entire View will be obtained. After the measure process, we can already call View#getMeasuredWidth()and View#getMeasuredHeight()get the measured width and height of the View.

layoutThe process determines the position and actual width and height of the View in the parent container, that is, the position of the four vertex coordinates of the View. Layout is the opposite of measure. Layout first obtains the position of the parent View, and then onLayoutrecursively obtains the position of the child View. After the layout process, View#getWidth()and View#getHeight()have a value, which is the actual width and height of the View.

drawIs the last step of drawing, calling onDrawthe method to draw the View on the screen. dispatchDrawOf course, the child View will also call methods recursively (through methods) layer by layer onDrawto go down.

Measure

The more important personal feeling in the drawing process is measurethe method, so let me just talk about it. When we look at the measure-related methods, we will definitely see MeasureSpecthis parameter. What is it?

MeasureSpecIt is a 32-bit int value, represented by the upper 2 bits SpecModeand the lower 30 bits SpecSize. The former represents the mode, and the latter represents the size. One value contains two meanings. We can MeasureSpec#makeMeasureSpec(size, mode)synthesize it through methods MeasureSpec, or we can get it through MeasureSpec#getMode(spec)and . Only after the MeasureSpec of the sub-View is generated, can we measure the size of the sub-View by calling the measure method of the sub-View.MeasureSpec#getSize(spec)MeasureSpecModeSize

So how is MeasureSpec created?

There are three types of SpecMode, which are UNSPECIFIED、EXACTLY、AT_MOSTthree types. Don't worry about UNSPECIFIED, it is used internally by the system. So when is EXACTLY and when is AT_MOST? Some students may know that its value LayoutParamsis related to . But it should be noted that it is not completely determined by its own LayoutParams. LayoutParams needs to work with the parent container to determine the SpecMode of the View itself . When View is DecorView, MeasureSpec can be determined according to the window size and its own LayoutParams:

# ViewRootImpl

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
        final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
    ...
    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width); // 注释1
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); // 注释2
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...
}

Note 1 and Note 2 get the size of the window and its own LayoutParams. When the top-level View MeasureSpecis confirmed, onMeasurethe method will measure the View of the next layer and obtain the MeasureSpec of the sub-View . We can take a look at the ViewGroup measureChildWithMarginsmethod, which is called in many ViewGroup onMeasures:

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); // 注释1

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                    + widthUsed, lp.width);  // 注释2
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                    + heightUsed, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 注释3
}

We can see that the sub-View itself is obtained first at Note 1 LayoutParams. Then at Note 2, the child View is generated based on the parent View MeasureSpecand . Finally, the length of the sub-View is measured by calling Note 3 . So what's the use of passing in the MeasureSpec of the parent View? The following code is a bit long, so I write the conclusion directly:paddingmarginMeasureSpecmeasure

当父View的MeasureMode是EXACTLY时,子View的LayoutParams如果是MATCH_PARENT或者写死的值,子View的MeasureMode是EXACTLY;子View的LayoutParams如果是WRAP_CONTENT,子View的MeasureMode是AT_MOST。

当父View的MeasureMode是AT_MOST时,子View的LayoutParams只有是写死的值时,子View的MeasureMode才会是EXACTLY,不然这种情况都是AT_MOST。

There is a method that is easier to understand, AT_MOSTusually the corresponding LayoutParams is WRAP_CONTENT. We can think about it, if the parent View is WRAP_CONTENT, even if the child View is MATCH_CONTENT, isn't the child View equivalent to an uncertain size? So the MeasureMode in this case of the child View is still AT_MOST. The MeasureMode of the View will be EXACTLY only if the size is determined . If there are students who are confused here, you can think about it again.

As for padding and margin, we probably think about it and know that they must be used when measuring length. For example, when calculating the length of a sub-View, it is necessary to remove its padding and margin to be accurate.

In addition, we observe View's onMeasure method and find that it provides a default implementation:

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

By setMeasuredDimension(width, height)setting the width and height of the View. But is getDefaultSize() accurate here? We can take a look at:

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; // 注释1
        break;
    }
    return result;
}

Note 1, we can see that when SpecMode is AT_MOST, specSize is directly used by default. So there is a problem here , because this specSize represents the size of the parent View, which will cause the effect of LayoutParams to WRAP_CONTENT to be MATCH_PARENT . So many official custom Views rewrite the onMeasure method to calculate the size by themselves. We also need to pay special attention to this when writing custom Views.

ViewGroup is an abstract class, and it does not implement the onMeasure method of View. That is because the layout rules of each ViewGroup are different, and the natural measurement methods will also be different. Each subclass needs to implement onMeasure by itself.

layout process

layoutThe process only needs to know that layout is mainly a process used by the parent container to determine the position of the easy neutron View . When the parent container determines the position, all its child elements will be traversed in the parent container onLayoutto call their layoutmethods. The layout method is actually to determine the position of the four vertex coordinates .

We can see that similar onLayoutto onMeasurethe method, neither View nor ViewGroup implements it, because each View has a different layout and needs to be implemented by itself. But because the length and width need to be used in the process of determining the coordinates, the order of the layout comes after the measure.

Observation View#getWidth()and View#getHeight()method:

public final int getWidth() {
    return mRight - mLeft;
}

public final int getWidth() {
    return mRight - mLeft;
}

It can be seen that they are actually the subtraction between the coordinates, so these two methods can only get the real value after the onLayout method. That is, the actual width and height of the View. In general, measureWidth and width will be equal, unless we deliberately rewrite the layout method (the layout method is different from the measure method, it can be rewritten):

public void layout(int l, int t, int r, int b) {
    super.layout(l, t, r+11, b-11)
}

In this way, the actual width and height will be inconsistent with the measured width and height.

Custom View

There are four types of custom Views:

  • Inherit View
    • This type is often used to draw some irregular graphics, and its onDrawmethod needs to be rewritten. It should be noted that the Padding property of the View does not work by default during the drawing process . If you want the Padding property to work, you need to onDrawdraw the Padding after you get it yourself. In addition, onMeasureyou need to consider the situation of wrap_content and padding, as mentioned above.
  • Inherit ViewGroup
    • This type is relatively rare and is generally used to implement custom layouts. That means custom layout rules, that is, custom onMeasure, onLayout. In onMeasure, you also need to deal with wrap_content and padding. In addition, in onMeasureand onLayout, it is also necessary to consider the scenario where padding and sub-element margin work together.
  • Inherit a specific View, such as TextView
    • This type is generally used to extend the function of an existing View, which is relatively easy to implement and does not require rewriting onMeasureor onLayoutmethods.
  • Inherit a specific ViewGroup, such as FrameLayout
    • This type is also very common. It is generally used to combine several Views together, but it is much simpler than the second method, and does not require rewriting onMeasureor onLayoutmethods.

In the custom View, you need to pay extra attention. If there are additional threads or animations in the View, you need to recycle or pause at the right time (such as the onDetachedFromWindow life cycle), otherwise it will easily cause memory leaks .

Six, Window and WindowManager

Window related classes

Window is an abstract class, and its concrete implementation is PhoneWindow. is created in the method PhoneWindow:Activity#attach

#Activity

final void attach(Context context...) {
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback); // 注释1
    ...
    mWindow.setWindowManager(
        (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
        mToken, mComponent.flattenToString(),
        (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); // 注释2

}

Note 1 is initialized PhoneWindow, and Note 2 is set for Window WindowManager. WindowManager, as the name suggests is used to manage Window, it inherits ViewManagerthe interface:

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

From the above code, we can see that managing Window is actually managing View. context.getSystemService(Context.WINDOW_SERVICE)What the method finally gets is WindowManagerthe implementation class WindowManagerImpl. We can observe WindowManagerImplthese three methods, such as addView:

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

addViewIt can be seen that it is actually mGlobalimplemented by Go, WindowManagerImpljust bridged. This mGlobalis WindowManagerGlobal, is a singleton, globally unique:

#WindowManagerImpl

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

Window type

The Window type is specifically the property of Window Type. TypeThe attribute is actually an int pointer, which is divided into three types:

  • application window
    • The common Activity Window belongs to the application window, and its int value range is 1-99.
  • child window
    • A child window means that it must be attached to other windows to exist. For example, PopupWindow belongs to a child window, and its int value range is 1000-1999.
  • system window
    • Toast, volume bar, and input method windows belong to the system window, and the int value range is 2000-2999. When we want to create a system window, we need to apply for the system permission android.permission.SYSTEM_ALERT_WINDOW.

Generally speaking, the larger the value of the type attribute is, the higher the Z-Order is sorted, and the closer the window is to the user.

Window operation

Add View

Let's continue the code above addViewand take a look at the process inside:

# WindowManagerImpl
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params); // 注释1
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
    // Only use the default token if we don't have a parent window.
    if (mDefaultToken != null && mParentWindow == null) {
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        // Only use the default token if we don't already have a token.
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
        if (wparams.token == null) {
            wparams.token = mDefaultToken;
        }
    }
}

# WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...
    root = new ViewRootImpl(view.getContext(), display); // 注释2

    view.setLayoutParams(wparams);

    // 注释3
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    // do this last because it fires off messages to start doing things
    try {
        // 注释4
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
            removeViewLocked(index, true);
        }
        throw e;
    }
}

Note 1 calls applyDefaultTokenthe method and tokenputs it in LayoutParams. We can find this token in many methods of Window. What exactly is this token and what is its function? In fact, this token is an IBinderobject, which is created when AMS creates an Activity. It is used to uniquely identify an Activity. After creation, WMS will also get a copy and store it. When the AMS calls scheduleLauncherActivitythe method and returns to the APP process, the token will be passed to the APP process. This token will be used when we operate on Window in the APP process. Because the final Window operation is in WMS, we will pass this token when we call the WMS method, and WMS will compare this token with the initially stored token to find out which Activity this Window belongs to. ,

Come back again, let's see that WindowManagerGlobal#addViewan object will be new every time in the method of comment 2 ViewRootImpl. ViewRootImplWe are familiar with it, as mentioned in the previous section, it is responsible for the drawing of View. Here it is not only responsible for the drawing of View, but also responsible for communicating with the final WMS. Specifically, you can see that the method is called in Note 4 ViewRootImpl#setView, and the method is called internally IWindowSession#addToDisplay.

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
        getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
    ...
}

IWindowSessionIn fact, it is the agent of WMS Session对象in the APP process, so our logic goes to WMS. WMS will complete the remaining addView operations, including assigning Surfaces to the added windows, determining the display order of windows, and finally handing over the Surfaces for processing SurfaceFlinger. , composited to the screen for display. Moreover, addToDisplaythe first parameter of the method, mWindow, is ViewRootImplan internal class W, which is the Binder implementation class of the app process, through which WMS can call methods of the app process.

In addition, we can see that WindowManagerGlobalthree lists are maintained at Note 3, one is View, one is ViewRootImpl, and one is Paramslayout parameters, which will be used when updating Window/removing Window.

summary

The process of adding a View can be summarized by the following figure:

update windows

The process of updating Window is similar to the process of adding, and the class relationship is exactly the same. The main difference is WindowManagerGlobalthat you need to get ViewRootImpl from the list and update the layout parameters:

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    ...
    synchronized (mLock) {
        int index = findViewLocked(view, true);
        ViewRootImpl root = mRoots.get(index);
        mParams.remove(index);
        mParams.add(index, wparams);
        root.setLayoutParams(wparams, false);
    }
}

ViewRootImpl will call scheduleTraversalsthe method to redraw the page, and finally performTraversalscall IWindowSession#relayoutthe method in the method to update the Window, and re-trigger the three major drawing processes of View.

Activity rendering

In the third section, we talked about the startup process of the Activity, but we haven't finished it yet, because the Activity is not actually rendered. How is the Activity rendered? Of course it also depends on Window.

ActivityThreadThe method that AMS will call when the interface is ready to interact with the user handleResumeActivity:

public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
    ...
    final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); // 注释1
    ...
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        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 (!a.mWindowAdded) {
                a.mWindowAdded = true;
                wm.addView(decor, l); // 注释2
            } else {
            ...
        }
        ...

Calling the method at comment 1 performResumeActivitywill internally trigger onResumethe life cycle of the Activity. Note 2 uses WindowManager#addViewthe method to draw the decorView to the Window, so that the entire Activity is rendered. This is why the Activity will only be displayed in the onResume life cycle.

Dialog、PopupWindow、Toast

In the end, I still feel that it is necessary to understand what the three things Dialog, PopupWindow, and Toast are. There is no doubt that they are all rendered through Window, but Dialog belongs to the application window, PopupWindow belongs to the child window, and Toast belongs to the system-level window.

When Dialog is created, it will create a PhoneWindow just like Activity:

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean      createContextThemeWrapper) {
    ···
    mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

    final Window w = new PhoneWindow(mContext); // 注释1
    mWindow = w;
    w.setCallback(this);
    w.setOnWindowDismissedCallback(this);
    w.setOnWindowSwipeDismissedCallback(() -> {
        if (mCancelable) {
            cancel();
        }
    });
    w.setWindowManager(mWindowManager, null, null);
    w.setGravity(Gravity.CENTER);

    mListenersHandler = new ListenersHandler(this);
}

We see that Dialog in Note 1 creates its own Window when it is initialized, so it does not exist attached to other Window, and does not belong to a child window, but an application window.

At Dialog#show()the time, the WindowManager will be used to add the DecorView to the window, and when it is removed, the DecorView will also be removed from the window. There is a special feature that the context of a normal dialog must be an Activity, otherwise an error will be reported during the show, because the token of the Activity needs to be verified when adding a window , unless we set the window of the dialog as a system window, it is unnecessary.

And why is PopupWindow a child window? We can check the source code of PopupWindow:

# PopupWindow

private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
public PopupWindow(View contentView, int width, int height, boolean focusable) {
    if (contentView != null) {
        mContext = contentView.getContext();
        // 注释1
        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE); 
    }

    setContentView(contentView);
    setWidth(width);
    setHeight(height);
    setFocusable(focusable);
}

public void showAtLocation(IBinder token, int gravity, int x, int y) {
    ...
    final WindowManager.LayoutParams p = createPopupLayoutParams(token); // 注释2
    preparePopup(p);

    ...
    invokePopup(p);
}
   
protected final WindowManager.LayoutParams createPopupLayoutParams(IBinder token) {
    final WindowManager.LayoutParams p = new WindowManager.LayoutParams();
    p.gravity = computeGravity();
    p.flags = computeFlags(p.flags);
    p.type = mWindowLayoutType; // 注释3
    p.token = token; // 注释4
    p.softInputMode = mSoftInputMode;
    p.windowAnimations = computeAnimationResource();
    ...
}
    
private void invokePopup(WindowManager.LayoutParams p) {
    ...
    final PopupDecorView decorView = mDecorView;
    decorView.setFitsSystemWindows(mLayoutInsetDecor);

    setLayoutDirectionFromAnchor();

    mWindowManager.addView(decorView, p); // 注释5
    ...
}

In the source code, we can see that there will not be any operation of creating a new Window in PopupWindow, because it depends on someone else's Window. Get the WindowNManager at Note 1, and call the method at Note 2 to assign createPopupLayoutParamsvalues ​​to the Window parameters. Pay special attention to the two fields in Note 3 and Note 4. typeThe field represents the type of window. We can see that the value of mWindowLayoutType is WindowManager.LayoutParams.TYPE_APPLICATION_PANEL, which means it is a child window. And the note 4 tokenis no longer the token of the Activity, but the token of the View passed in during the show.

WindowParams.LayoutParams.typeToast is also similar, we can see that the value in its source code WindowManager.LayoutParams.TYPE_TOASTcorresponds to the system window. The specific internal mechanism will not be analyzed here, and interested students can check it out by themselves.

Seven, Handler message mechanism

The main function of the Handler message mechanism is that in the context of multi-threading, through the message mechanism, you can switch from the sub-thread to the main thread to update the UI, and ensure thread safety.

First introduce the main members and the relationship between them .

  • Message
    • Message, which can be used to store data, and a message object can be obtained from the buffer pool through Message.obtain().
  • MessageQueue
    • A queue for storing messages.
  • Looper
    • The loop method will continue to monitor the MessageQueue, and when there is a message in the MessageQueue, it will take out the message for distribution.
    • A thread will only have one Looper, and a Looper corresponds to a MessageQueue. When the Loope is created, the corresponding MessageQueue will be created. A Handler can only be bound to one Looper, but a Looper can be bound to multiple Handlers, and Looper is in units of threads .
    • Looper thread isolation is implemented through Threadlocal . There is a static variable sThreadLocal under Looper. When calling Looper.myLooper() under each thread, it is taken from this variable to obtain the Looper under the current thread.
    • Looper is divided into sub-thread Looper and MainLooper. main方MainLooper will Looper.prepareMainLooper()be ready through the method in the ActivityThread method. The difference between them is that one can quit, and the other cannot quit. The so-called quit is to call looper.quitthe method, which is actually to quit in the MessageQueue and remove the messages in it. Quit is divided into whether it is safe to quit. Under safequit, the existing messages in the message queue will be sent before quitting.
  • Handler
    • Used to send Message and receive Message, as the target in Message .
  • Message.Callback
    • When Message.callback exists, the Handler will not receive the Message, but will call back directly to the callback . When the Handler posts a Runnable, it actually sends a message and uses the Runnable as the callback of the message.

What about the entire message communication process ? We can talk about it together.

  1. First, in the communication preparation stage, Looper.prepare()a Looper is created for the current thread, and a MessageQueue is created at the same time. new a Handler.
  2. handler.postOr handler.sendMessagesend a message and enqueue it to MessageQueue.
  3. Looper distributes the queues in MessageQueue to Handler according to priority.
  4. The Handler gets the Message and processes the message according to the data in it.

When writing business, we often have post/senda delayed message. How is this delayed message realized? Each Message has a whenfield corresponding to when it needs to be dispatched. When entering the MessageQueue, it will whenbe arranged according to the sequence. When the loop fetches a message, it will judge whether the distribution time of the message has arrived. If it has not arrived, it will not distribute it first, and then distribute it when the time is up.

Therefore, in the message mechanism, sending a message does not guarantee that it will be processed immediately, because there is a priority arrangement inside the mechanism. So how can we increase the priority of the message when we need it? We can call postAtFrontOfQueueand sendMessageAtFrontOfQueueput the message at the head of the queue. In fact, we set the when of this message to 0. Alternatively, use asynchronous messages with synchronous barriers.

Asynchronous messages and synchronous barriers

Asynchronous messages and synchronous barriers are rarely used in development, and are generally used by the system itself. The interface of the synchronous barrier is also hidden, and we can only call it reflectively. But because interviews are very common, I will mention them here by the way.

To create an asynchronous message,setAsynchronous we can call a method to set the Message as an asynchronous message when creating a Message, or pass parameters to select whether it is asynchronous when creating a Handler. If it is asynchronous, all messages sent by the Handler are asynchronous messages .

So what is a synchronization barrier? The essence of the synchronization barrier is actually a special Message. The special thing is that the Target of this Message is not a Handler, but null , so as to distinguish it from other Messages. We can MessageQueue的postSyncBarriersend a synchronization barrier by calling the method reflectively, and removeSyncBarrierthe method removes it.

We can combine synchronous barriers with asynchronous messages to improve the priority of asynchronous messages . The specific principle is that when the MessageQueue takes out the Message from the Loop, it will judge whether the Message is a synchronization barrier. If it is a synchronous barrier, you need to find the first asynchronous message in the queue for priority processing instead of processing the synchronous messages in the front:

#MessageQueue

Message next() {
    ...
    synchronized (this) {
        // Try to retrieve the next message.  Return if found.
        final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) {
            // Stalled by a barrier.  Find the next asynchronous message in the queue.
            do {
                prevMsg = msg;
                msg = msg.next;
            } while (msg != null && !msg.isAsynchronous()); // 注释1
        }
    ...
}

When the note 1 is found msg.targetto be null, it means that the message is a synchronous barrier, and the first asynchronous message in the queue will be fetched.

As mentioned above, the update operation of the view is mentioned. In the final drawing stage, ViewRootImpl will requestLayout()update the layout and requestLayout()call the method internally scheduleTraversals()to go through the three major drawing processes again.

# ViewRootImpl

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 注释1
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); //注释2
        if (!mUnbufferedInputDispatch) {
            scheduleConsumeBatchedInput();
        }
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

In this method, we did not see the triggering of the three major drawing processes, but sent a synchronization barrier at Note 1 to block the main thread message queue. Also listen for the VSYNC signal at note 2. When the VSYNC signal comes, Choreographer will send an asynchronous message, which will be executed with the help of the synchronization barrier and trigger the listener callback. In the monitoring callback, ViewRootImpl will remove the synchronization barrier, and call and performTraversals()execute the three major drawing processes to refresh the UI.

Summarize

This article introduces a total of seven points of Framework knowledge, namely: system startup process, application process startup process, Activity startup process, Context, working principle of View, Window and WindowManager, Handler message mechanism .

Android study notes

Android Performance Optimization: https://qr18.cn/FVlo89
Android Vehicle: https://qr18.cn/F05ZCM
Android Reverse Security Study Notes: https://qr18.cn/CQ5TcL
Android Framework Principles: https://qr18.cn/AQpN4J
Android Audio and Video: https://qr18.cn/Ei3VPD
Jetpack (including Compose): https://qr18.cn/A0gajp
Kotlin: https://qr18.cn/CdjtAF
Gradle: https://qr18.cn/DzrmMB
OkHttp Source Code Analysis Notes: https://qr18.cn/Cw0pBD
Flutter: https://qr18.cn/DIvKma
Android Eight Knowledge Body: https://qr18.cn/CyxarU
Android Core Notes: https://qr21.cn/CaZQLo
Android Past Interview Questions: https://qr18.cn/CKV8OZ
2023 Latest Android Interview Question Collection: https://qr18.cn/CgxrRy
Android Vehicle Development Job Interview Exercises: https://qr18.cn/FTlyCJ
Audio and Video Interview Questions:https://qr18.cn/AcV6Ap

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/131700942