Optimizing Android Performance: Boosting Startup Speed by 35%

Optimizing Android Performance: Boosting Startup Speed by 35%

Hot Articles Guide | Click the title to read

How to Advance to Become a Java and Android Architect?

Optimize Your RecycleView Performance from Multiple Angles

Personal Experience! A Few Job Hunting Tips for Older IT Workers

Author: Double Twelve Tech Guy

Source: http://www.apkbus.com/blog-960965-78111.html

1. Introduction

As project versions iterate, performance issues of the App will gradually emerge, and good user experience is closely related to performance. Starting from this article, I will begin a series on Android application performance optimization, from theory to practice, from entry-level to in-depth, guiding you to implement performance optimization in projects. Stay tuned!

For the first article, I will start with application startup optimization, creating a lightning-fast app startup speed based on actual cases.

2. Understanding Startup Acceleration

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

Application startup is divided into cold start, hot start, and warm start, and the slowest and most challenging is cold start: both the system and the App itself have more work to start from scratch! Before the app cold starts, three tasks must be executed:

  • Load the startup App;

  • Immediately display a blank Window after the App starts;

  • Create the App’s process;

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

  • Create the App object;

  • Start the Main Thread;

  • Create the starting Activity object;

  • Load the View;

  • Layout the screen;

  • Perform the first drawing;

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

Optimizing Android Performance: Boosting Startup Speed by 35%

As a regular application, we cannot actively control the creation of the App process, the optimizable parts are the creation and callback processes of Application and Activity.

Similarly, Google has also provided directions for startup acceleration:

  • Utilize the window that is displayed in advance to quickly show an interface, providing users with a quick feedback experience;

  • Avoid heavy initialization during startup (Heavy app initialization);

  • Identify issues: avoid I/O operations, deserialization, network operations, layout nesting, etc.

Note: Direction 1 is a superficial fix, just appearing faster; directions 2 and 3 can genuinely speed up startup. Next, we will apply this in practice.

3. Startup Acceleration: Theme Switching

According to the official documentation: use the Activity’s windowBackground theme attribute to provide a simple drawable for the starting Activity. Layout XML file:

Optimizing Android Performance: Boosting Startup Speed by 35%

Thus, at startup, a screen will first be displayed, which is the Style set in the Manifest, and once the Activity is loaded, the Activity’s interface will be loaded, while in the Activity’s interface, we will reset the theme to the normal theme, creating a sense of speed. However, as summarized above, this method does not genuinely accelerate the startup process, but rather optimizes the display effect through interactive experience.

Note: The screenshots also come from the official documentation “Launch-Time Performance”.

4. Startup Acceleration: Avoid Heavy App Initialization

Through code analysis, we can obtain the business workflow diagram for App startup: In this chapter, we focus on the initialization part: In the Application and the first screen Activity, we mainly did:

Optimizing Android Performance: Boosting Startup Speed by 35%

  • MultiDex and Tinker initialization, executed first; regarding MultiDex optimization, this article will not elaborate, refer to my previous

  • Various third-party component initializations are mainly done in the Application;

In the project, all third-party components except Listening Cloud are initialized in the Application main thread. This initialization method is definitely too heavy:

  • Consider initializing third-party components asynchronously to avoid blocking the main thread;

  • Delay some third-party component initializations; in fact, we roughly put all third-party components into asynchronous tasks, which may lead to errors where the WorkThread has not finished initializing but the MainThread has already used them, so it is advisable to delay initialization until just before use;

  • How to start WorkThread is also crucial; this topic will be discussed in detail later.

Project Modifications:

  • Initialize components like Umeng, Bugly, Listening Cloud, GrowingIO, BlockCanary in the WorkThread;

  • Delay the initialization of map location, ImageLoader, and self-statistics components: Map and self-statistics are delayed by 4 seconds, by which time the application is already open; ImageLoader cannot be asynchronous or delayed too long due to calling relationships, so its initialization is delayed from Application to SplashActivity; EventBus must be initialized in Application since it is used in Activity.

    Optimizing Android Performance: Boosting Startup Speed by 35%

Note: The 2-second stay on the splash page can be utilized to delay time-consuming operations.

5. Startup Acceleration: Diagnosing The Problem

