1. Prepare for 2021(1)

Ii. Prepare for 2021(2)

1. Start the Activity mode, and common usage

  • Standard mode: Each time an Activity is started, a new instance is created
  • SingleTop: If the new Activity is already at the top of the task stack, it is not created again and calls onNewIntent(Intent) back
  • SingleTask in-stack reuse mode: As long as the Activity exists in a task stack, it is not recreated and calls onNewIntent(Intent). If not, the system looks for a desired stack. If not, it creates a task stack and places the Activity in it. If it exists, it will be created to an existing stack
  • SingleInstance mode: An Activity with this mode can only be in a single task stack, and there is only one instance in that task stack.

Example: 1,2, 3 activities,2 is singleInstance mode, then 1->2,2->3, then frantically click back, after returning to the Home interface, click the menu button, find the first start 2Activity.

Task 1 and task 3 are one task, task 2 is a separate task, after we 2->3, the foreground task is returned from task 2 to task 1 and task 3. So the last task to exit is thread 2, and if not restart App. The last Task closed was 2

2. taskAffinity, allowTaskReparting

TaskAffinity is used to specify the tasks associated with the current Activity(Activity1), allowTaskReparenting is used to configure whether the Activity can change subordinate tasks, and the two are usually used together. Used to implement moving an Activity from one application to another application’s Task. AllowTaskReparenting is used to indicate whether an Activity can be moved from a started Task to a Task specified by taskAffinity. The default is inherited from the Application: allowTaskReparenting=false. It can be replaced. False: no.

The migration rule is to migrate from a task stack that is different from the Activity TaskAffinity property to a task stack that is the same as its TaskAffinity property.

For example, there are two applications A and B. A starts an Activity C of B, then press the Home button to return to the desktop, and then click the desktop icon of B. This does not start THE main Activity of B, but redisplays the Activity C that has been started by application A. We can also think of C moving from A’s task stack to B’s task stack. So to understand, because A launched the C, C at this time can only be run on A task stack, but C is B application, under normal circumstances, it TaskAffinity value must be impossible task and A stack is the same, so when B starts, B will create your own task stack, at this time of the system found that C originally want to task stack has been created, So we’re moving C off of A’s stack.

3. Activity lifecycle

  • A starts B’s and then presses the back key. What method does it perform? What about pressing the home button?

Start the ActivityB lifecycle from ActivityA: A.onCreate – >A.on start – >A.on Resume – >A.onPause – >B.on create – >B.on start – > B.On Resume – >A.on SaveInstancEstate – > A.onStop

Press the Back key: B.pause -> A.onan restart -> A.onan start -> A.onan Resume -> B.onan stop —- > B.onan destroy

Press the Home button: B.pause -> OnSaveInstanceState -> B.stop —- > B.destroy

  • OnSaveInstance method invocation timing

1. When the user presses the HOME button. 2. Hold down the HOME button to run another program. 3. Press the power button (turn off the screen display). 4. Start A new activity from Activity A. 5. When switching screen orientation, such as from portrait to landscape.

4. Bitmap memory optimization

  • How is Bitmap memory calculated

Memory occupied = Image width Image height Memory occupied per pixel

The memory used by bitmaps is independent of screen Density

  • How to optimize without changing the image quality

1: Android often does image scaling, so pre-scaling makes sense, reducing image size (not just scaling images, but manipulating Bitmaps), reducing memory allocation, and improving display performance. API for createScaledBitmap ().

InSampleSize, which can be used to downsample the original image by setting inJustDecodeBounds = true to get the width and height of the image without loading it into memory, calculate the appropriate compression ratio, and set inSampleSize. The principle of inSampleSize is to extract the most efficient interlacing rows directly from the lattice. Therefore, inSampleSize can only be an integer power of 2 to ensure efficiency

  • Bitmap memory overcommitment (options.inbitmap)

Bitmapfactory. Options provides a parameter, options.inBitmap

1. The size of the newly applied bitmap must be smaller than or equal to the size of the assigned bitmap

2. The newly applied bitmap must have the same decoding format as the old bitmap. For example, if the previous bitmap is 8888, the 4444 and 565 formats cannot be supported

  • Mega map loading

BitmapRegionDecoder

1. SetInputStream to get the true width and height of the image, and initialize our mDecoder

2. Assign a value to the recT of our display area in onMeasure, which is the size of view

3. In onTouchEvent, we listen for the move gesture, change the parameters of rect in the callback, perform boundary checking, and finally invalidate

4. In onDraw, get the bitmap according to recT and draw it

  • Passing large graphs across processes

IPC with Binder through AIDL is not subject to this limitation

Bundle bundle = new Bundle();
bundle.putBinder("binder".new IRemoteGetBitmap.Stub() {
    @Override
    public Bitmap getBitMap(a) throws RemoteException {
        returnmBitmap; }}); intent.putExtras(bundle);Copy the code

When the Intent starts, the filesystem descriptor FD is disabled. Bitmaps cannot use shared memory and can only be copied to buffers mapped by Binder. As a result, the buffer limit exceeds and exceptions are triggered. PutBinder prevents descriptors from being disabled by intEnts. Bitmap allowFds defaults to True when writing parcels and uses shared memory to efficiently transfer images

  • Resource file loading rules

APP follows the principle of first high and then low when searching image resources. Assuming that the resolution of the device is XXhdPI, the search sequence is as follows

1. Go to the drawable-xxhdpi folder and use the image if you have it. The image will not zoom at this time

2. If no, go to a folder with higher density, such as drawable-xxxhdpi. The density increases one by one

3. If all the high density folders are not available, we will go to the drawable-nodpi folder to find, if found, do not zoom, use the original image

4. If it still doesn’t have any, it will go to the folder with lower density, xHDPI, HDPI, etc. The density will decrease successively. If it finds the image, it will be enlarged, because the system thinks the image is for the use of low resolution devices

5. Cross-process communication

  • Android interprocess communication

Android is also based on the Linux kernel. The existing communication means of Linux processes are as follows:

1. Pipeline: allocate a page-size memory at creation time, the cache size is relatively limited;

2. Message queue: information is copied twice, extra CPU consumption; Not suitable for frequent or informative communication;

3. Shared memory: without replication, the shared buffer is directly attached to the process virtual address space, which is fast; However, the synchronization problem between processes cannot be realized by the operating system and must be solved by each process using the synchronization tool.

