preface

Series “alipay client architecture parsing will be from alipay client architecture design, segmentation and dismantling the client” container framework design “, “network optimization”, “performance start optimization”, “automatic log collection”, “RPC component design”, “mobile application monitoring, diagnosis, localization” the specific implementation, Lead you to further understand alipay in the client architecture of iteration and optimization process.

Starting an App is the most essential operation for users to use any App. The performance of the whole startup process, from clicking the App icon to displaying the home page, seriously affects the user experience. As a super App, the startup performance of Alipay client is certainly one of the important indicators we pay attention to. The following will introduce the specific design ideas of the startup performance optimization of Alipay on iOS terminal from three aspects.

Startup time optimization

Before analyzing startup time, let’s look at two ways to launch an App.

  • Hot start: When an application is started, the application processes and data already exist in the system memory. The system only switches the application status from background to foreground.
  • Cold start: When an application is started, it does not exist in the buffer cache of the system kernel. For example, it is started after the application is started for the first time or the device is restarted.

In contrast, cold start is more important, usually we analyze the start time, are referring to the cold start.

In order to analyze the startup time, we also need to understand the startup process, iOS app startup is generally divided into the following stages:

  • forpre-main() :

The time taken for the entire pre-main() phase can be obtained by adding the environment variable DYLD_PRINT_STATISTICS=1, as shown in the figure below.

These stages are controlled by the system. For specific optimization in these stages, you can refer to the scheme provided in WWDC2013 Session (address attached at the end of the article), which is not detailed here.

  • forpost-main() :

In this part, we mainly start the framework initialization, home page data acquisition, home page rendering and other business logic. In this part, we only reserve the necessary initialization operations, and try to put the logic after or in the background thread for execution. The optimization scheme needs to be analyzed based on actual business scenarios and application architectures, and corresponding policies should be adopted.

Background Fetch

In addition to these generic optimizations, we also explored some innovative approaches. Before introducing Background Fetch, let’s take a look at an example:

Operation:

First, start Alipay and press the Home button to access the background. Then, restart the phone and enter the desktop. Let stand for 10-30 seconds.

Phenomenon:

At this point, clicking on the desktop of Alipay (as well as taobao and almost all other apps) is the same as the usual cold startup, the whole startup process is at least one second.

The cold start time has been optimized, but is it possible to start every startup in seconds? We found that the system provided such a Background Fetch feature and decided to try it out.

Background the Fetch profile

Background Fetch is similar to an intelligent polling mechanism. The system will adapt to the user’s usage habits and trigger Background update before the user really starts the application to obtain data and update the page.

From apple’s official documentation

Background Fetch lets your app run periodically in the background so that it can update its content. Apps that update their content frequently, such as news apps or social media apps, can use this feature to ensure that their content is always up to date. Downloading data in the background before it is needed minimizes the lag time in displaying that data when the user launches the app.

Background Fetch has the following features:

  • The system scheduling
  • Adapt to the actual use pattern of each application on the device
  • Sensitive to power and data usage
  • It is irrelevant to the actual running status of the application

For example, if a user is used to using a news app at 1:00 PM, the system will learn and adapt to this habit. Before the user uses the app, the background will conduct scheduling to start the application and perform data update. The following figure clearly illustrates how the system learns the user’s usage patterns.

In response to this strategy, you might wonder, will this frequent background activation increase power consumption? Of course not, the system will invoke frequency control based on the power and data usage of the device to avoid frequent data acquisition during inactive hours. Moreover, the process takes a very short time to survive once it starts, and in most cases suspend immediately, with very little impact on power (compared to the case where many apps survive for close to 3 minutes after you press the background).

Background the Fetch using

According to the official data, the usage of Background Fetch is very simple, and the overall process is shown in the figure below.

  1. The UIBackgroundModes node in info.plist is configured with fetch values
  2. When didFinishLaunching configuration
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
Copy the code

The minimum interval set in this step, in seconds, is only a suggestion for the system. The system does not wake up the process regularly at the given interval.

  1. Implement the following callback and call completionHandler
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
Copy the code

