iOS 從App殺后臺開始了解MetricKit

背景

某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ù)和各項占比

其他數(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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末衩茸,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贮泞,更是在濱河造成了極大的恐慌递瑰,老刑警劉巖祟牲,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異抖部,居然都是意外死亡说贝,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進(jìn)店門慎颗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來乡恕,“玉大人,你說我怎么就攤上這事俯萎“烈耍” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵夫啊,是天一觀的道長函卒。 經(jīng)常有香客問我,道長撇眯,這世上最難降的妖魔是什么报嵌? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮熊榛,結(jié)果婚禮上锚国,老公的妹妹穿的比我還像新娘。我一直安慰自己玄坦,他們只是感情好血筑,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著煎楣,像睡著了一般豺总。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上择懂,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天园欣,我揣著相機(jī)與錄音,去河邊找鬼休蟹。 笑死沸枯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赂弓。 我是一名探鬼主播绑榴,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼盈魁!你這毒婦竟也來了翔怎?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎赤套,沒想到半個月后飘痛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡容握,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年宣脉,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剔氏。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡塑猖,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谈跛,到底是詐尸還是另有隱情羊苟,我是刑警寧澤,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布感憾,位于F島的核電站蜡励,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏阻桅。R本人自食惡果不足惜凉倚,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望鳍刷。 院中可真熱鬧占遥,春花似錦俯抖、人聲如沸输瓜。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尤揣。三九已至,卻和暖如春柬祠,著一層夾襖步出監(jiān)牢的瞬間北戏,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工漫蛔, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留嗜愈,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓莽龟,卻偏偏與公主長得像蠕嫁,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子毯盈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容