4. Socket: as a more general interface, low transmission efficiency, mainly used for communication between different machines or across networks;

5. Semaphore: Often used as a locking mechanism to prevent other processes from accessing a shared resource while one process is accessing it. Therefore, it is mainly used as a means of synchronization between processes and between different threads within the same process. Not suitable for information exchange, more suitable for process interrupt control, such as illegal memory access, killing a process, etc.

With the existing IPC approach, why redesign a Binder mechanism

1. Efficiency: The transmission efficiency is mainly influenced by the number of memory copies. The fewer the number of memory copies, the higher the transmission rate. Analysis from the perspective of Android process architecture: for message queues, sockets and pipes, data is first copied from the sender’s cache to the cache created by the kernel, and then copied from the kernel cache to the receiver’s cache, twice in total

2. Stability: With binders, shared memory performs better than Binder. Shared memory can handle concurrent synchronization, deadlocks, and resource contention, resulting in poor stability. Although Socket is based on C/S architecture, it is mainly used for communication between networks and the transmission efficiency is low. Binder based on C/S architecture, the Server and Client are relatively independent, providing high stability.

3. Security: The receiver of traditional Linux IPC cannot obtain the reliable UID/PID of the process of the other party, so it cannot identify the identity of the other party; The Binder mechanism assigns a UID/PID to each process and checks its validity when communicating with the Binder.

  • Memory size limits for binder communication

1. Normal Zygote incubated user processes map to Binder memory sizes of less than 1M

2. Special process ServiceManager process, it applies for its own Binder kernel space is 128K, this is inseparable with the use of ServiceManager, ServcieManager mainly system Service, Simply provide some addServcie, getService functionality

  • Binder mechanism principle

Binder communication uses the C/S architecture and consists of Client, Server, ServiceManager, and Binder drivers from a component perspective. The ServiceManager manages various services in the system. Client, Server, and Service Manager are implemented in user space, and Binder drivers are implemented in kernel space

1. Registration service

The Server process registers services with the Service Manager process through the Binder driver, creating a Binder object. After registering the Service, the Binder driver holds the Binder entities created by the Server process

2. Obtain services

The Binder driver is used by the Client process to obtain Service information from the ServiceManager process

  1. The use of the service

According to the obtained Service information (Binder agent object), the Client process uses the Binder driver to establish a link to communicate with the Server process of the Service and start using the Service

The Client process sends the parameters (integers A and B) to the Server process

// 1. The Client process writes data to a Parcel object
// data = Data = target method parameters (as passed in by the Client process) + IInterface Identifier descriptor of the interface object
  android.os.Parcel data = android.os.Parcel.obtain();
  data.writeInt(a); 
  data.writeInt(b); 

  data.writeInterfaceToken("add two int"); ;// The method object identifier lets the Server process find the corresponding IInterface object (plus created by the Server) in its Binder object by "add two int". The adding method that the Client process needs to call is in this object

  android.os.Parcel reply = android.os.Parcel.obtain();
  // reply: the result of the target method execution (here is the result of the addition)

// 2. Send the above data to Binder drivers by calling transact () of the proxy object
  binderproxy.transact(Stub.add, data, reply, 0)
  // Parameter description:
    // 1.stub. add: target method identifier (Client and Server processes themselves, can be arbitrary)
    // 2. data: the above Parcel object
    // 3. reply: returns the result
    // 0: no

// Note: The thread of the Client process is temporarily suspended after sending data
// Therefore, if the Server process performs time-consuming operations, do not use the main thread to prevent ANR


// 3. Binder driver finds the Server process of the corresponding real Binder object according to the proxy object (system automatic execution)
// 4. Binder drivers send data to the Server process and inform the Server process to unpack (automatic)
Copy the code

The Server process invokes the target method based on Client requirements

