Android master notes – start optimization

Time:2022-1-3
  • The only way to start and open the app, the first experience, is related to core data such as user retention and conversion rate;

Start analysis

startup type

  • Android vitals can monitor the application cold, hot and warm startup time.
  • Via ADB shell am start – w Execute the command to start and print the startup time-consuming information, which will be explained in detail in the startup monitoring below
1. Cold start
  • The application starts from scratch, and the system process does not create the application process until it is cold started
  • Start process: click event – > IPC – > process start -> ActivityThread -> bindApplication -> LifeCycle -> ViewRootImpl
  • Three tasks of the system in the cold start phase:
    1. Load and launch apps
    2. Displays a blank launch window for the application
    3. Create application process
  • The application process is responsible for the following stages:
    1. Create application object
    2. Start main thread
    3. Create master activity
    4. Expand view / load layout
    5. Layout screen
    6. Perform initial painting / first frame painting
  • After the application process completes the first drawing, the system process will replace the currently displayed startup window with the main activity. At this point, the user can start using the application.
2. Hot start
  • All the work of the system is to bring the activity to the foreground,
  • As long as all the activities of the application still reside in memory, the application does not have to repeatedly perform object initialization, layout expansion and rendering;
    However, if some memory is completely cleared in response to a memory grooming event (such as ontrimmemory()), you need to recreate the corresponding object in response to a hot start event;
  • The on-screen behavior of the hot start display is the same as that of the cold start scenario: the system process will display a blank screen before the application completes the activity rendering.
3. Warm start
  • Contains some operations that occur during cold start; At the same time, it is more expensive than hot start
  • Scenario 1: the user restarts the application after exiting the application (the process may survive, and recreate the activity from scratch through oncreate())
  • Scenario 2: the system evicts the application from memory, and then the user restarts (both processes and activities need to be restarted, but the saved instance state bundle passed to oncreate() is helpful to complete this task)
  • The startup mentioned below generally refers to cold startup

Start process

  1. (desktop) Click response to apply parsing
  2. (system) preview window display (created according to the theme property. If the theme is specified as transparent, you will still see the desktop)
  3. (application) application creation, splash page / startup page activity creation (a series of inflateview, onmeasure, onlayout)
  4. (system) flash screen display
  5. (application) mainactivity creation interface preparation
  6. (system) homepage / homepage display
  7. (business, application, data, initialization)
  8. Window operable

Start problem analysis

  • Three problems that users may encounter can be inferred from the startup process
1. Click the desktop icon and there is no response:
  • Reason: preview window is disabled or transparent background is specified in theme
//Advantages: avoid white screen and black screen when starting the app
//Disadvantages: it is easy to cause no response when clicking the desktop icon
//(it can be used in combination with three-party library lazy loading, asynchronous initialization and other schemes to reduce the initialization time)
//The implementation is as follows
//0. appTheme
 <!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
    <!-- Customize your theme here. -->
    <item name="colorPrimary">@color/c_ff000000</item>
    <item name="colorPrimaryDark">@color/c_ff000000</item>
    <item name="colorAccent">@color/c_ff000000</item>
    <item name="android:windowActionBar">false</item>
    <item name="android:windowNoTitle">true</item>
</style>
//1.  styles. Set in XML
//1.1 disable preview window
<style name="AppTheme.Launcher">
    <item name="android:windowBackground">@null</item>
    <item name="android:windowDisablePreview">true</item>
</style>
//1.2 specify transparent background
<style name="AppTheme.Launcher">
    <item name="android:windowBackground">@color/c_00ffffff</item>
    <item name="android:windowIsTranslucent">true</item>
</style>
//2.  Set theme for startup / splash page activity
<activity
    android:name=".splash.SplashActivity"
    android:screenOrientation="portrait"
    android:theme="@style/AppTheme.Launcher">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
//3.  In this activity Set apptheme in oncreate() (before setting layout ID)
//For example, if I am a method for obtaining layout ID extracted separately from the base class, the following configuration is added when overriding this method in the startup page:
 @Override
