preface

At the end of last year, I began to take over the technical optimization work of several old iOS projects that had been iterated for five or six years. The Internet company closed source N hand business old code, after a number of years and a number of waves of baptism, coupled with a number of deadline expediting support, has been riddled with holes, change something on thin ice. On the plus side, the more holes our predecessors bury, the more room our successors have to play. So I happily began the trip to the pit.

From improving efficiency and quality, from the first to identify the biggest pain points in a myriad of problems – packing time is too long, set out to optimize the compilation time, to comb coding standards and branch management team, to the old module refactoring, IAP single optimization, network optimization, and so on, all the way to the just completed background download optimization, some want to record and precipitation.

Most of the technologies covered in this series are not new, and you can easily find articles online. This series focuses more on how technology can help businesses solve users’ problems. After all, what excites me most in my work is that my solutions can be implemented and truly bring value to users and the company. So this series of articles will be written along the following lines:

  • What are the pain points on the business side
  • What is the current technical solution
  • How does the technical side analyze and identify the root cause of the problem
  • How does the technical side select and evaluate the appropriate solution given the resources
  • How will the technical side finally implement the scheme
  • How does it look online?

As the first article in this series, I’m going to cover the background download optimization we just completed.

directory

  • Background and pain points
  • Overview of iOS background mechanism
  • Trip to pit process
  • summary

I. Background and pain points

Just as all video websites provide mobile video caching service, the mobile products of my company also provide similar offline resource caching service. Caching services are already standard in almost every App that provides content services, with mature technology and various reference documents. Logically, if you type through the code from the document, there should be no doubt about this. However, in the user feedback of recent business combing, file download feedback has become the biggest slot point for users. Feedback from users was generally vague, with “it started and stopped” being the most common response. To cure the problem, we first need to dig out the real problem and then apply the right medicine.

In retrospect, the resource caching service has been online for a long time. The feedback was not so frequent in the past, which should be related to two recent changes:

  1. Added the batch caching function on the product side
  2. The technical side has optimized the resources and updated the technical plan

Product side modification

Batch caching lengthens the completion time of download tasks and increases the probability of errors, which may indeed lead to increased user barriers.

Technical modification

Here is a brief introduction to the background. In addition to the caching service of resources, we also provide online playback service of resources. So before this technical change, resources were always in two on the server side:

  1. One is the original resource folder, which contains all kinds of little filespng,txt,json,ts,wavA rich media collection of various formats for online playback. Load small file address directly online when playing.
  2. The other is a compressed package of raw resources for offline caching. The address of the compressed package is cached on the mobile terminal and decompressed.

The purpose of this adjustment is to save server resources. If you can remove the redundant compressed package and only retain the original resource folder, you can save nearly 50% of the space. However, the client also needs to do a major reconstruction. The offline cache mode is changed from compressed download to fragmented download, that is, each small file in the resource package is downloaded separately. In fact, the previous compression package download method is more friendly to the client, the implementation of lower difficulty, download speed is theoretically faster. After overall consideration and investigation, the actual download speed of fragment download and compressed package download is almost the same. Therefore, we decided to make a concession to the client first and adopt fragment download, which will not be expanded here.

Combined with product and technical changes, when users download multiple resources in batches, a series of small files in each resource need to be downloaded, which is much more complicated than single file download. If there is a problem in this process, especially on iOS devices with so many restrictions on background operation, it will indeed affect user experience.

Overview of iOS background mechanism

First, let’s take a look at the iOS background mechanism. After all, it is the product of the iOS7 era, and now it has been relatively unfamiliar.

You’ve probably worked with many backend technologies over the years, and these terms or apis will be familiar to you:

  1. Background Fetch.Background Task.Background Modes.Background Execution.Background Download
  2. beginBackgroundTaskWithName:expirationHandler:.endBackgroundTask:
  3. application:performFetchWithCompletionHandler:
  4. application:didReceiveRemoteNotification:fetchCompletionHandler:
  5. application:handleEventsForBackgroundURLSession:completionHandler:

What is Background Download, what is Bakcground Execution, and what are the pieces of content that this article addresses. With these questions in mind, let’s get the concept straight.