// 1. Upon receiving the Binder driver notification, the Server process unpacks data by calling the Binder object onTransact () & calling the target method
  public class Stub extends Binder {

          // duplicate onTransact ()
          @Override
          boolean onTransact(int code, Parcel data, Parcel reply, int flags){
          // code is the target method identifier agreed in transact ()

          switch (code) { 
                caseStub. Add: {A. Unpack the data in the Parcel
                       data.enforceInterface("add two int"); 
                        // a1. Parse the identifier of the target method object

                       int  arg0  = data.readInt();
                       int  arg1  = data.readInt();
                       // a2. Get the parameters of the target method
                      
                       // b. Use "add two int" to queryLocalIInterface () to get a reference to the corresponding IInterface object (plus created by the Server) and call the method with that reference
                       int  result = this.queryLocalIInterface("add two int") .add( arg0,  arg1); 
                      
                        // c. Write the calculated result to reply
                        reply.writeInt(result); 
                        
                        return true; }}return super.onTransact(code, data, reply, flags); 
      // 2. Return the settlement result to Binder driver
Copy the code

The Server process returns the result of the target method to the Client process

  // 1. The Binder driver returns the result along the path of the proxy object and notifies the Client process to get the result
  // 2. Receive the result through the proxy object (the previously suspended thread is awakened)

    binderproxy.transact(Stub.ADD, data, reply, 0); reply.readException(); ; Result = reply. ReadInt ();Copy the code
  • When the Binder mechanism for Application is started

Zygote launches binder mechanisms for applications after they are forked

  • Default maximum number of binder thread pools

The Binder thread pool is capped at 15 threads per process

  • Binder and AIDL

Binder is a class that implements the IBinder interface, which defines the protocol for interacting with remote objects. The IBinder interface is typically derived directly from Binder without the need to implement it for cross-process communication.

In addition to implementing the IBinder interface, two important interfaces are provided in Binder. (1) Transact(), the client call, for sending the call request (2) onTransact(), the server response, for receiving the call request

AIDL is an Android interface description language (AIDL), which is a file format provided by Android development tools. Binder mechanisms are quickly generated by pre-defining the interface in the file, eliminating the need to manually write Binder mechanisms

Notes for the AIDL process:

If a custom Parcelable object is used in an AIDL file

You must create an aiDL file with the same name and declare it as Parcelable. And in aiDL, except for basic types, other types of parameters must indicate the direction of int, OUT or inout, in input parameters, out output parameters, inout input and output parameters.

Aidl server thread synchronization

Because aiDL server methods are executed in binder thread pools, there are concurrent read and write problems when accessed by multiple clients. CopyOnWriteArrayList is commonly used to support concurrent reads and writes for automatic thread synchronization, as is ConcurrentHashMap.

ANR problems that may be caused by aiDL server time

If the server method is time-consuming, the client can call it on a non-UI thread.

Unregister the listener RemoteCallbackList across processes

Aidl unRegisterListener unRegisterListener fails to register a listener because the cross-process client and server are not the same object. This can be handled using RemoteCallbackList, which is the system-specific interface for removing cross-process Listeners. The map contains IBinder key values and callback values. The map contains IBinder key values and callback values. RemoteCallbackList is not a list in nature. Therefore, operations similar to list cannot be performed. BeginBroadcast and finishBroadcast must be used together.

  • AIDL oneway inout inout

The oneway keyword is used to modify the behavior of remote calls.

Oneway can be used to precede an interface, causing all methods in the interface to implicitly include oneway. Oneway can also precede methods in an interface. Methods decorated with oneway cannot have a return value, nor can they have arguments with out or inout.

The application process communicates with the service process through the binder driver. One-way means that the application process only sends data to the binder driver once and then stops and returns without waiting for the reply data. The onewayless approach waits for the binder driver to communicate with the server before sending data back to the application.

1. Local invocation (synchronous invocation)

If oneway is used for a local call, there is no impact and the call is still a synchronous call.

2. Remote invocation (asynchronous invocation)

With Oneway, remote calls do not block; It simply sends transaction data and returns it immediately. When the implementation of the interface finally receives this call, it receives it as a normal remote call from the Binder thread pool.

The oneway method does not generate the local variable _reply. The fourth parameter in Proxy transact must be Android.os.ibinder.flag_oneway.

Methods without oneway generate local variable _reply, but local variable _result is not generated when the method returns void, which is the real return value. The fourth transact parameter in Proxy must be 0.

The in parameter allows the argument to be passed to the server, but any changes made to the argument by the server are not reflected back to the caller.

The out argument means that the argument is never actually passed to the server, just the initial value of the argument (where the argument is only used as a return value, so that something else can be returned as well), but any changes made to the argument by the server are reflected back to the caller after the call.

The inout parameter is a combination of the two. The actual argument is passed to the server, and any changes made to the argument by the server are reflected back to the caller after the call.

In fact, inout is relative to the service side. The in argument causes the argument to be passed to the server, so in enters the server; The out argument causes the argument to be passed back from the server to the caller at the end of the call, so out comes out of the server.

7. Understand the Context in Android? Where do the contexts in the four components come from

  • The Context effect

Context provides an interface to global information about the application environment

  1. Use context to invoke methods, such as starting an Activity, accessing resources, calling system services, and so on.
  2. The context is passed when a method is called, such as popping up a Toast, creating a Dialog, etc.
  • The number of Context

Number of contexts = Number of Applications + Number of Activities + Number of Services

The Activity’s Context directly inherits the ContextThemeWrapper because it displays the UI

The Context of Application and Service directly inherits ContextWrapper

  • Where was the Context created

1.Application

2.Activity

3.Service

The context for ContentProvider is Application

The context of a BroadCastReceiver is an Activity or Service

7. Application starts the process

Application startup can be divided into three steps:

The first step is to create the application process ActivityThread.

The second step is to bind the application program and the system program, that is, through AIDL cross-process communication, the system process allocates application information such as PID and UID.

Third, create the Application and call the onCreate method.

Android has an ActivityThread class that represents the main thread of the application. Android first calls the main method in the ActivityThread to open the APP, which is the starting point of the APP process.

public static void main(String[] args) {... Looper.prepareMainLooper();/ / 1

        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
        // It will be in the format "seq=114"
        long startSeq = 0;
        if(args ! =null) {
            for (int i = args.length - 1; i >= 0; --i) {
                if(args[i] ! =null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                    startSeq = Long.parseLong(
                            args[i].substring(PROC_START_SEQ_IDENT.length()));
                }
            }
        }
        ActivityThread thread = new ActivityThread();/ / 2
        thread.attach(false, startSeq);/ / 3. Looper.loop();/ / 4

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
Copy the code

In the main method 1 and 4 are related to starting the Handler; ActivityThread is created at 2, and the attach method is called at 3, meaning connect.

  @UnsupportedAppUsage
  private void attach(boolean system, long startSeq) {...if(! system) { ...final IActivityManager mgr = ActivityManager.getService();/ / 1
            try {
                mgr.attachApplication(mAppThread, startSeq);/ / 2
            } catch (RemoteException ex) {
                throwex.rethrowFromSystemServer(); }... }else{... }... }Copy the code

MAppThread is a variable of type ApplicationThread of ActivityThread, which is a BInder object that can be accessed remotely through BInder mechanisms. In attach method, in note 1, the proxy object IActivityManager of AMS is obtained through AIDL, realizing the communication between application process and system process. The attachApplication method of IActivityManager is invoked in comment 2. This means to connect Application and AMS, that is, to bind APP to system Application. How to bind? Find the attachApplication method in the ActivityManagerService class and continue the analysis.

@Override
    public final void attachApplication(IApplicationThread thread, long startSeq) {
        if (thread == null) {
            throw new SecurityException("Invalid application interface");
        }
        synchronized (this) {
            int callingPid = Binder.getCallingPid();/ / 1
            final int callingUid = Binder.getCallingUid();/ / 2
            final long origId = Binder.clearCallingIdentity();
            attachApplicationLocked(thread, callingPid, callingUid, startSeq);/ / 3Binder.restoreCallingIdentity(origId); }}Copy the code

Thread is a proxy object for mAppThread. At comment 1 and comment 2, pid and UID are assigned to the application, respectively. At comment 3, attachApplicationLocked methods are called, and PID and UID are passed in. We also pass in the proxy class IApplicationThread for ActivityThread. Moving on, what’s going on in attachApplicationLocked?

@GuardedBy("this")
    private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
            int pid, int callingUid, long startSeq) {

        ProcessRecord app;  / / 1
        long startTime = SystemClock.uptimeMillis();
        if(pid ! = MY_PID && pid >=0) {
            synchronized (mPidsSelfLocked) {
                app = mPidsSelfLocked.get(pid);  / / 2}}...if(app.instr ! =null) {  // Whether the current process is activethread.bindApplication(processName, appInfo, providers, app.instr.mClass, profilerInfo, app.instr.mArguments, app.instr.mWatcher, app.instr.mUiAutomationConnection, testMode, mBinderTransactionTrackingEnabled, enableTrackAllocation, isRestrictedBackupMode || ! normalMode, app.persistent,new Configuration(getGlobalConfiguration()),
                    app.compat, getCommonServicesLocked(app.isolated), mCoreSettingsObserver.getCoreSettingsLocked(),
                    buildSerial, isAutofillCompatEnabled);   / / 3
        } else {
            //Application is bound to the current thread
            thread.bindApplication(processName, appInfo, providers, null, profilerInfo,
                    null.null.null, testMode, mBinderTransactionTrackingEnabled, enableTrackAllocation, isRestrictedBackupMode || ! normalMode, app.persistent,new Configuration(getGlobalConfiguration()), app.compat, getCommonServicesLocked(app.isolated),
                    mCoreSettingsObserver.getCoreSettingsLocked(), buildSerial, isAutofillCompatEnabled);  / / 4}...// Check whether the most visible Activity is waiting in the running process, and if so, create the Activity
        if (mStackSupervisor.attachApplicationLocked(app)) {  / / 5
            didSomething = true; }... }Copy the code

Note 1 ProcessRecord is a javabean that holds information about the current process (pid, UIP, ApplicationInfo, etc.). In note 2, the ProcessRecord class is obtained based on the PID. Both comments 3 and 4 bind the Application, using AIDL to call the bindApplication method in ActivityThread. Here you can see that in AMS the system only provides the basic information for the Application, but does not create it. From this we can guess that the Application creation is done in ActivityThread. To see if this is the case, let’s look at the bindApplication method of the ActivityThread class

@Override
        public final void bindApplication(String processName, ApplicationInfo appInfo,
                ProviderInfoList providerList, ComponentName instrumentationName,
                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
                IInstrumentationWatcher instrumentationWatcher,
                IUiAutomationConnection instrumentationUiConnection, int debugMode,
                boolean enableBinderTracking, boolean trackAllocation,
                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
                String buildSerial, AutofillOptions autofillOptions,
                ContentCaptureOptions contentCaptureOptions, long[] disabledCompatChanges) {
          
            setCoreSettings(coreSettings);

            AppBindData data = newAppBindData(); data.processName = processName; data.appInfo = appInfo; data.providers = providerList.getList(); data.instrumentationName = instrumentationName; . sendMessage(H.BIND_APPLICATION, data); }Copy the code

The bindApplication method encapsulates the parameters into an AppBindData object, data, and finally sends a message with handler, and carries the AppBindData data. H is an inner class in ActivityThread that inherits the Handler. Continue to find where the Handler processes the message

 public void handleMessage(Message msg) {
            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
            switch (msg.what) {
                case BIND_APPLICATION:
                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
                    AppBindData data = (AppBindData)msg.obj;
                    handleBindApplication(data);/ / 1
                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
                    break; . }}Copy the code

Comment 1 for the handleBindApplication method

# #ActivityThread 
private void handleBindApplication(AppBindData data) {.../ / create the Application
        app = data.info.makeApplication(data.restrictedBackupMode, null); . }Copy the code

In the handleBindApplication method, the makeApplication method in LoadedApk is called. Let’s look at how the makeApplication is created

@UnsupportedAppUsage
    public Application makeApplication(boolean forceDefaultAppClass,
            Instrumentation instrumentation) {
        if(mApplication ! =null) {
            return mApplication;
        }

        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");

        Application app = null;

        String appClass = mApplicationInfo.className;
        if (forceDefaultAppClass || (appClass == null)) {
            appClass = "android.app.Application";
        }

        try {
            // Get the loader
            final java.lang.ClassLoader cl = getClassLoader();
            if(! mPackageName.equals("android")) {
                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
                        "initializeJavaContextClassLoader"); initializeJavaContextClassLoader(); Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); }...// Get the context
            ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);/ / 1
            
            // Reflection gets Application
            app = mActivityThread.mInstrumentation.newApplication(
                    cl, appClass, appContext);/ / 2
            appContext.setOuterContext(app);
        } catch (Exception e) {
            if(! mActivityThread.mInstrumentation.onException(app, e)) { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);throw new RuntimeException(
                    "Unable to instantiate application " + appClass
                    + ":" + e.toString(), e);
            }
        }
        mActivityThread.mAllApplications.add(app);
        mApplication = app;

        if(instrumentation ! =null) {
            try {
                instrumentation.callApplicationOnCreate(app);/ / 3
            } catch (Exception e) {
                if(! instrumentation.onException(app, e)) { Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);throw new RuntimeException(
                        "Unable to create application " + app.getClass().getName()
                        + ":" + e.toString(), e);
                }
            }
        }

        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);

        return app;
    }