protected int getContentViewId() {
    setTheme(R.style.AppTheme_Launcher);
    return R.layout.activity_splash;
}
2. Slow display of home page
  • Cause: the startup process is complex, and there are too many initialized components & third-party libraries
3. Unable to operate after the home page is displayed
  • Reason: ibid

Start optimization

  • The method is basically the same as carton optimization, but the startup is too important and needs to be more careful;

Optimization tools

  • The performance loss of traceview is too large, and the results are not true;
  • Nanoscope is very real, but it only supports nexus 6p and x86 simulators for the time being, and cannot be tested for medium and low-end computers;
  • Simpleperf’s flame diagram is not suitable for start-up process analysis;
  • Systrace can easily track the time-consuming situation of key system calls, but it does not support the time-consuming analysis of application code;
  • On the whole, it seems that “systrace + function pile insertion” mentioned in the Caton optimization is an ideal scheme (refer to the homework after class). After getting the panorama of the whole startup process, we can clearly see the operation of the system, application processes and threads during this period of time;

optimization method

1. Screen Optimization:
  • Preview the flash screen (today’s headlines). The preview window is realized into a flash screen effect. The experience on the high-end machine is very good, but the total flash screen duration will be extended on the low-end machine (it is recommended to enable this scheme above Android 6.0);
//Advantages: avoid clicking on the desktop icon without response  
//Disadvantages: lengthen the total flashing time
//(it can be used in combination with three-party library lazy loading, asynchronous initialization and other schemes to reduce the initialization time)
//1.  Is to set a background image for the window background
<style name="AppTheme.Launcher">
    <item name="android:windowBackground">@drawable/bg_splash</item>
    <item name="android:windowFullscreen">true</item>
</style>  
//2.  bg_ The splash file is as follows (implemented using layer list)  
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:drawable="@color/color_ToolbarLeftItem" />
    <item>
        <bitmap
            android:antialias="true"
            android:gravity="center"
            android:src="@drawable/ic_splash" />
    </item>
</layer-list>
//3.  Set theme for startup / splash page activity
<activity
    android:name=".splash.SplashActivity"
    android:screenOrientation="portrait"
    android:theme="@style/AppTheme.Launcher">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>  
//4.  In this activity Set apptheme in oncreate() (before setting layout ID)
//For example, if I am a method for obtaining layout ID extracted separately from the base class, the following configuration is added when overriding this method in the startup page:
 @Override
protected int getContentViewId() {
    setTheme(R.style.AppTheme_Launcher);
    return R.layout.activity_splash;
}
  1. Merging the activity (wechat) of the flash screen and the main page, but it violates the principle of single responsibility and the business logic is complex;
2. Business sorting
  • Clarify which modules need to be started, which can be cut off and which can be lazy loaded (lazy loading should prevent centralization and avoid the situation that the home page is visible but the user cannot operate);
  • Determine different startup modes according to business scenarios;
  • Downgrade the low-end machine and make functional trade-offs;
  • The positive value of overall retention and transformation brought by startup optimization is greater than the negative impact of canceling preloading of a business;
3. Business optimization
  • Focus on the big and let go of the small, and solve the main time-consuming problems, such as optimizing the decryption algorithm;
  • Asynchronous threads are preloaded, but overuse will make the code logic more complex;
  • Repay the technical debt, and reconstruct the old code if necessary;
4. Thread optimization
  • Reduce the fluctuation caused by CPU scheduling and make the application startup time more stable
  1. Control the number of threads to avoid too many threads competing for CPU resources. Use a unified thread pool to control the number according to the machine performance;
  2. Check the lock between threads, especially to prevent the main thread from idling for a long time (the main thread does sub thread tasks such as lock);
