preface

To be a good Android developer, you need a completeThe knowledge systemHere, let’s grow up to be what we want to be.

In the overall knowledge of performance optimization, one of the most important is stability optimization, and we explored the boundaries of Android stability optimization in our last article, “In Depth Android Stability Optimization.” So, besides stability, which aspect of performance is most important in terms of performance latitude? No doubt, it’s the startup speed of the application. Now, let’s set sail and explore the mystery of Android startup speed optimization step by step.

Mind mapping outline

directory

  • First, the significance of starting optimization
  • Second,Application startup Process
    • 1. Application startup type
    • 2. Cold start analysis and its optimization direction
  • Three,Startup time detection
    • 1. Check Logcat
    • 2, the adb shell
    • 3. Code dotting (function piling)
    • 4. AOP(Aspect Oriented Programming)
    • 5. Start the speed analysis tool – TraceView
    • 6. Start the speed analysis tool – Systrace
    • 7. Start monitoring
  • Four,Start the optimization routine
    • 1. Theme switch
    • 2. Third-party libraries are loaded lazily
    • 3, asynchronous initialization preparatory knowledge – thread optimization
    • 4. Asynchronous initialization
    • 5. Delay initialization
    • 6. Optimize Multidex preloading
    • 7. Class preloading optimization
    • 8. WebView startup optimization
    • 9. Page data preloading
    • 10. Do not start child processes during startup
    • 11. Optimized the drawing of splash screen and home page

First, the significance of starting optimization

If we go to a restaurant and wait for a long time to order our food but no service staff comes, we may not have the patience to wait and just leave.

The same is true for apps. If the user clicks on the App and it doesn’t open for half a day, the user may lose patience and uninstall the App.

The startup speed is the first experience for users of our App. Only after opening the App can they use the powerful functions provided in it. No matter how beautifully designed and powerful the internal interface of our App is, if the startup speed is too slow, users will have a bad first impression.

Therefore, it is urgent to save the startup speed of App.

2. Application startup process

1. Application startup type

There are three types of application startup:

  • Cold start
  • Warm start
  • Wen started

Next, let’s analyze the characteristics and process of each startup type in detail.

Cold start

The entire process from clicking the app icon to the UI being fully displayed and accessible to the user.

The characteristics of

Most time consuming, the measure.

Start the process

Click Event -> IPC -> Process.start -> ActivityThread -> bindApplication -> LifeCycle -> ViewRootImpl

First, the user performs a click, and the click event triggers an IPC action, which is then executed in the Process’s start method, which is used to create the Process, and then in the ActivityThread’s main method. This method can be viewed as the entry point for our individual App process. It is equivalent to the Main method of the Java process, where the message loop is created and the main thread Handler is created. Once created, the bindApplication method is executed. Reflection is used here to create the Application and invoke the lifecycle associated with the Application. When the Application ends, the Activity lifecycle is executed. After the Activity lifecycle ends, and finally, It will go to ViewRootImpl, and then it will actually draw a page.

Warm start

Switch directly from the background to the foreground.

The characteristics of

The fastest startup time.

Wen started

Only the Activity lifecycle is retraced, not process creation, Application creation, and lifecycle.

The characteristics of

Fast, a speed between cold start and hot start.

Start the process

LifeCycle -> ViewRootImpl

What is ViewRootImpl?

It is the bridge between GUI management system and GUI rendering system. Each ViewRootImpl is associated with a Window. The ViewRootImpl will eventually bind the Window’s View through its setView method and use its performTraversals method to lay out, measure, and draw the View.

2. Cold start analysis and its optimization direction

Tasks related to cold start

Before cold start

  • First of all, it launches the App
  • Then, load the blank Window
  • Finally, create the process

It’s important to note that these are the actions of the system, and we generally can’t intervene directly.

Then the task

  • First, create Application
  • Start main thread
  • Create MainActivity
  • Loading layout
  • Decorate the screen
  • The first frame drawing

Usually when the first frame of the interface is drawn, we can consider the start to be over.

To optimize the direction

Our optimization direction is in this phase of the Application and Activity lifecycle, because the timing of this phase is controllable for us.

Third, start time detection

1. Check Logcat

If the keyword “Displayed” is Displayed in Android Studio Logcat, you can see the log of cold start time.

2, the adb shell

Use ADB Shell to get the startup time of the application

PackageName ADB shell am start -w [packageName]/[AppstartActivity full path]Copy the code

ThisTime, TotalTime, WaitTime

ThisTime

Indicates that the last Activity takes time to start.

TotalTime

Indicates the startup time of all activities.

WaitTime

Indicates the total time taken by AMS to start an Activity.

Generally speaking, you only need to check the Application startup time, which includes the process creation + Application initialization + Activity initialization to the interface display process.

Features:

  • 1, offline use is convenient, can not be brought online.
  • 2, non-rigorous, accurate time.

3. Code dotting (function piling)

You can write a time – counting utility class to record the time – taking of the entire process. Among them, we should pay attention to:

  • When uploading data to the server, you are advised to report the data according to the last number of the user ID.
  • Add dots to key callbacks and core methods of core base classes in your project.

The code looks like this:

/** * The time monitor object, which records the time of the entire process, can be used for many purposes, such as Activity startup time and Fragment startup time. */ public class TimeMonitor { private final String TAG = TimeMonitor.class.getSimpleName(); private int mMonitord = -1; Private HashMap<String, Long> MtimeMap = new HashMap<>(); private long mStartTime = 0; public TimeMonitor(int mMonitorId) { Log.d(TAG, "init TimeMonitor id: " + mMonitorId); this.mMonitorId = mMonitorId; } public int getMonitorId() { return mMonitorId; } public void startMonitor() {if (Mtime.com/tag.size () > 0) {Mtime.com/tag.clear (); } mStartTime = System.currentTimeMillis(); } public void recordingTimeTag(String tag) {// If (MtimetimeTag. Get (tag)! = 0) {// If (MtimetimeTag. = null) { mTimeTag.remove(tag); } long time = System.currentTimeMillis() - mStartTime; Log.d(TAG, 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; }}Copy the code

To make the code more manageable, we need to define a dot configuration class like this:

/** * Dot configuration class, used for statistics of each phase of the time, easy to code maintenance and management. */ public final class TimeMonitorConfig {public final int TIME_MONITOR_ID_APPLICATION_START = 1; }Copy the code

In addition, time statistics may be counted in multiple modules and classes, so you need a singleton class to manage each time statistics:

/** * Use singletons to manage each time statistics. */ public class TimeMonitorManager { private static TimeMonitorManager mTimeMonitorManager = null; private HashMap<Integer, TimeMonitor> mTimeMonitorMap = null; public synchronized static TimeMonitorManager getInstance() { if (mTimeMonitorManager == null) { mTimeMonitorManager = new TimeMonitorManager(); } return mTimeMonitorManager; } public TimeMonitorManager() { this.mTimeMonitorMap = new HashMap<Integer, TimeMonitor>(); } public void resetTimeMonitor(int id) {if (mtimemonitorMap.get (id)! = null) { mTimeMonitorMap.remove(id); } getTimeMonitor(id).startMonitor(); } public TimeMonitor getTimeMonitor(int id) {TimeMonitor monitor = mtimemonitorMap.get (id); if (monitor == null) { monitor = new TimeMonitor(id); mTimeMonitorMap.put(id, monitor); } return monitor; }}Copy the code

The following aspects need to be addressed:

  • Life cycle nodes of the application.
  • Important methods that need to be initialized at startup, such as database initialization and reading some data locally.
  • Other time-consuming algorithms.

For example, add dot statistics to the Application and the first Activity at startup:

Dot Application

@Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); TimeMonitorManager.getInstance().resetTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START); } @Override public void onCreate() { super.onCreate(); SoLoader.init(this, /* native exopackage */ false); TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recordingTimeTag("A pplication-onCreate"); }Copy the code

The first Activity is done

@Override protected void onCreate(Bundle savedInstanceState) { TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recordingTimeTag("S plashActivity-onCreate"); super.onCreate(savedInstanceState); initData(); TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).recordingTimeTag("S plashActivity-onCreate-Over"); } @Override protected void onStart() { super.onStart(); TimeMonitorManager.getInstance().getTimeMonitor(TimeMonitorConfig.TIME_MONITOR_ID_APPLICATION_START).end("SplashActivity -onStart", false); }Copy the code

The characteristics of

Precise, can be brought online, but the code is intrusive and costly to modify.

Matters needing attention

  • 1. When uploading data to the server, it is recommended to report the data according to the last number of the user ID.

  • 2. OnWindowFocusChanged is only the first frame time. The end point of App startup should be when the real data is displayed (usually the data in the first frame), as shown in the first data in the list. Remember to use getViewTreeObserver().addonPreDrawListener () (you can use addOnDrawListener above API 16), which will defer the task until the list is displayed, for example, There is a RecyclerView implementation list in the homepage of the Awesome WanAndroid project. The start and end time is the first frame time of the list, that is, the first data display time of the list. RecyclerView adaptor ArticleListAdapter convert (onBindViewHolder) = RecyclerView adaptor ArticleListAdapter

      if (helper.getLayoutPosition() == 1 && !mHasRecorded) {
          mHasRecorded = true;
          helper.getView(R.id.item_search_pager_group).getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
              @Override
              public boolean onPreDraw() {
                  helper.getView(R.id.item_search_pager_group).getViewTreeObserver().removeOnPreDrawListener(this);
                  LogHelper.i("FeedShow");
                  return true;
              }
          });
      }
    Copy the code

The specific instance code can be viewed here.

Why not use the onWindowFocusChanged method as the start end point?

Since the user needs to have a network request to return real data to see the real interface, onWindowFocusChanged is only the first frame of the interface drawing, but the data in the list needs to be downloaded from the network, so the first frame data in the list should be used as the start and end point.

4. AOP(Aspect Oriented Programming)

Aspect oriented programming is a technique that implements unified maintenance of program functions through precompilation and dynamic proxy at run time.

1,

Using AOP can isolate each part of the business logic, so that the coupling between the parts of the business logic is reduced, the reusability of the program is improved, and the development efficiency is greatly improved.

2. AOP core concepts

1. Crosscutting concerns

Which methods to intercept and what to do after intercepting.

2. Aspect

A class is an abstraction of a feature of an object, and a section is an abstraction of a crosscutting concern.

3. Join Points

The point (method, field, constructor) that was intercepted.

4. PointCut

The definition of intercepting JoinPoint.

5. Advice

The code to be executed after intercepting the JoinPoint is classified into three types: front, back, and surround.

3. Preparation: Access AspectJx for aspect coding

First, in order to use AOP in Android, AspectJ needs to be introduced. In the project root directory under build.gradle, add:

The classpath 'com. Hujiang. Aspectjx: gradle - android plugin - aspectjx: 2.0.0'Copy the code

Next, in the app directory under build.gradle, add:

Apply the plugin: 'android - aspectjx' implement org. Aspectj: aspectjrt: 1.8 + 'Copy the code

4, AOP buried point combat

Joinpoints are generally located in the following positions

  • 1. Function calls
  • 2. Get and set variables
  • 3. Class initialization

The PointCut is used to intercept the JoinPoint we specify, and the Advice is used to intercept the code to execute after the JoinPoint. Advice usually has the following types:

  • 1, Before: execute Before PointCut
  • 2, After: After the PointCut
  • 3, Around: execute before and after the PointCut