Copy the code

Comment 1 creates the ApplicationContext, which getApplicationContext() gets. It gets a ContextImpl object, which is a subclass of Context. Comment 2 is to create the Application. The Application is created in the makeApplication using the classloader and reflection. Note 3 calls the onCreate method of the Application.

  • How does AMS confirm that Application is started?

What are the key conditions?

Zygote returns PID to AMS; Application binder objects are reported to AMS in the ActivityThread#main method of the Application

8. Specific process of startActivity

www.jianshu.com/p/e654d2116…

Activity#setContentView

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
Copy the code

GetWindow (), getWindow().setContentView(layoutResID), initWindowDecorActionBar(). Let’s look at them one by one, in the getWindow() method

/**
 * Retrieve the current {@link android.view.Window} for the activity.
 * This can be used to directly access parts of the Window API that
 * are not available through Activity/Screen.
 *
 * @return Window The current window, or null if the activity is not
 *         visual.
 */
public Window getWindow(a) {
    return mWindow;
}
Copy the code

The getWindow() method returns a Window object, but there is no indication in this method when the Window object was actually created. We can see that the mWindow object is instantiated in the Attach method of the Activity by searching for the mWindow object in the Activity.

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window) {
        attachBaseContext(context);
        mFragments.attachHost(null /*parent*/);
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if(info.softInputMode ! = WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) { mWindow.setSoftInputMode(info.softInputMode); }if(info.uiOptions ! =0) { mWindow.setUiOptions(info.uiOptions); } mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) ! =0);
        if(mParent ! =null) {
            mWindow.setContainer(mParent.getWindow());
        }