//Viewing thread switching data through sched
  proc/[pid]/sched:
  nr_voluntary_switches:     
  The number of active context switches. The most common is Io because the thread cannot obtain the required resources.    
  nr_involuntary_switches:   
  The number of passive context switches. Threads are forcibly scheduled by the system, resulting in context switches. For example, a large number of threads are preempting the CPU.
  • There are many start-up frameworks that use the pipeline mechanism to specify the business initialization time according to the business priority. For example, the mmkernel of wechat and the alpha of Ali will establish dependencies for tasks and finally form a directed acyclic graph;
  • The following is a custom thread pool tool class that can distinguish between multiple types of tasks. It can also be used for asynchronous initialization
//-Pay attention to distinguishing task types:
//- IO intensive tasks: do not consume CPU, and the core pool can be large, such as file reading and writing, network requests, etc.
//- CPU intensive tasks: the size of the core pool is related to the number of CPU cores, such as complex computing, which requires a large number of CPU computing units.
//
//The tasks performed are CPU intensive
DispatcherExecutor.getCPUExecutor().execute(YourRunable());

//The tasks performed are IO intensive
DispatcherExecutor.getIOExecutor().execute(YourRunable());

/**
 * @Author: LiuJinYang
 * @CreateDate: 2020/12/16
 * 
 *Implement the underlying thread pool for performing multiple types of tasks
 */
public class DispatcherExecutor {

    /**
     *Thread pool for CPU intensive tasks
     */
    private static ThreadPoolExecutor sCPUThreadPoolExecutor;

    /**
     *Thread pool for IO intensive tasks
     */
    private static ExecutorService sIOThreadPoolExecutor;

    /**
     *The number of CPU cores that can be used by the current device
     */
    private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();

    /**
     *The number of core threads in the thread pool, which is in the range of 2 ~ 5
     */
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 5));

    /**
     *Maximum number of threads in the thread pool: specified here as the size of the number of core threads
     */
    private static final int MAXIMUM_POOL_SIZE = CORE_POOL_SIZE;

    /**
     *The timeout for idle threads in the thread pool to wait for work, when in the thread pool
     *The number of threads is greater than corepoolsize or
     *When allowcorethreadtimeout is set,
     *The thread will check the activity according to the value of keepalivetime, and destroy the thread once it times out.
     *Otherwise, the thread will always wait for new work.
     */
    private static final int KEEP_ALIVE_SECONDS = 5;

    /**
     *Create a blocking queue based on linked list nodes
     */
    private static final BlockingQueue<Runnable> S_POOL_WORK_QUEUE = new LinkedBlockingQueue<>();

    /**
     *Thread factory for creating threads
     */
    private static final DefaultThreadFactory S_THREAD_FACTORY = new DefaultThreadFactory();

    /**
     *Exception occurs when the thread pool executes time-consuming tasks. It is required to reject execution
     *General note: not implemented here
     */
    private static final RejectedExecutionHandler S_HANDLER = new RejectedExecutionHandler() {
        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            Executors.newCachedThreadPool().execute(r);
        }
    };

    /**
     *Get CPU thread pool
     *
     *@ return CPU thread pool
     */
    public static ThreadPoolExecutor getCPUExecutor() {
        return sCPUThreadPoolExecutor;
    }

    /**
     *Get IO thread pool
     *
     *@ return IO thread pool
     */
    public static ExecutorService getIOExecutor() {
        return sIOThreadPoolExecutor;
    }

    /**
     *Implement a default thread factory
     */
    private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger POOL_NUMBER = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                    Thread.currentThread().getThreadGroup();
            namePrefix = "TaskDispatcherPool-" +
                    POOL_NUMBER.getAndIncrement() +
                    "-Thread-";
        }

        @Override
        public Thread newThread(Runnable r) {
            //Each newly created thread is assigned to a thread group
            Thread t = new Thread(group, r,
                    namePrefix + threadNumber.getAndIncrement(),
                    0);
            if (t.isDaemon()) {
                //Non daemon thread
                t.setDaemon(false);
            }
            //Set thread priority
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }

    static {
        sCPUThreadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                S_POOL_WORK_QUEUE, S_THREAD_FACTORY, S_HANDLER);
        //Set whether to allow idle core threads to time out. The thread will conduct activity check according to the value of keepalivetime. Once it times out, the thread will be destroyed. Otherwise, the thread will always wait for new work.
        sCPUThreadPoolExecutor.allowCoreThreadTimeOut(true);
        //The IO intensive task thread pool is directly implemented by cachedthreadpool,
        //It can assign up to integer MAX_ Value is a non core thread used to perform tasks
        sIOThreadPoolExecutor = Executors.newCachedThreadPool(S_THREAD_FACTORY);
    }

}
5. GC optimization
  • During the start-up process, the number of GC shall be minimized to avoid long-time jamming of the main line
