Optimization idea

Optimization criteria

The process of optimization should be based on the user experience, from clicking the icon to the user can actually operate the whole process.

The business card

The first step is to sort out every module currently running in the startup process, which are definitely needed, which can be cut, and which can be lazy to load. Making sure that every feature and business loaded during startup is required can make a big difference on low – and mid-range machines.

Business optimization

After the business is sorted out, the rest are the modules that must be used to start the process. At this time, we can only do further optimization. In the early stage of optimization, we need to “catch the big and release the small”, and see where the main thread is time-consuming.

For example, consider whether these time-consuming tasks can be achieved by asynchronous thread preloading, but it is important to note that too much thread preloading will make our logic more complicated. It is recommended to consider the maintenance cost of modification before deciding whether to use this method. In addition, lazy loading to prevent centralization, otherwise easy to appear on the home page but users can not operate the situation.

Application startup process analysis

Three states of initiation

There are three startup states: 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.

It is best to optimize on the basis of cold start, so as to improve the performance of both warm start and hot start.

Cold start

To start a cold startup, the system first does three tasks:

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

The whole cold start flow chart is as follows:

Warm start

Hot startup of applications is much simpler and less expensive than cold startup.

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 perform object initialization, layout filling, and rendering repeatedly.

Wen started

A warm start contains some of the operations that occur during a cold start and is more expensive than a hot start. The following conditions can be regarded as warm start:

  • The user exits the application and then restarts it. The process may have continued running, but the application must pass the callonCreate()Recreate from scratchActivity. The application just rewalksActivityWithout retracing the process creation,ApplicationCreation and life cycle, etc.
  • The system ejects your application from memory, and then the user restarts it. The process andActivityNeed to restart, but pass toonCreate()state bundleThe instance is saved.

Common problems with the startup process

1. No response after clicking the app icon for a long time

Before pulling up the application process, the system creates a preview window based on the Theme property of the application. This takes some time, especially on low – and mid-range machines.

Solution: You can disable the preview window or specify the preview window as transparent, but the user will still see the desktop during this time, to the user’s feeling will be how no reaction, is not clicking the icon? The experience wasn’t great.

2. The home page of your app is too slow

As the business of an application becomes more complex, with all kinds of frames and splash screen ads, all of which have to be done during the app launch phase, the front page can take a long time to show up.

3. The home page is displayed and cannot be operated

An obvious solution to problem 2 is to try to asynchronously execute the initialization tasks during the startup phase.

It is important to note that resources that need to be ready for normal use if handled asynchronously are likely to cause a white screen on the home page, or can not operate after the home page appears.

The tools that do a good job must be sharpened first

Log and ADB

1. View logs

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.

Because it is the system server that provides this log, not the application itself, remember to check to turn off the filter

You can see logs like this:

I/ActivityManager: Displayed com.xxx.xxx/.activity.xxxActivity: +1s539ms
Copy the code

2. The adb command line

adb shell am start -S -W  com.xxx.xxx/com.xxx.xxx.activity.xxxActivity
Copy the code

-s: Forcibly stops the target application before starting the Activity

-w: Waits until the startup is complete

After successful startup, you can see the startup time data: generally only care about TotalTime, which is the actual startup time of your application.

Status: ok
Activity: com.xxx.xxx/com.xxx.xxx.activity.xxxActivity
ThisTime: 1539  
TotalTime: 1539 
WaitTime: 1621  
Complete
Copy the code

ThisTime: Indicates how long it takes to start the last Activity in a series of activities

TotalTime: Indicates the start time of a new application, including the start of a new process and the start of an Activity, but excluding the start time of the previous application Activity Pause

WaitTime: Total time, including the previous application Activity Pause time and the new application start time

If you’re interested in how this command came to be three times, look at Groffa’s answer:

How to calculate the startup time of APK?

A suitable startup optimization analysis tool

Common startup analysis tools are Traceview and Systrace:

The performance loss of Traceview is too high and the results obtained are not real.

Systrace makes it easy to track the elapsed time of key system calls, but does not support elapsed time analysis of application code.

TraceView tries to gather information about the performance of all functions at a given stage. It wants to directly locate key functions when you don’t know which one is faulty. Unfortunately, gathering all this information is not practical, and its runtime overhead interferes with the runtime environment.

Systrace works the other way around, taking some basic information and letting developers figure out the cause of the problem step by step through a hypothesis-analysis-verification process.

Systrace, combined with functional piling, is an excellent way.

Systrace + function peg. After staking functions requiring statistical time consumption, systrace can generate HTML reports for analysis.

Function peg is very simple, with the help of the system’s own Trace class, the specific use of the following details ~

Optimization process analysis

Systrace