The background related technical panorama is drawn according to the official document, as shown below:

Background running (Bakcground Execution)

All of these concepts fall under the umbrella of Bakcground Execution, which is the name for the tasks an App runs in the background.

Background Tasks (Background Task)

The term sounds like a catch-all for all Background related tasks (to be distinguished from Background Execution), but it actually refers to a specific type of task: Sometimes the App still needs to run for a short period of time after entering the Background, so you can apply for running permission to the system with Background Task-related API, and notify the system that the App can be suspended after running.

Background mode (Background Modes)

Apps that need to run tasks in the background for a long time need to explicitly apply for permissions from the system. Here, Background Audio and Background Fetch are used as examples. The former allows apps to play audio in the background, such as QQ music. The latter allows the App to wake up from time to time to update some data.

Background download (Background Download)

Specifically to the configured backgroundSessionConfiguration NSURLSession management download process. The system process takes over the download of App data, so it can continue downloading even if the App is suspended, killed or crashed by the system. Once the download is complete, the App wakes up to handle status updates and callbacks.

After briefly explaining related concepts, we will return to the problem we want to solve. Firstly, we will lock Background Task and Background Download. Initially, we suspect that the reasons leading to the poor effect of Background Download are as follows:

  1. There are other things in the AppBackground TaskThe App is killed prematurely due to timeout, and the active background download time is too short
  2. Background downloads are not implemented correctly
  3. Our download scenario is quite special, the background download can not hold

There were other questions on my mind, like:

  1. How long the App will survive in the background
  2. Can App be downloaded in the background after being killed by the system
  3. What is the experience of waking up your App
  4. If the system memory is sufficient, the longer the App lives, the longer the background download will be

With so many uncertain questions, let’s go straight to the actual effect.

3. Pit trip process

My ideal background download experience would look like this:

  1. Download a batch of resources in batches
  2. A night’s sleep
  3. Woke up and found it all downloaded

But reality is bony:

  1. Download a batch of resources in batches
  2. Take a bath
  3. App was found to be killed, the first resource is only less than half

Background download is basically in invalid state, this kind of experience users can not ridicule it.

Stage 1: Make sure background downloads are available

Debugging related problems in the background is troublesome, because Xcode’s debugger prevents the App from being suspended by the system and cannot simulate the real App background behavior. Therefore, debugging cannot be used. At the same time, since the simulator may not be able to accurately simulate App behavior, it is better to test it on a real machine.

You can choose to log, connect the real computer to the Mac, use the Mac Console program to view the log.

To make logging easier, I added some logs with eigenvalues (beginning with hello) in the BackgroundDownloader:

@implementation BackgroundDownloader

- (void)addURLs:(NSArray<NSURL *> *)urls {
	[urls enumerateObjectsUsingBlock:^(NSURL *url, NSUInteger idx, BOOL * _Nonnull stop) {
        NSURLSessionTask *task = [self.urlSession downloadTaskWithURL:url];
        [task resume];
        
        NSLog(@" Hello add download task: %@", url.absoluteString);
    }];
}

- (void)handleEventsForBackgroundURLSession:(NSString *)aBackgroundURLSessionIdentifier completionHandler:(void (^)())aCompletionHandler {
    NSLog(@ "hello handleEventsForBackgroundURLSession: completionHandler: call.");
    // Do other stuff
}

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {    
	NSLog(@" Hello fragment download completed: %@", downloadTask.originalRequest.URL.absoluteString);
}

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
    NSLog(@ "hello URLSessionDidFinishEventsForBackgroundURLSession: call.");
    // Do other stuff
}

@end

Copy the code

By filtering messages -> Hello in Console, you can eliminate all kinds of interfering logs and keep only the ones you want to see:

To better understand the order in which methods are called in the log, here’s a simple addition to the resource download logic:

As mentioned above, the company’s resources go through shard downloads, that is, each resource is composed of a group of shards, and a shard is a small file to be downloaded. For shards, the maximum number of concurrent downloads is 4. When all shards in the same resource are downloaded, the next shard will be downloaded.