The Background Fetch mechanism is to allow App to pull and prepare data in the Background, but Alipay only aims to achieve “second start”. The system suspends the App process by calling completionHandler. And the system must call completionHandler within 30 seconds or the process will be killed. In addition, according to the documentation, the system determines how often the background invokes the App based on the time it takes for the background to call the completionHandler. Therefore, it is considered possible to “fake” a one-second delay, that is, call the completionHandler after one second. Code similar to the following:

- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        completionHandler(UIBackgroundFetchResultNewData);
    });
}
Copy the code

Background the Fetch practice

Apple’s motivation for this feature is that the background triggers fetching data and updating pages, ensuring that users always see the latest content when they use it. However, Alipay is only for the realization of “second start”, so the seemingly simple implementation, but hidden huge risks. These problems were found during testing:

  1. The success rate of Sync decreases as the process hangs rapidly

During the gray scale period, the developer found that the success rate of Sync service decreased a lot, and searched and found the reason: Because after the process woke up, the network long connection thread was activated and established long connection immediately, and the completionHandler was called 1 second later, and the process was suspended again. The sync message on the server timed out.

  1. Frequently suspended or woken up processes increase the number of network connections

The system predicts the time when the user will use the App and wakes up the App before the user realizes it, giving the App background the opportunity to prepare data. Coupled with the accuracy of the prediction, the process is awakened far more often than the user uses it. The long network connection is established immediately after the process is woken up. As a result, the number of network construction increased, and even doubled.

  1. Timers, delayed calls, etc. “different time than expected” due to process hanging

For example, a timer with an interval interval of 60 seconds will be triggered immediately when the next process wakes up because the process has been suspended for more than 60 seconds. (Delay calling dispatch_after, etc.). For the process itself, the timer may be abnormal. You need to check all the timer logic to check whether suspension may cause service level exception.

  1. Get timestamp

Because the process is suspended, the time stamps obtained before and after are far apart.

In order to solve the problems encountered and predicted above, after discussion, it is decided not to establish a long connection during Background Fetch wake up.

  • Call completionHandler after 10 seconds.

There are two types of wake up in the background: a process starts from nothing, and a process starts from suspension to recovery. The former requires sufficient time to complete the background cold startup process of the App, so the time of 10 seconds is defined.

  • Background Fetch time does not establish a long connection.

“Background” the Background of the Fetch time is defined as: performFetchWithCompletionHandler callback and until the completionHandler call time.

We maintain a global variable, underBackgroundFetch, to identify this time. All network requests during this time are blocked and retry judgments are added. Long connection is actively re-established when App enters foreground (willEnterForeground). In some other cases where the backend needs to establish a long connection (for example, WatchApp connection, PUSH fast recovery), the flag is also actively modified and the network layer is notified to establish a long connection. The underBackgroundFetch modification is performed in the main thread, but the network long connection is established in the child thread, and the process is woken up before the underBackgroundFetch modification. Now for the first time the callback performFetchWithCompletionHandler, still exists the “gap” in network connection is established, but the subsequent Background Fetch time state is accurate. (How to make this gap more accurate, the necessity and the plan are under discussion, so far there are no unsolvable problems)

  • Network requests are blocked when the background is not connected, avoiding pop-up Windows such as Toast.

The interception operation adds a buried point to Fetch all RPCS intercepted during Background Fetch time. During the gray scale, all RPCS were collected and the owners were found one by one, so that everyone could evaluate the impact and avoid pop-up prompts such as Toast. Ensure that the outermost exception catch of all RPC exceptions is not Toast for exceptions intercepted by RPC.

  • Timeout judgment

The service logic needs to be modified to determine the timeout of the timer and delayed call due to process suspension. The process runs on the operating system and cannot be affected by process suspension or recovery.

Despite all the schemes used to ensure the stability of the application, lines are actually not immune to some strange problems:

  1. CompletionHandler is called twice

A small number of user calls to completionHandler are found twice during grayscale, resulting in flash back. Make the user log found performFetchWithCompletionHandler in 1 seconds was system callback for two consecutive times. The completionHandler is stored as a member variable in the AppDelegate, and the same completionHandler is called twice after the 10-second timeout expires.

To avoid this problem, instead of storing the completionHandler with a member variable, use dispatch_after to let the block catch the completionHandler directly, This, however, leads to another minimal probability of a flash backoff where the block is empty in libDispatch.