In this section, we actually locate time-consuming operations. During the development phase, we generally use BlockCanary or ANRWatchDog to find time-consuming operations, which is straightforward, but cannot obtain the execution time of each method and more detailed comparison information. We can obtain more comprehensive and detailed information through Method Tracing or DDMS.

Start the application, click Start Method Tracing, and after the application starts, click again to automatically open the previously recorded .trace file; it is recommended to use DDMS to view, as it is more convenient and comprehensive.

Optimizing Android Performance: Boosting Startup Speed by 35%

Optimizing Android Performance: Boosting Startup Speed by 35%

The left side shows the specific thread that occurred, and the right side shows the timeline of occurrences, with specific method information below. Pay attention to the two columns: Real Time/Call (actual occurrence time), Calls+RecurCalls/Total (number of occurrences); from the above image, we can obtain the following information:

  • It can be visually seen that the MainThread’s timeline is very long, indicating that most tasks are executed in the MainThread;

  • By sorting by Real Time/Call in descending order, we can see that some parts of the code in the program are indeed very time-consuming;

  • On the next page, it can be seen that some third-party SDKs are also quite time-consuming;

Even if there are time-consuming operations, as long as they occur correctly in the WorkThread, it is not a 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 ANR conditions, but stuttering is inevitable! Combining the business operation flow chart of App cold startup from the previous chapter and analyzing the code again, we can see that some time-consuming operations, such as IO reading, indeed occur in the main thread. In fact, in traceview, clicking on the name of the executed function not only allows tracking of the parent and subclass method execution times but also shows specifically in which thread and the duration of the interface flashing in the method execution timeline.

After analyzing that some time-consuming operations occur in the main thread, does that mean we can just move all time-consuming operations to the child thread and everything will be fine? Not at all!!

  • Stuttering cannot be solved solely by asynchronous means; incorrect use of engineering threads may not only fail to improve stuttering but may actually exacerbate it. Whether to start a working thread needs to be analyzed based on the specific performance bottlenecks and addressed accordingly; it cannot be generalized;

  • How to start a thread also has its own knowledge: Thread, ThreadPoolExecutor, AsyncTask, HandlerThread, IntentService, etc., each have their advantages and disadvantages; for example, under normal circumstances, ThreadPoolExecutor is more efficient than Thread, with obvious advantages, but in specific scenarios, the performance of a single Thread may be better than that of ThreadPoolExecutor; creating objects incurs a noticeable overhead for ThreadPoolExecutor compared to Thread;

  • Correctly starting a thread is not a panacea; for example, executing a network request will create a thread pool, and correctly creating a thread pool in the Application will inevitably reduce startup speed; thus, delaying operations is also essential.

Through detailed tracking of traceview and detailed comparison of code, I found that stuttering occurs in:

  • Some database and IO operations occur in the main thread of the first screen Activity;

  • A thread pool was created in the Application;

  • The first screen Activity has intensive network requests;

  • The working thread is not set with a priority;

  • Information is not cached, repeating the same information retrieval;

  • Process issues: for example, downloading the splash image every time, for that use;

And other detail issues:

  • Executing useless old code;

  • Executing code used during development;

  • Executing duplicate logic;

  • Calling unnecessary code from third-party SDKs or demos;

Project Modifications:1. Move database and IO operations to the working thread and set the thread priority to THREAD_PRIORITY_BACKGROUND, so that the working thread can obtain a maximum of 10% of the time slice, prioritizing the execution of the main thread.

2. Process sorting, delayed execution; in fact, this step is the most effective for project startup acceleration. By sorting the process, it was found that some timing of process calls was too early, causing resource competition, such as:

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

  • Called the switch made to avoid review in IOS, causing intensive network requests;

  • Self-statistics created a fixed number of 5 threads in the Application call, causing resource competition; in the traceview function description diagram above, the last line shows that number 12 executed 5 times, ranking high on the time-consuming list; this thread pool creation is necessary but can be delayed.

  • Modify the advertisement splash logic to take effect next time.

