[From entry to practical] Android startup optimization in-depth analysis

[From entry to practical] Android startup optimization in-depth analysis

Preface

There are many performance optimization schemes, but not many can be applied to the project. When I
learned about layout optimization some time ago, I summarized many layout optimization schemes, but in the end they couldn t be applied to the project: [From entry to abandonment] android Layout optimization in-depth analysis
Today, as always, do not do the title party, I will introduce some practical startup optimization solutions to everyone~

for

android APP
In other words, start-up time is the user's first experience. If the start-up time is too long, users are likely to be lost. Therefore, startup optimization is also an important direction for us to optimize performance.
This article mainly includes the following
1. What are the optimization directions for startup optimization?
2. How to accurately measure the startup time?
3. What are the practical optimization methods?

What are the optimization directions for startup optimization?

The application has three startup states, each of which affects the time required for the application to be displayed to the user: cold startup, warm startup, or hot startup. In a cold start, the application starts from the beginning. In the other two states, the system needs to bring applications running in the background to the foreground.
The start optimization mentioned in this article refers to cold start optimization

To optimize applications for fast startup, it is helpful to understand the system and application levels and how they interact in each state.

Cold start

Cold start means that the application starts from the beginning: the system process creates the application process after the cold start.
A cold start occurs when the application is started for the first time since the device is started or the system terminates the application.
This kind of startup brings the biggest challenge to minimizing the startup time, because the system and applications have more work to do than in the other two startup states.

At the beginning of the cold start, the system has three tasks, they are:
1. Load and start the application.
2. The blank startup window of the application is displayed immediately after startup.
3. Create an application process.

Once the system creates an application process, the application process is responsible for the subsequent stages:
1. Create an application object.
2. Start the main thread.
3. Create the master

Activity
.
4. Fill the view.
5. Layout the screen.
6. Perform initial drawing.

Once the application process finishes drawing for the first time, the system process will replace the currently displayed background window and replace it with the main

Activity
. At this point, the user can start using the application.

As shown above,

Application
versus
Activity
The life cycle of is our optimization direction,
generally
Application onCreate
Method and the first
Activity
Time-consuming to load

How to measure startup time?

The easiest way

By viewing

logcat
You can quickly understand the startup time
in
Android Studio Logcat
Filter keywords in
Displayed
, You can see the corresponding cold start time-consuming log.

Command measurement

for i in `seq 1 10` do adb shell am force-stop com.xx.xx sleep 2 adb shell am start-activity -W -n package name/activity name | grep "TotalTime" | cut -d '' -f 2 done Copy code

Sometimes we need to count the cold start performance of the app, and the single result is often inaccurate, and it needs to be counted several times and then averaged.
As above, use the script to start the homepage 10 times

Activity
, Can get the cold start performance more accurately

The command measurement method is easy to use offline and can test competing products,
but it cannot be brought online and the measurement time cannot be accurately controlled.
So we usually need to bury the points manually

Buried point measurement

The key point of buried point measurement is the appropriate start and end time.
We generally use

Application attachBaseContext
As the start time
, there are many options for the start end time

IdleHandler

IdleHandler
in
MessageQueue
It will call back when it is idle, that is, when the task of the thread has been executed, it will be executed when the thread is idle
Idle
Tasks in the list.
Under normal circumstances, when the main thread process is in an idle state, it can be considered that the cold start has been completed, which is a relatively good timing.
But there is a problem, if
UI
The task of the thread has not been completed? If there are other tasks added
MessageQueue
But the page is already visible?
IdleHandler
With certain uncontrollable characteristics, readers can judge whether to use according to the characteristics of the project

onWindowFocusChanged

when

Activity
Callback
onWindowFocusChanged
When we can think
Activity
It s already visible, so you can take care of it at this time,
but
onWindowFocusChanged
The method is just
Activity
Is the first frame time of
Activity
There is still some time difference between the time of the first drawing, the time of the first frame and the complete display of the interface, which does not really mean that the interface has been displayed.

but

onWindowFocusChanged
Mode and business coupling is less, low intrusiveness, and more convenient to use.
In our project, the callback time and the interface display time have very little difference. It can be used as an optional solution according to the actual situation.

onPrewDrawListener

As mentioned above, the correct time to calculate the start-up time is to wait for the actual data to be displayed, for example, when the first item of the list is displayed, the start-up time is calculated.
We can add to the first item of the list

onPreDrawListener
Monitoring, this method is more accurate.
However , this method is strongly related to the business code and is more intrusive. Readers can also use according to the actual situation

