Android Performance Analysis & [Startup Optimization]

Author: Shen Guojun

performance analysis tool

First, let's learn how to use performance analysis tools. We start with a specific example, that is, how to analyze the performance of application startup.

Android Profiler

configuration

Let's take a look at the Android Profiler first. In order to be able to capture analysis data as soon as the application starts, we need to configure it according to the following steps:

  • Select Run -> Edit Configurations

  • Select the Profiling tab in the settings, and then select Start recording CPU activity on startup. Note that the Sample Java Methods selected here means that Java code can be located. See cpu-profiler#configurations for the meaning of other options. If you want more detailed information, you can select Enable advanced profiling.

  • After configuration, select Run -> Profiler

Stop monitoring after the page startup is complete, and you can get the CPU, memory network and power consumption information during the startup process, as shown in the figure below:

CPU monitoring

Analysis process

Click to enter the CPU module

You can select a thread and see the time-consuming specific code of the thread. As the following example

Green means that the code we wrote is time-consuming, and we can choose the main thread for observation. It is shown here that it takes 620ms during the Applicaiton onCreate process. The more time-consuming methods are registerByCourseKey and initYouzanSDK. And through the Call Chart view, you can see the specific reasons why this method takes time.

Through such continuous down-analysis, you can roughly locate the cause of the time-consuming startup of the CPU. Below we give a specific example of optimization.

Optimization example

Before optimization:

As shown in the figure above, RxBroadcast brings a large time-consuming during the startup process

Check out the code:

private fun initBroadcast() {
    val filter = IntentFilter()
    ……
    disposables.add(RxBroadcast.fromLocalBroadcast(context, filter)
        .subscribe({ intent ->
            ……
        },
        { throwable: Throwable ->
            ……
        }
   ))
}

Indeed the method is used in initBroadcast RxBroadcast.fromLocalBroadcast(), we try to use LocalBroadcastManager.registerReceiverinstead. Modify it to the following code:

private fun initBroadcast() {
    val filter = IntentFilter()
    ……
    LocalBroadcastManager.getInstance(context).registerReceiver(broadcastReceiver, filter)
}

Restart CPU analysis after optimization:

It can be seen that the initialization time is reduced by 90ms compared to before optimization. From this, we can also conclude that using RxBroadcast is cool, but it is a time-consuming behavior, so the use of RxBroadcast should be minimized.

Precautions

  • It should be noted that some of the time-consuming here is when the CPU is in the Sleep state. In the Sleep state, it means that the CPU is occupied by other threads. At this time, it is necessary to analyze the situation of other threads in the Sleep state of the main thread. For example:

This shows that the main thread is in the Sleeping state at around 00:06. At this time, check the CPU usage of other threads.

It is found that the threads in MemoryAg are occupying CPU resources. In this case, it should not be considered that the corresponding main thread method is time-consuming, but should consider the situation such as memory recovery or other threads occupying CPU resources.

  • It should also be noted that not every time you click "Profiler", the information will be recorded normally. Occasionally, the application will crash and exit. This may be a bug in Android Studio or the log is too large. Don't be discouraged in this situation, just try a few more times and you'll be fine.

Perfect UI

Use process

On Android 10 mobile phones, the developer mode has added a "system tracking" function, we first open the "system tracking" in the developer mode:

We can also choose the category of information we care about from the "Category" options:

After setting, we will find that there is a lollipop-shaped icon in the drop-down shortcut option

At this time, kill the application we need to debug, then click to open the lollipop, then open the application, wait for the application to fully open, and then click the lollipop again to end the recording.

Then we save the recorded file with the suffix ".perfetto-trace" and then we select Open trace file on the perfetto ui website to upload the file we just got

After rendering, we can get an analysis similar to the previous systrace, and we can control it more easily through the Perfetto UI

Analysis process

First of all, we need to know that the result obtained through "system trace" is similar to the result of selecting "Trace System Calls" in Profiler in Android Studio. We can see all the running tasks of all CPUs in the system on the time axis. And we can also see all the processes in the system and all the thread tasks in the process.

We expand the main thread in the debugging application of Perfetto UI:

You can see the time-consuming of each step in the thread. We can view the system calls in each time period by continuously zooming in.

Optimization example

Before optimization:

It can be seen that in the process of inflate on the home page, there is an icon "bg_simple_dict_blueriver.jpg" which took 29ms to load. Analyze the code where it is located:

<ImageView
    android:id="@+id/iv_simple_dict_bg"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:src="@drawable/bg_simple_dict_blueriver"
    android:scaleType="centerCrop"
    android:visibility="gone"
/>

Since this picture will only exist as a placeholder when the network is not smooth, the simple method here can be

android:src="@drawable/bg_simple_dict_blueriver"

A better way is to change ImageView to ViewStub and then render it when necessary, saving layout rendering time. Optimized:

It can be seen that after optimization, the inflate time is reduced from 118ms to 103ms, and there is no process of loading the bg_simple_dict_blueriver.jpg image during the inflate process.

start optimization

With the above analysis of Sample Java Methods and Trace System Calls, we can get the start-up task time consumption from the macro code level and the micro CPU execution level.

Proguard & R8

In addition to the lazy loading processing of the business, we can see that the loading time of the dex file occupies most of the startup time. The loading time of dex is related to the magnitude of the code. Due to the long-term introduction of a large number of third-party libraries and the increase in the amount of code brought about by the growth of our own business, the loading speed of our dex is also getting slower and slower. In order to solve the problem of slow loading of dex, we can use two aspects: firstly, to deal with the reinforcement process that has a great impact on dex loading, which can be communicated with Hangyan. The second is to add code compression and obfuscation to the code.

Code compression and obfuscation can make the dex file smaller, thereby reducing the loading time of the dex file. But adding code compression and obfuscation from scratch is a very arduous process, because after code compression and obfuscation, ClassNotFoundException and NoSuchMethodError will easily occur, and codes that depend on class names and attribute names such as push and serialization will be invalid. Adding code minification and obfuscation requires extra care and a lot of work.

In the process of adding code compression and obfuscation, we summarize the following method steps:

local code

  • Check all code using annotations and add proguard rules
  • Check all JNI related codes and add proguard rules
  • Check all code that uses reflection and add proguard rules
  • Check all serialization and code that will be converted to Modle using Json, and add proguard rules
  • Check all codes used according to the class name, such as Push, etc., and add proguard rules
  • Requires that future code refactorings require corresponding changes to Proguard
  • It is required that the newly added code needs to add Proguard rules

Three-party code

  • Determine whether the three-party library reference in External Libraries is a release dependency or a debug dependency, and if so, continue
  • Determine whether the lib library is required by the current code. If the reference is not used or all the places used in the current code are no longer used, then clean up the lib and clean up the related unused code
  • If the lib library is required by the current code, go to the official website of the lib library to find the corresponding proguard rules, and paste them into the proguard-rules.pro file
  • If the lib official website library does not have corresponding proguard rules, then observe whether the lib library is useful for native code, annotation or reflection, which requires proguard processing, and if so, add corresponding rules
  • After adding proguard rules, find the place where this library is used in the current project, and try to see if there will be a crash
  • If there is a crash, add the corresponding proguard rules according to the crash prompt

In order to help you better understand performance optimization in a comprehensive and clear way, we have prepared relevant learning routes and core notes (returning the underlying logic): https://qr18.cn/FVlo89You can learn for reference:

Android performance analysis and optimization practical advanced manual

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/131574993