First, let’s take a small chestnut:

@Before("execution(* android.app.Activity.on**(..) )") public void onActivityCalled(JoinPoint joinPoint) throws Throwable { ... }Copy the code

In execution there is a matching rule, where the first * matches any method return value, followed by syntax code that matches all activities that start with on.

In AspectJx, execution is the type that handles the Join Point. There are two types:

  • 1, call: inserted in the function body
  • 2. Execution: Insert outside the function body

How do I collect the time of all methods in the Application?

@Aspect
public class ApplicationAop {

    @Around("call (* com.json.chao.application.BaseApplication.**(..))")
    public void getTime(ProceedingJoinPoint joinPoint) {
    Signature signature = joinPoint.getSignature();
    String name = signature.toShortString();
    long time = System.currentTimeMillis();
    try {
        joinPoint.proceed();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
    }
    Log.i(TAG, name + " cost" +     (System.currentTimeMillis() - time));
    }
}
Copy the code

In the above code, we need to note that different Action types have different method input parameters. The differences are as follows:

  • When Action is Before or After, the method input parameter is JoinPoint.
  • When Action is Around, the method parameter is ProceedingPoint.
The biggest difference between Around and Before and After:

ProceedingPoint is different from JoinPoint in that it provides the proceed method to execute the target method.

Summarize AOP features

  • 1. Non-invasive
  • 2, easy to modify, recommended to use

5. Start the speed analysis tool – TraceView

1. Use mode

  • 1. Add debug.startMethodTracing (), detection method, debug.stopMethodTracing () to the code. (You need to export the generated.trace file to your computer using ADB pull and load it using Android Studio Profiler)
  • 2, open Profiler -> CPU -> click Record -> Stop -> view Profiler Top Down/Bottom Up area to find out the hot point method of time.

2, Profile CPU

Using the CPU module of the Profile can help us quickly find the time consuming hotspot methods. Let’s examine this module in detail.

1, Trace types

There are four Trace types, as shown below.

1, Trace Java Methods

Time and CPU information are recorded for each method. The runtime performance is significantly affected.

2, Sample Java Methods

Compared to Trace Java Methods, which records the time and CPU information of each method, it frequently captures the application’s call stack during the execution of the application’s Java code, has less impact on runtime performance, and can record a larger area of data.

3, Sample C/C++ Functions

To deploy to Android 8.0 and above, use SimplePerf internally to track your application’s native code, or use SimplePerf on the command line.

4, Trace System Calls

  • Check the interaction between applications and system resources.
  • View CPU bottlenecks for all cores.
  • Systrace is used internally, and the systrace command can also be used.

2, the Event timeline

Used to show the activities that an application transitions between different states during its life cycle, such as user interactions, screen rotation events, and so on.

3, CPU timeline

Displays real-time CPU usage of applications, real-time CPU usage of other processes, and total number of threads used by applications.

4. Thread activity timeline

Lists each thread in the application process and uses a different color to indicate its activity on its timeline.

  • Green: Threads are active or ready to use the CPU.
  • Yellow: The thread is waiting for AN IO operation. (important)
  • Gray: The thread is sleeping and does not consume CPU time.

5. Check the trace data window

Profiles provide four Windows for checking trace data, as shown below:

1, Call Chart

Provides a graphical representation of function tracking data.

  • Horizontal axis: represents the time period and time of the call.
  • Vertical axis: Displays the called party.
  • Orange: System API.
  • Green: use your own method.
  • Blue: Third-party apis (including Java apis).
prompt

Right-click on Jump to Source to Jump to the specified function.

2, Flame Chart

Gather the exact same methods with the same order of callers.

  • Horizontal axis: The relative amount of time to execute each method.
  • Vertical axis: Displays the called party.
Use skills

There may be performance issues if you look at which function at the top takes up the most width (the flat top).

3, Top Down

  • Recursive call list, providing self, children, total time, and ratio to represent the function being called.
  • The Flame Chart is a graphical representation of the Top Down list data.

4, Bottom Up

  • Expanding the function shows the caller.
  • Sort the functions in descending order of CPU time consumption.

Matters needing attention

When we look at the above four areas of tracking data, we should pay attention to the two times on the right, as follows:

  • Wall Clock Time: Indicates the execution Time of the program.
  • Thread Time: CPU execution Time.

3. TraceView summary

The characteristics of

  • 1. Graphical display of execution time, call stack, etc.
  • 2, comprehensive information, including all threads.
  • 3, the run time is expensive, the whole will be slow, the results are not true.
  • 4. Find the most time consuming path: Flame Chart, Top Down.
  • 5, Find the most time consuming node: Bottom Up.

role

It mainly conducts hotspot analysis to obtain the following two kinds of data:

  • The most time-consuming method to execute at a time.
  • The most frequently executed method.

6. Start the speed analysis tool – Systrace

1. Usage: Code piling

First, we can define a static Trace factory class that encapsulates trace.begainSection (), trace.endSection () into I and O methods, and then pile before and after the methods we want to analyze.

Then, on the command line, run the systrace. Py script as follows:

python /Users/quchao/Library/Android/sdk/platform-tools/systrace/systrace.py -t 20 sched gfx view wm am app webview -a "com.wanandroid.json.chao" -o ~/Documents/open-project/systrace_data/wanandroid_start_1.html
Copy the code

The meanings of parameters are as follows:

  • -t: indicates that the statistics collection period is 20s.
  • Shced: CPU scheduling information.
  • GFX: Graphic information.
  • View: view.
  • Wm: Window management.
  • Am: Event management.
  • App: Indicates the application information.
  • Webview: Indicates webView information.
  • -a: specifies the package name of the target application.
  • -o: the generated systrace. HTML file.

How do I view the data?

In the UIThread column you can see the core system method time zones and our own method time zones captured using code stubs.