AOP measurement method is time consuming

our

Application
A lot of third-party libraries are initialized in, sometimes we need to count the time-consuming of each method to determine which method is more time-consuming, if it is very troublesome to add one by one,
this can be used.
AOP
Aspect-Oriented Programming
If you want to learn more, please refer to the time-consuming method of using AOP measurement

Use of TraceView and SystraceView

TraceView

TraceView
Can track
App
All the methods that have been called in a certain period of time. This is one of the common ways to measure the execution performance of the application.
Through it we can find out
App
The specific method was called at startup, and how long did it take.
This function is
Android
Provided by the system, we can manually call it in the code
android.os.Debug.startMethodTracing()
with
stopMethodTracing()
Method to start and end
Tracing
, And the system will put
Tracing
Save the result to the phone
.trace
File.

In addition, in addition to writing code to

Trace
, We also have a more convenient way. For example, you can also pass
Android Studio Profiler
inner
Method Tracer
Come
Trace
. However, for
App
Cold start, we usually use
Android
System comes with
Am
Command to track, because it can accurately
App
Start when it starts
Trace
:

# Start the specified Activity, and sample tracking at the same time, -P end profiler app when entering the idle state adb shell am start -n com.xxx.android/com.xxx.android.app.ui.activity.MainActivity -P/data/local/tmp/xxx-startup.trace --sampling 1000 # Pull .trace current directory files to the machine adb pull/data/local/tmp/xx-startup.trace. Copy code

As shown above: -P parameter means

app
enter
idle
Will automatically end when the state, eliminating the need to manually RBI
after the start, by
adb pull
Pull
trace
After the file, drag it directly to
android studio
Open in the middle to find time-consuming methods

More about

TraceView
For examples of positioning optimization problems, readers can refer to:
TraceView uses Zhihu
Android client to start optimization-Retrofit Agent

Systrace

TraceView
Although it is a great tool to find time-consuming methods, the implementation
TraceView
There will be a huge gap between the operating environment at the time and the environment where the user ultimately runs, because
TraceView
Will seriously slow down
App
The speed of execution.
Even if sampling and tracking are used, the measured results and actual results must still have a large deviation, which can only be used as a reference.
and
TraceView
It is more inclined to trace the internal factors of the application, and for external factors such as the operating environment (locks,
GC
, Lack of resources, etc.) seem to be very weak.
So we can use another
Google
Officially recommended tool-"
Systrace
'' to track the actual running of the App

run

app
After that, kill it manually. then
cd
To
SDK
Under the directory
platform-tools/systrace
Next, use the command:

python systrace.py -t 10 -o/Users/xxx/trace.html -a com.xx.xxx duplicated code

among them:

-t
10 means tracking for 10 seconds,
-o
Indicates that the file is output to the specified directory,
-a
Is the name of the specified application package.
After entering this command, you can see the prompt to start tracking. see
Starting tracing
After that, manually open our application.

Wait until the end of the run to turn on the output

trace.html

In addition to the above, we can also pass

TraceCompact.beginSection
To specify the time period of interest.
More about
Systrace
For examples of use, readers can refer to:
systrace use
knowing Android client startup optimization-Retrofit agent-Systrace

summary

1.

TraceView
Can be used to locate specific time-consuming methods
2.
TraceView
The runtime overhead is serious, resulting in slower overall performance, which may be biased in the optimization direction
3.
Systrace
The cost is small and can be reflected intuitively
Cpu
Utilization rate, easy to find external factors such as operating environment (lock,
GC
), etc.
4.
TraceView
versus
Systrace
You can bury points and specify the area of interest

Conventional optimization methods

1.Theme switch

Theme
Settings can be said to be an essential means to
start optimization. Start
Activity
of
windowBackground
The theme attribute presets a startup picture (
layer-list
Realized), after startup, in
Activity
of
onCreate()
In the method
super.onCreate()
Before again
setTheme(R.style.AppTheme)
.

Advantages
1. Simple to use.
2. Avoid starting the white screen and clicking the start icon does not respond.

Disadvantages
Treat the symptoms but not the root cause, but on the surface it produces a sense of quickness.

2. Asynchronous solution

We usually

Application
of
onCreate
Initialize many tasks in the middle, such as the initialization of third-party libraries, and it is serial.
The time-consuming initialization tasks are usually not small, so an optimization idea is parallel initialization.
This changes the initialization time from addition to seeking the maximum value.

Core idea: sub-threads share the tasks of the main thread, reducing time in parallel