//1.  View the GC time of the whole startup process separately through systrace
  python systrace.py dalvik -b 90960 -a com.sample.gc
  //2.  Through debug Startalloccounting monitors the total GC time during startup
  //The total time spent by GC in milliseconds
  Debug.getRuntimeStat("art.gc.gc-time");
  //Total time spent blocking GC
  Debug.getRuntimeStat("art.gc.blocking-gc-time");
  //If it is found that the main thread has more GC synchronization waiting, it needs to be further analyzed through the allocation tool
  • The startup process avoids a large number of string operations, serialization and deserialization, and reduces object creation (improve or move to native Implementation);
  • Java object escape is also easy to cause GC. Ensure that the object life cycle is as short as possible and destroy it on the stack;
6. System call optimization
  • Through the system service type of systrace, you can see the CPU operation of system server during startup
  • Try not to make system calls during startup, such as packagemanagerservice operation and binder call
  • Don’t pull up other application processes too early in the startup process. System server and new processes will compete for CPU resources. When memory is insufficient, the system’s low memory killer mechanism may be triggered, causing the system to kill and pull up (keep alive) a large number of processes, thus affecting the foreground processes

Start advanced method

1. IO optimization

  • When the load is too high, the IO performance will decline rapidly, especially for low-end machines;
  • Network IO is not recommended during startup
  • Disk IO should be clear about what files are read during startup, how many bytes, buffer size, how much time it takes, what threads are used, etc
  • Heavy users are the groups that must be covered when starting optimization, such as local cache, database and SP files
  • For the selection of data structure, for example, only a few fields in the SP file may be required during startup, and sharedpreference needs to be stored separately to avoid taking too long to parse all SP data;
  • The startup process is suitable for using the data structure of random reading and writing. Arraymap can be transformed into a data storage mode that supports random reading and writing and delayed parsing.

2. Data rearrangement

  • Linux file I / O process
When the Linux file system reads files from the disk, it will read them from the disk in block, generally block 
The size is 4KB. That is, the disk read / write size is at least 4KB at a time, and then 4KB data will be put into the page cache
 Page cache. If the next read file data is already in the page cache, the real disk will not occur 
 I / O, but read directly from the page cache, which greatly improves the reading speed.

For example, when reading 1KB data in a file, because the buffer is accidentally written as 1 byte, it needs to be read 1000 times in total.
Does the system really read the disk 1000 times? In fact, 1000 read operations are only the number of times we initiate,
It's not the real disk I / O times. Although we read it 1000 times, in fact, it only happens once 
I / O, other data will be obtained in the page cache.

The classes used in dex files and various resource files in the installation package APK are generally small, but they are read very frequently.
We can use the system mechanism to rearrange them according to the reading order to reduce the number of real disk I / O;

