App电量消耗优化
App中的耗电大户:CPU和I/O操作
如何获取电量
IOKit framework
是专门用于跟硬件或内核服务通信的。所以,我们可以通过IOKit framework
来获取硬件信息,进而获取到电量消耗信息。
使用:导入IOPowerSources.h
、IOPSKeys.h
、IOKit
,将batteryMonitoringEnable
设置为true
,通过代码获取精确到1%的电量信息:
1 |
|
诊断CPU造成的电量消耗
获取所有线程信息
1
2
3
4thread_act_array_t threads;
mach_msg_type_number_t threadCount = 0;
const task_t thisTask = mach_task_self();
kern_return_t kr = task_threads(thisTask, &threads, &threadCount);检查多线程的CPU使用情况,找到CPU占用高的线程,获取线程堆栈信息,并优化线程操作。
线程基本信息结构体:
1
2
3
4
5
6
7
8
9
10struct thread_basic_info {
time_value_t user_time; /* user 运行的时间 */
time_value_t system_time; /* system 运行的时间 */
integer_t cpu_usage; /* CPU 使用百分比 */
policy_t policy; /* 有效的计划策略 */
integer_t run_state; /* run state (see below) */
integer_t flags; /* various flags (see below) */
integer_t suspend_count; /* suspend count for thread */
integer_t sleep_time; /* 休眠时间 */
};通过遍历所有线程,去查看是哪个线程的 CPU 使用百分比过高了。如果某个线程的CPU使用率长时间都比较高的话,比如超过了90%,就能够推断出它是有问题的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// 轮询检查多个线程 CPU 情况
+ (void)updateCPU {
thread_act_array_t threads;
mach_msg_type_number_t threadCount = 0;
const task_t thisTask = mach_task_self();
kern_return_t kr = task_threads(thisTask, &threads, &threadCount);
if (kr != KERN_SUCCESS) {
return;
}
for (int i = 0; i < threadCount; i++) {
thread_info_data_t threadInfo;
thread_basic_info_t threadBaseInfo;
mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX;
if (thread_info((thread_act_t)threads[i], THREAD_BASIC_INFO, (thread_info_t)threadInfo, &threadInfoCount) == KERN_SUCCESS) {
threadBaseInfo = (thread_basic_info_t)threadInfo;
if (!(threadBaseInfo->flags & TH_FLAGS_IDLE)) {
integer_t cpuUsage = threadBaseInfo->cpu_usage / 10;
if (cpuUsage > 90) {
//cup 消耗大于 90 时打印和记录堆栈
NSString *reStr = smStackOfThread(threads[i]);
//记录数据库中
[[[SMLagDB shareInstance] increaseWithStackString:reStr] subscribeNext:^(id x) {}];
NSLog(@"CPU useage overload thread stack:\n%@",reStr);
}
}
}
}
}IO操作优化
任何的 I/O 操作,都会破坏掉低功耗状态。针对优化 I/O 操作,普遍的做法是,将碎片化的数据磁盘存储操作延后,先在内存中聚合,然后再进行磁盘存储。碎片化的数据进行聚合,在内存中进行存储的机制,可以使用系统自带的
NSCache
来完成。NSCache
是线程安全的,NSCache
会在到达预设缓存空间值时清理缓存,这时会触发cache:willEvictObject:
方法的回调,在这个回调里就可以对数据进行 I/O 操作,达到将聚合的数据 I/O 延后的目的。I/O 操作的次数减少了,对电量的消耗也就减少了。比如SDWebImage
,在图片的读取缓存处理时没有直接使用I/O,而是使用了NSCache
:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17- (UIImage *)imageFromMemoryCacheForKey:(NSString *)key {
return [self.memCache objectForKey:key];
}
- (UIImage *)imageFromDiskCacheForKey:(NSString *)key {
// 检查 NSCache 里是否有
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
return image;
}
// 从磁盘里读
UIImage *diskImage = [self diskImageForKey:key];
if (diskImage && self.shouldCacheImagesInMemory) {
NSUInteger cost = SDCacheCostForImage(diskImage);
[self.memCache setObject:diskImage forKey:key cost:cost];
}
return diskImage;SDWebImage
将获取的图片数据都放到了NSCache
里,利用NSCache
缓存策略进行图片缓存内存的管理。每次读取图片时,会检查NSCache
是否已经存在图片数据:如果有,就直接从NSCache
里读取;如果没有,才会通过 I/O 读取磁盘缓存图片。使用了
NSCache
内存缓存能够有效减少 I/O 操作,你在写类似功能时也可以采用这样的思路,让你的 App 更省电。其他优化
对于大量数据的复杂计算,应该把数据传到服务器去处理,如果必须要在 App 内处理复杂数据计算,可以通过 GCD 的
dispatch_block_create_with_qos_class
方法指定队列的Qos
为QOS_CLASS_UTILITY
,将计算工作放到这个队列的block
里。在QOS_CLASS_UTILITY
这种Qos
模式下,系统针对大量数据的计算,以及复杂数据处理专门做了电量优化。