2. Systrace principle

  • First, insert some information (labels) into some key links of the system (such as SystemServcie, virtual machines, Binder drivers).
  • Then, the execution time of a certain core process is determined by the start and end of the Label, and the running time information of the critical path of the system is obtained by collecting the Label information. Finally, the running performance information of the whole system is obtained.

Among them, some important modules in the Android Framework are inserted with label information, and users can also add customized labels in their apps.

3. Systrace summary

The characteristics of

  • Generate Html reports with data from the Android kernel.
  • The higher the system version, the more available system labels are added to the Android Framework, and the more system modules can be supported and analyzed.
  • Having to manually narrow down the scope will help you speed up the analysis of the convergence problem so that you can quickly locate and resolve the problem.

role

  • Mainly used for rendering performance analysis.
  • It takes time to analyze the system’s key methods and application methods.

7. Start monitoring

1. Laboratory monitoring: video recording

  • 80% draw
  • Image recognition

Pay attention to

Cover different scenarios of high and low end models.

2. Online monitoring

The target

Accurate startup time statistics are required.

1. Timing of the start end

Whether the startup end time is displayed on the interface and can be operated by the user.

2, start time deduction logic

Splash screens, ads, and novice guides should all be deducted from startup time.

3. Start the exclusion logic

You need to exclude statistics when starting Broadcast and Server and entering the background.

4. What indicators are used to measure the speed of startup?

Average startup time problem

Some users with a poor experience are likely to be averaged.

Recommended indicators
  • 1. The ratio of fast to slow

Such as the fast driving ratio of 2s and slow driving ratio of 5S, we can see what proportion of users have good experience and what proportion of users have bad experience.

  • 2. 90% of user startup time

If 90% of users start up in less than 5s, then the start time in the 90% range is 5s.

5. What are the types of startup?

  • Initial installation startup
  • Overwrite installation startup
  • Cold Start (indicator)
  • Hot start (reflects activity or survivability of the program)

Using Profilo tool of Facebook for reference, it monitors the time of starting the whole process, makes automatic comparison between different versions in the background, and monitors whether new time-consuming functions are added in the new version.

Iv. Start the optimization routine plan

Common problems during startup

  • 1. No response after clicking the icon for a long time: Preview window is disabled or set to transparent.
  • 2. Home page display is too slow: too many initialization tasks.
  • 3. Unable to operate after the home page is displayed: too many delayed initialization tasks occupy the main thread CPU time slice.

Optimization of regional

Application, Activity creation, and callback processes.

1. Theme switch

Use the Activity’s windowBackground theme property to pre-set a launch image (layer-list implementation). After the launch, SetTheme (R.style.appTheme) precedes super.oncreate () in the Activity’s onCreate() method.

advantages

  • Easy to use.
  • Avoid starting the white screen and click on the launch icon does not respond to the situation.

disadvantages

  • Treat the symptoms, but not the root cause, to create a superficial sense of quickness.
  • For medium and low-end phones, the total splash screen time will be longer. Therefore, you are advised to enable preview splash screen only on Android6.0/7.0 or above to provide better experience for users with good mobile performance.

2. Third-party libraries are loaded lazily

On-demand initialization, especially for libraries that do not need to be initialized when the application starts, can wait until time to load.

3, asynchronous initialization preparatory knowledge – thread optimization

1. Analysis of Android thread scheduling principle

Thread Scheduling Principle

  • 1. At any given time, only one thread is running on the CPU.
  • 2, multi-threaded concurrent, take turns to obtain the use of CPU.
  • 3, the JVM is responsible for thread scheduling, according to a specific mechanism to allocate CPU use rights.

Thread scheduling model

1. Time-sharing scheduling model

The CPU is allocated in turn and evenly distributed.

2. Preemptive scheduling model

Obtain with a high priority.

How do I interfere with thread scheduling?

Set the thread priority.

Android Thread Scheduling

1, the nice value
  • Process.
  • The smaller the value, the higher the priority.
  • The default is THREAD_PRIORITY_DEFAUT, 0.
2, the cgroup

It is a more strict group scheduling policy. It can be classified into the following two types:

  • Background group (default).
  • Foreground group, to ensure that foreground threads can get more CPU

Pay attention to the point

  • Too many threads cause frequent CPU switching, which reduces the thread running efficiency.
  • Identify task importance to determine which thread priority to use.
  • Priorities are inherited.

2. Android asynchronous mode

1, the Thread

  • The simplest and most common asynchronous mode.
  • It is not easy to reuse and costly to create and destroy frequently.
  • Complex scenarios are not easy to use.

2, HandlerThread

  • A thread that has its own message loop.
  • Serial execution.
  • Running for a long time, constantly fetching tasks from the queue.

3, IntentService

  • The HandlerThread is created internally from the Service.
  • Asynchronous, does not occupy the main thread.
  • It has a high priority and is not easily killed by the system.

4, AsyncTask

  • The utility classes provided by Android.
  • No need to handle thread switching yourself.
  • Note version inconsistency (API 14 resolved)

5. Thread pool

  • Thread pools provided by Java.
  • Easy to reuse, reduce the time of frequent creation and destruction.
  • Powerful functions, such as timing, task queue, concurrency control and so on.

6, RxJava

Provided by the powerful Scheduler Scheduler collection.

Different types of Scheduler:

  • IO
  • Computation

Asynchronous Mode summary

  • Recommended: From the back to the front.
  • Choose the right way for the right scene.

3, Android thread optimization actual combat

