preface
Performance optimization scheme is not little, but can be born not in the application to the project Recently study layout optimization, summarizes many layout optimization scheme, but in the end, but not in the project application: 【 from entry to give up 】 deep parsing android layout optimization Today, as always, don’t do title party, to introduce some practical to start the optimization scheme
For Android APP, the startup time is the first experience of users. If the startup time is too long, users may lose. Therefore, startup optimization is also an important direction of performance optimization. What are the optimization directions for startup optimization? 2. How to accurately measure startup time? 3. What are the practical optimization methods?
What are the optimization directions for startup optimization?
An application has three startup states, each of which affects how long it takes to display the application to the user: cold startup, warm startup, or hot startup. In cold startup, the application starts from scratch. In the other two states, the system needs to bring applications running in the background to the foreground. The startup optimization mentioned in this paper refers to cold startup optimization
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.
Cold start
In cold startup, an application starts from the beginning: The system processes create application processes after cold startup. Cold startup occurs when the application is started for the first time after the device is started or the application is terminated. This startup presents the biggest challenge in minimizing startup time, because the system and application have more work to do than in the other two startup states.
At the beginning of cold boot, the system has three tasks, they are: 1. Load and start the application. 2. A blank startup window is displayed immediately after the application starts. 3. Create an application process.
Once an application process is created, the application process is responsible for the following phases: 1. Create an application object. 2. Start the main thread. 3. Create the main Activity. 4. Populate the view. 5. Layout the screen. 6. Perform initial drawing.
Once the application process has finished drawing for the first time, the system process replaces the currently displayed background window with the main Activity. At this point, the user can start using the application.
As shown above,Application
withActivity
Life cycle is our optimization direction
As a general rule, beApplication onCreate
Method and firstActivity
Loading time
How to measure startup time?
The easiest way
You can view logcat to quickly learn about the startup time. If the Displayed keyword is Displayed in Android Studio Logcat, you can view logs about the cold startup time.
The command measure
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 doneCopy the code
Sometimes, we need to calculate the cold start performance of an app. The single result is often inaccurate, and we need to calculate the average value after several times of statistics. As shown above, starting the home page Activity for 10 times with a script can accurately obtain the cold start performance
Command measurement method is easy to use offline, can test competing products but cannot be brought online, can not accurately control the measurement time, so we usually need to manually buried point
Buried point measurement
The key point of buried measurement is the appropriate start and end times. We usually use Application attachBaseContext as the start time and there are many options for the start and end time
IdleHandler
IdleHandler will call back when the MessageQueue is Idle. That is, the task in the Idle thread will be executed only when the task in the Idle thread has been executed. Normally, when the main thread is idle, you can say that the cold start is done, which is a good time to do it but there’s a problem, what if the UI thread never completes its task? What if other tasks have been added to MessageQueue but the page is already visible? The IdleHandler has certain uncontrollable features. You can determine whether to use the IdleHandler based on the project features
onWindowFocusChanged
When the Activity callback onWindowFocusChanged, we can assume that the Activity is already visible, so we can click at this point. However, the onWindowFocusChanged method is only the first frame of the Activity. Is the time when the Activity is first drawn. There is still a time lag between the first frame and the complete display of the interface, which does not truly represent that the interface has been displayed.
However, onWindowFocusChanged is less coupled with business and less intrusive, so it is more convenient to use. In our project, there is little difference between the callback time and the interface display time, so it can be used as an optional scheme according to the actual situation
onPrewDrawListener
As mentioned above, the correct time to calculate startup time is to wait for actual data to be presented, such as the first item in the list. We can add an onPreDrawListener to the first item in the list, which is more accurate but more intrusive because it is highly relevant to the business code. Readers can also use according to the actual situation
AOP measurement methods are time consuming
We initialize many third-party libraries in our Application, and sometimes we need to count the time of each method and determine which method is time-consuming. AOP can be used for aspect programming if adding one method at a time is too cumbersome. For more details, see Measuring Method Time with AOP
TraceView is used with SystraceView
TraceView
TraceView can track all methods called by an App over a period of time, which is one of the most common ways to measure application performance. We can use it to find out how long it takes to call methods when the App starts. This functionality is provided by the Android system, we can manually in the code called Android. OS. Debug. StartMethodTracing () and stopMethodTracing () method to start and end Tracing, The system will then save the Tracing results to the.trace file on the phone.
Also, there are more convenient ways to Trace it than just writing code. For example, you can also Trace using Method Tracer in the Android Studio Profiler. However, for the cold startup of the App, we usually use the Am command of the Android system to track it, because it can Trace exactly when the App starts:
#Start the specified Activity and sample trace at the same time. -p profiler ends when the app enters 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 the.trace file to the current directory on this machine
adb pull /data/local/tmp/xx-startup.trace .
Copy the code
The command output is as follows: -p indicates inapp
Enter theidle
The status will automatically end, so there is no need to manually type
At the end of the startup, passadb pull
pulltrace
After the file, drag it directly toandroid studio
Open to find time-consuming methods
For more examples of TraceView localization boot optimization problems, the reader can see: TraceView Uses Zhihu Android client boot optimization – Retrofit agent
Systrace
Although TraceView is a powerful tool for finding time-consuming methods, there is a huge difference between the environment in which TraceView is executed and the environment in which the user ends up running, because TraceView will seriously slow down the execution speed of the App. Even with the use of sampling tracking, the measured results and the actual results must be a large deviation, only as a reference. In addition, TraceView tends to trace the internal cause of the application, but is unable to trace the external cause (lock, GC, resource shortage, etc.) of the operating environment. So, we can use another tool that Is highly recommended by Google officials – “Systrace” to track the actual running of the App
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/xxx/trace.html -a com.xx.xxx
Copy the code
-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.
Wait until the end of the run to open the outputtrace.html
In addition to the above, we can also through TraceCompact beginSection used to specify the time period of concern more about Systrace instance, readers can refer to: Systrace start optimization using Zhihu Android client – Retrofit agent -Systrace
summary
1.TraceView can be used to locate time-consuming methods. 2. 4.TraceView and Systrace can be embedded to specify the area of interest
Conventional optimization method
1. Switch the Theme
Start the Activity’s windowBackground Theme property with a preconfigured launch image (layer-list implementation). SetTheme (r.style.appTheme) before super.oncreate () in the Activity’s onCreate() method.
Advantages 1. Simple to use. 2. Avoid blank screen and no response when you click the startup icon.
The defect treats the symptoms rather than the root cause, producing a kind of fast feeling on the surface.
2. Asynchronous solution
We usually initialize a lot of tasks in the onCreate of the Application, such as third-party library initializations, which are serial and usually take a lot of time, so one optimization idea is to do it in parallel so that the initialization time is changed from adding to maximizing
The main idea: sub-threads share tasks with the main thread and reduce time in parallel
Problems with conventional asynchronous schemes
1. Code is not elegant. If we have 100 initialization tasks, we need to commit 100 tasks.
Some third-party library initialization tasks need to be performed in the onCreate method of the Application. Although CountDownLatch can be used to wait, it is still a bit tedious.
Some initialization tasks have dependencies. For example, aurora push requires a device ID, and initDeviceId() is also an initialization task.
Asynchronous initiator solution
The core idea of the initiator is to make full use of the multi-core CPU and automatically comb the task order.
1. The first step is to task-ify the code, which is an acronym for abstracting startup logic into a task.
2. The second step is to generate a directed acyclic graph based on the ordering of all tasks. This graph is automatically generated, that is, to sort all tasks. Let’s say we have task A and task B, and task B needs task A to complete before it can execute, so that we can get specific data, such as initDeviceId mentioned above.
3. The third step is multi-threading according to the sorted priorities, for example, we now have three tasks A, B and C. If task B depends on task A, then the generated directed acyclic graph is ACB. A and C can be executed in advance, and B must be executed after A.
The general process of an initiator is shown above. Here are several open source initiator solutions for readers’ reference
JetPack App Startup
The library provides a component that can be initialized at application Startup time. 2. Developers can use this component to simplify the startup sequence and explicitly set the initialization order. 3. Instead of defining a separate ContentProvider for each component,App Startup allows all componentization you define to share a single ContentProvider.
This can greatly reduce the Startup time of a high application, but App Startup simply supports combining multiple ContentProviders into a single ContentProvider and specifying the order in which it is launched. The problem of slow startup caused by excessive use of ContentProvider by third-party libraries does not support asynchronous and asynchronous task management, so it does not meet our requirements
Ali alpha
Alpha is an Android asynchronous startup framework based on PERT diagrams. It is simple, efficient, and fully functional. We usually have a lot of work to do when an application starts up, and we try to make it as concurrent as possible to speed it up. However, these tasks may be dependent on each other, so we need to find ways to ensure that they are executed in the correct order. Alpha is designed for this purpose, so users can define their own tasks, describe the tasks they depend on, and add them to a Project. The framework automatically executes these tasks concurrently and sequentially and throws out the results of the execution. Since Android applications support multiple processes, Alpha supports different startup modes for different processes.
Alpha is basically enough for our use, but it doesn’t support whether tasks need to wait or not, and its code is old and feels like it hasn’t been maintained for a long time, so we finally decided to use AnchorTask framework
AnchorTask
AnchorTask is similar to Alpha 1. Supports concurrent execution of multiple tasks 2. Inter-task dependency and topology sorting 3. Task listening and time consumption statistics 4. Task priority 5. Support to specify whether to run on the main thread and whether to wait
The main point is that AnchorTask documentation is quite powerful, with a series of articles ranging from data structures to topological sorting, to design to detail, which is why I finally decided to use it
Simple use of the following, through the chain call flexible configuration tasks and dependencies:
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 the code
Delayed initialization scheme
Conventional scheme
For some tasks we need to load lazily, the general approach is to send a delayed message via the handler. postDelayed method, such as 100 ms later.
Problems with conventional programs
This method has the following problems: 1. It is difficult to control the timing and cannot determine a suitable delay time 2. The code is not elegant, it is expensive to maintain, and you need to add 3 multiple times if you have multiple tasks. This can cause the main thread to stall. If the task is delayed for 200 milliseconds and the user is still swiping the list, it will still stall.
Better solution
IdleHandler will continue to listen when it returns true, and return false to end the listening. Therefore, it can return false after all tasks are completed, which is implemented as follows:
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); }}/ / call
DelayInitDispatcher delayInitDispatcher = new DelayInitDispatcher();
delayInitDispatcher.addTask(new DelayInitTaskA())
.addTask(new DelayInitTaskB())
.start();
Copy the code
Extreme lazy loading with preloading
Home page extreme lazy loading
Our home page usually has multiple tabs, and when we launch, we only need to initialize one TAB. We often use ViewPager for simple lazy loading, such as asking for network instructions only when the Fragment is visible
This has some effect, but the View’s inflate,measure, and layout also needs some time. A more extreme lazy loading scheme is as follows: 1. When the first screen is loaded, only the default TAB is inserted into the ViewPager, and the remaining TAB is replaced with an empty Fragment. There is only a blank FrameLayout 3 in the Fragment. When a placeholder Fragment is visible, the actual Fragment to be displayed is added to the blank FrameLayout for actual initialization
With this solution, you can only inflate,measure, and layout the View of the main Fragment during startup, and the other tabs will be filled only when they are visible. If your layout is complicated, this method can greatly improve the startup performance
Layout preloading
Officially, a class is provided that can be synchronized synchronized, but has two drawbacks: 1. A view that is loaded asynchronously can only be retrieved by callback. If the Activity is initialized, the callback does not reduce the load time and still needs to wait
Because of the above, one way to think about this is whether you can pre-inflate the child thread constructors and then pull the ID out of the Activity with the core idea as follows: 1. The View is inflate in the child thread when initialized, and is stored in the cache. 2. When the Activity initializes, it takes the View from the cache and returns 3. No view, but the child thread is in the inflate, waiting to return 4. The UI thread inflate if it has not already started
The advantage of this scheme: can greatly reduce the View creation time, after using this scheme, the View is basically within 10ms.
Disadvantages 1. Since the View is created in advance and exists in a map, you need to remove the View from the map according to your own business scenarios, otherwise memory leaks will occur. 2. Otherwise, sometimes strange things happen.
In general, the advantages and disadvantages of this inflate are obvious. Readers can depend on the actual situation (mainly whether the time of the inflate in the project is long or not, and whether the benefits are obvious after the premature loading?). See the magic of preloading (Preloading Views instead of data)
conclusion
This paper mainly summarizes the direction of startup optimization and the way of accurate measurement of startup time, focusing on several practical startup optimization schemes: 1. Asynchronous initiator to speed up the initialization speed 2. Lazy loader to reduce the lag, more elegant code 3. Ultra-lazy loading of the homepage reduces the time of the homepage inflate,measure, and layout by 4. Layout preloading solution greatly reduces View creation time, readers can use according to the actual situation
These solutions are practical and can be applied in projects to see how much improvement there is
The resources
Application startup time Several quick methods to obtain android interface performance data 【 Performance optimization 】Android cold startup optimization Zhihuandroid client startup optimization – Retrofit agent explore android startup optimization methods Android Startup Speed Optimization (Part 1)