Problems with conventional asynchronous solutions

1. The code is not elegant enough.
If we have 100 initialization tasks, then we need to submit 100 tasks.

2. Cannot be restricted to

onCreate
Completed
some initialization tasks required third-party libraries in
Application
of
onCreate
The execution is completed in the method, although it can be used
CountDownLatch
Waiting is realized, but it is still a bit cumbersome.

3. Unable to realize the existence of dependencies.
Some initialization tasks have dependencies, for example, Jiguang Push requires equipment

ID
,and
initDeviceId()
This method is also an initialization task.

Asynchronous starter scheme

The above introduces several problems of conventional asynchronous solutions. We can solve them through the
launcher. The core idea of the launcher is to make full use of multi-core

CPU
, Automatically sort out the order of tasks.

1. The first step is that we want to taskize the code. Taskization is an abbreviation, such as abstracting the startup logic into a task.

2. The second step is to generate a directed acyclic graph based on the sorting of dependencies of all tasks. This graph is automatically generated, that is, all tasks are sorted.
For example, we have a task

A
And task
B
,task
B
Need tasks before execution
A
After execution, in order to get specific data, such as the above mentioned
initDeviceId
.

3. The third step is to execute multiple threads in sequence according to the sorted priority. For example, we now have three tasks

A
,
B
,
C
. If the task
B
Task dependent
A
, The directed acyclic graph generated at this time is
ACB
,
A
with
C
Can be executed in advance,
B
Must be ranked
A
Execute afterwards.

The general process of the launcher is shown above. We will introduce several open source launcher solutions for readers' reference.

JetPack App Startup

1.

App Startup
This library provides a component that can be initialized when the application starts.
2. Developers can use this component to streamline the startup sequence and explicitly set the order of initialization.
3. We do not need to define a separate for each component
ContentProvider
,
App Startup
Allows all components you define to share a content provider.

This can greatly reduce the startup time of high applications, but

App Startup
Just support multiple
ContentProvider
Merge into one
ContentProvider
And specify a certain dependency order.
Its purpose is to manage the use of third-party libraries.
ContentProvider
Too much, causing the problem of slow startup speed. It
does not support asynchronous and asynchronous task management, so it does not meet our requirements.

Ali-alpha

Alpha
Is based on
PERT
Graph construction
Android
Asynchronous start framework, it is simple, efficient and fully functional. When the application starts, we usually have a lot of work to do. In order to improve the startup speed, we will try our best to make these work concurrently. However, there may be a dependency between these tasks, so we need to find a way to ensure the correctness of their execution order.
Alpha
It s designed for this, users only need to define their own
task
And describe what it depends on
task
, Add it to
Project
in. The framework will automatically execute these concurrently and orderly
task
, And throw out the results of the execution. due to
Android
The application supports multiple processes, so
Alpha
Supports configuring different startup modes for different processes.

alpha
It has basically satisfied our use, but it does not support whether the task needs to be waited. At the same time, its code is relatively old and it feels that it has not been maintained for a long time, so I finally decided to use
AnchorTask
frame

AnchorTask

AnchorTask is similar to Alpha
1. Supports concurrent execution of multiple tasks
2. Supports inter-task dependency and topological sorting
3. Supports task monitoring and time-consuming statistics
4. Supports designated task priority
5. Supports specifying whether to run in the main thread and whether to wait

The main point is

AnchorTask
The document is relatively powerful, from data structure to topological sorting, to design to detailed explanation, there are a series of articles, which is why I finally decided to use it
AnchorTask by programmer Xu Gong

Simple use is as follows, flexible configuration tasks and dependencies can be invoked through chain:

val project = AnchorProject.Builder().setContext(context).setLogLevel(LogUtils.LogLevel.DEBUG) .setAnchorTaskCreator(ApplicationAnchorTaskCreator()) .addTask(TASK_NAME_ZERO) .addTask(TASK_NAME_ONE) .addTask(TASK_NAME_TWO) .addTask(TASK_NAME_THREE).afterTask( TASK_NAME_ZERO, TASK_NAME_ONE ) .addTask(TASK_NAME_FOUR).afterTask( TASK_NAME_ONE, TASK_NAME_TWO ) .addTask(TASK_NAME_FIVE).afterTask( TASK_NAME_THREE, TASK_NAME_FOUR ) .setThreadPoolExecutor(TaskExecutorManager.instance.cpuThreadPoolExecutor) .build() project.start().await() Copy code

Delayed initialization scheme

Conventional plan