Class rearrangement
//The loading order of the startup process class can be obtained by copying classloader
class MyClassLoader extends PathClassLoader {
    public Class<?> findClass(String name) {
        //Record name to file
        writeToFile(name,"coldstart_classes.txt");
        return super.findClass(name);
    }
}
//Then adjust the arrangement order of classes in dex through REDEX's interdix. Finally, you can use 010 editor to view the modified effect.
Resource file rearrangement
  • Facebook used “resource heat map” to rearrange resource files earlier
  • Alipay describes the principle and landing methods of resource rearrangement in detail by installing the package rearrangement to optimize Android boot performance.
  • In the implementation, a special ROM is compiled by modifying the kernel source code. In order to facilitate the statistics of resource files, the effect measurement and process automation are realized after rearrangement
  • If only for statistics, you can also use hook
    //Hook, a method to obtain the loading order of Android resources by using Frida
    resourceImpl.loadXmlResourceParser.implementation=function(a,b,c,d){
       send('file:'+a)
       return this.loadXmlResourceParser(a,b,c,d)
    }
    resourceImpl.loadDrawableForCookie.implementation=function(a,b,c,d,e){
       send("file:"+a)
       return this.loadDrawableForCookie(a,b,c,d,e)
    }
    //Frida is relatively small and will replace other more mature hook frameworks later
    //To adjust the arrangement of installation package files, you need to modify the 7zip source code to support the list order of incoming files. Similarly, you can use 010 editor to view the modified effect;
    • The so-called innovation does not necessarily mean creating something unprecedented. It is a great innovation for us to transplant the existing scheme to the new platform and implement it in combination with the characteristics of the platform

3. Class loading

  • There is a verify class step during class loading. It needs to verify each instruction of the method. It is a time-consuming operation. You can remove the verify step through hook
  • The biggest optimization scenario is the first and overlay installation
//Dalvik platform: set classverifymode to verify_ MODE_ NONE
// Dalvik Globals.h
gDvm.classVerifyMode = VERIFY_MODE_NONE;
// Art runtime.cc
verify_ = verifier::VerifyMode::kNone;

//The art platform is much more complex, and hook needs to be compatible with several versions
//Most DEX has been optimized during installation. Removing verify on the art platform will only bring some benefits to dynamically loaded DEX
//Dalvik in atlas_ hack-3.0.0.5. Jar can remove verify by the following method
AndroidRuntime runtime = AndroidRuntime.getInstance();
runtime.init(context);
runtime.setVerificationEnabled(false);
//This black technology can greatly reduce the speed of the first start-up at the cost of having a slight impact on the subsequent operation. At the same time, compatibility should also be considered. It is not recommended to use it on the art platform for the time being

4. Black Technology

Keep alive:
  • Keeping alive can reduce the time of application creation and initialization, and turn cold start into warm start. However, after target 26, it has become more and more difficult to keep alive; (large manufacturers usually cooperate with manufacturers, such as hardcoder scheme of wechat and hyper boost scheme launched by oppo. When the application volume is large enough, manufacturers can be forced to optimize them)
Plug in and hot fix:
  • In fact, most frameworks have a large number of hook and private API calls in design, which brings two main disadvantages:
  1. Stability / compatibility: Manufacturer’s compatibility, installation failure, dex2oat failure, etc., and non SDK interface call restrictions launched by Android P
  2. Performance: each version of Android runtime has many optimizations, and black technology will lead to failure
Application reinforcement:
  • It’s a disaster for startup speed. Sometimes we need to make some trade-offs and choices
GC suppression:
  • Refer to the Alipay client architecture, analyze the garbage collection optimization of -Android client startup speed.
  • Allow the heap to grow until GC suppression is stopped manually or oom

5. Multidex optimization

Apk compilation process / what happens after Android studio presses the compile button?
1. Package the resource file and generate the r.java file (use the tool AAPT)
2. Process Aidl file and generate java code (ignored if there is no Aidl)
3. Compile java files to generate corresponding Class file (Java compiler)
4. . Convert class file to DEX file (DEX)
5. Package it into an unsigned APK (use the tool apkbuilder)
6. Use the signature tool to sign APK (use the tool jarsigner)
7. To the signature Apk files are aligned. Without alignment, they cannot be published to Google market (use the tool zippalign)

In step 4, the number of methods in a single DEX file cannot exceed 65536, otherwise the compilation will report an error: unable to execute DEX: method id not in [0, 0xFFFF]: 65536, so we generally use multidex:

1. Configuration in gradle
 defaultConfig {
    ...
    multiDexEnabled true
 }
 
 implementation 'androidx.multidex:multidex:2.0.1'
 
2. Initialization in application
@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
}

However, this multidex process is time-consuming, so can we optimize this problem?

Two schemes of multidex optimization
1. Sub thread install (not recommended):
  • The splash screen page opens a sub thread to execute multidex Install, and then jump to the home page after loading,
It should be noted that the activity of the flash page, including other classes referenced in the flash page, must be in the main DEX,
Or at multidex Loading these classes that are not in the main DEX before install will report an error class not found.
This can be configured through gradle, as follows:
defaultConfig {
    //Subcontracting, specifying a class in main Dex
    multiDexEnabled true
    Multidexkeepprogruard file ('multidexkeep. Pro ') // confusion rules of these classes packaged in main DEX. An empty file is given without special requirements
    Multidexkeepfile ('maindexlist. TXT ') // specify which classes to put in main DEX
}
2. Today’s headline program
  1. In the attachbasecontext method of the main process application, if you need to use multidex, create a temporary file, then open a process (loaddexactivity), display loading, and execute multidex asynchronously Install logic, delete the temporary file and finish yourself after execution.
  2. The attachbasecontext of the main process application enters the while code block to periodically cycle whether the temporary file has been deleted. If it has been deleted, it indicates that the execution of multidex has been completed. Then it jumps out of the loop and continues the normal application startup process.
  3. Note that loaddexactivity must be configured in main DEX.
  4. Specific implementation reference itemsMultiDexTest

6. Preload optimization

1. Class preload:
  • Load classes that take a long time to initialize asynchronously in advance in the application
2. Preload data page:
  • When the home page is idle, load the data of other pages and save them to memory or database
3. WebView preload:
  1. It takes time to create WebView for the first time. You can create WebView in advance and initialize its kernel in advance;
  2. When using the WebView cache pool, all places where WebView is used are taken from the cache pool. There is no cache in the cache pool to create. Pay attention to memory leakage.
  3. HTML and CSS are preset locally. When WebView is created, the local html is preloaded first, and then the content part is filled through JS script.
4. Activity pre creation: (today’s headlines)
  • The activity object calls the following code when the child thread is new in advance, for example, when the screen page is waiting for an advertisement
DispatcherExecutor.getCPUExecutor().execute(new Runnable() {
    @Override
    public void run() {
            long startTime = System.currentTimeMillis();
            MainActivity mainActivity = new MainActivity();
            LjyLogUtil. D ("prenewactivity time consuming:" + (system. Currenttimemillis() - starttime));
    }
});

When the object is first created, the Java virtual machine first checks whether the class object corresponding to the class has been loaded. If it is not loaded, the JVM looks it up based on the class name Class file and load its class object. When the same class is new for the second time, it does not need to load the class object, but is instantiated directly, and the creation time is shortened.

7. The startup phase does not start the child process

  • Child processes will share CPU resources, resulting in CPU tension in the main process

8. CPU frequency locking

  • At present, the CPU performance of mobile devices increases sharply, but the general utilization is not high. We can violently stretch the CPU frequency at startup to increase the startup speed
  • However, it will lead to increased power consumption
  • In Android system, CPU related information is stored in the file in / sys / devices / system / CPU directory. By writing values to specific files in this directory, the CPU frequency and other status information can be changed.
-CPU operating mode
Performance: the highest performance mode. Even if the system load is very low, the CPU runs at the highest frequency.
Powersave: power saving mode. Contrary to the performance mode, the CPU always runs at the lowest frequency.
Ondemand: the CPU frequency changes with the system load.
Userspace: it can be simply understood as a user-defined mode, in which the frequency can be set.

Start monitoring / time consuming detection

logcat

  • Filter keyword displayed in logcat of Android studio

adb shell