Systrace is a new performance data sampling and analysis tool for Android4.1. It can help developers to collect Android key subsystems such as SurfaceFlinger/SystemServer/Kernel/Input/Display Framework and some key module, a service, the View system, etc.) the operation of the information, To help developers more intuitive analysis of system bottlenecks, improve performance.

Systrace Script file: XXX /Android/ SDK /platform-tools/systrace

To generate an HTML report, execute the following command:

python xxx/Android/sdk/platform-tools/systrace/systrace.py -o mynewtrace.html sched ss dalvik am
Copy the code

Through command parameters, you can view common data and analyze the startup process. Dalvik, Sched, SS and AM are the types we are concerned about:

  • Sched: CPU Scheduling, which shows what threads the CPU is running at each time period, and thread Scheduling, such as lock information.
  • Ss: the System Server
  • Dalvik: Dalvik VM, virtual machine-related information, such as GC pauses
  • Am: Activity Manager, it is useful to analyze the startup process of an Activity

The above types may not be supported on some models. Adb connects to the test machine and uses the following command to view the types supported by Systrace.

python systrace.py --list-categories
Copy the code

The systrace command is used

Perfetto analyzes the report

You can view the report directly by opening the HTML file in your browser

On a MAC, The HTML generated above is blank when Chrome opens it directly.

Solution: Type Chrome: Tracing in the Chrome address bar and click the Load button to select your trace.html file.

However, the Perfetto tool is more recommended:

Perfetto is a new platform-level tracing tool introduced in Android 10. This is a more general and complex open source tracking project for Android, Linux, and Chrome. Unlike Systrace, it provides a superset of data sources that allows you to record trace records of any length in a protobuf-encoded binary stream.

Our device may be below Android10 and cannot use the new tracking method provided by Perfetto. But it also supports opening trace files generated through Systrace, making its UI interaction more comfortable to use.

Trace tracking

Through the Trace class, you can Trace the method to the call process, with systrace view analysis, very useful, such as tracking the onResume method

protected void onResume() { super.onResume(); Trace.beginSection("START_TEST"); // Your operation... Trace.endSection(); Log.i(TAG, "[onResume]"); }Copy the code

Note: if you want to againsystraceTo see the trace phase, add it to the previous command line-a [Package name]

Python XXX/Android/SDK/platform - the tools/systrace/systrace. Py - o mynewtrace. HTML sched ss dalvik am - a "package name"Copy the code

Open the mynewtrace.html file in Perfetto, find the process that generated the package name, and you can see that the Trace Trace we added appears (blue entry) :

chestnut

Run a demo program and exit the application. Execute command:

Python XXX/Android/SDK/platform - the tools/systrace/systrace. Py - o mynewtrace. HTML sched ss dalvik am - a "package name"Copy the code

Use Perfetto to open the file and find the application process, which contains multiple stages of application startup:

If you select one of these phases, you can see a lot of time statistics. There are two important time indicators:

  • Wall Duration: code Duration, i.e. the length of time this code takes
  • CPU Duration: The actual time this code takes on the CPU

If the time difference between Wall Duration and CPU Duration is significant, this part of the code is obviously time-consuming and can be considered for optimization.

For example, the ActivityStart process takes time:

Wall Duration 78.282 ms
CPU Duration 71.004 ms

You can see that the code time and the actual CPU time are close

Next, analyze the bindApplication process:

Wall Duration 3053505 ms
CPU Duration 49.767 ms

Wall Duration took 3s+, and the CPU actually took 49ms. This was not the case because the CPU was not working all the time.

At this point, the bindApplication process should be analyzed from the code, the Application layer can analyze the Application code, my code is like this:

public class SampleApplication extends Application { private static Context sContext; @Override public void onCreate() { super.onCreate(); sContext = this; Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } } public static Context getContext(){ return sContext; }}Copy the code

The obvious reason is that I slept for 3s in onCreate of Application. Of course, this is only a simple simulation of time-consuming operations, and the actual business is certainly more complex than this, but the overall analysis idea is the same.

Statistical Method Time plan

How do you see how long each method takes to execute?

Time consuming and printing by AOP statistics

If you just want to make it easier to count the time spent on a method, you can use AOP to insert the code for counting the time spent, and add a line of annotations to each method to get a log print of the time spent on that method.

Specific implementation can refer to the author of this blog for Android engineers AOP knowledge

Custom plug-in

If you want to see the elapsed time of each method using the Systrace tool, you need to add Trace detection code before and after each method

If you add it manually, this is definitely a manual job, not the job of a hungry programmer. At this point, we need to find a way to automate. We can use the custom plug-in method to plug our Trace code.

Fortunately, this plug-in has been written by the big guy, we can refer to learn below:

The plug-in USES

The use of this plug-in is detailed in the readme of the plug-in, which is not copied here.

Plug-in parsing

