An overview,

When an application is clicked on the mobile desktop, users expect the application to respond quickly and load quickly. Apps that take too long to launch can disappoint users. This poor experience can result in users giving your app low ratings on the Play Store, or even abandoning your app altogether.

This article explains how to analyze and optimize startup times. Firstly, the mechanism of startup process is introduced. Then, how to detect startup time and analyze tools are discussed. Finally, the general startup optimization scheme is given.

2. Introduction to application startup process

According to the official documentation, there are three startup states: cold startup, warm startup, and hot startup.

  • Cold start In cold start mode, an application is started from the beginning: An application process is created after a cold start. Cold startup occurs when the application is started for the first time after the device is started or the application is terminated. For example, you can manually kill an application process from the task list and then restart the application.

  • Hot start Hot start is much simpler and less expensive than cold start. In a hot start, all the system does is bring your Activity to the fore. As long as all of the application’s activities remain in memory, the application does not have to repeat the creation of processes, applications, and activities. For example, press the home button to the desktop and then tap the icon to launch the application.

  • Warm start Warm start contains some of the actions that occur during cold start; At the same time, it is more expensive than hot boot. There are many potential states that can be viewed as warm initiation. For example, a user presses the back key to exit an application and then restarts the application. The process is already running, but the application must recreate the Activity from scratch by calling onCreate().

Startup optimization is optimized on the basis of cold startup. To optimize your application for fast startup, it helps to know what the system and application layers are like and how they interact in various states.

At the beginning of cold start, the system has three tasks, which are:

  • Load and start the application.
  • Displays a blank startup window for the application immediately after startup.
  • Create an application process.

Once the system creates an application process, the application process is responsible for subsequent phases:

  • Start the main thread.
  • Create an application object.
  • Create the main Activity.
  • Load the view.
  • Perform the initial drawing.

Once the application process has finished drawing for the first time, the system process replaces the currently displayed StartingWindow with the main Activity. At this point, the user can start using the application.

For a complete analysis of the startup process, please refer to my article “How to start an Activity (based on 10.0 source code)”. This article introduces the complete process from clicking the application icon to adding Windows. It is recommended to continue this priming optimization study after reading comprehension.

The following startup process flow chart from the official documentation shows the transition between system processes and application processes. A brief overview of the actual startup process.

Third, optimize the core idea

The question is, which steps of the startup process are optimized for startup optimization?

That’s a good question. We know that users care about clicking on the desktop icon to display the first page as quickly as possible and being able to interact. According to the analysis of the startup process, the display page can interact with the user, which is what the main thread does. This requires us not to do time-consuming operations on the main thread. We can’t interfere with system tasks during startup, but only with performance issues that may occur during application creation and Activity creation. This process is as follows:

  • The Application of attachBaseContext
  • The Application of onCreate
  • The activity’s onCreate
  • The activity of the onStart
  • The activity’s onResume

The activity’s onResume method completes before the first frame is drawn. So time-consuming operations in these methods are something that we want to avoid.

In addition, usually, the data of the home page of an application needs to be requested by the network, so users want to enter the home page quickly and see the data of the home page when they start the application, which is also a basis for us to calculate the start end time.

Four, time detection

4.1 Displayed

In Android 4.4 (API level 19) and later, logcat contains an output line that contains a value named “Displayed.” This value represents the time from starting the process to finishing drawing the corresponding Activity on the screen. The elapsed time includes the following sequence of events:

  • Start the process.
  • Initialize the object.
  • Create and initialize the Activity.
  • Expand the layout.
  • First drawing.

This is my demo app startup log print, view

2020-07-13 19:54:38.256 18137-18137/com.hfy.androidlearning I/hfy: onResume begin. 
2020-07-13 19:54:38.257 18137-18137/com.hfy.androidlearning I/hfy: onResume end. 
2020-07-13 19:54:38.269 1797-16782/? I/WindowManager: addWindow: Window{1402051 u0 com.hfy.androidlearning/com.hfy.demo01.MainActivity}
2020-07-13 19:54:38.391 1797-2017/? I/ActivityTaskManager: Displayed com.hfy.androidlearning/com.hfy.demo01.MainActivity: +2s251ms
Copy the code