adb shell am start -W com.ljy.publicdemo.lite/com.ljy.publicdemo.activity.MainActivity
Execution results:
Starting: Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] cmp=com.ljy.publicdemo.lite/com.ljy.publicdemo.activity.MainActivity }
Status: ok
LaunchState: COLD 
Activity: com.ljy.publicdemo.lite/com.ljy.publicdemo.activity.MainActivity
TotalTime: 2065
WaitTime: 2069
Complete
//Launchstate indicates hot and cold start
//Totaltime: indicates the time taken to start all activities. (main data, including the process from creation process + application initialization + activity initialization to interface display)
//Waittime: indicates the total time taken by AMS to start the activity.

Laboratory monitoring

  • Through regular automatic screen recording and analysis, it is also suitable for comparative test of competitive products
  • How to find the starting and ending point
    • 80% draw
    • image recognition
  • High threshold, suitable for large factories

Online monitoring

Details of starting time-consuming calculation:
  • Statistical timing of start and end: use the time when the user can really operate
  • Deduction logic of startup time: the time of flash screen, advertising and novice guidance should be deducted
  • Startup exclusion logic: when the broadcast and server are pulled up and the startup process enters the background, it needs to be eliminated
Standard for measuring the speed of start-up
  • Average startup time (users with poor experience may be averaged)
  • Fast open slow open ratio, such as 2-second fast open ratio and 5-second slow open ratio
  • 90% user startup time
Distinguish between startup types:
  • First installation startup, covering installation startup, cold startup, warm startup and hot startup
  • The proportion of hot start can also reflect the activity or viability of our program
In addition to the monitoring of indicators, it is more difficult to start online stack monitoring. Facebook will use the profilo tool to launch
The whole process is time-consuming, and different versions are automatically compared in the background to monitor whether there are new time-consuming functions in the new version.

For start-up optimization, we should be vigilant about KPI. What we want to solve is not a number, but the real experience of users.

Code management (function pile insertion), the disadvantage is that the code is highly invasive
/**
 * @Author: LiuJinYang
 * @CreateDate: 2020/12/14
 *
 *Add management points where time needs to be counted in the project, such as
 *The lifecycle node of the application.
 *Important methods that need to be initialized during startup, such as database initialization and reading some local data.
 *Some other time-consuming algorithms.
 */
public class TimeMonitor {
    private int mMonitorId = -1;

    /**
     *Save various time consumption of a time consumption statistics module, and the tag corresponds to the time of a certain stage
     */
    private HashMap<String, Long> mTimeTag = new HashMap<>();
    private long mStartTime = 0;

    public TimeMonitor(int mMonitorId) {
        LjyLogUtil.d("init TimeMonitor id: " + mMonitorId);
        this.mMonitorId = mMonitorId;
    }

    public int getMonitorId() {
        return mMonitorId;
    }

    public void startMonitor() {
        //Clear the previous data every time you restart to avoid counting wrong data
        if (mTimeTag.size() > 0) {
            mTimeTag.clear();
        }
        mStartTime = System.currentTimeMillis();
    }

    /**
     *Record the time consumption of a tag every time you click
     */
    public void recordingTimeTag(String tag) {
        //If the same tag has been saved, clear it first
        if (mTimeTag.get(tag) != null) {
            mTimeTag.remove(tag);
        }
        long time = System.currentTimeMillis() - mStartTime;
        LjyLogUtil.d(tag + ": " + time);
        mTimeTag.put(tag, time);
    }

    public void end(String tag, boolean writeLog) {
        recordingTimeTag(tag);
        end(writeLog);
    }

    public void end(boolean writeLog) {
        if (writeLog) {
            //Write to local file
        }
    }

    public HashMap<String, Long> getTimeTags() {
        return mTimeTag;
    }
}
  • Time consuming statistics may need to be managed in multiple modules and classes, so a singleton class is needed to manage the data of each time consuming statistics:
/**
 * @Author: LiuJinYang
 * @CreateDate: 2020/12/14
 */