Assume that the download task of three resources A, B, and C is started in batches, containing 100, 200, and 300 fragments respectively. Initially, four shard download tasks in resource A are started at the same time, and each download completion callback is received to start A new shard download. Until all shards in A have been downloaded, then four shards in B have been downloaded simultaneously, and so on until all resources have been downloaded. A similar process can be observed in the log above.

To see the system logs at the same time, filter any -> BackgroundDemo in the Console and enter the background at 18:02:20:

Then the App continued to download for nearly three minutes and stopped downloading at 18:05:17:

We have seen, also calls the URLSessionDidFinishEventsForBackgroundURLSession: here, said the url events are dealt with in the session.

The Background downloader does enable the Background Task after entering the Background, which is consistent with the statement that the Background Task can execute 180s after entering the Background:

- (void)didEnterBackground:(NSNotification *)notification {
    self.backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithName:NSStringFromClass([self class])
                                                                       expirationHandler:^{
                                                                           [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTask];
                                                                           self.backgroundTask = UIBackgroundTaskInvalid;
                                                                       }];
}
Copy the code

Without enabling the Background Task, the download Task stopped after a few seconds

The background execution time of three minutes is far from enough for batch download. What happened after three minutes? Let’s take a look at the logs.

18:05:17	assertiond	[BackgroundDemo:12636] Setting up BG permission check timer for30s 18:05:17 BackgroundDemo Hello Download progress: 0.0751051 18:05:17 BackgroundDemo Hello Add download task: https://test.com/class_ocs/slice/923373023660117946/raw/ts/669b14bdd49d7b1704b5c2241c492b1b/a5c175b811175d2005c9cf96cd2f830b.ts 18:05:17 BackgroundDemo hello URLSessionDidFinishEventsForBackgroundURLSession: Calling 18:05:43 Assertiond [BackgroundDemo:12636] Sending background Permission expiration warning! 18:05:48 assertiond [BackgroundDemo:12636] Forcing crash report with description: BackgroundDemo:12636 has active assertions beyond permitted time: <BKProcessAssertion: 0x104273920;"com.apple.nsurlsessiond.handlesession com.mycompany.backgrounddemo.background1"(backgroundDownload:30s); Id:... AEB425907B3C> (owner: nsurlsessiond:94) 18:05:48 assertiond [BackgroundDemo:12636] Finished crash reporting.Copy the code

You can see:

  1. 18:05:17The system issued a background execution ultimatum, 30 seconds do not end to color see!
  2. 18:05:43The system issued its last warning
  3. 18:05:48The system kills the App forcibly and generates an App crash report

Let’s open the crash log and take a look:

See the famous error code 0x8BADF00D, “ate bad food” :

The exception code 0x8badf00d indicates that an application has been terminated by iOS because a watchdog timeout occurred. The application took too long to launch, terminate, or respond to system events. One common cause of this is doing synchronous networking on the main thread. Whatever operation is on Thread 0 needs to be moved to a background thread, or processed differently, so that it does not block the main thread.

The official website mentions that because the main thread is stuck for too long, the system watchdog will forcibly kill App. The LEAKED Background Task is included in the App, and the Task is not ended correctly. In which case, we can further analyze the main thread Stack at crash time:

Thread 0 Crashed:
0   libsystem_kernel.dylib        	0x0000000196505c60 mach_msg_trap + 8
1   CoreFoundation                	0x000000019690de10 __CFRunLoopServiceMachPort + 240
2   CoreFoundation                	0x0000000196908ab4 __CFRunLoopRun + 1344
3   CoreFoundation                	0x0000000196908254 CFRunLoopRunSpecific + 452
4   GraphicsServices              	0x0000000198b47d8c GSEventRunModal + 108
5   UIKitCore                     	0x00000001c3c504c0 UIApplicationMain + 216
6   BackgroundDemo                	0x00000001047025c4 0x104688000 + 501188
7   libdyld.dylib                 	0x00000001963c4fd8 start + 4
Copy the code

Very similar to the Stack mentioned in MrPeak:

The stack is a classic example of a stack that doesn’t need a symbolicate to know what it’s doing. This is the stack where the UI thread runloop is idle, waiting for a message from the kernel. The UI thread is in the idle state, which is likely to be killed by Background Task.