Copy the code

When we get the mWindow object, we call the mWindow.setContentView method. When we click on it, we see the Window abstract class, we don’t see the actual implementation. MWindow = new PhoneWindow(this, window); Let’s go to PhoneWindow and look at the setContentView method.

    @Override
    public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if(! hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if(cb ! =null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
Copy the code

If mContentParent is empty then we need to fetch a DecorView. If mContentParent is not empty then remove all child views of the current ViewGroup. We then add the XML file we specify and update the view with a Callback. There are three other things that you might not be aware of. The first is where the mLayoutInflater comes from, the second is where the Callback goes, and the third is what the installDecor() method does.

    public PhoneWindow(Context context) {
        super(context);
        mLayoutInflater = LayoutInflater.from(context);
    }
Copy the code

The mLayoutInflater is assigned when the PhoneWindow is instantiated, and the getCallback() method corresponds to the setCallback() method set in the Attach method of the Activity. You can see here that the position of the callback is Activity.

What does the installDecor() method do

if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if(! mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures ! =0) { mDecor.postOnAnimation(mInvalidatePanelMenuRunnable); }}else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) { mContentParent = generateLayout(mDecor); }}protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, getContext().getResources());
                if(mTheme ! = -1) { context.setTheme(mTheme); }}}else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }
    
    
        protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme..// Load different styleable
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.requestFeature(FEATURE_ACTION_BAR); }...// Inflate the window decor.

        intlayoutResource; . layoutResource = R.layout.screen_simple;// System.out.println("Simple!" );
        }

        mDecor.startChanging();
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view"); }... mDecor.finishChanging();return contentParent;
    }
Copy the code

MDecor is a DecorView object. Check if mDecor is empty. If not, create a mDecor object and add it to the Window.

mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); So this method call is going to fill in the DecorView what’s going on in there

   void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {... mDecorCaptionView = createDecorCaptionView(inflater);final View root = inflater.inflate(layoutResource, null);
        if(mDecorCaptionView ! =null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0.new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

Copy the code

In the above method, we add our following layout fill to the DecorView

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="? attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="? android:attr/windowContentOverlay" />
</LinearLayout>

Copy the code

The layout you set in the Activity’s onCreate() method will be added to. Go back to the PhoneWindow setContentView() method and you can see that mLayOutInflater.inflate (layoutResID, mContentParent); Add the layout to R.I.D.C tent.

Note: after setContentView(), the View is still invisible, wait for the DecorView to be added to the window, and then trigger the ViewRootImpl#performTraversals method to begin drawing, measuring, etc

  • When is the layout of the Activity displayed? When was the ViewRootImpl initialized? What does it do

The handleResumeActivity method of the ActivityThread adds the previously initialized decorView to the window through the AddView method of the WindowManager, and then completes the measurement, layout, and drawing of its layout. Then make it visible

@Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {...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;
                    // Add the decorView to the window
                    wm.addView(decor, l);
                } else{... }}}else if(! willBeVisible) {if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
            r.hideForNow = true;
        }

        if(! r.activity.mFinished && willBeVisible && r.activity.mDecor ! =null && !r.hideForNow) {
            ......
            if(r.activity.mVisibleFromClient) { r.activity.makeVisible(); }}... }Copy the code

Here wm is Windows ManagerGlobal:

    public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {... ViewRootImpl root; View panelParentView =null;

        synchronized (mLock) {
           ......
            int index = findViewLocked(view, false); .// Initialize ViewRootImpl
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            try {
                // Associate ViewRootImpl with decorView and start the layout
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throwe; }}}Copy the code

The setView method in ViewRootImpl does the important work of adding the DecorView to the window, making the layout, and drawing the measurement layout. It is also where the click event receiver is initialized, so the click event is first passed to the DecorView

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) { mView = view; .// Invoke the layout processrequestLayout(); .try{...// Add to window
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                } catch(RemoteException e) { ...... }...// Set viewrootimpl to parent of decorView
                view.assignParent(this); . }}Copy the code

RequestLayout calls scheduleTraversals->doTraversal->performTraversals, In performTraversals all mattachInfo initialized in viewrootimpl will be assigned to the child view:

    private void performTraversals(a) {... host.dispatchAttachedToWindow(mAttachInfo,0); . }Copy the code

All child views are measured, laid out, and drawn, and the mattachInfo variable is set for all child Views. The mView variable in ViewRootImpl is called Decorview. Then each of the mattachInfo variables in the word View is held as an instance reference in the ViewRootImpl.

The ViewRootImpl is not really a View, it is just a medium between a DecorView and window management, responsible for the laying-related management of the interface. MAttachInfo holds a reference to ViewRootImpl, so an important role for mAttachInfo is to coordinate view layout redrawing related functions.

10. Choreographer mechanism Flow

  • RequestLayout of ViewRootImpl starts the drawing process
@Override public void requestLayout() { if (! mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } } void scheduleTraversals() { if (! mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (! mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); pokeDrawLockIfNeeded(); }}Copy the code

Here are two main concerns:

  1. PostSyncBarrier: Synchronization barrier for handlers. It intercepts Looper’s attempts to get and distribute synchronous messages. After joining the synchronization barrier, Looper will only get and process asynchronous messages. If there are no asynchronous messages, Looper will block. That is, processing operations that render a View can take precedence (set to asynchronous messages).
  2. Choreographer: Choreographer. Unify animation, input, and drawing timing. This chapter also needs to focus on the analysis of content.

Choreographer plays the role of the go-between for Android rendering links

Responsible for receiving and processing various update messages and callbacks of the App until the arrival of Vsync. For example, processing Input(mainly processing Input events), Animation(Animation related), Traversal(including measure, layout, draw and other operations), judging the frame delay, recording CallBack time, etc