Some tasks we need to delay loading, the conventional method is through

Handler.postDelayed
The method sends a delayed message, such as execution after a delay of 100 milliseconds.

Problems with conventional solutions

This method has the following problems:
1. The timing is inconvenient to control, and a suitable delay time cannot be determined
. 2. The code is not elegant enough and the maintenance cost is high. If there are multiple tasks, it needs to be added multiple times
. 3. It may cause the main thread to freeze. If the task is executed after 200 milliseconds is delayed, and the user is still sliding the list after 200, it will still be stuck.

Better solution

Core idea: Initialize and utilize delayed tasks in batches

IdleHandler
The feature to execute when the current message queue is idle, to implement a delayed starter
IdleHandler
In return
true
Will continue to monitor and return
false
End the monitoring,
so return after the task is all completed
false
That can be achieved as follows:

public class DelayInitDispatcher { private Queue<Task> mDelayTasks = new LinkedList<>(); private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() { @Override public boolean queueIdle () { 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 () { Looper.myQueue().addIdleHandler(mIdleHandler); } } //Call DelayInitDispatcher delayInitDispatcher = new DelayInitDispatcher(); delayInitDispatcher.addTask( new DelayInitTaskA()) .addTask( new DelayInitTaskB()) .start(); Copy code

Extremely lazy loading and early loading

Home page extremely lazy loading

Our homepage usually has multiple

tab
, And when we start, we only need to initialize one
tab
Can
we often use
ViewPager
To implement simple lazy loading, such as only when
Fragment
Go online when it is visible

This has a certain effect, but

View
of
inflate
,
measure
,
layout
It also takes some time. The
more extreme lazy loading scheme is as follows:
1. When the first screen loads, only go to
ViewPager
Insert the default display
tab
, The remaining
tab
Empty placeholder
Fragment
Instead of
2.
Fragment
There is only one blank
FrameLayout

3. When occupying
Fragment
When it s visible, what will actually be displayed
Fragment
Add to blank
FrameLayout
, Perform the real initialization

With this scheme, it can be done at startup, only

inflate
,
measure
,
layout
Home page
Fragment
of
View
,other
Tab
It will only be filled
if it is visible. If your
Layout
If it is more complicated, the startup performance can be greatly improved in this way

Layout preload

The official provides a class that can be used for asynchronous

inflate
, But there are two shortcomings:
1. Must be on site every time
new
One comes out
2. Asynchronous loading
view
Can only pass
callback
Callback can be obtained, inconvenient to use (dead point)
3. If in
Activity
Initialized in
callback
When calling back, it did not reduce the loading time, still need to wait

Due to the above problems, one way of thinking is, can it be in the child thread in advance

inflate
Layout and then
Activity
Pass
id
Take it out. The
core idea is as follows:
1. In the sub-thread during initialization
inflate
Layout, stored in the cache
2.
Activity
When initializing, first get it from the cached result
View
,got it
view
Go directly back
3. Didn't get it
view
, But the child thread is
inflate
In, wait to return
4. If it has not started
inflate
,by
UI
Threading
inflate

The advantages of this scheme:
can greatly reduce

View
The time of creation, after using this scheme, get
View
It is basically within 10ms.

Disadvantage
1. Due to

View
Is created in advance and will exist in a
map
, You need to change
View
From
map
Remove it, otherwise memory leak will occur
2.
View
If cached, remember to reset it when appropriate
view
Status, otherwise weird phenomena sometimes occur.

Generally speaking, the advantages and disadvantages are obvious. Readers can follow the actual situation (mainly in the project).

inflate
Does it take a long time? Is the benefit obvious after switching to pre-loading? ), Decide whether to use it according to the actual situation. For
specific implementation, please refer to: Magical pre-loading (pre-loading View, not data)

summary

This article mainly summarizes the direction of startup optimization and the method of accurately measuring startup time. It
focuses on several practical startup optimization solutions:
1. Asynchronous starter speeds up initialization
2. Lazy loader reduces stalls and makes code more elegant
3. The home page is extremely lazy to load, reducing the home page

inflate
,
measure
,
layout
time
4. The layout pre-loading scheme is greatly reduced
View
Creation time, readers can use according to actual situation

These programs are relatively practical, readers can try to apply in the project to see how much improvement ~

Reference

Application startup time
Several quick ways to obtain android interface performance data
[Performance optimization] Android cold start optimization
knows Android client startup optimization-Retrofit agent
explores Android startup optimization methods In-
depth exploration of Android startup speed optimization (on)