Plug-in source code is more, the author extracted the most core structure code analysis:

1. Custom Extension: This Extension class supports properties used under build.gradle

class SystraceExtension { boolean enable String baseMethodMapFile String blackListFile String output SystraceExtension()  { enable = true baseMethodMapFile = "" blackListFile = "" output = "" } }Copy the code

2. Apply custom plugins:

@Override void apply(Project project) { project.extensions.create("systrace", SystraceExtension) if (! project.plugins.hasPlugin('com.android.application')) { throw new GradleException('Systrace Plugin, Android Application plugin required') } project.afterEvaluate { def android = project.extensions.android def configuration = project.systrace android.applicationVariants.all { variant -> String output = configuration.output if (Util.isNullOrNil(output)) { configuration.output = project.getBuildDir().getAbsolutePath() + File.separator + "systrace_output" Log.i(TAG, "set Systrace output file to " + configuration.output) } Log.i(TAG, "Trace enable is %s", Configuration. The enable)/configuration/determine whether open the if (configuration. The enable) {SystemTraceTransform. Inject (project, the variant)}}}}Copy the code

3. User-defined Transform: For the method that needs to insert pile, insert pile Trace method

public void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { transformInvocation.inputs.each { TransformInput input -> input.directoryInputs.each { DirectoryInput dirInput -> collectAndIdentifyDir(scrInputMap, dirInput, rootOutput, isIncremental) } input.jarInputs.each { JarInput jarInput -> if (jarInput.getStatus() ! = Status.REMOVED) { collectAndIdentifyJar(jarInputMap, scrInputMap, jarInput, rootOutput, isIncremental) } } } MethodCollector methodCollector = new MethodCollector(traceConfig, MappingCollector) // Collect all methods in source code and JAR files TraceMethod> collectedMethodMap = methodCollector.collect( scrInputMap.keySet().toList(), jarInputMap.keySet().toList()) MethodTracer methodTracer = new MethodTracer(traceConfig, collectedMethodMap, MethodCollector. GetCollectedClassExtendMap ()) / / for all pile method in code methodTracer. Trace (scrInputMap, jarInputMap) origTransform.transform(transformInvocation) }Copy the code

4. Traverse all method peg trace code

# MethodTracer private void innerTraceMethodFromSrc(File input, File output) { for (File classFile : classFileList) { if (mTraceConfig.isNeedTraceClass(classFile.getName())) { is = new FileInputStream(classFile); ClassReader classReader = new ClassReader(is); ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS); ClassVisitor classVisitor = new TraceClassAdapter(Opcodes.ASM5, classWriter); classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES); is.close(); if (output.isDirectory()) { os = new FileOutputStream(changedFileOutput); } else { os = new FileOutputStream(output); } os.write(classWriter.toByteArray()); os.close(); } } } @Override public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { if (isABSClass) { return super.visitMethod(access, name, desc, signature, exceptions); } else { MethodVisitor methodVisitor = cv.visitMethod(access, name, desc, signature, exceptions); return new TraceMethodAdapter(api, methodVisitor, access, name, desc, this.className, isMethodBeatClass); }}Copy the code

5. Modify the bytecode using ASM

public final static String MATRIX_TRACE_METHOD_BEAT_CLASS = "com/sample/systrace/TraceTag"; # MethodTracer private class TraceMethodAdapter extends AdviceAdapter { TraceMethod traceMethod = mCollectedMethodMap.get(methodName); if (traceMethod ! = null) { traceMethodCount.incrementAndGet(); String sectionName = methodName; int length = sectionName.length(); If (length > TraceBuildConstants.MAX_SECTION_NAME_LEN) {int parmIndex = sectionName.indexof ('('); sectionName = sectionName.substring(0, parmIndex); // If it is still larger, crop length = sectionName.length(); if (length > TraceBuildConstants.MAX_SECTION_NAME_LEN) { sectionName = sectionName.substring(length - TraceBuildConstants.MAX_SECTION_NAME_LEN); }} // visitLdcInsn: constant mv.visitcinsn (sectionName) loaded on the stack; / / visitMethodInsn: The instruction to call the method, Here call TraceTag I method of mv. VisitMethodInsn (INVOKESTATIC, TraceBuildConstants MATRIX_TRACE_METHOD_BEAT_CLASS, "I", "(Ljava/lang/String;) V", false); Protected void onMethodExit(int opcode) {TraceMethod TraceMethod = McOllectedmethodmap. get(methodName); if (traceMethod ! = null) { traceMethodCount.incrementAndGet(); / / visitMethodInsn: The instruction to call the method, Here call TraceTag o method of mv. VisitMethodInsn (INVOKESTATIC, TraceBuildConstants MATRIX_TRACE_METHOD_BEAT_CLASS, "o", "V" (), false); }}}Copy the code