Start down: responsible for requesting and receiving Vsync signals. Receive Vsync event callback (through FrameDisplayEventReceiver onVsync); Request Vsync (FrameDisplayEventReceiver scheduleVsync).

  • Choreographer’s workflow
  1. Choreographer initialization

  2. Initialize the FrameHandler and bind Looper

  3. Initialize FrameDisplayEventReceiver, establish communication with SurfaceFlinger Vsync used to receive and request

  4. Initialize CallBackQueues

  5. SurfaceFlinger’s appEventThread wakes up to send Vsync, Choreographer callback FrameDisplayEventReceiver onVsync, into SurfaceFlinger doFrame main processing function

  6. Choreographer. DoFrame calculates the frame drop logic

  7. Choreographer. DoFrame handles Choreographer’s first callback: input

  8. Choreographer. DoFrame handles Choreographer’s second callback: animation

  9. Choreographer. DoFrame handles Choreographer’s third callback: Insets animation

  10. Choreographer. DoFrame handles Choreographer’s fourth callback: traversal

  11. Traversal-draw In which UIThread and RenderThread synchronize data

  12. Choreographer. DoFrame handles Choreographer’s fifth callback: Commit?

  13. RenderThread processes the drawing data to actually render

  14. Render the rendered Buffer swap to SurfaceFlinger for composition

  • Initialization of Choreographer
  1. Singleton initialization of Choreographer
    public static Choreographer getInstance(a) {
            return sThreadInstance.get();
        }
    
    // Thread local storage for the choreographer.
    private static final ThreadLocal<Choreographer> sThreadInstance =
            new ThreadLocal<Choreographer>() {
        @Override
        protected Choreographer initialValue(a) {
            Looper looper = Looper.myLooper();
            if (looper == null) {
                throw new IllegalStateException("The current thread must have a looper!");
            }
            Choreographer choreographer = new Choreographer(looper, VSYNC_SOURCE_APP);
            if (looper == Looper.getMainLooper()) {
                mMainInstance = choreographer;
            }
            returnchoreographer; }};Copy the code
  1. The constructor for Choreographer
private Choreographer(Looper looper, int vsyncSource) { mLooper = looper; MHandler = new FrameHandler(looper); MDisplayEventReceiver = USE_VSYNC? new FrameDisplayEventReceiver(looper, vsyncSource) : null; mLastFrameTimeNanos = Long.MIN_VALUE; mFrameIntervalNanos = (long)(1000000000 / getRefreshRate()); Callbackqueues = new CallbackQueue[CALLBACK_LAST + 1]; for (int i = 0; i <= CALLBACK_LAST; i++) { mCallbackQueues[i] = new CallbackQueue(); }... }Copy the code

There is a Looper and a FrameHandler variable in the Choreographer class. Variable USE_VSYNC is used to represent a system with Vsync synchronization mechanism, if the system USES the Vsync synchronization mechanism, create a FrameDisplayEventReceiver object to request and receive Vsync events, Finally Choreographer has created an array of CallbackQueue queues of size 4, with each element as a header pointer leading to the corresponding type of linked list through which the four types of events can be maintained.

There are four types of Callback:

    public static final int CALLBACK_INPUT = 0;
    public static final int CALLBACK_ANIMATION = 1;
    public static final int CALLBACK_TRAVERSAL = 2;
    public static final int CALLBACK_COMMIT = 3;
Copy the code
  1. FrameHandler
private final class FrameHandler extends Handler { ...... Public void handleMessage(Message MSG) {switch (MSG. What) {case MSG_DO_FRAME:// start rendering the next frame 0); break; Case MSG_DO_SCHEDULE_VSYNC:// request Vsync doScheduleVsync(); break; Case MSG_DO_SCHEDULE_CALLBACK:// Process Callback doScheduleCallback(msg.arg1); break; }}}Copy the code
  1. Choreographer initializes chains

During the Activity startup process, after onResume is executed, activity.makevisible () is called, and then addView() is called, which leads to the following method

ActivityThread.handleResumeActivity(IBinder, boolean, boolean, String) (android.app) -->WindowManagerImpl.addView(View, LayoutParams) (android.view) -->WindowManagerGlobal.addView(View, LayoutParams, Display, Window) (android.view) -->ViewRootImpl.ViewRootImpl(Context, Display) (android.view) public ViewRootImpl(Context context, Display display) { ...... mChoreographer = Choreographer.getInstance(); . }Copy the code
  1. FrameDisplayEventReceiver

Vsync registration and callback through FrameDisplayEventReceiver FrameDisplayEventReceiver inherit this class DisplayEventReceiver, there are three important methods