The Displayed time was printed after window was added and the window was added after the onResume method.

4.2 the adb shell

You can also run the application using adb commands to measure the initial display time:

Adb shell am start -w [ApplicationId]/[full path of the root Activity] If the ApplicationId and package are the same, the packageName can be omitted from the full path of the root Activity.

The Displayed indicator appears in the logcat output as before:

2020-07-14 14:53:05.294 1797-2017/? I/ActivityTaskManager: Displayed com.hfy.androidlearning/com.hfy.demo01.MainActivity: +2s98ms
Copy the code

Your terminal window should also display the following after adb command execution:

hufeiyangdeMacBook-Pro:~ hufeiyang$ adb shell am start -W com.hfy.androidlearning/com.hfy.demo01.MainActivity
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.hfy.androidlearning/com.hfy.demo01.MainActivity }
Status: ok
LaunchState: COLD
Activity: com.hfy.androidlearning/com.hfy.demo01.MainActivity
TotalTime: 2098
WaitTime: 2100
Complete
Copy the code

TotalTime is the startup time of the Application, including the process creation + Application initialization + Activity initialization to the screen display.

4.3 reportFullyDrawn ()

You can use the reportFullyDrawn() (API19 and above) method to measure the time from application startup to full display of all resource and view hierarchies. What does that mean? “Displayed” and “TotalTime” are Displayed from the start of the first frame to the start of the first frame. So how do you obtain the time from the start to the finish of the refresh after the network request?

To solve this problem, you can manually call the Activity’s reportFullyDrawn() method to let the system know that your Activity has finished lazy loading. When you use this method, logcat displays the value for the time between the creation of the application object and the call of reportFullyDrawn(). The following is an example:

    @Override
    protected void onResume(a) {
        super.onResume();
        new Thread(new Runnable() {
            @Override
            public void run(a) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                runOnUiThread(new Runnable() {
                    @Override
                    public void run(a) {
                        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { reportFullyDrawn(); }}}); } }).start(); }Copy the code

Simulate data loading using child thread sleep for 1 second, then call reportFullyDrawn(), and here is the output of logcat.

2020-07-14 15:26:00.979 1797-2017/? I/ActivityTaskManager: Displayed com.hfy.androidlearning/com.hfy.demo01.MainActivity: +2s133ms
2020-07-14 15:26:01.788 1797-2017/? I/ActivityTaskManager: Fully drawn com.hfy.androidlearning/com.hfy.demo01.MainActivity: +2s943ms
Copy the code

4.4 Code typing

Write a dotting tool class, record the beginning and end respectively, and report the time to the server.

This method can be brought online, but the code is invasive.

The starting point of recording is in the Application’s attachBaseContext method, which is the earliest lifecycle callback method that our Application receives.

Two ways to calculate the start end time

  • One is to calculate the startup time in the onWindowFocusChanged method. The onWindowFocusChanged method is only the first frame time of the Activity, which is the time when the Activity is drawn for the first time. There is still a time lag between the first frame time and the complete display of the interface, which cannot truly represent that the interface has been displayed.

  • It is not accurate to calculate the startup time by the first frame, we want the time when the user actually sees our interface. The correct time to calculate startup time is to wait for actual data to be presented, such as the first item in the list. (Add a Boolean variable to the Adapter to determine the startup time to avoid unnecessary calculations when the onBindViewHolder method is called multiple times.)

// The first item is not recorded
  if (helper.getLayoutPosition() == 1 && !mHasRecorded) {
      mHasRecorded = true;
      helper.getView(R.id.xxx).getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
          @Override
          public boolean onPreDraw(a) {
              helper.getView(R.id.xxx).getViewTreeObserver().removeOnPreDrawListener(this);
              LogHelper.i("Finish it!");
              return true; }}); }Copy the code

