背景
某APP某次發(fā)版后有用戶反饋APP退到后臺很快就被系統(tǒng)殺死了匪凡,然后進(jìn)入APP就是冷啟動膊畴,APP退到后臺前的場景消失了。
影響:在編輯文章的頁面病游, 去其他的APP或者網(wǎng)頁中找參考資料唇跨,再回來續(xù)寫時,重啟了衬衬。在某個商品出售頁面买猖, 去其他APP參考商品的價格,再回來時APP重啟了
第一部分:為什么退到后臺很快被系統(tǒng)殺死了滋尉,app在后臺被終止的原因
1. 崩潰(Crashes)
這種大多數(shù)代碼問題,可通過參考常規(guī)的carsh解決方案,查看日志等解決問題
2. 看門狗(Watchdog)
在 app 關(guān)鍵切換期間長時間掛起等待, 比如說打開APP玉控、切到后臺、即將進(jìn)入到前臺這3種切換有大概 20 秒的時間限制狮惜,如果附加了調(diào)試器是不會發(fā)生這種終止情況的;
出現(xiàn)看門狗 通常意味著發(fā)生了嚴(yán)重問題高诺,比如說:死鎖、無限循環(huán)碾篡、在主線程上發(fā)生的其他無限同步工作
解決方案:
使用Xcode查看和導(dǎo)出崩潰日志 使用 MXCrashDiagnostic 獲仁(見崩潰)
3. CPU資源限制(CPU resource limit)
發(fā)生CPU資源限制被系統(tǒng)終止
可能原因,后臺長時間占用CPU資源過高(High sustained CPU load in background)
可以通過 MXCPUExceptionDiagnostic 查看
4. 內(nèi)存超出系統(tǒng)限制(Memory limit exceeded)
后臺的中央處理器內(nèi)存占用持續(xù)很高時 , 系統(tǒng)會生成一份能量例外報告. 如果此持續(xù)性工作的時間長到一定程度 ,系統(tǒng)會終止 app 的運行 ;如果內(nèi)存占用太多, 系統(tǒng)會在內(nèi)存占用率超過界限值時 馬上終止 app 的運行, 前臺和后臺的占用率界限值一樣.
比如:低版本APNGKit處理.apng圖片時开泽,內(nèi)存暴漲薛窥,會終止 app 的運行
可以在 Xcode Organizer 中查看 中央處理器的資源例外日志 也能通過 MXCPUExceptionDiagnostic 查看
解決方案
這些報告包含調(diào)用棧, 以此識別出 你的 app 在終止發(fā)生時正在做什么. 也許你的代碼里有漏洞 ,造成了中央處理器運行的任務(wù)過多, 修改一下就好. 但如果你需要在后臺進(jìn)行很高強度的工作 1.可以考慮把工作移入后臺處理任務(wù)
注意 : 不同設(shè)備的界限值也不同 . 一般來說: 設(shè)備越老 界限值越低 若你的 app 的目標(biāo)設(shè)備早于 iPhone 6s 就需要盡量把內(nèi)存占用量 始終控制在 200MB 以下
5. 內(nèi)存自動清理(Memory pressure exit)
發(fā)生原因
通常不是程序問題(Not a bug with you app) (壓力退出-自動清理)系統(tǒng)為了給其他APP內(nèi)存而殺掉后臺的程序機(jī)制
如何解決
盡量保證程序在后臺占用內(nèi)存小于50MB(Aim for less than 50MB in the background)
6. 后臺任務(wù)超時(Background task timeout)
執(zhí)行后臺任務(wù)時,未在30s內(nèi)結(jié)束后臺任務(wù)(Failure ro end the task explicitly result in termination.(in 30s))
如何發(fā)現(xiàn)?
iOS14控制臺會有超時任務(wù)消息打印 使用MXBackgrounndExitData(iOS14 MetricKit)統(tǒng)計和發(fā)現(xiàn)
如何解決?
從前臺轉(zhuǎn)到后臺時 可以通過調(diào)用指令 UIApplication.beginBackgroundTask 獲得額外的運行時間完成關(guān)鍵工作 當(dāng)工作完成時 需調(diào)用 endBackgroundTask 指令
30s內(nèi)結(jié)束后臺任務(wù)
檢查后臺任務(wù)的剩余時間
只有在時間足夠的前提下開啟任務(wù) 時間小于5s時盡快結(jié)束任務(wù)
注意:開始后臺任務(wù)UIApplication.beginBackgroundTask和結(jié)束endBackgroundTask需要成對調(diào)用眼姐,即诅迷,有開啟的必須有結(jié)束的
查找殺后臺的真正原因:
使用MetricKit 框架收集APP退出的類型:
@available(iOS 14.0, *)
open class MXBackgroundExitData : NSObject, NSSecureCoding {
// 正常退出次數(shù)
open var cumulativeNormalAppExitCount: Int { get }
// 內(nèi)存溢出引起程序退出次數(shù)
open var cumulativeMemoryResourceLimitExitCount: Int { get }
// cpu資源超限引起退出次數(shù)
open var cumulativeCPUResourceLimitExitCount: Int { get }
// 系統(tǒng)內(nèi)存自動清理引起退出次數(shù)
open var cumulativeMemoryPressureExitCount: Int { get }
// 非法訪問(SIGSEGV/SIGBUS)引起退出次數(shù)
open var cumulativeBadAccessExitCount: Int { get }
// 函數(shù)中止引起退出次數(shù)
open var cumulativeAbnormalExitCount: Int { get }
// 非法指令(SIG)引起退出次數(shù)
open var cumulativeIllegalInstructionExitCount: Int { get }
// 看門狗(WatchDog)引起的退出次數(shù)
open var cumulativeAppWatchdogExitCount: Int { get }
// 后臺讀寫文件引起的退出次數(shù)
open var cumulativeSuspendedWithLockedFileExitCount: Int { get }
// 后臺任務(wù)超時引起的退出次數(shù)
open var cumulativeBackgroundTaskAssertionTimeoutExitCount: Int { get }
}
問題解決:
控制臺有超時任務(wù)消息打印,經(jīng)過MXBackgroundExitData統(tǒng)計確認(rèn)众旗,是因為后臺任務(wù)超時引起的退出罢杉。然后定位開啟后臺任務(wù)的位置在某客服SDK中, 升級SDK后解決問題贡歧。
一些SDK在退到后臺清理資源的時候經(jīng)常用到:例如:SDWebImage滩租、Kingfisher退到后臺后清理磁盤中過期圖片。打點庫退到后臺后上報未上報的打點利朵。
第二部分:關(guān)于MeticKit
MetricKit 是蘋果的框架(iOS13-1.0律想,iOS14-2.0),他會在一天結(jié)束后绍弟,將過去 24 小時內(nèi)收集的性能數(shù)據(jù)歸集在一起技即,并在下一次 App 啟動時,通過 delegate 方法回調(diào)給我們樟遣。
MetricKit主要類
@available(iOS 13.0, *)
open class MXMetricManager : NSObject {
open var pastPayloads: [MXMetricPayload] { get }
@available(iOS 14.0, *)
open var pastDiagnosticPayloads: [MXDiagnosticPayload] { get }
open class var shared: MXMetricManager { get }
open class func makeLogHandle(category: String) -> OSLog
open func add(_ subscriber: MXMetricManagerSubscriber)
open func remove(_ subscriber: MXMetricManagerSubscriber)
}
@available(iOS 13.0, *)
public protocol MXMetricManagerSubscriber : NSObjectProtocol {
// 向度量管理器注冊的對象交付新的度量報告
optional func didReceive(_ payloads: [MXMetricPayload])
// 向指標(biāo)管理器注冊的對象提供新的診斷報告
@available(iOS 14.0, *)
optional func didReceive(_ payloads: [MXDiagnosticPayload])
}
MetricKit可采集的性能數(shù)據(jù)
@available(iOS 13.0, *)
open class MXMetricPayload : NSObject, NSSecureCoding {
// 收集此數(shù)據(jù)時的最高版本信息
open var latestApplicationVersion: String { get }
// 是否包含多個版本數(shù)據(jù)
open var includesMultipleApplicationVersions: Bool { get }
// 數(shù)據(jù)采集的開始時間
open var timeStampBegin: Date { get }
open var timeStampEnd: Date { get }
// 【電量】
// 表示一個周期內(nèi) CPU 執(zhí)行的指令總數(shù)而叼。這個指標(biāo)是衡量 App 在 CPU 工作負(fù)荷的絕對指標(biāo)身笤,
// 與硬件和頻率都沒有關(guān)系。他能幫助我們更準(zhǔn)確地量化 App 的總負(fù)荷葵陵。
open var cpuMetrics: MXCPUMetric? { get }
open var gpuMetrics: MXGPUMetric? { get }
// 蜂窩網(wǎng)絡(luò)
open var cellularConditionMetrics: MXCellularConditionMetric? { get }
open var locationActivityMetrics: MXLocationActivityMetric? { get }
open var networkTransferMetrics: MXNetworkTransferMetric? { get }
// 【性能】運行時間指標(biāo)液荸,前/后臺運行時間,后臺媒體脱篙、定位的運行時間等
open var applicationTimeMetrics: MXAppRunTimeMetric? { get }
open var applicationLaunchMetrics: MXAppLaunchMetric? { get }
// 用戶交互相關(guān)的響應(yīng)性指標(biāo)
open var applicationResponsivenessMetrics: MXAppResponsivenessMetric? { get }
open var diskIOMetrics: MXDiskIOMetric? { get }
open var memoryMetrics: MXMemoryMetric? { get }
open var displayMetrics: MXDisplayMetric? { get }
// 他指的是渲染的幀在滾動過程中的預(yù)期時間未出現(xiàn)在屏幕上娇钱,也就是我們俗稱的掉幀。
// MetricKit 會為我們提供 App 中滾動卡頓的時間與滾動的時間之比來幫我們了解 App 的滾動性能绊困。
@available(iOS 14.0, *)
open var animationMetrics: MXAnimationMetric? { get }
// 他統(tǒng)計的是每天應(yīng)用程序在前臺文搂、后臺運行的時候退出或被殺的原因概述。
@available(iOS 14.0, *)
open var applicationExitMetrics: MXAppExitMetric? { get }
// 自定義指標(biāo)
open var signpostMetrics: [MXSignpostMetric]? { get }
// 雜項元數(shù)據(jù)指標(biāo) (手機(jī)版本考抄,os版本,Build等)
open var metaData: MXMetaData? { get }
// 數(shù)據(jù)轉(zhuǎn)為json格式
open func jsonRepresentation() -> Data
// 數(shù)據(jù)轉(zhuǎn)為字典格式
@available(iOS, introduced: 13.0, deprecated: 100000)
open func dictionaryRepresentation() -> [AnyHashable : Any]
}
MetricKit可采集的診斷數(shù)據(jù)
@available(iOS 14.0, *)
open class MXDiagnosticPayload : NSObject, NSSecureCoding {
open var cpuExceptionDiagnostics: [MXCPUExceptionDiagnostic]? { get }
open var diskWriteExceptionDiagnostics: [MXDiskWriteExceptionDiagnostic]? { get }
open var hangDiagnostics: [MXHangDiagnostic]? { get }
open var crashDiagnostics: [MXCrashDiagnostic]? { get }
open var timeStampBegin: Date { get }
open var timeStampEnd: Date { get }
open func jsonRepresentation() -> Data
open func dictionaryRepresentation() -> [AnyHashable : Any]
}
MetricKit使用
一個 MXMetricPayload 對象就是一個周期(24 小時)內(nèi)收集到的所有性能指標(biāo)的集合蔗彤。如果有 24 小時以前未被收集過的數(shù)據(jù)川梅,也會在這里一并返回給我們。所以 delegate 方法這里給到我們的是一個數(shù)組然遏。
// 引入
import MetricKit
class MySubscriber: NSObject, MXMetricManagerSubscriber {
var metricManager: MXMetricManager?
override init() {
super.init()
// 獲取 MXMetricManager 單例
metricManager = MXMetricManager.shared
// 添加訂閱者
metricManager?.add(self)
}
deinit {
metricManager?.remove(self)
}
// 實現(xiàn) delegate回調(diào)
// 性能信息
func didReceive(_ payloads: [MXMetricPayload]) {
for item in payloads {
print(item)
print(item.jsonRepresentation())
print(item.dictionaryRepresentation())
}
}
// 診斷信息
func didReceive(_ payloads: [MXDiagnosticPayload]) {
for item in payloads {
print(item)
print(item.jsonRepresentation())
print(item.dictionaryRepresentation())
}
}
}
自定義打點, 收集某個范圍內(nèi)的指標(biāo)數(shù)據(jù)
override func viewDidLoad() {
super.viewDidLoad()
let photosLogHandle: OSLog = MXMetricManager.makeLogHandle(category: "Photos")
mxSignpost(.begin, log: photosLogHandle, name: "SavePhoto")
savePhotoAction()
mxSignpost(.end, log: photosLogHandle, name: "SavePhoto")
mxSignpost(.begin, log: photosLogHandle, name: "UploadPhoto")
uploadPhotoAction()
mxSignpost(.end, log: photosLogHandle, name: "UploadPhoto")
}
func savePhotoAction() {}
func uploadPhotoAction() {}
That's it ! 注意如果接入到app中,但是打印沒有數(shù)據(jù),需要查看下手機(jī)的設(shè)置 - 隱私 - 分析與改進(jìn) - 與App開發(fā)者共享, 這個有沒有打開, 這個打開了才能獲取到數(shù)據(jù).
但是一天只能獲取一次, 根本無法調(diào)試, 可以通過xcode模擬一次獲取MetricKit payloads贫途,需要真機(jī).
系統(tǒng)還非常貼心的提供了MXMetricPayload轉(zhuǎn)成字典和json的方法, 可以使用方便的查看信息, 數(shù)據(jù)格式示例.
- (NSData *)JSONRepresentation;
- (NSDictionary *)dictionaryRepresentation
部分?jǐn)?shù)據(jù)樣式:
[metaData: {
appBuildVersion = 1;
deviceType = "iPhone8,2";
osVersion = "iPhone OS 14.7.1 (18G82)";
platformArchitecture = arm64;
regionFormat = US;
},
cellularConditionMetrics: {
cellConditionTime = {
histogramNumBuckets = 3;
histogramValue = {
0 = {
bucketCount = 20;
bucketEnd = "1 bars";
bucketStart = "1 bars";
};
1 = {
bucketCount = 30;
bucketEnd = "2 bars";
bucketStart = "2 bars";
};
2 = {
bucketCount = 50;
bucketEnd = "3 bars";
bucketStart = "3 bars";
};
};
};
},
cpuMetrics: {
cumulativeCPUInstructions = "100 kiloinstructions";
cumulativeCPUTime = "100 sec";
},
diskIOMetrics: {
cumulativeLogicalWrites = "1,300 kB";
},
applicationExitMetrics: {
backgroundExitData = {
cumulativeAbnormalExitCount = 1;
cumulativeAppWatchdogExitCount = 1;
cumulativeBackgroundFetchCompletionTimeoutExitCount = 1;
cumulativeBackgroundTaskAssertionTimeoutExitCount = 1;
cumulativeBackgroundURLSessionCompletionTimeoutExitCount = 1;
cumulativeBadAccessExitCount = 1;
cumulativeCPUResourceLimitExitCount = 1;
cumulativeIllegalInstructionExitCount = 1;
cumulativeMemoryPressureExitCount = 1;
cumulativeMemoryResourceLimitExitCount = 1;
cumulativeNormalAppExitCount = 1;
cumulativeSuspendedWithLockedFileExitCount = 1;
};
}]
系統(tǒng)是怎么收集 MetricKit 的數(shù)據(jù)的
系統(tǒng)會在一天你使用 App 的時候被動地獲取、匯總 App 的性能數(shù)據(jù)待侵。而且為了保護(hù)用戶的隱私丢早,這些數(shù)據(jù)都是匿名存放的。最終一天結(jié)束的時候秧倾,系統(tǒng)會將一天內(nèi)收集到的這些數(shù)據(jù)打包成一個 MetricKit 的數(shù)據(jù)包怨酝,也就是前面提到的 MXMetricPayload。
雖然接入很簡單, 但是沒有什么必要, 蘋果已經(jīng)幫我們收集好并統(tǒng)計了數(shù)據(jù),可以在xcode -> window -> organizer 看到統(tǒng)計后的數(shù)據(jù), 如果想要具體的定制數(shù)據(jù)那就需要把這些數(shù)據(jù)上傳并統(tǒng)計了.
Xcode Metrics Organizer
什么是 Xcode Metrics Organizer那先?
- 開箱即用的電量和性能分析工具
- 無須改動 App
- 數(shù)據(jù)收集滿足用戶隱私
Xcode Metrics Organizer 如何運作
當(dāng)使用 App 時候农猬,iOS 會記錄各項指標(biāo),然后發(fā)送到蘋果服務(wù)端上售淡,并自動生成相關(guān)的可視化報告斤葱。
可以通過xcode -> Window -> Organizer -> Metrics 查看,可以查看各項指標(biāo)揖闸,并且和歷史版本進(jìn)行對比揍堕。
后臺平均被殺次數(shù)和各項占比:
其他數(shù)據(jù)分析:
內(nèi)存提供了峰值和平均內(nèi)存
電量有前后臺占用情況,CPU 汤纸、定位和網(wǎng)絡(luò)的比重等
歷史版本啟動耗時
主線程卡頓情況
寫磁盤數(shù)據(jù)量
參考:
關(guān)于MetricKit:
https://developer.apple.com/videos/play/wwdc2019/417/
https://developer.apple.com/videos/play/wwdc2020/10081/
https://blog.csdn.net/u014600626/article/details/122028794
關(guān)于app被終止:
https://developer.apple.com/wwdc20/10078
https://blog.csdn.net/u014600626/article/details/120371297
beginBackgroundTask && endBackgroundTask 成對使用:
https://blog.csdn.net/u014600626/article/details/121716987