  1. OnVsync — Vsync signal callback
  2. Run — Performs doFrame
  3. ScheduleVsync — Request a Vsync signal
private final class FrameDisplayEventReceiver extends DisplayEventReceiver implements Runnable { ...... @Override public void onVsync(long timestampNanos, long physicalDisplayId, int frame) { ...... mTimestampNanos = timestampNanos; mFrame = frame; Message msg = Message.obtain(mHandler, this); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS); } @Override public void run() { mHavePendingVsync = false; doFrame(mTimestampNanos, mFrame); } public void scheduleVsync() { ...... nativeScheduleVsync(mReceiverPtr); . }}Copy the code

Visible onVsync () process is to the main thread through FrameHandler which sends a message of the callback for FrameDisplayEventReceiver callback. When the main thread when performing to the message, which is called FrameDisplayEventReceiver. The run () method, call doFrame were followed.

void doFrame(long frameTimeNanos, int frame) { final long startNanos; synchronized (mLock) { if (! mFrameScheduled) { return; // No work to do} long intendedFrameTimeNanos = frameTimeNanos; startNanos = System.nanoTime(); Final Long jitterNanos = startNanos-frametimenanos; final Long jitterNanos = startNanos-frametimenanos; If (jitterNanos >= mFrameIntervalNanos) {final Long skippedFrames = jitterNanos / mFrameIntervalNanos; final long lastFrameOffset = jitterNanos % mFrameIntervalNanos; frameTimeNanos = startNanos - lastFrameOffset; } // If frameTimeNanos is less than one screen refresh cycle, If (frameTimeNanos < mLastFrameTimeNanos) {if (DEBUG_JANK) {log.d (TAG, "Frame time appears to be going backwards. May be due to a " + "previously skipped frame. Waiting for next vsync."); } 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); / / callback CALLBACK_INPUT CALLBACK_ANIMATION, CALLBACK_TRAVERSAL mFrameInfo. MarkInputHandlingStart (); doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos); mFrameInfo.markAnimationsStart(); doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos); mFrameInfo.markPerformTraversalsStart(); doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos); doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos); } finally { AnimationUtils.unlockAnimationClock(); Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

When a Vsync event arrives, the four callbacks registered in the CallbackQueue are executed in sequence.

void doCallbacks(int callbackType, long frameTimeNanos) { CallbackRecord callbacks; synchronized (mLock) { final long now = System.nanoTime(); / / from the specified type of search for execution time to CallbackRecord CallbackQueue the queue callbacks = mCallbackQueues [callbackType] extractDueCallbacksLocked (now / TimeUtils.NANOS_PER_MS); if (callbacks == null) { return; } mCallbacksRunning = true; if (callbackType == Choreographer.CALLBACK_COMMIT) { final long jitterNanos = now - frameTimeNanos; Trace.traceCounter(Trace.TRACE_TAG_VIEW, "jitterNanos", (int) jitterNanos); if (jitterNanos >= 2 * mFrameIntervalNanos) { final long lastFrameOffset = jitterNanos % mFrameIntervalNanos + mFrameIntervalNanos; frameTimeNanos = now - lastFrameOffset; mLastFrameTimeNanos = frameTimeNanos; } } } try { Trace.traceBegin(Trace.TRACE_TAG_VIEW, CALLBACK_TRACE_TITLES[callbackType]); (CallbackRecord c = callbacks; c ! = null; c = c.next) { if (DEBUG_FRAMES) { c.run(frameTimeNanos); } } finally { synchronized (mLock) { mCallbacksRunning = false; do { final CallbackRecord next = callbacks.next; recycleCallbackLocked(callbacks); callbacks = next; } while (callbacks ! = null); } Trace.traceEnd(Trace.TRACE_TAG_VIEW); }}Copy the code

Pick up where we left off

void scheduleTraversals() {
    ...             
    mChoreographer.postCallback(
        Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
}
Copy the code

MTraversalRunnable corresponds to:

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal()
        } 
}
Copy the code

The run method is executed, so doTraversal() is executed, which opens the View’s rendering process.

Reference:

  1. www.jianshu.com/p/bab0b454e…
  2. Juejin. Cn/post / 684490…

11. How are messages stored? Are delayed messages always on time? How is the delay time guaranteed?

  • How is the message sent

Handler::sendMessage

public final boolean sendMessage(@NonNull Message msg) {
        return sendMessageDelayed(msg, 0);
    }
Copy the code

Handler:: sendMessageDelayed

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
        if (delayMillis < 0) {
            delayMillis = 0;
        }
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }
Copy the code

Handler:: sendMessageAtTime

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }
Copy the code
  • How are messages stored

MessageQueue::enqueueMessage

boolean enqueueMessage(Message msg, If (MSG. Target == null) {throw new IllegalArgumentException("Message must have a target."); If (MSG. IsInUse ()) {throw new IllegalStateException(MSG + "This message is already in use."); Synchronized (this) {if (mspapers) {// Synchronize MSG to mys.recycle (); return false; } msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; If (p = = null | | the when = = 0 | | the when < p.w hen) {/ / p is null (on behalf of the MessageQueue no news) or the triggering time of the MSG is the earliest in the queue, is access to the branch of the MSG. The next = p; mMessages = msg; needWake = mBlocked; } else {// Insert messages into MessageQueue in chronological order. NeedWake = mBlocked && p.target == null && msg.isasynchronous (); needWake = mBlocked && p.target == null && msg.isasynchronous (); Message prev; for (;;) { prev = p; p = p.next; if (p == null || when < p.when) { break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } // We can assume mPtr ! If (needWake) {// To wake up the thread nativeWake(mPtr); } } return true; }Copy the code

MessageQueue is arranged in order of Message firing time, with the Message at the head of the queue being the Message that will be fired earliest. When a message needs to be added to the message queue, the queue is traversed from the queue header until it finds the appropriate place to insert the message to ensure that all messages are in chronological order.

  • Is the Delay of sending messages by the Handler reliable?

The answer is unreliable, and the reasons are as follows

  1. Too many messages are sent, and the higher the Looper load, the more likely tasks are to backlog, leading to a lag
  2. Some of the message processing in the message queue is very time consuming, resulting in delayed message processing later on
  3. More reliable than Handler Looper cycles (e.g., main thread >50ms)
  4. Do not use handler delay as an immediate basis for time accuracy

How to optimize to ensure reliability

Messages are condensed and processed quantitatively

  1. Queue optimization, duplicate message filtering
  2. The mutually exclusive message is cancelled
  3. Reuse message
  • Handler#dispatchMessage
public void dispatchMessage(Message msg) { if (msg.callback ! // Call msg.callback.run() when a Message callback exists; handleCallback(msg); } else { if (mCallback ! // Call handleMessage() when Handler has a Callback member variable; if (mCallback.handleMessage(msg)) { return; HandleMessage () handleMessage(MSG); }}Copy the code

Message distribution process:

  1. If Message’s callback method is not null, the callback method msg.callback.run() is called, where callback data type is Runnable; otherwise, step 2 is performed.
  2. If the Handler’s mCallback member variable is not empty, the method McAllback.handlemessage (MSG) is called; otherwise, go to Step 3.
  3. Handler’s own callback method, handleMessage(), is called, which defaults to null, and Handler subclasses override it to do the specific logic.
  • How is the message retrieved

MessageQueue::next()

Message next() { final long ptr = mPtr; If (PTR == 0) {return null if (PTR == 0) {return null if (PTR == 0); } int pendingIdleHandlerCount = -1; // The first iteration of the loop is -1 int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis ! = 0) { Binder.flushPendingCommands(); } // Block operations that return nativePollOnce(PTR, nextPollTimeoutMillis) while waiting for nextPollTimeoutMillis or when the message queue is woken up; Final long now = systemclock. uptimeMillis(); synchronized (this) {final long now = systemclock. uptimeMillis(); Message prevMsg = null; Message msg = mMessages; // Async message if (MSG! Target == null) {// Find the next asynchronous message in the queue do {prevMsg = MSG; msg = msg.next; } while (msg ! = null && ! msg.isAsynchronous()); } if (msg ! = null) {if (now < msg.when) {// If (now < msg.when) NextPollTimeoutMillis = (int) math.min (msg.when - now, integer.max_value); } else {// Get a message and return mBlocked = false; if (prevMsg ! = null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); / / set the message using state, namely flags | = FLAG_IN_USE MSG. MarkInUse (); return msg; }} else {// No message nextPollTimeoutMillis = -1; // Now that all pending messages have been processed, dispose of the exit message if (McOntract) {dispose(); return null; } // When the message queue is empty, Or the first message, the message queue if (pendingIdleHandlerCount < 0 && (mMessages = = null | | now < mMessages. When)) {pendingIdleHandlerCount  = mIdleHandlers.size(); } if (pendingIdleHandlerCount <= 0) {// No idle handlers need to run, loop and wait. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Idle Handlers are run at the first time and set pendingIdleHandlerCount to 0. For (int I = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // Remove handler references Boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) {log. WTF (TAG, "IdleHandler threw Exception ", t); } if (! keep) { synchronized (this) { mIdleHandlers.remove(idler); PendingIdleHandlerCount = 0; pendingIdleHandlerCount = 0; NextPollTimeoutMillis = 0; // When an idle handler is called, a new message can be distributed, so there is no need to wait for pending messages. }}Copy the code

NativePollOnce is a blocking operation, where nextPollTimeoutMillis represents the waiting time before the next message arrives. When nextPollTimeoutMillis = -1, it indicates that there is no message in the message queue and it will wait forever.

When idle, methods in IdleHandler tend to be executed. When nativePollOnce() returns, next() extracts a message from mMessages. Let’s look at how native Pollonce is implemented

Messagenext() gets the next message from the queue and returns it. If the queue is empty, the method calls native void nativePollOnce(long, int), which blocks until a new Message is added. When Message is added to the queue, the framework calls the enqueueMessage method, This method not only inserts the message into the queue, Native static void nativeWake(long). The core magic of nativePollOnce and nativeWake occurs in native code A Linux system call to epoll that monitors IO events in file descriptors. NativePollOnce calls epoll_wait on a file descriptor and nativeWake writes an IO operation to the descriptor. Epoll_wait waiting. The kernel then retrieves the epoll wait thread from the wait state, and the thread continues to process the new message.

Conclusion:

NativePollOnce. It simply indicates that processing of all messages has completed and the thread is waiting for the next message.

  • Why doesn’t Looper#loop cause ANR

When the main thread MessageQueue has no message, it blocks in the nativePollOnce() method of the loop queue.next(), at which point the main thread releases CPU resources and goes to sleep until the next message arrives or a transaction occurs. Wake up the main thread by writing data to the pipe end

  • HandlerIn theIdleHandlerUnderstand? Appropriate call?

IdleHandler is a callback interface to which implementation classes can be added via addIdleHandler in MessageQueue. The interface is called back when the task in MessageQueue is temporarily finished (no new task or next task is delayed), returns false, it is removed, and returns true to continue the callback when the next message is finished.

Suitable scenarios can start from the following points:

  • Message queue correlation
  • Things the main thread can do
  • Returning true and false gives different results

Scene:

1.Activity startup optimization: onCreate, onStart, onResume code that takes a short time but is not necessary can be put into IdleHandler to reduce startup time

2. If you want a View to be drawn and then add other views that depend on that View, you can do View#post() as well, except that the former will be executed when the message queue is idle.

3. An IdleHandler occurs that returns true and keeps a View flashing so that when the user is in a daze they can be induced to click on the View, which is also cool.

4. Used in some third-party libraries, such as LeakCanary and Glide

  • Handler Message types

Handler messages are classified into three types:

Ordinary message

Barrier message

Asynchronous messaging

How are barrier messages inserted into message queues?

Synchronization barriers are inserted into message queues through the postSyncBarrier method of MessageQueue

How can I tell the difference between normal and asynchronous messages

The difference between a barrier message and a normal message is that a barrier does not have a TARtGET. A normal message has a target because it needs to be distributed to the corresponding target. A barrier does not need to be distributed.

  • The Message object pool

Obtaining Message objects using the obtain method allows Message to be reused, reducing the time it takes to acquire space each time a Message is obtained

/** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ public static Message obtain() { synchronized (sPoolSync) { if (sPool ! = null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }Copy the code

Maximum limited capacity

Private static final int MAX_POOL_SIZE = 50; private static final int MAX_POOL_SIZE = 50;Copy the code

How to send a Runnable to Handler without using the Handler#post(Runnable Run) API

  1. Public static Message obtain(Handler H, Runnable callback)

  2. Reflection sets the Message#callback attribute

12.ThreadLocal and ThreadLocalMap analysis

  • ThreadLocal set () method
Public void set(T value) {public void set(T value) { ThreadLocalMap = getMap(t); ThreadLocalMap = getMap(t); // If map is not empty, then k-v is assigned, and k is this, i.e. the current ThreaLocal object if (map! = null) map.set(this, value); Create a map and save the k- V relation else createMap(t, value); }Copy the code

1. Get the current thread and get the corresponding ThreadLocalMap based on the current thread

2. If the corresponding ThreadLocalMap is not null, call its set method to save the mapping

3. If the map is null, the constructor of ThreadLocalMap is finally called to create a ThreadLocalMap and save the corresponding relationship

  • ThreadLocal the get () method
Public T get() {Thread T = thread.currentThread (); ThreadLocalMap map = getMap(t); // If (map! Threadlocalmap. Entry e = map.getentry (this); // If the Entry is not null, the corresponding value is returned. Otherwise, setInitialValue is executed. = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; }} // If ThreadLocalMap does not already exist, return setInitialValue(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; } private T setInitialValue() {// getInitialValue (); Thread t = thread.currentThread (); ThreadLocalMap map = getMap(t); // Map is not null if (map! Map.set (this, value); Else create a ThreadLocalMap and assign createMap(t, value); return value; } /** * construct the argument to create a ThreadLocalMap code * ThreadLocal is the key, our generic is value */ ThreadLocalMap(ThreadLocal<? > firstKey, Object firstValue) {// Initialize table size to 16 table = new Entry[INITIAL_CAPACITY]; / / by hashcode & length (1) operation, to determine the location of the key/value pair int I = firstKey. ThreadLocalHashCode & (INITIAL_CAPACITY - 1); Table [I] = new Entry(firstKey, firstValue); // set table element to 1 size = 1; // Set the capacity expansion threshold setThreshold(INITIAL_CAPACITY); }Copy the code

1. Get the current thread and get the corresponding ThreadLocalMap based on the current thread

2. Get the Entry node of the ThreadLocalMap and return the corresponding value

3. If ThreadLocalMap is null, the setInitialValue method is called

1) When the setInitialValue method is called, the ThreadLocalMap is double guaranteed to be fetched again

2) If it is still null, the constructor of ThreadLocalMap is finally called