iPad Pro 3rd Gen 无缘无故杀死前台应用程序

Posted

技术标签:

【中文标题】iPad Pro 3rd Gen 无缘无故杀死前台应用程序【英文标题】:iPad Pro 3rd Gen Killing Foreground App Without Cause 【发布时间】:2019-10-14 20:51:16 【问题描述】:

我有一个应用已经流行多年了。

此应用为了在离线时 100% 正常运行,只需一次下载数十万张图像(每个对象 1 张)(增量更新会根据需要进行处理)。

对象数据本身没有问题。

但是,最近,我们的应用在仅下载图像时开始崩溃,但仅限于较新的 iPad(具有大量存储空间的第三代 iPad Pro)。

图像下载过程使用 NSOperationQueue 中的 NSURLSession 下载任务。

我们开始看到能源日志指出 CPU 使用率过高,因此我们修改了参数以在每个图像之间以及每批图像之间添加一个中断,使用

[NSThread sleepForTimeInterval:someTime];

这将我们的 CPU 使用率从远高于 95%(这很公平)降至 18% 以下!

不幸的是,该应用仍然会在几个小时后在较新的 iPad 上崩溃。但是,在我们的 2016 iPad Pro 第一代上,该应用完全不会崩溃,即使在下载 24 小时后也是如此。

从设备中提取崩溃日志时,我们看到的只是 CPU 使用率超过 50% 超过 3 分钟。没有出现其他崩溃日志。

这些设备都已插入电源,并将其锁定时间设置为从不,以使 iPad 保持唤醒状态并让我们的应用程序处于前台。

为了解决这个问题,我们降低了性能,基本上每张图像之间等待 30 秒,每批图像之间等待整整 2 分钟。这行得通,崩溃停止了,但是,下载我们所有的图像需要几天时间。

我们正在努力寻找性能合理且应用不会崩溃的快乐媒介。

然而,困扰我的是,无论设置如何,即使在全口径性能下,该应用程序也不会在旧设备上崩溃,它只会在新设备上崩溃。

传统观点认为这是不可能的。

我在这里错过了什么?

当我使用 Instruments 进行分析时,我看到该应用在下载时平均处于 13% 的舒适状态,并且批次之间有 20 秒的间隔,因此 iPad 应该有足够的时间进行任何清理工作。

有人有什么想法吗?随意索取更多信息,我不确定还有什么有用的。

编辑 1:下载器代码如下:

//Assume the following instance variables are set up:

self.operationQueue = NSOperationQueue to download the images.

self.urlSession = NSURLSession with ephemeralSessionConfiguration, 60 second timeoutIntervalForRequest

self.conditions = NSMutableArray to house the NSConditions used below.

self.countRemaining = NSUInteger which keeps track of how many images are left to be downloaded.

//Starts the downloading process by setting up the variables needed for downloading.
-(void)startDownloading

    //If the operation queue doesn't exist, re-create it here.
    if(!self.operationQueue)
    
        self.operationQueue = [[NSOperationQueue alloc] init];
        [self.operationQueue addObserver:self forKeyPath:KEY_PATH options:0 context:nil];
        [self.operationQueue setName:QUEUE_NAME];
        [self.operationQueue setMaxConcurrentOperationCount:2];
    

    //If the session is nil, re-create it here.
    if(!self.urlSession)
    
        self.urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration]
                                                        delegate:self
                                                   delegateQueue:nil];
    

    if([self.countRemaining count] == 0)
    
        [self performSelectorInBackground:@selector(startDownloadForNextBatch:) withObject:nil];

        self.countRemaining = 1;
    


//Starts each batch. Called again on observance of the operation queue's task count being 0.
-(void)startDownloadForNextBatch:

    [NSThread sleepForTimeInterval:20.0]; // 20 second gap between batches

    self.countRemaining = //Go get the count remaining from the database.

    if (countRemaining > 0)
    
        NSArray *imageRecordsToDownload = //Go get the next batch of URLs for the images to download from the database.

        [imageRecordsToDownload enumerateObjectsUsingBlock:^(NSDictionary *imageRecord,
                                                                          NSUInteger index,
                                                                          BOOL *stop)
         
            NSInvocationOperation *invokeOp = [[NSInvocationOperation alloc] initWithTarget:self
                                                                                   selector:@selector(downloadImageForRecord:)
                                                                                     object:imageRecord];
            [self.operationQueue addOperation:invokeOp];
         ];
    


//Performs one image download.
-(void)downloadImageForRecord:(NSDictionary *)imageRecord

    NSCondition downloadCondition = [[NSCondition alloc] init];
    [self.conditions addObject:downloadCondition];

    [[self.urlSession downloadTaskWithURL:imageURL
                        completionHandler:^(NSURL *location,
                                            NSURLResponse *response,
                                            NSError *error)
              
                  if(error)
                  
                    //Record error below.
                  
                  else
                  
                    //Move the downloaded image to the correct directory.
                    NSError *moveError;
                    [[NSFileManager defaultManager] moveItemAtURL:location toURL:finalURL error:&moveError];

                    //Create a thumbnail version of the image for use in a search grid.
                  

                  //Record the final outcome for this record by updating the database with either an error code, or the file path to where the image was saved.

                  //Sleep for some time to allow the CPU to rest.
                  [NSThread sleepForTimeInterval:0.05]; // 0.05 second gap between images.

                  //Finally, signal our condition.
                  [downloadCondition signal];
              ]
        resume];

    [downloadCondition lock];
    [downloadCondition wait];
    [downloadCondition unlock];