4.5 Aspect Oriented Programming (AOP

For section-oriented programming, you can use AspectJ. For example, you can cut Application’s onCreate method to calculate its elapsed time. Features are non-invasive code, can be brought online. Since the specific usage will not be expanded, please refer to this article “In-depth Exploration of Compile Piling Technology (II, AspectJ)” for detailed usage.

5. Analysis tool introduction

Analysis method time-consuming tools: Systrace, Traceview, the two are complementary to each other, we need to use different tools in different scenarios, so that the tool can play the maximum role.

5.1 Traceview

Traceview can graphically display code execution time and call stack information, and Traceview provides comprehensive information because it covers all threads.

The use of Traceview can be divided into two steps: start tracing and analyze the results. Let’s see how it works.

StartMethodTracing with debug.startmethodtracing (tracepath) to record CPU usage over a period of time. Call debug.stopMethodTracing () to stop the trace method, and the system will generate a.trace file for us, which we can view through Traceview.

The default location for file generation is under Android/data/ package name /files. Here is an example.

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "onCreate begin. ");
        super.onCreate(savedInstanceState);
		// The default path is Android/data/ package name /files/dmtrace.trace
        Debug.startMethodTracing();
        // You can also customize paths
       //Debug.startMethodTracing(getExternalFilesDir(null)+"test.trace");

        setContentView(R.layout.activity_main);
        Intent intent = getIntent();
        String name = intent.getStringExtra("name");
        Log.i(TAG, "onCreate: name = "+ name); initConfig(); initView(); initData(); . Debug.stopMethodTracing(); }Copy the code

After running the application and entering the home page, we locate the directory /sdcard/ Android /data/ package name /files/ to check that the file manager does have the trace file:

For details, see checking CPU Activity using CPU Profiler.

You can see that the testHandler method, the one that takes the most time out of the onCreate method, takes a nap.

5.2 Systrace

Systrace combines the Android kernel data, analyzes thread activity and gives us a very accurate HTML report.

Systrace principle: Insert some information (Label) into some key links of the system (such as SystemServcie, virtual machine, Binder driver). Then, the execution time of a certain core process is determined by the start and end of the Label, and the Label information is collected to obtain the running time information of the system critical path, and finally the operating performance information of the whole system. Some important modules in the Android Framework have label information, and users can add custom Lable in their apps.

The Trace utility class provided by Systrace can only be used in projects above API 18 by default. If our compatible version is lower than API 18, we can use TraceCompat. Using Systrace is similar to using Traceview in the following two steps.

  • Call trace method
  • View trace Results

For example, use tracecompat.beginSection and tracecompat.endSection before and after onCreate respectively:

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        Log.i(TAG, "onCreate begin. ");

        super.onCreate(savedInstanceState);

        TraceCompat.beginSection("MainActivity onCreate");

        Debug.startMethodTracing();//dmtrace.trace
// Debug.startMethodTracing(getExternalFilesDir(null)+"test.trace");

        setContentView(R.layout.activity_main);

        initConfig();
        initView();
        initData();
        
        Debug.stopMethodTracing();

        TraceCompat.endSection();
    }
Copy the code

After running the app, manually kill it. CD to platform-tools/systrace in the SDK directory, run the following command:

python systrace.py -t 10 -o /Users/hufeiyang/trace.html -a com.hfy.androidlearning

-t 10 indicates that the file is traced for 10 seconds, -o indicates that the file is exported to a specified directory, and -a indicates the name of the application package.

After entering this command line, you will be prompted to start tracing. After seeing “Starting Tracing”, manually open our application.

The following is an example:

hufeiyangdeMacBook-Pro:~ hufeiyang$ cd  /Users/hufeiyang/Library/Android/sdk/platform-tools/systrace

