Android performance optimization: 35% faster startup

Android performance optimization: 35% faster startup

1. Introduction

As the project version iterates, the performance problems of the App will gradually be exposed, and a good user experience is closely related to performance. Starting from this article, I will start a special topic on Android application performance optimization, from theory to practice, from entry to deep digging, and practice performance optimization in the project step by step. Welcome to continue to pay attention!

So in the first article, I will start with the application startup optimization, and create a lightning-fast App startup speed based on actual cases.

2. Introduction to startup acceleration

Let’s take a look at the official Google document “Launch-Time Performance” (https://ldeveloper.android.com/topic/performance/launch-time.html) for an overview of application startup optimization;

Application startup can be divided into cold startup, hot startup, and warm startup. The slowest and most challenging startup is the cold startup: the system and the App itself have more work to do from scratch!

Before the application is cold started, it must perform three tasks:

  • Load and start the App;
  • A blank Window is displayed immediately after the App is launched;
  • Create the process of App;

After these three tasks are completed, the following tasks will be executed immediately:

  • Create an App object;
  • Start Main Thread;
  • Create the launched Activity object;
  • Load View;
  • Arrange the screen;
  • Make the first drawing;

Once the App process completes the first drawing, the system process will replace the displayed Background Window with the Main Activity, and the user can then use the App.

As a common application, we cannot actively control the creation of the App process. What we can optimize are the processes of Application, Activity creation and callback.

Similarly, Google also gave directions for starting acceleration:

  • Use the pre-displayed Window to quickly display an interface and give users a quick feedback experience;
  • Avoid heavy app initialization at startup;
  • Locate the problem: avoid I/O operations, deserialization, network operations, layout nesting, etc.

Note: Direction 1 is a temporary solution and only appears faster on the surface; Directions 2 and 3 can actually speed up the startup.

Next we will apply it in the project.

3. Startup acceleration theme switching

According to the official documentation: Use the windowBackground theme attribute of Activity to provide a simple drawable for the launched Activity.

Layout XML file:

Manifest file:

In this way, when starting up, an interface will be displayed first. This interface is the Style set in the Manifest. After the Activity is loaded, the Activity interface will be loaded. In the Activity interface, we reset the theme to the normal theme, which creates a sense of speed. However, as summarized above, this method does not really speed up the startup process, but optimizes the display effect through interactive experience.

Note: The screenshots are also from the official document "Launch-Time Performance".

4. Startup acceleration: Avoid Heavy App Initialization

Through code analysis, we can get the business workflow diagram started by App:

In this chapter, we focus on the initialization part: in Application and the first screen Activity, we mainly do the following:

  • MultiDex and Tinker initialization are executed first;
  • The Application mainly initializes various third-party components;

In the project, except for Tingyun, all other third-party components take the lead and are initialized in the main thread of Application. This initialization method is definitely too heavy:

  • Consider asynchronous initialization of third-party components without blocking the main thread;
  • Delay the initialization of some third-party components. In fact, we put all third-party components into asynchronous tasks in a coarse-grained manner. This may cause errors in which the components are not initialized in WorkThread but are already used in MainThread. Therefore, it is recommended to delay initialization until before use.
  • There are also some considerations on how to enable WorkThread, which will be discussed in detail below.

Project Modifications:

  • Initialize components such as Umeng, Bugly, Tingyun, GrowingIO, BlockCanary, etc. in WorkThread;
  • Delay initialization of map positioning, ImageLoader, and own statistics: Map and own statistics are delayed for 4 seconds, at which time the application is already open; while ImageLoader
  • Because the calling relationship cannot be asynchronous and delayed for too long, initialization is delayed from Application to SplashActivity; and EventBus must be initialized in Application because it is used in Activity.

Note: The 2-second pause of the splash screen page can be used to delay time-consuming operations to this time interval.

5. Diagnosing the Problem

In this section, we actually locate the time-consuming operations. In the development stage, we usually use BlockCanary or ANRWatchDog to find time-consuming operations. It is simple and clear, but it cannot get the execution time of each method and more detailed comparison information. We can use Method Tracing or DDMS to obtain more comprehensive and detailed information.

Start the application and click Start Method Tracing. Click it again after the application starts. It will automatically open the .trace file recorded by the previous operation. It is recommended to use DDMS to view it, which is more convenient and comprehensive.

The left side shows the specific thread where the call occurred, the right side shows the timeline where the call occurred, and below that is the specific method where the call occurred. Note the two columns: Real Time/Call (actual time of occurrence), Calls+RecurCalls/Total (number of occurrences);

From the above figure we can get the following information:

  • It can be seen intuitively that the timeline of MainThread is very long, indicating that most tasks are executed in MainThread;
  • By sorting Real Time/Call in descending order, we can see that some codes in the program are indeed very time-consuming;
  • On the next page, you can see that some third-party SDKs are also time-consuming;

Even if it is a time-consuming operation, as long as it happens correctly in WorkThread, there is no problem. Therefore, we need to confirm the threads in which these methods are executed and when they occur. If these operations occur in the main thread, they may not constitute the conditions for the occurrence of ANR, but jamming is inevitable! Combining the business operations and analysis diagrams in the App cold start business workflow diagram in the previous chapter, we can see from the code again that some time-consuming operations such as IO reading do occur in the main thread. In fact, clicking the name of the execution function in traceview can not only track the time-consuming methods of the parent and child classes, but also see which thread is in the method execution timeline and the time-consuming interface flashes.

After analyzing that some time-consuming operations occur in the main thread, will everything be fine if we move all the time-consuming operations to the child thread? No!!

The lag cannot be solved by asynchrony. Incorrect use of engineering threads will not only fail to improve the lag, but may aggravate it. Whether to enable the working thread needs to be analyzed according to the specific root cause of the performance bottleneck, and the right remedy cannot be generalized;

How to start a thread is also a science: Thread, ThreadPoolExecutor, AsyncTask, HandlerThread, IntentService, etc. all have their own advantages and disadvantages; for example, ThreadPoolExecutor is usually more efficient and has obvious advantages than Thread, but in a specific scenario, Thread will perform better than ThreadPoolExecutor at a single point in time: for the same object creation, the overhead of ThreadPoolExecutor is significantly greater than that of Thread;

Correctly starting a thread is not a cure-all. For example, executing a network request will create a thread pool, and correctly creating a thread pool in the Application will inevitably slow down the startup speed; therefore, delayed operations are also essential.

Through detailed tracking of traceview and detailed comparison of the code, I found that the jam occurred in:

  • Some database and IO operations occur in the main thread of the first screen Activity;
  • A thread pool is created in Application;
  • The first screen Activity has intensive network requests;
  • The worker thread uses an unset priority;
  • The information is not cached, and the same information is obtained repeatedly;
  • Process issues: For example, the splash screen image is downloaded each time and used at that time;

And other details:

  • Execute useless old code;
  • Execute the code used during the development phase;
  • Execute repetition logic;
  • Calling redundant code in third-party SDKs or demos;

Project Modifications:

1. Move database and IO operations to the worker thread, and set the thread priority to THREAD_PRIORITY_BACKGROUND. This way, the worker thread can get a maximum of 10% of the time slice, giving priority to the main thread.

2. Process sorting and delayed execution;

In fact, this step is the most effective in accelerating the project launch. Through process analysis, it is found that some processes are called too early or have errors, such as:

Update and other operations do not need to be called before the first screen is displayed, causing resource competition;

The switch made by IOS to circumvent audit was called, resulting in intensive network requests;

Our own statistics show that a fixed number of 5 thread pools are created in the Application call, causing resource competition. In the last line of the traceview function description in the above figure, we can see that number 12 is executed 5 times, ranking first in terms of time consumption; the creation of the thread pool here is necessary but can be postponed.

Modify the ad splash screen logic to take effect next time.

3. Other optimizations;

  • Remove old code that is useless but executed;
  • Remove the code used in the development phase but executed online;
  • Remove duplicate logic execution code;
  • Remove unnecessary code from third-party SDKs or demos;
  • Information caching, commonly used information is only obtained the first time, and then taken from the cache;
  • The project is a multi-process architecture, and only the Application's onCreate() is executed in the main process;

Through the above three steps and the optimization of the third-party components: the main thread does not waste time or compete for resources during the callback of the Application and the first screen Activity. In addition, some technologies such as layout optimization and memory optimization are also involved. Since cold start of an application is generally not a bottleneck, we will not discuss them in detail here and can deal with them according to the actual project.

6. Contrast effect:

Use the ADB command to count the application startup time: adb shell am start -W first screen Activity.

Under the same conditions, MX3 and Nexus 6P were used to start up 5 times, and the startup time before and after optimization was compared;

Before optimization:

MX3

Nexus 6P

After optimization:

MX3

Nexus 6P

contrast:

MX3 improved by 35%

Nexus 6P improved by 39%

Command meaning:

  • ThisTime: The startup time of the last launched Activity;
  • TotalTime: the time it takes to start all your Activities;
  • WaitTime: The total time that ActivityManagerService takes to start the App's Activity (including onPause() of the current Activity and the start of its own Activity).

7. Question:

1. What directions can be further optimized?

  • The project uses Retrofit network request library and FastConverterFactory as Json parser. TraceView shows that FastConverterFactory is time-consuming to create, so we consider replacing it with GsonConverterFactory. However, due to the inheritance relationship of the class, it cannot be directly replaced in a short time, so it is temporarily retained as an optimization point;
  • You can consider merging some interfaces into one at startup based on actual conditions to reduce the number of network requests and lower the frequency;
  • Only one component with the same function is retained, for example, Umeng, GrowingIO, and proprietary statistics have duplicate functions;
  • Use ReDex for optimization; the experiment with Redex found that the Apk size is indeed a little smaller, but the startup speed has not changed, so further research may be needed.

2. What is the basis for asynchronous and delayed initialization and operation?

Note: Not every component initialization and operation can be asynchronous or delayed; whether it can be done depends on the calling relationship of the components and the specific business needs of your own project. Follow one rule: asynchronous what can be done asynchronously, and delay what cannot be done asynchronously as much as possible. Start the application first, then operate.

3. General application startup acceleration routine?

  • Use themes to quickly display the interface;
  • Initialize components asynchronously;
  • Sort out business logic and delay initialization of components and operations;
  • Use threads correctly;
  • Remove useless code, duplicate logic, etc.

4. Others

  • Speeding up the startup by 35% does not mean that all the previous code is a problem. From a business perspective, the code is not wrong and meets business requirements. However, during the startup phase, which focuses on speed, ignoring details will lead to performance bottlenecks.
  • During the development process, use TraceView to analyze core modules and application stages such as startup to identify bottlenecks as early as possible.

<<:  What are the things to pay attention to when designing long forms on mobile devices? This article summarizes them all!

>>:  After disassembling the iPhone 11 Pro Max, they discovered these secrets

Recommend

What's going on with the golden monkeys in Yunnan, Guizhou and Sichuan?

The golden snub-nosed monkey is a very famous rar...

IEA: Hydrogen Monitoring Report for Northwest Europe 2024

Northwest Europe is at the forefront of low-emiss...

Response to AFNetworking security bug

Last week a number of publications broke the stor...

10 Practical but Paranoid Java Programming Techniques

[[147453]] After you've been coding for a whi...