美文网首页iOS学习iOS记录
iOS 从App杀后台开始了解MetricKit

iOS 从App杀后台开始了解MetricKit

作者: 呦释原点 | 来源:发表于2022-05-02 23:00 被阅读0次
    背景

    某APP某次发版后有用户反馈APP退到后台很快就被系统杀死了,然后进入APP就是冷启动,APP退到后台前的场景消失了。
    影响:在编辑文章的页面, 去其他的APP或者网页中找参考资料,再回来续写时,重启了。在某个商品出售页面, 去其他APP参考商品的价格,再回来时APP重启了

    第一部分:为什么退到后台很快被系统杀死了,app在后台被终止的原因

    1. 崩溃(Crashes)

    这种大多数代码问题,可通过参考常规的carsh解决方案,查看日志等解决问题

    2. 看门狗(Watchdog)
    在 app 关键切换期间长时间挂起等待, 比如说打开APP、切到后台、即将进入到前台

    这3种切换有大概 20 秒的时间限制,如果附加了调试器是不会发生这种终止情况的;
    出现看门狗 通常意味着发生了严重问题,比如说:死锁、无限循环、在主线程上发生的其他无限同步工作
    解决方案:
    使用Xcode查看和导出崩溃日志 使用 MXCrashDiagnostic 获取(见崩溃)

    3. CPU资源限制(CPU resource limit)

    发生CPU资源限制被系统终止
    可能原因,后台长时间占用CPU资源过高(High sustained CPU load in background)
    可以通过 MXCPUExceptionDiagnostic 查看

    4. 内存超出系统限制(Memory limit exceeded)

    后台的中央处理器内存占用持续很高时 , 系统会生成一份能量例外报告. 如果此持续性工作的时间长到一定程度 ,系统会终止 app 的运行 ;如果内存占用太多, 系统会在内存占用率超过界限值时 马上终止 app 的运行, 前台和后台的占用率界限值一样.
    比如:低版本APNGKit处理.apng图片时,内存暴涨,会终止 app 的运行

    可以在 Xcode Organizer 中查看 中央处理器的资源例外日志 也能通过 MXCPUExceptionDiagnostic 查看

    解决方案

    这些报告包含调用栈, 以此识别出 你的 app 在终止发生时正在做什么. 也许你的代码里有漏洞 ,造成了中央处理器运行的任务过多, 修改一下就好. 但如果你需要在后台进行很高强度的工作 1.可以考虑把工作移入后台处理任务

    注意 : 不同设备的界限值也不同 . 一般来说: 设备越老 界限值越低 若你的 app 的目标设备早于 iPhone 6s 就需要尽量把内存占用量 始终控制在 200MB 以下

    5. 内存自动清理(Memory pressure exit)

    发生原因

    通常不是程序问题(Not a bug with you app) (压力退出-自动清理)系统为了给其他APP内存而杀掉后台的程序机制

    如何解决

    尽量保证程序在后台占用内存小于50MB(Aim for less than 50MB in the background)

    6. 后台任务超时(Background task timeout)

    执行后台任务时,未在30s内结束后台任务(Failure ro end the task explicitly result in termination.(in 30s))

    如何发现?

    iOS14控制台会有超时任务消息打印 使用MXBackgrounndExitData(iOS14 MetricKit)统计和发现



    如何解决?

    从前台转到后台时 可以通过调用指令 UIApplication.beginBackgroundTask 获得额外的运行时间完成关键工作 当工作完成时 需调用 endBackgroundTask 指令
    30s内结束后台任务
    检查后台任务的剩余时间
    只有在时间足够的前提下开启任务 时间小于5s时尽快结束任务
    注意:开始后台任务UIApplication.beginBackgroundTask和结束endBackgroundTask需要成对调用,即,有开启的必须有结束的

    查找杀后台的真正原因:

    使用MetricKit 框架收集APP退出的类型:

    @available(iOS 14.0, *)
    open class MXBackgroundExitData : NSObject, NSSecureCoding {
        // 正常退出次数
        open var cumulativeNormalAppExitCount: Int { get }
        // 内存溢出引起程序退出次数
        open var cumulativeMemoryResourceLimitExitCount: Int { get }
        // cpu资源超限引起退出次数
        open var cumulativeCPUResourceLimitExitCount: Int { get }
        // 系统内存自动清理引起退出次数
        open var cumulativeMemoryPressureExitCount: Int { get }
        // 非法访问(SIGSEGV/SIGBUS)引起退出次数
        open var cumulativeBadAccessExitCount: Int { get }
        // 函数中止引起退出次数
        open var cumulativeAbnormalExitCount: Int { get }
        // 非法指令(SIG)引起退出次数
        open var cumulativeIllegalInstructionExitCount: Int { get }
        // 看门狗(WatchDog)引起的退出次数
        open var cumulativeAppWatchdogExitCount: Int { get }
        // 后台读写文件引起的退出次数
        open var cumulativeSuspendedWithLockedFileExitCount: Int { get }
        // 后台任务超时引起的退出次数
        open var cumulativeBackgroundTaskAssertionTimeoutExitCount: Int { get }
    }
    
    问题解决:

    控制台有超时任务消息打印,经过MXBackgroundExitData统计确认,是因为后台任务超时引起的退出。然后定位开启后台任务的位置在某客服SDK中, 升级SDK后解决问题。
    一些SDK在退到后台清理资源的时候经常用到:例如:SDWebImage、Kingfisher退到后台后清理磁盘中过期图片。打点库退到后台后上报未上报的打点。

    第二部分:关于MeticKit

    MetricKit 是苹果的框架(iOS13-1.0,iOS14-2.0),他会在一天结束后,将过去 24 小时内收集的性能数据归集在一起,并在下一次 App 启动时,通过 delegate 方法回调给我们。

    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])
    
        // 向指标管理器注册的对象提供新的诊断报告
        @available(iOS 14.0, *)
        optional func didReceive(_ payloads: [MXDiagnosticPayload])
    }
    
    
    MetricKit可采集的性能数据
    @available(iOS 13.0, *)
    open class MXMetricPayload : NSObject, NSSecureCoding {
    // 收集此数据时的最高版本信息
        open var latestApplicationVersion: String { get }
    // 是否包含多个版本数据
        open var includesMultipleApplicationVersions: Bool { get }
    // 数据采集的开始时间
        open var timeStampBegin: Date { get }
        open var timeStampEnd: Date { get }
    
        
    // 【电量】
    
    // 表示一个周期内 CPU 执行的指令总数。这个指标是衡量 App 在 CPU 工作负荷的绝对指标,
    // 与硬件和频率都没有关系。他能帮助我们更准确地量化 App 的总负荷。
        open var cpuMetrics: MXCPUMetric? { get }
        open var gpuMetrics: MXGPUMetric? { get }
    // 蜂窝网络
        open var cellularConditionMetrics: MXCellularConditionMetric? { get }
        open var locationActivityMetrics: MXLocationActivityMetric? { get }
        open var networkTransferMetrics: MXNetworkTransferMetric? { get }
    // 【性能】运行时间指标,前/后台运行时间,后台媒体、定位的运行时间等
        open var applicationTimeMetrics: MXAppRunTimeMetric? { get }
        open var applicationLaunchMetrics: MXAppLaunchMetric? { get }
    // 用户交互相关的响应性指标
        open var applicationResponsivenessMetrics: MXAppResponsivenessMetric? { get }
        open var diskIOMetrics: MXDiskIOMetric? { get }
        open var memoryMetrics: MXMemoryMetric? { get }
        open var displayMetrics: MXDisplayMetric? { get }
    // 他指的是渲染的帧在滚动过程中的预期时间未出现在屏幕上,也就是我们俗称的掉帧。
    // MetricKit 会为我们提供 App 中滚动卡顿的时间与滚动的时间之比来帮我们了解 App 的滚动性能。
        @available(iOS 14.0, *)
        open var animationMetrics: MXAnimationMetric? { get }
    // 他统计的是每天应用程序在前台、后台运行的时候退出或被杀的原因概述。
        @available(iOS 14.0, *)
        open var applicationExitMetrics: MXAppExitMetric? { get }
    // 自定义指标
        open var signpostMetrics: [MXSignpostMetric]? { get }
    // 杂项元数据指标 (手机版本,os版本,Build等)
        open var metaData: MXMetaData? { get }
        // 数据转为json格式
        open func jsonRepresentation() -> Data
        // 数据转为字典格式
        @available(iOS, introduced: 13.0, deprecated: 100000)
        open func dictionaryRepresentation() -> [AnyHashable : Any]
    }
    
    MetricKit可采集的诊断数据
    @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 小时)内收集到的所有性能指标的集合。如果有 24 小时以前未被收集过的数据,也会在这里一并返回给我们。所以 delegate 方法这里给到我们的是一个数组。

    // 引入
    import MetricKit
    class MySubscriber: NSObject, MXMetricManagerSubscriber {
        var metricManager: MXMetricManager?
        
        override init() {
            super.init()
            // 获取 MXMetricManager 单例
            metricManager = MXMetricManager.shared
            // 添加订阅者
            metricManager?.add(self)
        }
    
        deinit {
            metricManager?.remove(self)
        }
        
        // 实现 delegate回调
        // 性能信息
        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())
            }
        }
    }
    

    自定义打点, 收集某个范围内的指标数据

    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中,但是打印没有数据,需要查看下手机的设置 - 隐私 - 分析与改进 - 与App开发者共享, 这个有没有打开, 这个打开了才能获取到数据.

    但是一天只能获取一次, 根本无法调试, 可以通过xcode模拟一次获取MetricKit payloads,需要真机.


    系统还非常贴心的提供了MXMetricPayload转成字典和json的方法, 可以使用方便的查看信息, 数据格式示例.

    • (NSData *)JSONRepresentation;
    • (NSDictionary *)dictionaryRepresentation
    部分数据样式:
    [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;
        };
    }]
    
    系统是怎么收集 MetricKit 的数据的

    系统会在一天你使用 App 的时候被动地获取、汇总 App 的性能数据。而且为了保护用户的隐私,这些数据都是匿名存放的。最终一天结束的时候,系统会将一天内收集到的这些数据打包成一个 MetricKit 的数据包,也就是前面提到的 MXMetricPayload。

    虽然接入很简单, 但是没有什么必要, 苹果已经帮我们收集好并统计了数据,可以在xcode -> window -> organizer 看到统计后的数据, 如果想要具体的定制数据那就需要把这些数据上传并统计了.

    Xcode Metrics Organizer

    什么是 Xcode Metrics Organizer?

    • 开箱即用的电量和性能分析工具
    • 无须改动 App
    • 数据收集满足用户隐私

    Xcode Metrics Organizer 如何运作

    当使用 App 时候,iOS 会记录各项指标,然后发送到苹果服务端上,并自动生成相关的可视化报告。

    可以通过xcode -> Window -> Organizer -> Metrics 查看,可以查看各项指标,并且和历史版本进行对比。

    后台平均被杀次数和各项占比:


    后台平均被杀次数和各项占比

    其他数据分析:
    内存提供了峰值和平均内存
    电量有前后台占用情况,CPU 、定位和网络的比重等
    历史版本启动耗时
    主线程卡顿情况
    写磁盘数据量

    参考:
    关于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
    关于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

    相关文章

      网友评论

        本文标题:iOS 从App杀后台开始了解MetricKit

        本文链接:https://www.haomeiwen.com/subject/grhztrtx.html