hufeiyangdeMacBook-Pro:systrace hufeiyang$ python systrace.py -t 10 -o /Users/hufeiyang/trace.html  -a com.hfy.androidlearning

Starting tracing (10 seconds)
Tracing completed. Collecting output...
Outputting Systrace results...
Tracing complete, writing results

Wrote trace HTML file: file:///Users/hufeiyang/trace.html
Copy the code

Trace for 10 seconds, then generate the HTML file in the specified directory, let’s open it:

Systrace Overview

Two features of Traceview are summarized

  • One of the benefits of Traceview is that you can bury points in your code, which can then be analyzed using a CPU Profiler. Because we are now optimizing the code at the startup stage, if we open the App and record directly through the CPU Profiler, we require you to have been single for 30 years and click start to record the time exactly the same as the startup time of the application. With Traceview, even the elderly can log the call stack information involved in the startup process.
  • The runtime overhead of Traceview is very high, and it causes our program to run slowly. It is slow because it grabs all the call stacks of all our current threads through the virtual machine Profiler. Because of this problem, Traceview may also skew our optimization direction. For example, we have a method that normally takes not much time, but after adding Traceview, we may find that it takes ten times or more time.

Two features of Systrace

  • Low overhead Systrace is very low overhead, unlike Traceview, because it only records at our buried points. Traceview logs stack calls made by all threads.
  • Intuitive In Systrace we can see the CPU utilization very intuitively. When we find that CPU utilization is low, we can consider making more code execute asynchronously to improve CPU utilization.

Two differences between Traceview and Systrace

  • Use Profiler to view the Traceview analysis results. Systrace analyzes the result by viewing the HTML file in the browser.
  • The buried utility class Traceview uses debug.startMethodTracing (). Systrace uses trace.beginSection () and tracecompat.beginSection ().

6. Start the optimization plan

The optimization scheme has two directions:

  • Visual optimization, startup time is not less, but the startup process to give users a better experience.
  • Speed optimization, reduce the main thread time, real fast start.

6.1 Visual Optimization

As mentioned in Activity Launch, a window named StartingWindow is displayed before the Activity starts. The window’s background is the WindowBackground configured in the Theme to start the Activity.

Because before you start the root activity, you need to create a process and so on, and it takes some time, and the purpose of showing StartingWindow is to show the user that your click is reactive, but it’s in process, and then after the activity starts, The Activity window replaces the StartingWindow. Without the StartingWindow, there would be no response for a while after clicking, giving the user the wrong impression.

This is why the app starts with a blank screen.

Here’s a visual solution: Replace the Theme for the first activity (usually a splash page), replace the white background with a Logot diagram, and then change it back in the activity’s onCreate. This way, you will see your configured logo at startup.

Specific operation:

        <activity android:name=".MainActivity" android:theme="@style/SplashTheme">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
Copy the code

Here, my first activity is MainActivity, which is configured with r.style.splashTheme. Take a look:

    <style name="SplashTheme" parent="AppNoActionBarAlphaAnimTheme">
        <item name="android:windowBackground">@drawable/splash_background</item>
    </style>
Copy the code

The key point is that Android :windowBackground is configured with a custom drawable, whereas windowBackground is white by default. Take a look at the custom drawable:

<? The XML version = "1.0" encoding = "utf-8"? > <layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque"> <! <item android:drawable="@android:color/white"/> <item> <bitmap android: SRC ="@drawable/dog" android:gravity="center"/> </item> </layer-list>Copy the code

The root node of drawable is

, then one layer is white, and one layer is our logo image.

Finally, change the Theme back to R.style.appTheme in the activity’s onCreate (before super.oncreate).

    protected void onCreate(Bundle savedInstanceState) {
        setTheme(R.style.AppTheme);
        super.onCreate(savedInstanceState);
    }
Copy the code

The effect is as follows:

But it doesn’t actually start faster, so here’s a look at some of the ways you can actually speed things up.

6.2 Asynchronous Initialization