//If the downloads need to be stopped, for whatever reason (i.e. the user logs out), this function is called to stop the process entirely:
-(void)stopDownloading

    //Immediately suspend the queue.
    [self.operationQueue setSuspended:YES];

    //If any conditions remain, signal them, then remove them. This was added to avoid deadlock issues with the user logging out and then logging back in in rapid succession.
    [self.conditions enumerateObjectsUsingBlock:^(NSCondition *condition,
                                                  NSUInteger idx,
                                                  BOOL * _Nonnull stop)
     

         [condition signal];
     ];

    [self setConditions:nil];
    [self setConditions:[NSMutableArray array]];
    [self.urlSession invalidateAndCancel];
    [self setImagesRemaining:0];
    [self.operationQueue cancelAllOperations];
    [self setOperationQueue:nil];

编辑 2:Instruments 的 CPU 使用率截图。峰值约为 50%,谷值约为 13% CPU 使用率。

编辑 3:在控制台中运行应用程序直到失败,观察到内存问题

好的!下载图像一个多小时后,终于观察到我的 iPhone 11 Pro 崩溃了,这与我其他测试人员报告的情况相符。

控制台报告我的应用程序因使用过多内存而专门被终止。如果我正确阅读了这份报告,我的应用程序使用了超过 2 GB 的 RAM。我假设这与 NSURLSESSIOND 的内部管理有关,因为它在使用 Xcode 或 Instruments 进行调试期间没有显示此泄漏。

控制台报告:“内核 232912.788 内存状态:killing_specific_process pid 7075 [PharosSales](每个进程限制 10)2148353KB - memorystatus_available_pages:38718”

谢天谢地,我在 1 小时左右开始收到内存警告。我应该能够暂停(暂停)我的操作队列一段时间(比如说 30 秒),以便让系统清除其内存。

或者,我可以调用 stop,调用后使用 gcd 调度重新开始。

你们如何看待这个解决方案?有没有更优雅的方式来响应内存警告?

您认为这种内存使用量来自哪里?

【问题讨论】:

我们不能只是猜测并尝试帮助您,您需要发布相关代码以便我们测试/检查问题所在。请阅读如何创建minimal, reproducible example。 @AlejandroIván 你说得对!很抱歉,我已经更新了帖子以显示正在运行的伪代码。谢谢! 很可能新 iPad 拥有的东西——除了更多的存储空间——是更多的 CPU 内核。他们可能不会按比例增加 RAM。我的猜测是您超出了内存限制并被ios杀死。我建议使用内存调试器并看看会发生什么。 @marko,我只是让应用程序运行了大约半小时,运行了几批。在 Xcode 中运行的内存调试器显示内存使用情况均匀,除了初始增长外没有增长。它在模拟器中运行时稳定在 105MB 左右。我知道这对于设备测量来说并不是 100% 准确的,但如果这是一个问题,它应该会显示出无限的增长,对吧?我应该使用其他工具进行内存调试吗?我也在 Instruments 中使用 Allocations 运行它,并收到相同的结果,没有无限的内存增长。 @marko 你的预感似乎是正确的!请参阅我的第 3 次编辑,似乎我在某个地方存在无法通过内存调试或仪器检测到的泄漏。请让我知道您对我的最新更新的看法。谢谢! 【参考方案1】:

编辑 4:尤里卡!!发现内部 Apple API 内存泄漏

在挖掘了我'杀死特定进程'内存相关的控制台消息后,我发现了以下帖子:

Stack Overflow NSData leak discussion

基于围绕使用 NSData writeToFile:error: 的讨论,我环顾四周,看看我是否以某种方式使用了这个函数。

事实证明,我用于从原始图像生成缩略图的逻辑使用此语句将生成的缩略图图像写入磁盘。

如果我注释掉这个逻辑,应用程序将不再崩溃(能够拉下所有图像而不会失败!)。

我已经计划将这个旧版 Core Graphics 代码换成 WWDC 2018 展示的 ImageIO 用法。

在重新编写此函数以使用 ImageIO 后,我很高兴地报告应用程序不再崩溃,并且缩略图逻辑也得到了超级优化!

感谢您的帮助!

【讨论】:

以上是关于iPad Pro 3rd Gen 无缘无故杀死前台应用程序的主要内容,如果未能解决你的问题,请参考以下文章

在Win10如何安装3rd Gen Core processor DRAM Controller - 0154驱动?

极客日报:网传快手裁员30%;iPad Pro 2022或支持无线充电;Chrome OS 96发布

在不通过通用测试的情况下杀死 gen_server

通过 iPad 上的 3rd 方 VoIP 应用程序发起呼叫

平板电脑开不了机怎么办?如何处理

Android服务被杀死[重复]