Thread usage guidelines

  • 1. It is forbidden to use new Thread.
  • 2. Provide a basic thread pool for each line of service to prevent each line of service from maintaining its own set of thread pools, resulting in too many threads.
  • 3, select the appropriate asynchronous mode according to the task type: low priority, long execution, HandlerThread; Timed execution of time-consuming tasks, thread pools.
  • 4, create threads must be named to make it easier to locate the Thread’s home, change the name at runtime thread.currentThread ().setName.
  • 5, key asynchronous task monitoring, note that asynchronous is not equal to no time, it is recommended to use AOP to do the monitoring.
  • 6, emphasis on the priority setting () on the basis of the condition of the task, Process, setThreadPriority () can be set up for many times.

How do I lock the thread creator

Lock thread to create background

  • Converge threads as the project gets larger.
  • Project source code, three – party library, AAR in the creation of threads.

Lock the thread creation scheme

Hook points: constructors or specific methods, such as the constructor for Thread.

In actual combat

Here we Hook Thread directly with dimension’s Epic. AttachBaseContext invokes the DexposedBridge. HookAllConstructors method, as shown below:

DexposedBridge.hookAllConstructors(Thread.class, new XC_MethodHook() { 
    @Override protected void afterHookedMethod(MethodHookParam param)throws Throwable {                         
        super.afterHookedMethod(param); 
        Thread thread = (Thread) param.thisObject; 
        LogUtils.i("stack " + Log.getStackTraceString(new Throwable());
    }
);
Copy the code

Find the thread creation information from the log and communicate the solution with the relevant business party based on the stack information.

5, thread convergence elegant practice preliminary

Thread convergence conventional scheme

  • Use the same thread library based on thread creation stack considerations.
  • Each business has its own thread library offline.

Question: How does the base library use threads?

Relies directly on the thread library, but the problem is that updates to the thread library can cause updates to the underlying library.

The base library uses threads elegantly

  • The underlying library internally exposes the API: setExecutor.
  • The unified thread library is injected during initialization.

Distinguish task types when unifying the thread library

  • IO intensive tasks: IO intensive tasks do not consume CPU, and the core pool can be large. Common IO – intensive tasks such as file reading, writing, network requests and so on.
  • CPU intensive tasks: The core pool size is related to the number of CPU cores. Common CPU-intensive tasks, such as complex computing operations, require a large number of CPU units.

Implement the underlying thread pool component for performing multiple types of tasks

The basic thread pool component is currently in the initiator SDK and is very simple to use. The sample code looks like this:

// If the current task is CPU intensive, From basic threads ChiZu / / DispatcherExecutor get to used to perform cpu-intensive task thread pool DispatcherExecutor. GetCPUExecutor (). The execute (YourRunable ()); // If the current task is IO intensive, From basic threads ChiZu / / DispatcherExecutor gets to the thread pool is used to perform IO intensive tasks DispatcherExecutor. GetIOExecutor (). The execute (YourRunable ());Copy the code

The specific implementation of the source code is relatively simple, and I have a detailed explanation of each code, not a specific analysis. The code looks like this:

Public class DispatcherExecutor {/** * Thread pool for CPU-concentrated tasks */ private static ThreadPoolExecutor sCPUThreadPoolExecutor; /** * Thread pool for IO - heavy tasks */ private static ExecutorService sIOThreadPoolExecutor; Private static final int CPU_COUNT = runtime.geTruntime ().availableProcessors(); /** * Number of CPU cores available to the device */ private static final int CPU_COUNT = runtime.geTruntime ().availableProcessors(); */ private static final int CORE_POOL_SIZE = math.max (2, math.min (CPU_COUNT - 1, 5)); */ private static final int MAXIMUM_POOL_SIZE = CORE_POOL_SIZE; / / private static final int MAXIMUM_POOL_SIZE = CORE_POOL_SIZE; /** * The number of idle threads in the pool waiting for a timeout. If the number of threads in the pool is greater than corePoolSize or if the allowCoreThreadTimeOut setting is set, * Threads are checked for activity based on the keepAliveTime value and are destroyed upon timeout. Otherwise, the thread will wait forever for new work. */ private static final int KEEP_ALIVE_SECONDS = 5; Private static final BlockingQueue<Runnable> S_POOL_WORK_QUEUE = new LinkedBlockingQueue<>(); Private static final DefaultThreadFactory S_THREAD_FACTORY = new DefaultThreadFactory(); /** * The thread pool is executing a time-consuming task. */ private static final RejectedExecutionHandler S_HANDLER = new RejectedExecutionHandler() {@override public  void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { Executors.newCachedThreadPool().execute(r); }}; Public static ThreadPoolExecutor getCPUExecutor() {return sCPUThreadPoolExecutor; } /** * Get the IO thread pool ** @return IO thread pool */ public static ExecutorService getIOExecutor() {return sIOThreadPoolExecutor; } /** * implement a ThreadFactory */ private static class implements ThreadFactory {private static final implements ThreadFactory 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) {Thread t = newThread(group, r); namePrefix + threadNumber.getAndIncrement(), 0); If (t.setdaemon ()) {// 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 be active against the keepAliveTime value, once the timeout will destroy the thread. Otherwise, the thread will wait forever for new work. sCPUThreadPoolExecutor.allowCoreThreadTimeOut(true); // CachedThreadPool for IO intensive tasks / / it can be allocated most Integer. MAX_VALUE a non-core threads used to perform a task sIOThreadPoolExecutor = Executors. NewCachedThreadPool (S_THREAD_FACTORY); }}Copy the code

6. Core issues of thread optimization

1. Why do we encounter problems with thread usage?

During the development phase of the project, the infrastructure was neglected and a uniform thread pool was not adopted, resulting in too many threads.

form

Asynchronous task execution takes too long, causing the main thread to stall.

Question why
  • 1, Java thread scheduling is preemptive, thread priority is more important, need to distinguish.
  • 2. There is no distinction between IO and CPU-intensive tasks, so the main thread cannot grab the CPU.

2. How to optimize threads in a project?

Core: Thread convergence
  • Find the stack information of the corresponding thread by Hook method, discuss with the business side whether to set up a single thread, and use a unified thread pool as far as possible.
  • Each base library exposes a way to set up the thread pool to avoid the problem that thread library updates cause the base library to need to be updated.
  • In a unified thread pool, pay attention to the distinction between IO and CPU intensive tasks.
  • Other details: important asynchronous task statistics time, attention to asynchronous task priority and thread name Settings.

4. Asynchronous initialization

1. Core ideas

Subthreads share tasks from the main thread, reducing time in parallel.

2. Note points for asynchronous optimization

  • 1, do not meet the asynchronous requirements.
  • 2. It needs to be done in one phase (using CountDownLatch to ensure that the asynchronous task is complete before moving on to the next phase).
  • 3. If the main thread is not initialized when it is used, it will be initialized before the use.
  • 4. Distinguish between CPU-intensive and IO intensive tasks.

3. Evolution of asynchronous initialization scheme

  • 1, the new Thread
  • 2, IntentService
  • 3. Thread pools (properly configured and selected CPU – and IO – intensive thread pools)
  • 4. Asynchronous initiator

4, the optimal solution of asynchronous optimization: asynchronous initiator

Asynchronous initiator source code and use demo address

Conventional asynchronous optimization of pain points

  • 1. Code inelegance: for example, multiple executorService.submit code blocks when multiple parallel asynchronous tasks are implemented using thread pools.
  • 2. The scenario is not easy to handle: there are dependencies among initialization tasks. For example, the initialization task of pushing the SDK depends on the initialization task of obtaining the device ID. In addition, some tasks need to be initialized at certain times, such as before the Application’s onCreate method completes execution.
  • 3. High maintenance cost.

Core idea of starter

Make full use of CPU multi-core, automatically comb the task sequence.

Initiator process

The flowchart of an initiator is as follows:

The topic flow of the initiator is the middle area in the figure above, namely the main thread and the concurrent block. It is important to note that the head task and tail task in the figure above are not included in the topic process of the initiator. They are only used to handle some general tasks before and after startup. For example, we can do some operations to obtain general information in the head task. The tail task can output logs and report data.

So, here’s a summary of the core startup process, as follows:

  • 1. Task task-ization, the startup logic is abstracted into Task (Task corresponds to one initializing Task).
  • 2, according to sort all task dependencies to generate a directed acyclic graph, such as the above said to push the SDK initialization tasks need to rely on the acquisition device id initialization tasks, are likely to have dependencies between the tasks, so will be ordered their dependencies to generate a directed acyclic graph to maximize parallel efficiency.
  • 3. Multithreading executes in order of priority: for example, the initialization task of obtaining the device ID must be initialized before the initialization task of pushing the SDK can be carried out.

Asynchronous initiator optimization actual combat and source code analysis

Next, we will use the asynchronous initiator to perform asynchronous optimization in the onCreate method of the Application, as follows:

Taskdispatcher.init (this); / / 2, creates a starter instance, every time to get here is a new object TaskDispatcher dispatcher. = TaskDispatcher createInstance (); AddTask (new InitAMapTask()).addTask(new InitStethoTask()).addTask(new InitStethoTask()).addTask(new) InitWeexTask()) .addTask(new InitBuglyTask()) .addTask(new InitFrescoTask()) .addTask(new InitJPushTask()) .addTask(new InitUmengTask()) .addTask(new GetDeviceIdTask()) .start(); // 4. Wait for the completion of wechat SDK initialization before the program can execute dispatcher.await();Copy the code

Here the TaskDispatcher is our initiator call class. First, in comment 1, we need to initialize the initiator by calling the Init method of the TaskDispatcher. The source code looks like this:

public static void init(Context context) {
    if (context != null) {
        sContext = context;
        sHasInit = true;
        sIsMainProcess = Utils.isMainProcess(sContext);
    }
}
Copy the code

As you can see, only a few base fields are initialized. Next, in comment 2, we create the initiator instance, whose source code looks like this:

Public static TaskDispatcher createInstance() {if (! sHasInit) { throw new RuntimeException("must call TaskDispatcher.init first"); } return new TaskDispatcher(); }Copy the code

In the createInstance method we create a new TaskDispatcher instance every time. Then, in note 3, we configure a series of initializing tasks for the initiator and start the initiator. Note that the Task here can be used to execute either an asynchronous Task (the child thread) or a non-asynchronous Task (the main thread). InitStethoTask: InitStethoTask: InitStethoTask: InitStethoTask: InitStethoTask

/** * async Task */ public class extends Task {@override public void run() { Stetho.initializeWithDefaults(mContext); }}Copy the code

InitStethoTask is directly inherited from Task. The runOnMainThread method in Task returns false, indicating that Task is used to process asynchronous tasks. The run method is the run method of Runnable. Next, let’s look at another example for initializing non-asynchronous tasks, such as InitWeexTask for wechat SDK initialization. The code looks like this:

Public class extends Task {@override public Boolean needWait() {return true; } @Override public void run() { InitConfig config = new InitConfig.Builder().build(); WXSDKEngine.initialize((Application) mContext, config); }}Copy the code

As you can see, it directly inherits from MainTask, whose source code looks like this:

public abstract class MainTask extends Task { @Override public boolean runOnMainThread() { return true; }}Copy the code

MainTask inherits Task directly and simply overwrites the runOnMainThread method to return true, indicating that it is used to initialize non-asynchronous tasks in the main thread.

In addition, notice that InitWeexTask overwrites needWait and returns true, so that at some point you must wait for InitWeexTask initialization to complete before you can proceed. A moment here is where our code at comment 4 in the onCreate method of Application executes: dispatcher.await(), whose implementation source looks like this:

/** * Number of tasks to wait */ private AtomicInteger mNeedWaitCount = new AtomicInteger(); Private List<Task> mNeedWaitTasks = new ArrayList<>(); private List<Task> mNeedWaitTasks = new ArrayList<>(); private CountDownLatch mCountDownLatch; private static final int WAITTIME = 10000; @uithRead public void await() {try {if (dispatcherlog.isdebug ()) { DispatcherLog.i("still has " + mNeedWaitCount.get()); for (Task task : mNeedWaitTasks) { DispatcherLog.i("needWait: " + task.getClass().getSimpleName()); }} // 2, call the await method on mCountDownLatch to wait as long as the task is not complete. If (mNeedWaitCoun.get () > 0) {if (mCountDownLatch == NULL) {throw new RuntimeException("You have to call start() before call await()"); } mCountDownLatch.await(WAITTIME, TimeUnit.MILLISECONDS); } } catch (InterruptedException e) { } }Copy the code

First, in comment 1, we print the number of waiting tasks and task names only during the test phase. Then, in comment 2, call the await method on mCountDownLatch to wait as long as the number of tasks to wait is greater than 0, that is, as long as there are still tasks to wait for, notice that we have set the timeout time to 10s. When a task is completed, whether it is asynchronous or non-asynchronous, it will be executed in the markTaskDone(mTask) method of mTaskDispatcher.

/** * Task */ private volatile List<Class<? extends Task>> mFinishedTasks = new ArrayList<>(100); public void markTaskDone(Task task) { if (ifNeedWait(task)) { mFinishedTasks.add(task.getClass()); mNeedWaitTasks.remove(task); mCountDownLatch.countDown(); mNeedWaitCount.getAndDecrement(); }}Copy the code

As you can see, the completion of each task here decreases the lock count of mCountDownLatch by one, and at the same time decreases the number of our mNeedWaitCount atomic integer wrapper class by one.

In addition, we mentioned that initiators abstract the dependencies between tasks into a directed acyclic graph. InitJPushTask is dependent on GetDeviceIdTask in the initialization code above. How do we tell initiators about their dependencies?

Just override the dependsOn() method in InitJPushTask and return a list of tasks containing GetDeviceIdTask as follows:

/** * InitJPushTask extends Task {@override public List< class <? extends Task>> dependsOn() { List<Class<? extends Task>> task = new ArrayList<>(); task.add(GetDeviceIdTask.class); return task; } @Override public void run() { JPushInterface.init(mContext); MyApplication app = (MyApplication) mContext; JPushInterface.setAlias(mContext, 0, app.getDeviceId()); }}Copy the code

At this point, our asynchronous initiator is analyzed. Let’s take a look at how to do delayed initialization efficiently.

5. Delay initialization

1. General scheme: Use the residence time of the splash screen page for partial initialization

  • The new Handler (.) postDelayed ().
  • Called after the UI is displayed.

2. Routine initialization of pain points

  • Timing is not easy to control: Handler postDelayed specifies a delay time that is not easy to estimate.
  • The UI is stuck: the user may still be scrolling through the list.

3. The optimal solution of delay optimization: delay initiator

Delay initiator source code and use demo address

core idea

The IdleHandler feature is used to perform batch initialization of delayed tasks when the CPU is idle.

Delay starter optimization actual combat and source analysis

The code for delaying initializing an initiator is simple, as follows:

Public class DelayInitDispatcher {private Queue<Task> mDelayTasks = new LinkedList<>(); private MessageQueue.IdleHandler mIdleHandler = new MessageQueue.IdleHandler() { @Override public boolean queueIdle() { // The advantage of batching is that each task occupies the main thread for a relatively short time and the CPU is idle. 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); }}Copy the code

In DelayInitDispatcher, we provide the mDelayTasks queue for each task to be added, and the consumer simply calls the addTask method. When the CPU is idle, the mIdleHandler calls back to its queueIdle method, and we can pick up and execute tasks one by one. The benefit of batching is that each task takes up the main thread for a relatively short period of time, while the CPU is idle, which effectively avoids UI delays and really improves the user experience.

Using SplashActivity is very simple. We can use SplashActivity’s AD page stay time directly to delay initialization, as shown in this code:

@Override
public void onWindowFocusChanged(boolean hasFocus) {
    super.onWindowFocusChanged(hasFocus);
    GlobalHandler.getInstance().getHandler().post((Runnable) () -> {
        if (hasFocus) {
            DelayInitDispatcher delayInitDispatcher = new DelayInitDispatcher();
            delayInitDispatcher.addTask(new InitOtherTask())
                    .start();
        }
    });
}
Copy the code

Note that tasks that can be asynchronous are loaded using an asynchronous initiator in the Application’s onCreate method (or non-distinct tasks that must be executed before the Application’s onCreate method completes). For tasks that cannot be asynchronous, We can use the delayed initiator to load. If the task can be loaded on time, you can use lazy loading.

Delayed initiator advantage

  • The timing of execution is clear.
  • Relieve the interface UI lag.
  • Really improve the user experience.

6. Optimize Multidex preloading

As we all know, the first time MultiDex takes too long to install or upgrade, we need to optimize MultiDex for preloading.

1. Optimization steps

  • 1. Start a process to asynchronously load Multidex for the first time, that is, extract the Dex and perform the Dexopt operation.
  • 2. At this point, the main process Application enters the while loop to continuously check whether the Multidex operation is complete.
  • 3. When the execution reaches Multidex, the Dex has been found and optimized, and the execution is direct. After MultiDex executes, the main Application process continues to execute ContentProvider initialization and the Application’s onCreate method.

Multidex Optimizes Demo addresses

Pay attention to

Above 5.0, ART is used by default, and class. dex has been converted to OAT file during installation, so there is no need to optimize. Therefore, judge that Multidex should be preloaded only when the main process and SDK below 5.0.

2. What is the dex-OPT process?

It mainly includes the optimization of inline and quick directives.

So what is inline?

Causes the compiler to replace the function call instruction with the function body code at the function call.

Inline?

The transfer operation of function calls has a time and space overhead, especially for functions that are small and frequently called, and it is important to solve the efficiency problem. The introduction of inline functions is to solve this problem.

How is inline optimized?

Inline functions improve the time performance of a program in at least three ways:

  • 1, to avoid the function call must be performed on the stack out of the stack operation.
  • 2. Since the function body code is moved to the function call, the compiler can obtain more contextual information and further optimize the function body code and the called code based on this information.
  • 3. If an inline function is not used, the program executes at the point where the function is called and then executes the code at the point where the function body is located. Generally, the location of the function call and the location of the function code are not close in the code segment, so it is easy to form the operating system page break. The operating system will need to move the code from hard disk to memory, and the time required to move it will increase by an order of magnitude. Using inline functions reduces the chance of page – missing interrupts.

What should we be aware of with inline usage?

  • 1. Since inline functions insert the body of the function instead of calling the function, memory can be wasted if the function is called in many places in the program.
  • 2, the general program of the stack out of the stack operation also needs a certain code, this code to complete the stack pointer adjustment, parameter passing, site protection and recovery and other operations.

If the body of a function is smaller than the code generated by the compiler, it can be safely declared inline, which reduces the footprint. Declaring the function inline increases memory usage when the body of the function is larger than the print-out code.

  • A C++ application should determine whether to define inline functions based on the specific application scenario, function size, function location, function call frequency, time performance, memory performance requirements, and other factors.
  • Loop and switch statements are not allowed in inline functions.

3. Optimize Tiktok BoostMultiDex

In order to completely solve the problem of slow MutiDex loading time, the tiktok team dug deep into the underlying system mechanism of Dalvik virtual machine, redesigned and optimized the DEX related processing logic, and launched the BoostMultiDex solution, which can reduce the black screen waiting time by more than 80%. Save the upgrade experience for older Android users.

The specific implementation principle is as follows: at the first startup, directly load the original DEX that has not been optimized by OPT, so that the APP can start normally first. Then start a separate process in the background, slowly finish the OPT of DEX, as far as possible to avoid affecting the normal use of the foreground APP. The solution to bypass ODEX and directly load DEX is as follows:

  • 1) Extract the bytecode of the original Secondary DEX file from APK
  • 2) Obtain dvm_dalvik_system_DexFile array from DLSYM
  • 3), query in the array Dalvik_dalvik_system_DexFile_openDexFile_bytearray function
  • 4), call this function, pass in the DEX bytecode obtained from APK one by one, complete DEX loading, get a legitimate DexFile object
  • 5) Add DexFile objects to the pathList of the APP PathClassLoader

Add: getDex will throw an exception because memMap needs to be assigned, but Dalvik_dalvik_system_DexFile_openDexFile_bytearray does not. After analyzing the code, we found that as long as the dex_object object is not empty, it will return directly, and will not go down to the memMap. Therefore, after loading the DEX array, we can generate a dex_object and inject it into pDvmDex.

If you are interested, you can check out this article: Tiktok BoostMultiDex Optimization Practice: Reducing APP first startup time by 80% on Android low version

7. Class preloading optimization

Classes that take a long time to initialize are loaded asynchronously in advance in the Application.

How do I find long classes?

Replace the system ClassLoader, print the class loading time, and select the classes that need to be loaded asynchronously.

Pay attention to

  • Class.forname () only loads the Class itself and reference classes to its static variables.
  • New class instances can optionally load references to class member variables.

8. WebView startup optimization

  • 1. It takes time to create a WebView for the first time, so you need to create a WebView and initialize its kernel in advance.
  • 2, use WebView cache pool, when using WebView from the cache pool, pay attention to memory leak problem.
  • 3. Local offline package: preset static page resources.

9. Page data preloading

When the home page is idle, the data of other pages is loaded and saved to memory or database. When the page is opened, it is judged that it has been preloaded, and the data is fetched directly from memory or database and displayed.

10. Do not start child processes during startup

Child processes share CPU resources, causing CPU shortage in the main process. In addition, in the case of multiple processes, it is important to be able to do some initialization in onCreate to separate processes.

Pay attention to startup sequence

App onCreate is preceded by ContentProvider initialization.

11. Optimized the drawing of splash screen and home page

  • 1. Layout optimization.
  • 2. Transition rendering optimization.

For layout and drawing optimization, see Drawing optimization for Android Performance Optimization.

Reference links:

1. Launch optimization of Android Development Master course

2, Alipay client architecture analysis: Android client startup speed optimization of “garbage collection”

3. Alipay App construction optimization analysis: Optimize Android startup performance by rearranging installation packages

4. Facebook Redex bytecode optimization tool

5. Evolution of wechat Android hot patch practice

6, Android App hot patch dynamic repair technology introduction

7, Dalvik Optimization and Verification With Dexopt

Wechat opened the Hardcoder on Github. What impact will it have on Android developers?

After three years of research and development, how does OPPO’s Hyper Boost engine accelerate systems, games and applications?

Sorry, Xposed really can do whatever you want

11. Understanding of wall clock time, user CPU time, and system CPU time

12. Best Practices for Android Application Performance Optimization

13, will know will be | Android in aspects of performance optimization are here

Geek Time’s Top team takes you through Android performance analysis and optimization

15, the initiator source code

16, MultiDex optimization source code

17, Use Gradle to automatically add Trace tags

Thank you for reading this article. I hope you can share it with your friends or tech groups. It means a lot to me.

I hope we can be friends beforeGithub,The Denver nuggetsLast time we shared our knowledge.