如何减少APP电量消耗?

news/2024/11/26 3:41:59/

手机设备电量有限,App 开发时如不注意电量的的消耗,当用户发现你的 App 是耗电大户时,就会毫不犹豫地将其抛弃。所以,每次开发完,我们都需要去检查自己的 App 有没有耗电的问题。

耗电的原因有千万种,如果每次遇到耗电过多的问题,我们都从头查找一番的话,那必然会效率低下。

就比如说,测试同学过来跟你说“某个页面的前一个版本还好好的,这个版本的耗电怎么多了那么多”,那么你首先想到可能就是这个页面有没有开启定位,网络请求是不是频繁,亦或是定时任务时间是不是间隔过小。接下来,你会去查找耗电问题到底是怎么引起的。你去翻代码的时候却发现,这个页面的相关功能在好几个版本中都没改过了。

那么,到底是什么原因使得这一个版本的耗电量突然增加呢?不如就使用排除法吧,你把功能一个个都注释掉,却发现耗电量还是没有减少。这时,你应该怎么办呢?接下来,我就在今天的文章里面和你详细分享一下这个问题的解法吧。

我们首先需要明确的是,只有获取到电量,才能够发现电量问题。所以,我就先从如何获取电量和你讲起。

如何获取电量?

在 iOS 中,IOKit framework 是专门用于跟硬件或内核服务通信的。所以,我们可以通过 IOKit framework 来获取硬件信息,进而获取到电量消耗信息。在使用 IOKit framework 时,你需要:

  • 首先,把 IOPowerSources.h、IOPSKeys.h 和 IOKit 这三个文件导入到工程中;
  • 然后,把 batteryMonitoringEnabled 置为 true;
  • 最后,通过如下代码获取 1% 精确度的电量信息。
    #import "IOPSKeys.h"
    #import "IOPowerSources.h"-(double) getBatteryLevel{// 返回电量信息CFTypeRef blob = IOPSCopyPowerSourcesInfo();// 返回电量句柄列表数据CFArrayRef sources = IOPSCopyPowerSourcesList(blob);CFDictionaryRef pSource = NULL;const void *psValue;// 返回数组大小int numOfSources = CFArrayGetCount(sources);// 计算大小出错处理if (numOfSources == 0) {NSLog(@"Error in CFArrayGetCount");return -1.0f;}// 计算所剩电量for (int i=0; i<numOfSources; i++) {// 返回电源可读信息的字典pSource = IOPSGetPowerSourceDescription(blob, CFArrayGetValueAtIndex(sources, i));if (!pSource) {NSLog(@"Error in IOPSGetPowerSourceDescription");return -1.0f;}psValue = (CFStringRef) CFDictionaryGetValue(pSource, CFSTR(kIOPSNameKey));int curCapacity = 0;int maxCapacity = 0;double percentage;psValue = CFDictionaryGetValue(pSource, CFSTR(kIOPSCurrentCapacityKey));CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &curCapacity);psValue = CFDictionaryGetValue(pSource, CFSTR(kIOPSMaxCapacityKey));CFNumberGetValue((CFNumberRef)psValue, kCFNumberSInt32Type, &maxCapacity);percentage = ((double) curCapacity / (double) maxCapacity * 100.0f);NSLog(@"curCapacity : %d / maxCapacity: %d , percentage: %.1f ", curCapacity, maxCapacity, percentage);return percentage;}return -1.

    说完耗电量的获取方法,我们再继续看如何解决电量问题。

    如何诊断电量问题?

    回到最开始的问题,当你用排除法将所有功能注释掉后,如果还有问题,那么这个耗电一定是由其他线程引起的。创建这个耗电线程的地方可能是在其他地方,比如是由第三方库引起,或者是公司其他团队开发的二方库。

    所以,你需要逆向地去思考这个问题。这里,你不妨回顾一下,我们之前“iOS 崩溃千奇百怪,如何全面监控”中是怎么定位问题的。

    也就是说,我们还是先反过来看看出现电量问题的期间,哪个线程是有问题的。通过下面的这段代码,你就可以获取到所有线程的信息:

  • 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);
    

    从上面代码可以看出,通过 task_threads 函数,我们就能够得到所有的线程信息数组 threads,以及线程总数 threadCount。threads 数组里的线程信息结构体 thread_basic_info 里有一个记录 CPU 使用百分比的字段 cpu_usage。thread_basic_info 结构体的代码如下:

  • struct 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_usage 字段,你就可以通过遍历所有线程,去查看是哪个线程的 CPU 使用百分比过高了。如果某个线程的 CPU 使用率长时间都比较高的话,比如超过了 90%,就能够推断出它是有问题的。这时,将其方法堆栈记录下来,你就可以知道到底是哪段代码让你 App 的电量消耗多了。

    通过这种方法,你就可以快速定位到问题,有针对性地进行代码优化。多线程 CPU 使用率检查的完整代码如下:

  • // 轮询检查多个线程 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);}}}}
    }

    优化电量

    现在我们已经知道了在线上碰到电量问题时,应该如何解决,但是电量的不合理消耗也可能来自其他方面。CPU 是耗电的大头,引起 CPU 耗电的单点问题可以通过监控来解决,但点滴汇聚终成大海,每一个不合理的小的电量消耗,最终都可能会造成大的电量浪费。所以,我们在平时的开发工作中,时刻关注对耗电量的优化也非常重要。

    对 CPU 的使用要精打细算,要避免让 CPU 做多余的事情。对于大量数据的复杂计算,应该把数据传到服务器去处理,如果必须要在 App 内处理复杂数据计算,可以通过 GCD 的 dispatch_block_create_with_qos_class 方法指定队列的 Qos 为 QOS_CLASS_UTILITY,将计算工作放到这个队列的 block 里。在 QOS_CLASS_UTILITY 这种 Qos 模式下,系统针对大量数据的计算,以及复杂数据处理专门做了电量优化。

    接下来,我们再看看除了 CPU 会影响耗电,对电量影响较大的因素还有哪些呢?

    除了 CPU,I/O 操作也是耗电大户。任何的 I/O 操作,都会破坏掉低功耗状态。那么,针对 I/O 操作要怎么优化呢?

    业内的普遍做法是,将碎片化的数据磁盘存储操作延后,先在内存中聚合,然后再进行磁盘存储。碎片化的数据进行聚合,在内存中进行存储的机制,可以使用系统自带的 NSCache 来完成。

    NSCache 是线程安全的,NSCache 会在到达预设缓存空间值时清理缓存,这时会触发 cache:willEvictObject: 方法的回调,在这个回调里就可以对数据进行 I/O 操作,达到将聚合的数据 I/O 延后的目的。I/O 操作的次数减少了,对电量的消耗也就减少了。

    SDWebImage 图片加载框架,在图片的读取缓存处理时没有直接使用 I/O,而是使用了 NSCache。使用 NSCache 的相关代码如下:

  • - (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 更省电。

  • CPU 和 I/O 这两大耗电问题都解决后,还有什么要注意的呢?这里还有两份关于 App 电量消耗的资料,你可以对照你的 App 来查看。

    苹果公司专门维护了一个电量优化指南“Energy Efficiency Guide for iOS Apps”,分别从 CPU、设备唤醒、网络、图形、动画、视频、定位、加速度计、陀螺仪、磁力计、蓝牙等多方面因素提出了电量优化方面的建议。所以,当使用了苹果公司的电量优化指南里提到的功能时,严格按照指南里的最佳实践去做就能够保证这些功能不会引起不合理的电量消耗。

    同时,苹果公司在 2017 年 WWDC 的 Session 238 也分享了一个关于如何编写节能 App 的主题“Writing Energy Efficient Apps”。

  • 小结

    今天我跟你分享了如何通过获取线程信息里的 cpu_usage 字段来判断耗电线程,进而得到当前线程执行方法堆栈,从而精准、快速地定位到引起耗电的具体方法。我曾经用这个方法解决了几起难以定位的耗电问题,这些问题都出在二方库上。通过获取到的方法堆栈,我就有了充足的证据去推动其他团队进行电量优化。

    除此之外,我还跟你介绍了如何在平时开发中关注电量的问题。在我看来,减少 App 耗电也是开发者的天职,不然如何向我们可爱的用户交代呢。


http://www.ppmy.cn/news/197763.html

相关文章

android+省电播放器,真的能省电?五款Android省电应用实测

测试方法 智能手机的普及丰富了我们的碎片时间&#xff0c;但是没电的苦恼却困扰着几乎所有的智能手机用户。“用android手机一定是好男人&#xff0c;因为晚上都要回家充电。”这个段子绝对不是空穴来风&#xff0c;如果你手头正有这样一部电老虎似的智能机的话&#xff0c;那…

省电续航-省电技术

前言 手机省电本身就是个很为难的事情&#xff0c;目前手机的内存、CPU、摄像头等硬件性能越来越强大&#xff0c;软件叠加的功能和交互设计酷炫&#xff0c;几乎个个都在考验一直没什么大进步的电池技术。目前的省电技术是一个持续引入和改善的过程&#xff0c;以不纯粹为省电…

Win系统 - 电池续航越来越短?自带工具一秒揪出耗电应用

前言 当你跟一个出差的人聊天/谈论工作 有些文件传着传着就失败了 有些人说着说着就不见了&#xff1f; 糟糕&#xff0c;没带电源&#xff01; 对于移动办公人士来说 笔记本没电了 这跟手机没电带来的恐慌感绝对有得一拼&#xff01; 如果能够找出究竟是什么应用或进程耗电最多…

浅析能耗分析系统在数据中心节能降耗中的应用

王兰 安科瑞电气股份有限公司 上海嘉定201801 摘要&#xff1a;数据中心的能耗问题从未像今天这样引起行业、全国甚至全世界的关注。在未来相当长的时间内&#xff0c;数据中心能耗分析优化不仅是数据中心重点研究对象&#xff0c;同时也是运维管理的重点难点。本文简述了数据…

助力企业节能降耗:综合能效管理之场景控制

企业综合能效管理系统是为企业提供能耗管理、电能质量和用能安全监测的整套解决方案&#xff0c;系统可采集多种类型能源&#xff08;电、水、天然气、工业气体、冷热量等&#xff09;数据&#xff0c;并对能源消耗进行分析&#xff0c;包括分类分项能耗、区域能耗、部门能耗数…

怎样才能让手机省电?

使用屏幕自动亮度调节 智能手机的屏幕是所有元件里最耗电的&#xff0c;所以降低屏幕亮度是降低电池损耗的最简单办法。在一个为时一小时的测试里&#xff0c;一台把屏幕调到最暗的iPhone6s手机要比把亮度调到最亮的手机节省54%的电量。在安卓手机测试里&#xff0c;能节省30%…

节能降耗 | AIRIOT智慧电力综合管理解决方案

电力技术的发展推动各行各业的生产力&#xff0c;与此同时&#xff0c;企业中高能耗设备的应用以及输配电过程中的电能损耗&#xff0c;也在一定程度上加剧了电能供应压力。以工业制造业为例&#xff0c;企业的管理水平、能耗结构、生产组织方式都关系到能源的有效利用率&#…

Android省电策略

Android省电策略 本文阐述 Android 上偏上层的一些功耗节省的策略&#xff0c;本文阐述的 API 到 Android P。 前言 自从大屏智能手机的诞生&#xff0c;续航问题就一直是让用户和手机厂商头疼&#xff0c;以至于“一天一充”普遍存在。 Android 在功耗问题上也下了不少功夫&a…