public class TimeMonitorManager {
    private HashMap<Integer, TimeMonitor> mTimeMonitorMap;

    private TimeMonitorManager() {
        this.mTimeMonitorMap = new HashMap<>();
    }

    private static class TimeMonitorManagerHolder {
        private static TimeMonitorManager mTimeMonitorManager = new TimeMonitorManager();
    }

    public static TimeMonitorManager getInstance() {
        return TimeMonitorManagerHolder.mTimeMonitorManager;
    }

    /**
     *Initialization management module
     */
    public void resetTimeMonitor(int id) {
        if (mTimeMonitorMap.get(id) != null) {
            mTimeMonitorMap.remove(id);
        }
        getTimeMonitor(id).startMonitor();
    }

    /**
     *Get Dotter
     */
    public TimeMonitor getTimeMonitor(int id) {
        TimeMonitor monitor = mTimeMonitorMap.get(id);
        if (monitor == null) {
            monitor = new TimeMonitor(id);
            mTimeMonitorMap.put(id, monitor);
        }
        return monitor;
    }
}
AOP management, such as counting the consumption of all methods in the application
1. Through AspectJ
//1.  Integrating AspectJ
   //Root directory: build In gradle
  classpath 'com.hujiang.aspectjx:gradle-android-plugin-aspectjx:2.0.10'
  //Build. Of APP module In gradle
  apply plugin: 'android-aspectjx'
  //If the error cause: zip file is empty is encountered, the following configuration can be added
  android{
    aspectjx {
        include 'com.ljy.publicdemo'
    }
  }
//2.  Create annotation class  
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface GetTime {
    String tag() default "";
}

//3.  Use AspectJ to parse annotations and implement time-consuming records
@Aspect
public class AspectHelper {
    @Around("execution(@GetTime * *(..))")
    public void getTime(ProceedingJoinPoint joinPoint) throws Throwable {
        MethodSignature joinPointObject = (MethodSignature) joinPoint.getSignature();
        Method method = joinPointObject.getMethod();

        boolean flag = method.isAnnotationPresent(GetTime.class);
        LjyLogUtil.d("flag:"+flag);
        String tag = null;
        if (flag) {
            GetTime getTime = method.getAnnotation(GetTime.class);
            tag = getTime.tag();
        }
        if (TextUtils.isEmpty(tag)) {
            Signature signature = joinPoint.getSignature();
            tag = signature.toShortString();
        }
        long time = System.currentTimeMillis();
        try {
            joinPoint.proceed();
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        LjyLogUtil.d( tag+" get time: " + (System.currentTimeMillis() - time));
    }
}
2. Through epic third-party library
//At present, epic supports thumb-2 / arm64 instruction set of Android 5.0 ~ 11, arm32 / x86 / x86_ 64 / MIPS / MIPS64 is not supported.
//1.  Add epic dependency
implementation 'me.weishu:epic:0.11.0'
//2.  Using epic
public static class ActivityMethodHook extends XC_MethodHook{

    private long startTime;

    @Override
    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                super.beforeHookedMethod(param);
        startTime = System.currentTimeMillis();
    }

    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        Activity act = (Activity) param.thisObject;
        String methodName=param.method.getName();
        LjyLogUtil.d( act.getLocalClassName()+"."+methodName+" get time: " + (System.currentTimeMillis() - startTime));
    }
}
private void initEpic() {
    //Print the oncreate execution time of all activities
     DexposedBridge.hookAllMethods(Activity.class, "onCreate", new ActivityMethodHook());
}
//It can also be used to lock the thread creator
DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() {
    @Override
    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
        super.afterHookedMethod(param);
        Thread thread = (Thread) param.thisObject;
        LjyLogUtil.i("stack " + Log.getStackTraceString(new Throwable()));
    }
});

Homework after class

reference resources

I am today, if you want to step in and know more dry cargo, welcome to WeChat official account “Jinyang” to receive my latest article.