3. Other optimizations;

  • Remove useless but executed old code;

  • Remove code used during development but executed online;

  • Remove duplicate logic execution code;

  • Remove unnecessary code from third-party SDKs or demos;

  • Cache information, only retrieve commonly used information the first time, then retrieve from cache;

  • The project is a multi-process architecture, only execute Application’s onCreate() in the main process; through the above three steps and optimization of third-party components: during the callbacks of Application and the first screen Activity, there are no time-consuming operations or resource competition in the main thread. Additionally, it also involves layout optimization, memory optimization, etc., as these are generally not bottleneck points for application cold startup, they will not be elaborated on here, and can be handled based on actual project needs.

  • Optimizing Android Performance: Boosting Startup Speed by 35%

6. Comparison of Results:

By using ADB commands to count the application startup time: adb shell am start -W FirstScreenActivity. Under the same conditions using MX3 and Nexus6P, starting 5 times, comparing the startup time before and after optimization;

Before Optimization:MX3

ThisTime TotalTime WaitTime
1237 2205 2214
1280 2181 2189
1622 2508 2513
1485 2434 2443
1442 2418 2429

Nexus6P

ThisTime TotalTime WaitTime
1229 1832 1868
1268 1849 1880
1184 1780 1812
1262 1845 1876
1164 1766 1807

After Optimization:MX3

ThisTime TotalTime WaitTime
865 1516 1523
911 1565 1573
812 1406 1418
962 1564 1574
925 1566 1577

Nexus6P

ThisTime TotalTime WaitTime
603 1192 1243
614 1076 1115
650 1120 1163
642 1107 1139
624 1084 1124

Comparison:MX3 improved by 35%

Comparison ThisTime Average TotalTime Average WaitTime Average
Before Optimization 1413 2349 2357
After Optimization 895 1523 1533

Nexus6P improved by 39%

Comparison ThisTime Average TotalTime Average WaitTime Average
Before Optimization 1221 1814 1848
After Optimization 626 1115 1156
  • Meaning of commands:

ThisTime: Time taken to start the last Activity; TotalTime: Time taken to start all of its Activities; WaitTime: Total time taken by ActivityManagerService to start the Activity of the App (including the onPause() of the current Activity and the startup of its own Activity).

7. Questions:

1. Are there further optimization directions?

  • In the project, the Retrofit network request library is used, with FastConverterFactory as the JSON parser; TraceView shows that FastConverterFactory is also quite time-consuming during creation, consider replacing it with GsonConverterFactory. However, due to the inheritance relationship, it cannot be directly replaced in the short term, so it will be left as an optimization point for now;

  • Consider merging some interfaces during startup based on actual conditions to reduce the number of network requests and lower the frequency;

  • Keep only one component for the same functionality, such as: Umeng, GrowingIO, self-statistics, etc.;

  • Use ReDex: https://github.com/facebook/redex for optimization; experiments with Redex show that the APK size is indeed smaller, but the startup speed has not changed, perhaps further research is needed.

2. What are the bases for asynchronous, delayed initialization and operations? Note: Not every component’s initialization and operation can be asynchronous or delayed; whether it can depends on the calling relationships of the components and the specific needs of your project. Ensure a principle: make everything that can be asynchronous asynchronous, and delay as much as possible for those that cannot. Let the application start first, then operate.

3. What are the common strategies for application startup acceleration?

  • Use themes to quickly display interfaces;

  • Asynchronously initialize components;

  • Sort business logic, delay the initialization of components and operations;

  • Correctly use threads;

  • Remove useless code, duplicate logic, etc.

4. Others

  • Speeding up startup by 35% does not mean that the previous code was all problematic; from a business perspective, the code is not wrong and meets business requirements. However, during the speed-focused phase of startup, overlooked details can lead to performance bottlenecks.

  • During development, analyze core modules and application phases like startup using TraceView to identify bottlenecks as early as possible.

If you have good articles to share with everyone, feel free to submit the links directly to me.

Finally, everyone is welcome to join our knowledge circle, this period ends on March 10, 2019, so the earlier you join, the better; there are nearly 1000 people in the circle now, and the price will increase significantly when it reaches 1000 people (there are only a few spots left), so get on board soon!

Optimizing Android Performance: Boosting Startup Speed by 35%

Scan the QR code or click the above QR code to receive advanced resources for Android, Python, AI, Java, etc.

For more study materials, click on the “Read the original text” below to obtain

Optimizing Android Performance: Boosting Startup Speed by 35%

Leave a Comment

Your email address will not be published. Required fields are marked *