So we use member variables to store completionHandler, In the first line of a performFetchWithCompletionHandler judge storage completionHandler and incoming completionHandler are the same. The general code is as follows:

- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    if(_backgroundFetchCompletionHandler && _backgroundFetchCompletionHandler ! = completionHandler){// Avoid performFetch being called again and again quickly. If completionHandler is different, complete the last completionHandler first; If they are the same, avoid calling them twice. [self callBackgroundFetchCompletionHandler]; / / internal call completionHandler} _backgroundFetchCompletionHandler = completionHandler; // Copy to member variables //...Copy the code
  1. IOS7 flash back

The most liked answer to this flashback StackOverflow encounter doesn’t actually solve the problem.

This flash back is only produced in iOS7, after all parties information is considered to be iOS7 system bug. BackgroundFetch is no longer enabled on iOS7 devices.

if ios 7 : 
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalNever];
else.Copy the code

Background Fetch mechanism allows iOS apps to “hot start”, but it leads to a large increase in the number of processes suspended and woken up, which brings an “unstable” running mode to the code that has been running stably for a long time. Every detail must be carefully considered.

Image preloading

[UIImage imageNamed:@” XXX “] is the API for loading images in iOS, it is used more frequently, so how about its performance? In the process of analyzing the startup performance, we found that this method takes a lot of time. Under iPhone5S, each time is between 20ms and 50ms. During the loading process of the home page, there are more than 10 pictures loaded in this way. In view of the whole phenomenon, we use a picture preloading method to optimize in Alipay.

Design idea

UIImage imageNamed:]

In iOS 9 and later, this method is thread safe.

Immediately after seeing it, I wondered if I could preload the home page image from a child thread early in the process startup. Why in the early days? According to Instruments analysis, the CPU usage of Alipay is not so full in the early startup stage. In order to make full use of CPU during the startup process, the child thread is started as early as possible.

First, hook all images loaded by imageNamed on the home page. Then, the general code is as follows:

int main(){
    @autoreleasepool{
        //if>= iOS9 dispatch_async(dispatch_get_global_queue(0, 0), ^{NSArray<NSString*> *images = @[// 10.0@"Launcher.bundle/TabBar_BG"The @"Launcher.bundle/TabBar_HomeBar", / /... ];for (NSString *name inimages) { [UIImage imageNamed:name]; } } // AppDelegate.... }}Copy the code

Problems and Solutions

After optimization, there are also some unstable problems:

  • There will be a small probability of Crash when the App is started.

Based on our analysis, we decided to move this code into the didFinishLaunching of the AppDelegate and add a switch.

  • The iPhone7 doesn’t require preloading

When the iPhone7 device came out, we found that the startup performance of the iPhone7 was worse than that of the iPhone6S. After analysis, it is found that on iPhone7 with better performance, due to the fast startup, the imageNamed of the child thread and the imageNamed of the main thread are interspersed with each other, while the granularity of the imageNamed internal thread safety lock is very small, resulting in excessive lock consumption. The diagram below:

As a result, preloading is no longer enabled on the better-performing iPhone7.

conclusion

The optimization of startup performance through Background Fetch and image preloading provides us with another way of thinking. The optimization should not be limited to the box, but should be innovated appropriately. However, for this kind of “innovative” code, there must be a “switch” to enhance the awareness of risk. Of course, performance optimization is not a quick fix, it is an ongoing topic that deserves our attention.

There are many technical points that we can’t cover because of space constraints. The corresponding technical kernel is also applied in mPaaS and exported. Welcome to experience it:

Tech.antfin.com/docs/2/4954…

We also look forward to your feedback on the design ideas and specific practices of iOS startup performance optimization. Welcome to discuss and communicate with us.

Note: WWDC2013 Session developer.apple.com/videos/play…

Past reading

The opening | modular and decoupling type development in ant gold mPaaS theorypractice probing depth,

Alipay Mobile Terminal Dynamic Scheme Practice

Analysis of Alipay Client Architecture: A Preliminary Study on iOS Container Framework

Analysis of Alipay Client Architecture: A Preliminary Study on Android Container Framework

Analysis of Alipay Client Architecture: Optimization of Startup Speed of Android Client “Garbage Collection”

Follow our official account for first-hand mPaaS technology practices