As mentioned earlier, the idea of improving startup speed is to reduce time-consuming operations on the main thread. The main thread that can control the startup time is the onCreate method of the Application, onCreate, onStart, and onResume methods of the Activity.

The onCreate method of the Application usually performs a lot of initialization operations, such as third-party library initialization, so this process needs to be focused on.

Methods to reduce the time of the main thread can be subdivided into asynchronous initialization and deferred initialization, that is, the main thread tasks to be executed in child threads or postponed execution. Let’s take a look at how asynchronous initialization is implemented.

Perform asynchronous requests, typically using thread pools, for example:

        Runnable initTask = new Runnable() {
            @Override
            public void run(a) {
                //init task}}; ExecutorService fixedThreadPool = Executors.newFixedThreadPool(threadCount); fixedThreadPool.execute(initTask);Copy the code

But there are three problems with the way initialization tasks are handled by thread pools:

  • If we had 100 initialization tasks, code like the one above would have to be written 100 times and submitted 100 times.
  • Some third-party library initializations need to be performed in the onCreate method of the Application. You can use CountDownLatch to wait, but it’s still a bit cumbersome.
  • Some initialization tasks have dependencies. For example, aurora push requires a device ID, and initDeviceId() is also an initialization task.

So what’s the solution? Starter!

The LauncherStarter, or launcher, is the solution to all three of these problems by repackaging thread pools with CountDownLatch to take full advantage of CPU multicore and automatically comb through task order.

Usage:

  • Introduction of depend on
  • Divide tasks and identify dependencies and constraints
  • Add a task and start it

First rely on introduction:

implementation 'com. Making. Zeshaoaaa: LaunchStarter: 0.0.1'
Copy the code

Then divide the initialization task into tasks; Clarify dependencies. For example, task 2 depends on the completion of task 1 before starting. Tasks such as 3 need to be completed before the onCreate method ends; Task 4 will be executed on the main thread.

Then add the tasks, start the tasks, and set up the wait.

Specific use is also relatively simple, the code is as follows:

public class MyApplication extends Application {

    private static final String TAG = "MyApplication";
    
    @Override
    public void onCreate(a) {
        super.onCreate();
        
        TaskDispatcher.init(getBaseContext());
        TaskDispatcher taskDispatcher = TaskDispatcher.createInstance();

        // task2 depends on task1;
        // the taskdispatcher.await () task is not completed;
        Test4 is executed on the main thread
        // Each task takes one second
        Task1 task1 = new Task1();
        Task2 task2 = new Task2();
        Task3 task3 = new Task3();
        Task4 task4Main = new Task4();

        taskDispatcher.addTask(task1);
        taskDispatcher.addTask(task2);
        taskDispatcher.addTask(task3);
        taskDispatcher.addTask(task4Main);

        Log.i(TAG, "onCreate: taskDispatcher.start()");
        taskDispatcher.start();/ /
	
        taskDispatcher.await();// Wait until task3 is complete
        Log.i(TAG, "onCreate: end.");
    }

    private static class Task1 extends Task {
        @Override
        public void run(a) {
            Log.i(TAG, Thread.currentThread().getName()+" run start: task1");
            doTask();
            Log.i(TAG, Thread.currentThread().getName()+" run end: task1"); }}private static class Task2 extends Task {
        @Override
        public void run(a) {
            Log.i(TAG, Thread.currentThread().getName()+" run start: task2");
            doTask();
            Log.i(TAG, Thread.currentThread().getName()+" run end: task2");
        }

        @Override
        public List<Class<? extends Task>> dependsOn() {
        	// Rely on task1 and wait until task1 finishes executing
            ArrayList<Class<? extends Task>> classes = new ArrayList<>();
            classes.add(Task1.class);
            returnclasses; }}private static class Task3 extends Task {
        @Override
        public void run(a) {
            Log.i(TAG, Thread.currentThread().getName()+" run start: task3");
            doTask();
            Log.i(TAG, Thread.currentThread().getName()+" run end: task3");
        }

        @Override
        public boolean needWait(a) {
        	// Wait at taskDispatcher.await() when task3 is not completed. This is just to make sure it's done before onCreate ends.
            return true; }}private static class Task4 extends MainTask {
    // Inherits from MainTask, which is guaranteed to be executed on the main thread
        @Override
        public void run(a) {
            Log.i(TAG, Thread.currentThread().getName()+" run start: task4");
            doTask();
            Log.i(TAG, Thread.currentThread().getName()+" run end: task4"); }}private static void doTask(a) {
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code

There are four initialization tasks, each of which takes one second. If they were all executed on the main thread, they would take four seconds. This is done using initiators, and the task requirements described above are guaranteed. The log is as follows:

2020-07-17 12:06:20.648 26324-26324/com.hfy.androidlearning I/MyApplication: onCreate: taskDispatcher.start()
2020-07-17 12:06:20.650 26324-26324/com.hfy.androidlearning I/MyApplication: main run start: task4
2020-07-17 12:06:20.651 26324-26427/com.hfy.androidlearning I/MyApplication: TaskDispatcherPool-1-Thread-1 run start: task1
2020-07-17 12:06:20.657 26324-26428/com.hfy.androidlearning I/MyApplication: TaskDispatcherPool-1-Thread-2 run start: task3

2020-07-17 12:06:21.689 26324-26324/com.hfy.androidlearning I/MyApplication: main run end: task4
2020-07-17 12:06:21.689 26324-26427/com.hfy.androidlearning I/MyApplication: TaskDispatcherPool-1-Thread-1 run end: task1
2020-07-17 12:06:21.690 26324-26429/com.hfy.androidlearning I/MyApplication: TaskDispatcherPool-1-Thread-3 run start: task2
2020-07-17 12:06:21.697 26324-26428/com.hfy.androidlearning I/MyApplication: TaskDispatcherPool-1-Thread-2 run end: task3
2020-07-17 12:06:21.697 26324-26324/com.hfy.androidlearning I/MyApplication: onCreate: end.

2020-07-17 12:06:22.729 26324-26429/com.hfy.androidlearning I/MyApplication: TaskDispatcherPool-1-Thread-3 run end: task2
Copy the code

See that the main thread takes only 1 second. Note that task3 and task4 must be completed within onCreate, and task1 and task2 may be completed some time after onCreate, so you cannot use task1 and Task2 libraries in your Activity. Be careful when dividing tasks and identifying dependencies and constraints.

So much for asynchronous initialization, the principle part can read the source code directly, it is easy to understand. And then lazy initialization.

6.3 Delayed Initialization

There may be low-priority initialization tasks in applications and activities. Consider delaying the initialization of these tasks. Lazy initialization does not reduce main thread time, but rather gives way to time-consuming operations and resources for UI drawing, postponing time-consuming operations until the UI is finished loading.

So the question is, how do you delay it?

  • Use the new Handler().postdelay () method, or view.postdelay () — but the delay is tricky, not knowing when the UI is finished loading.
  • Use view.getViewTreeObserver ().addonPreDrawListener () to listen — this ensures that the View is drawn, but interactions occur, such as when the user is swiping a list, causing a lag.

So what’s the solution? Delay the starter!

The delay initiator uses the IdleHandler feature to perform batch initialization of delayed tasks when the CPU is idle. In this way, the execution time is clear and UI delays are alleviated. The deferred starter is one of the LauncherStarter classes above.

public class DelayInitDispatcher {

    private Queue<Task> mDelayTasks = new LinkedList<>();

    private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() {
        @Override
        public boolean queueIdle(a) {
            if(mDelayTasks.size()>0){
                Task task = mDelayTasks.poll();
                new DispatchRunnable(task).run();
            }
            return !mDelayTasks.isEmpty();
        }
    };

    public DelayInitDispatcher addTask(Task task){
        mDelayTasks.add(task);
        return this;
    }

    public void start(a){ Looper.myQueue().addIdleHandler(mIdleHandler); }}Copy the code

It is also easy to use, such as adding a task start to a splash screen:

//SpalshActivity

DelayInitDispatcher delayInitDispatcher = new DelayInitDispatcher();

protected void onCreate(Bundle savedInstanceState) {
        delayInitDispatcher.addTask(new Task() {
            @Override
            public void run(a) {
                Log.i(TAG, "run: delay task begin");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Log.i(TAG, "run: delay task end"); }}); delayInitDispatcher.start(); }Copy the code

After testing, it is true that the task is started after the layout is displayed. However, if it takes a long time (3 seconds in the example) and you swipe the screen during the process, you will not respond in a timely manner and will feel a significant lag.

Therefore, tasks that can be asynchronously loaded preferentially use asynchronous initiators in the onCreate method of the Application, while tasks that cannot be asynchronously and require less time can be loaded using lazy initiators. If the task can be loaded at time, use lazy loading.

IdleHandler principle analysis:

//MessageQueue.java
    Message next(a) {
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }

        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if(nextPollTimeoutMillis ! =0) {
                Binder.flushPendingCommands();
            }

            nativePollOnce(ptr, nextPollTimeoutMillis);

            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());
                }
                if(msg ! =null) {
                    if (now < msg.when) {
                        // Next message is not ready. Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if(prevMsg ! =null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        returnmsg; }}else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }

                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run. Loop and wait some more.
                    mBlocked = true;
                    continue;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf(TAG, "IdleHandler threw exception", t);
                }

                if(! keep) {synchronized (this) { mIdleHandlers.remove(idler); }}}// Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0; }}Copy the code

When a message is fetched from a message queue, if no message is fetched, an idle IdleHandler is executed and removed.

6.4 Multidex preloading optimization

It takes too long to install or upgrade MultiDex for the first time, so we need to optimize the preloading of MultiDex.

ART is used by default above 5.0, class.dex has been converted to OAT file during installation, so there is no need to optimize, so you should decide to preload Multidex only in the main process and SDK 5.0 below

Optimization practices of Douyin BoostMultiDex:

Optimization practice of Douyin BoostMultiDex: Reduce the first startup time of APP on Android versions by 80% (I)

Github address: BoostMultiDex

Quick access:

  • Add dependencies to build. Gradle dependencies
dependencies {
    // For specific version number, please refer to app demo
    implementation 'com. Bytedance. Boost_multidex: boost_multidex: 1.0.1'
}
Copy the code
  • With the official MultiDex similar, in the Application. The front of the attachBaseContext can be initialized:
public class YourApplication extends Application {

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        BoostMultiDex.install(base);
    }
Copy the code

Toutiao 5.0, BoostMultiDex, MultiDex startup speed comparison

6.5 Preloading Page Data

Data preloading on the flash page and home page: The data on the flash page and home page is cached to the local PC and read from the cache when you log in next time. The home page read cache to memory operation can also be advanced to the splash screen page.

6.6 Page Rendering Optimization

Flash page and home page drawing optimization, here involves drawing optimization related knowledge, such as reducing the layout level.

Seven,

We first introduce the startup process, optimization ideas, time detection, analysis tools, and then give common optimization schemes: asynchronous initialization, delayed initialization. Involves a lot of new knowledge and tools, some places are not expanded in the article, you can refer to the link given for detailed study. After all, performance optimization is the comprehensive use of various technical knowledge, and it is necessary to systematically master the corresponding workflow, analysis tools and solutions to carry out in-depth performance optimization.

Well, that’s all for today, welcome to comment ~

Reference and thanks:

Application startup time

Dig deeper into Android startup speed optimization

Explore ways to optimize Android startup

.

Well, that’s all for today, welcome to comment ~

Your likes, comments, favorites, forwarding, is a great encouragement to me!

Welcome to my public account: