App 的耗电量和性能是用户体验的重要部分,在 iOS 13 中推出了MetricKit,它用于收集和处理电池和性能指标。Improving Battery Life and Performance 中提出了三个用于不同阶段的工具进行电量和性能数据的收集,便于发现各个阶段出现的电量和性能问题。
- XCTest Metrics (开发和测试阶段)
- 可以使用 blocks 测试性能
- MetricsKit (Beta 和 Public Release 阶段)
- 用于收集电量和性能数据
- Xcode Metrics Organizer (Public Release 阶段)
- Xcode 中汇总了,电池,性能和 I/O 的指标
重要的两个分类指标
- Battery
- Performance
耗电的体现在下面这几种情况:
- Processing (CPU 和 GPU 耗时时间)
- Location (累计使用时长和后台使用时长)
- Display (平均像素亮度)
- Networking (上传和下载字节,连接性)
- Accessories(外围设备:蓝牙等)
- Multimedia(多媒体)
- Camera(相机)
性能的体现在下面这几种情况:
- Hangs (主线程卡顿)
- Disk (I/O)
- Application Launch (启动时间)
- Memory (内存)
开发和测试阶段
可以通过下方代码来测试一些操作的指标
- (void)testReSizeImagePerformance {
__auto_type app = [XCUIApplication new];
[self
measureWithMetrics:@[
XCTMemoryMetric.new,
XCTClockMetric.new,
XCTCPUMetric.new
]
block:^{
[app.buttons[@"reSize"] tap];
}];
}
启动时间
- (void)testLaunchPerformance {
if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
[self measureWithMetrics:@[XCTOSSignpostMetric.applicationLaunchMetric] block:^{
[[[XCUIApplication alloc] init] launch];
}];
}
}
MetricsKit
#import "AppMetricManagerSubscriber.h"
@import MetricKit;
@interface AppMetricManagerSubscriber()
<
MXMetricManagerSubscriber
>
@end
@implementation AppMetricManagerSubscriber
- (instancetype)init {
self = [super init];
if (!self) { return nil; }
// 1.添加订阅
[MXMetricManager.sharedManager addSubscriber:self];
return self;
}
// 2.实现指标数据回调
- (void)didReceiveMetricPayloads:(NSArray *)payloads {
[payloads enumerateObjectsUsingBlock:^(MXMetricPayload * _Nonnull payload, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@", payload.DictionaryRepresentation);
}];
}
- (void)dealloc {
// 3.移除订阅
[MXMetricManager.sharedManager removeSubscriber:self];
}
@end
回调时机是,当 App 累计运行了 24 小时
就进行回调
回调数据格式
{
appVersion = "1.0";
applicationLaunchMetrics = {
histogrammedResumeTime = {
histogramNumBuckets = 3;
histogramValue = {
0 = {
bucketCount = 60;
bucketEnd = "210 ms";
bucketStart = "200 ms";
};
1 = {
bucketCount = 70;
bucketEnd = "310 ms";
bucketStart = "300 ms";
};
2 = {
bucketCount = 80;
bucketEnd = "510 ms";
bucketStart = "500 ms";
};
};
};
histogrammedTimeToFirstDrawKey = {
histogramNumBuckets = 3;
histogramValue = {
0 = {
bucketCount = 50;
bucketEnd = "1,010 ms";
bucketStart = "1,000 ms";
};
1 = {
bucketCount = 60;
bucketEnd = "2,010 ms";
bucketStart = "2,000 ms";
};
2 = {
bucketCount = 30;
bucketEnd = "3,010 ms";
bucketStart = "3,000 ms";
};
};
};
};
applicationResponsivenessMetrics = {
histogrammedAppHangTime = {
histogramNumBuckets = 3;
histogramValue = {
0 = {
bucketCount = 50;
bucketEnd = "100 ms";
bucketStart = "0 ms";
};
1 = {
bucketCount = 60;
bucketEnd = "400 ms";
bucketStart = "100 ms";
};
2 = {
bucketCount = 30;
bucketEnd = "700 ms";
bucketStart = "400 ms";
};
};
};
};
applicationTimeMetrics = {
cumulativeBackgroundAudioTime = "30 sec";
cumulativeBackgroundLocationTime = "30 sec";
cumulativeBackgroundTime = "40 sec";
cumulativeForegroundTime = "700 sec";
};
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 = {
cumulativeCPUTime = "100 sec";
};
diskIOMetrics = {
cumulativeLogicalWrites = "1,300 kB";
};
displayMetrics = {
averagePixelLuminance = {
averageValue = "50 apl";
sampleCount = 500;
standardDeviation = 0;
};
};
gpuMetrics = {
cumulativeGPUTime = "20 sec";
};
locationActivityMetrics = {
cumulativeBestAccuracyForNavigationTime = "20 sec";
cumulativeBestAccuracyTime = "30 sec";
cumulativeHundredMetersAccuracyTime = "30 sec";
cumulativeKilometerAccuracyTime = "20 sec";
cumulativeNearestTenMetersAccuracyTime = "30 sec";
cumulativeThreeKilometersAccuracyTime = "20 sec";
};
memoryMetrics = {
averageSuspendedMemory = {
averageValue = "100,000 kB";
sampleCount = 500;
standardDeviation = 0;
};
peakMemoryUsage = "200,000 kB";
};
metaData = {
appBuildVersion = 1;
deviceType = "iPhone11,8";
osVersion = "iPhone OS 13.3 (17C54)";
regionFormat = CN;
};
networkTransferMetrics = {
cumulativeCellularDownload = "80,000 kB";
cumulativeCellularUpload = "70,000 kB";
cumulativeWifiDownload = "60,000 kB";
cumulativeWifiUpload = "50,000 kB";
};
signpostMetrics = (
{
signpostCategory = TestSignpostCategory1;
signpostIntervalData = {
histogrammedSignpostDurations = {
histogramNumBuckets = 3;
histogramValue = {
0 = {
bucketCount = 50;
bucketEnd = "100 ms";
bucketStart = "0 ms";
};
1 = {
bucketCount = 60;
bucketEnd = "400 ms";
bucketStart = "100 ms";
};
2 = {
bucketCount = 30;
bucketEnd = "700 ms";
bucketStart = "400 ms";
};
};
};
signpostAverageMemory = "100,000 kB";
signpostCumulativeCPUTime = "30,000 ms";
signpostCumulativeLogicalWrites = "600 kB";
};
signpostName = TestSignpostName1;
totalSignpostCount = 30;
},
{
signpostCategory = TestSignpostCategory2;
signpostIntervalData = {
histogrammedSignpostDurations = {
histogramNumBuckets = 3;
histogramValue = {
0 = {
bucketCount = 60;
bucketEnd = "200 ms";
bucketStart = "0 ms";
};
1 = {
bucketCount = 70;
bucketEnd = "300 ms";
bucketStart = "201 ms";
};
2 = {
bucketCount = 80;
bucketEnd = "500 ms";
bucketStart = "301 ms";
};
};
};
signpostAverageMemory = "60,000 kB";
signpostCumulativeCPUTime = "50,000 ms";
signpostCumulativeLogicalWrites = "700 kB";
};
signpostName = TestSignpostName2;
totalSignpostCount = 40;
}
);
timeStampBegin = "2020-02-08 16:00:00 +0000";
timeStampEnd = "2020-02-09 15:59:00 +0000";
}
自定义打点, 收集某个范围内的指标数据
__auto_type reSizeLog = [MXMetricManager makeLogHandleWithCategory:@"Image"];
os_signpost_id_t signpost_id = os_signpost_id_generate(reSizeLog);
MXSignpostIntervalBegin(reSizeLog, signpost_id, "reSizeImage");
[self reSizeImage];
MXSignpostIntervalEnd(reSizeLog, signpost_id, "reSizeImage");
Xcode Metrics Organizer
什么是 Xcode Metrics Organizer?
- 开箱即用的电量和性能分析工具
- 无须改动 App
- 数据收集满足用户隐私
Xcode Metrics Organizer 如何运作
当使用 App 时候,iOS 会记录各项指标,然后发送到苹果服务端上,并自动生成相关的可视化报告。
可以通过 Window -> Organizer -> Metrics 查看,可以查看各项指标,并且和历史版本进行对比。
- 内存提供了峰值和平均内存
- 电量有前后台占用情况,CPU 、定位和网络的比重等。
- 历史版本启动耗时
- 主线程卡顿情况
- 写磁盘数据量