The leaked Background Task is responsible for the crash.

So the culprit is a background crash? Not necessarily, because background downloads still work if the App crashes, is suspended, or is killed. Background downloads become invalid only when the user manually kills the App. Please refer to the official reply:

The behaviour of background sessions after a force quit has changed over time:

  • In iOS 7 the background session and all its tasks just ‘disappear’.
  • In iOS 8 and later the background session persists but all of the tasks are cancelled.

When you terminate an app via the multitasking UI (force quit), the system interprets that as a strong indication that the app should do no more work in the background, and that includes NSURLSession background tasks.

Objc.io also mentions:

Tasks added to a background session are run in an external process and continue even if your app is suspended, crashes, or is killed.

(It has been proved that App crashes or is killed in the background does continue to download, but there are some points and pits, you can refer to the iOS native level background download details, write very detailed.)

So let’s look again for traces of background downloads in the following logs. Continue to intercept some key logs:

18:05:48	symptomsd	Entry, display name com.mycompany.backgrounddemo uuid (null) pid 12636 isFront 0
18:11:28	nsurlsessiond	[C3794 Hostname#aca88929:443 tcp, bundle id: com.mycompany.backgrounddemo, url hash: 8f857432, traffic class: 100, tls, indefinite] start
18:11:30	nsurlsessiond	[C3794 Hostname#aca88929:443 tcp, bundle id: com.mycompany.backgrounddemo, url hash: 8f857432, traffic class: 100, tls, indefinite] cancel
18:11:30	nsurlsessiond	[C3794 Hostname#aca88929:443 tcp, bundle id: com.mycompany.backgrounddemo, url hash: 8f857432, traffic class: 100, tls, indefinite] cancelled
Copy the code

18:05:48 was the last one after the background crash, and there were no logs for about 5 minutes and 30 seconds until 18:11:28 when a process named Nsurlsessiond printed several logs. Nsurlsessiond is essentially the daemon process that takes over the App download background. Strangely enough, it starts and then immediately cancels, very weird behavior. Forget it for a moment, then read the log:

18:14:29	nsurlsessiond	[C3795 Hostname#aca88929:443 tcp, bundle id: com.mycompany.backgrounddemo, url hash: fae4ed2d, traffic class: 100, tls, indefinite] start
18:14:31	nsurlsessiond	[FBSSystemService][0xc652] Sending request to open "com.mycompany.backgrounddemo"
18:14:31	SpringBoard	[FBSystemService][0xc652] Received request to open "com.mycompany.backgrounddemo" from nsurlsessiond:94.
18:14:31	SpringBoard	Received trusted open application request for "com.mycompany.backgrounddemo" from <FBProcess: 0x28396e370; nsurlsessiond; pid: 94>.
18:14:31	SpringBoard	Executing request: <SBMainWorkspaceTransitionRequest: 0x28245b380; eventLabel: OpenApplication(com.mycompany.backgrounddemo)ForRequester(nsurlsessiond.94); display: Main; source: FBSystemService>
18:14:31	SpringBoard	Executing suspended-activation immediately: OpenApplication(com.mycompany.backgrounddemo)ForRequester(nsurlsessiond.94)
18:14:31	SpringBoard	Bootstrapping com.mycompany.backgrounddemo with intent background
18:14:31	SpringBoard	Application process state changed for com.mycompany.backgrounddemo: <SBApplicationProcessState: 0x280ba7ae0; pid: 12648; taskState: Running; visibility: Unknown>
Copy the code

Three minutes later, at 18:14:29, nSURlsessiond started again. This time it held up and asked the system to open our App. SpringBoard then agreed and helped it successfully launch the App from the background, setting the App status to RUNNING. The following:

18:14:31	assertiond	[BackgroundDemo:12648] Add assertion: <BKProcessAssertion: 0x1046029b0; id: 94-679EF71C-A8E3-42E4-B679-DE0D01BDBB0F; name: "com.apple.nsurlsessiond.handlesession com.mycompany.backgrounddemo.background1"; state: active; reason: backgroundDownload; Duration: 30.0 s >Copy the code

System announced that nsurlsessiond tasks are done, will call application in the App: handleEventsForBackgroundURLSession: completionHandler: method, And the incoming sessionIdentifier for com. Mycompany. Backgrounddemo. Background1, and he gave the App background execution time of up to 30 seconds. The following:

18:14:31	assertiond	[BackgroundDemo:12648] Mutating assertion reason from finishTask to finishTaskAfterBackgroundDownload
18:14:31	assertiond	[BackgroundDemo:12648] Add assertion: <BKProcessAssertion: 0x1042246a0; id: 12648-B0511F94-D84E-479C-AD8F-CA13B7CA55F6; name: "Shared Background Assertion 1 for com.mycompany.backgrounddemo"; state: active; reason: finishTaskAfterBackgroundDownload; Duration: 40.0 s >Copy the code

Then the system changed its mind and gave the App up to 40 seconds of background execution time. The exact cause is unknown. Sure enough, the system then warned and killed the App again about 40 seconds later:

18:15:07	assertiond	[BackgroundDemo:12648] Sending background permission expiration warning!
18:15:12	assertiond	[BackgroundDemo:12648] Forcing crash report with description: BackgroundDemo:12648 has active assertions beyond permitted time
Copy the code

The crash error code is also 0x8BADF00D. Then, the App no longer generates any logs, and it can be considered that this wave of App background behavior is over.

After opening the App, it is found that the download progress is still in the App background after three minutes of being killed, indicating that the background download taken over by Nsurlsessiond does not take effect at all.

HMM, there must be something wrong. After reviewing the code, I found a low-level error, which may be caused by communication problems between different teams: Although in the AppDelegate implements application: handleEventsForBackgroundURLSession: completionHandler:, but without the right parameter to BackgroundDownloader, The background download is incomplete. This may have caused some of the odd dots in the log above. OK, run again after fixing the bug to see if there are any other pits.

To get a better sense of what’s going on, I pulled the following image to see what’s going on over time as the App goes into the background. The horizontal axis is time in seconds:

The general summary is: Go to the background -> System process work -> system wakes up App -> App adds a new task -> App notifies the system that processing is completed and suspended -> System process work -> System process work -> App adds a new task -> App notifies the system that processing is completed and suspended ->……. This cycle continues until the system gets tired and stops waking up the App (nothing happens after 1290 seconds, for reasons explained later).

As a quick note, when the system downloads the batch to add to the four shards in the background, App is already suspend, or even suspend, so no callbacks in App are executed. Only when the system completes all four sharding tasks added in this batch will the App wake up, and then the App takes this opportunity to add a new batch of four sharding tasks.

Anyway, background downloads are working.

The second stage: improve the efficiency of background download

Let’s see if we can keep optimizing.

From the sequence diagram above, we can see:

  1. Each time the system gets up and running, it can only download four shards. The reason is that every time the App wakes up, four shard download tasks are added and then suspended. Then the system takes time out to start a new round of work.
  2. Each time the system starts to work the time span is very long, probably takes three or five minutes to finish. The reason is that the start time of background download is not fixed, it is dynamically allocated by the system according to the operating environment and available resources, the system may start fragment download every few minutes, may stop for a while, or stop downloading under the cellular network.
  3. After five or six cycles, the system stopped working, resulting in the background download only completed 20+ fragments of the download, even a complete resource can not be finished, let alone batch of multiple resources. The reason is explained in the official documentation:

When the system resumes or relaunches your app, it uses a rate limiter to prevent abuse of background downloads. When your app starts a new download task while in the background, the task doesn’t begin until the delay expires. The delay increases each time the system resumes or relaunches your app. As a result, if your app starts a single background download, gets resumed when the download completes, and then starts a new download, it will greatly increase the delay.

In short, background downloads are delayed as the number of cycles increases until the system finally fails. The official documentation of the solution also mentions:

Instead, 【 cite 】 Use a small number of background sessions — cited just one — and use these sessions to start many download tasks at once. This allows the system to perform multiple downloads at once, and resume your app when they have completed.

That is to say, use an NSURLSession to open as many tasks as possible, and maybe that will improve. But the document adds:

Keep in mind, though, that each task has its own overhead. If you find you need to launch thousands of download tasks, change your design to perform fewer, larger transfers.

It is not recommended to start each small shard task at a cost. There are hundreds of small shards in a resource, and it is not recommended to start all of them at the same time. There is also more support for downloading compressed packages. As mentioned before, shard download is the result of the tradeoff between the client and the server, so we can only optimize it.

Let’s see what happens when all the shards in a resource start downloading at the same time. The previous method of controlling concurrency was to allow a maximum of four tasks to be resumeed in a period of time, and to resume a new task every time a shard is finished. Resume all tasks at once instead. Control the number of concurrent requests at the same time to 4:

sessionConfiguration.HTTPMaximumConnectionsPerHost = 4;
Copy the code

In this way, excessive concurrency is avoided and all sharding tasks are guaranteed to be in the NSURLSession queue. I ran it again, and the whole process was as follows:

Similar to the previous process, the difference is that the background download cycle has increased from completing 4 shards to completing all the shards in the resource. A total of 4 full resource downloads were completed, which is quite an improvement.

It’s not officially recommended, but it’s a step closer.

By the way, you can tell if a background download is in progress by looking at the Console for similar logs:

The third stage: industry plan research

Of course we are not satisfied with this, after all, batch download 30 resources, only completed 4, is no matter what can not say. And the whole time span is relatively long, limited by the background strategy of the system, can not stop to download.

From our previous tests, we can conclude that we should try to add all tasks to NSURLSession at once so that the background download is durable.

If we add resources in batches and give all fragments of all resources to NSURLSession at once, it should be possible to load all fragments.

But there are two problems with this:

  1. Further violations of official best practices can lead to performance issues and other unexpected issues
  2. Existing code transformation costs a lot

When you feel lost, see if the industry has similar problems and what they do about them.

My colleague studied Tencent Video MAC version before, and found that the downloaded files are TS small files, so it is speculated that Tencent Video iOS terminal should also adopt a similar way of fragment download.

I tried the background download of Tencent Video and IQiyi, and found that the effect was very good, and the batch added tasks could be completed smoothly. It is not uncommon for batch downloading of large files, but Tencent video can also have such a good effect for fragment downloading, I decided to study it.

Connect the mobile phone to the MAC and use Console to view the run log.

  1. No matter Tencent video in the foreground or background, the Console log is similar
  2. No matter how long you leave it in the background, the Console log is almost the same as before
  3. Some characteristic logs for background downloads, such as those listed earlier in this article, are not found

Does Apple have a better relationship with the goose factory so… Stop it. That’s a low point. (Look at iQiyi’s run log, similar)

All signs seem to indicate that Tencent Video has not actually been suspended by the system, but remains active in the background. So, how does it work.

Are there any kinds of apps that can survive in the background? Yes, like navigation apps and music apps.

If an App has Background Audio and plays music in the Background, there’s no reason for the system to suspend it.

So if the music is an empty piece of audio with no sound, there should be no way for the system to know.

So Tencent video and iQiyi are not through similar means to achieve background download, let’s take a look.

Let’s use the PP assistant to get the IPA.

Tencent video

We found an unusual audio file in the resource pack sound_drag_refresh. Wav:

Open it in QuickTimePlayer, go to Edit -> Trim, and you can see its waveform:

Here is the normal audio waveform, and here is sound_drag_refresh. Wav, which should be empty. Why you put an empty audio in the bundle and give it a seemingly unrelated name is intriguing.

Open with a Hopper, search backgroundaudio, can find a called startInfiniteBackgroundAudioTask: :

This method has a dubious name, but there is no direct evidence that it has anything to do with the empty audio.

Searching sound_drag_refresh further, you can see that it is referenced by a method called restartPlayer:

Look at the implementation of the restartPlayer method:

It’s basically similar to the code in this article about iOS survival:

// Mute the file
NSString *filePath = [[NSBundle mainBundle] pathForResource:@" Audio file + file name" ofType:@"mp3"];
NSURL *fileURL = [[NSURL alloc] initFileURLWithPath:filePath];
self.playerBack = [[AVAudioPlayer alloc] initWithContentsOfURL:fileURL error:nil];
[self.playerBack prepareToPlay];
// 0.0~1.0, the default is 1.0
self.playerBack.volume = 0.01;
// Loop
self.playerBack.numberOfLoops = - 1;
Copy the code

Note this passage:

mov.w      r2, #0xffffffff
movt       r0, #0x1e9 ; @selector(setNumberOfLoops:), :upper16:(0x21f2f88 - 0x361cee)
add        r0, pc       ; @selector(setNumberOfLoops:)
ldr        r1, [r0]     ; @selector(setNumberOfLoops:),"setNumberOfLoops:"
Copy the code

0xffffFFFF should be -1, placed in r2 register and passed as an argument to [setNumberOfLoops:].

In armv7, the first four parameters are passed from r0 to R3, with r0 putting self and R1 putting _cmd.

Basically solved the case. As for how internal calls, startInfiniteBackgroundAudioTask: and how restartPlayer together, very fee energy research, and the emphasis is not on, here is ignored. Interested readers can check it out.

iQIYI

Look at IQiyi in the same way.

We also found an empty audio file in the resource pack, JM.wav (which later colleagues assumed was silent Pinyin 2333) :

Then open with Hopper and search jM to find the following methods:

QYOfflineBaseModelUtil isOpenJMAudio

The general meaning is described in pseudocode:

+ (BOOL)isOpenJMAudio {
	if ([self isUseURLSession]) {
		// If you use URLSession, do not enable the background save (this is probably a switch, can be native background download mode and play silent audio save)
		return NO;
	} else {
		// Otherwise, if there is a task downloading, enable background save, otherwise do not enable
		return[[QYDownloadTaskManager sharedInstance] isAnyTaskCanBeDownloaded]; }}Copy the code

Let’s look at the implementation of [AppDelegate turnOnJM] :

We’ve seen the familiar [setNumberOfLoops:] and 0xffffFFFF, as well as the jM.wav.

All right, we got another one. At this point, the industry practices are basically clear: simple and crude, through the background preservation mechanism, make App in the background as in the foreground behavior.

Fortunately, our App already has the permission of Background Audio, so we can add the Background preservation mechanism according to the same way, and remove the Background implementation related to NSURLSession.

Measured down the power consumption did not increase much, batch download and Tencent video as smooth, can be all finished smoothly.

Online since a month, received the relevant user report basic did not have. At this point, the pit trip came to an end.

4. Summary

A simple comparison between NSURLSession and background keepalive:

indicators NSURLSession The background keep alive
Power consumption Less, the system will be optimized Many, but measured down the increase is limited
speed slow fast
Bulk download of large files You can do it all You can do it all
Batch fragment download Implementation cost is high, the official does not recommend, may not be able to finish all Low implementation cost, can be all finished
Encounter collapse You can continue to download Downloading process stopped
permissions No additional permission is required Need to apply forBackground Audiopermissions

To sum up, you can adopt different strategies to achieve iOS background download according to your company’s business demands, or try to combine the two to deal with the collapse. (I tested both mechanisms before and there seems to be a problem, if you are interested, you can investigate it)

The next article in this series will cover the optimization of IAP drop orders.

To the end.

Reference links:

  • Downloading Files in the Background
  • Background Execution
  • Preparing Your App to Run in the Background
  • Testing Background Session Code
  • iOS11 watchdog timeout crashes (0x8badf00d) but code not on stack
  • 0x8badf00d – “ate bad food”
  • Understanding and Analyzing Application Crash Reports
  • iOS Background Download with silent notification iOS 11
  • Multitasking in iOS 7
  • IOS background download and management library
  • IOS native level background download details
  • IOS application background is alive
  • Working with NSURLSession: Part 4
  • Download multiple files using iOS background transfer service
  • Background Transfer Service in iOS 7 SDK: How To Download File in Background
  • iOS Swift download lots of small files in background
  • iOS 7 NSURLSession Download multiple files in Background
  • Downloading a list of 100 files one by one using NSURLSession in Background