现如今市面上各种大型手机游戏层出不穷,这对手机的续航与功耗提出了更高的要求。许多市面上流行的爆款手游早在设计与开发之初就已将用户体验与手机耗电进行完美兼顾。
那么,这些爆款手游在设计与开发过程中解决了哪些高耗电场景呢?
一、高耗电场景举例
1、Push
Push 即 notification 消息的一个交互,是一种消息推送机制,在安卓系统中存在多个 Push 通道,这导致了应用的频繁唤醒。
安卓系统推送机制有 GCM 即 Google Cloud
Messaging,主要用于消息推送,即使在应用没有唤起的情况下,客户端也能通过 GCM 收到来自服务器的消息,该功能是安卓系统原生功能,因服务器同步问题,在国内安卓版本上没有进行广泛推广应用;国内现以华为为首的手机厂商根据各自系统版本推出各自定制的 Push 通道;除此之外,像微信这样的应用也会有独立的 Push 通道,通过这个通道可以将服务端与手机上的客户端进行数据连接;
在安卓系统上,由于应用拥有独立的 Push 通道导致多个应用客户端与服务端不停交互,系统始终无法休眠,导致系统耗电加快;这是当前安卓系统比较耗电的场景。
2、Wakelock 长时间持锁
通过上图可以了解安卓系统的休眠机制,开始系统是规律的、红色那些线,这表示系统是频繁地被唤醒。如果说灭屏以后屏幕静止了,手机放在桌上过段时间后,系统就会进入一个浅睡眠,就是上图第一个 Doze 状态,这时没有网络访问、Syncs 以及 Jobs
Deferred 都不推荐使用,定期还会有一个维护窗口,这时应用是可以被唤醒的;再过一段时间,系统会进入深度睡眠状态,此时限制更多,No
wakelocks、No network access、Alarms/Syncs/Jobs Deferred 不推荐使用、No GPS/Wi-Fi
Scans,这只是一个理想的睡眠状态,实际很难进入这样一个状态;
当应用设置 Wakelock 时,可以避免因系统休眠而导致应用进程结束。Wakelock 有两种使用方式,第一种是系统上层通过使用 PowerManagerService 申请 Wakelock,这段时间系统不会进入休眠状态,应用可以继续运行;另一种方式可以采用底层 wake_lock/wake_unlock 接口来避免系统进入休眠状态,比如说一些 native 接口或者底层一些 Wifi、GPS 驱动。
一旦持锁打开后忘记关闭会导致系统一直处于该状态,会使系统处于耗电异常状态。
在程序设计过程中,程序在获取持锁后因为某种原因异常退出,结果导致 Wakelock 一直呈现打开状态,此时系统耗电也处于异常,所以 Wakelock 在使用过程中需特别注意。
二、编译技术在低功耗开发中的应用
理论上所有把一种编程语言转化为另一种语言或格式都叫编译。
常见的三种编译技术
1、把编程语言直接编译为机器码,典型的如 C/C++ 的编译器
2、把编程语言编译为字节码,由虚拟机执行
3、领域特定语言(DSL)的编译器
在产品中应用编译技术的几个可行方面
1、研究编译器选项,或者通过迭代编译获得最佳选项,从而在产品中获得性能提升;
2、增加编译器扩展 (pragma, _attribute_),进行额外的编译检查和辅助代码生成;
3、基于编译器前端生成的抽象语法树(AST)进行代码静态分析,以及基于 AST 重写来进行自动化的代码重构;
4、基于编译器后端输入的中间表达式(IR)进行跨函数/跨 TU 的分析;
5、基于编译指令修改的运行时错误发现;
CLANG&LLVM
Wakelock 持锁后,系统没有得到及时释放,在前端代码静态检测过程中,可以通过 CLANG&LLVM 编译进行及时检测。CLANG&LLVM 是一个类似 GCC 的编译器,但与 GCC 相比具有更加开放、模块性更优、扩展性更强等优势,具有抽象语法树,语法分析,语法数等功能。
Clang 的模块架构
Clang 的主要模块以及功能
Clang&LLVM 优势:
1、学习曲线比 GCC 更平缓;
2、Clang&LLVM 使用 BSD License,相比 GPL 更加友好;
3、高度的模块化,比 GCC 更容易扩展和二次开发;
4、有设计良好的接口和模式,便于访问内部数据,如:访问抽象语法树(AST)节点、获取控制流图节点(CFG
Node)、进行上下文符号获取等等;
5、Clang 对 C++ 标准的支持更完整、更快;
6、良好的 GCC 兼容性,包括:GCC 内置扩展语法、内置关键字的支持;
7、效率更高(编译速度、内存开销、部分平台上代码执行效率以超过 GCC);
代码静态分析
在编码过程中,怎样通过代码的静态分析来识别编码错误?什么又叫代码静态分析呢?
代码静态分析,被分析程序不需要运行起来,不依赖执行环境,通过对程序的源代码或者某种形式的中间代码进行分析来发现代码中的缺陷,在大型软件分析中,非常有价值。
基于 Clang 三种不同层次的代码分析
1、Preprocessor Matcher(Clang PPCallback)预处理
通过向 Clang 的 Preprocess 模块注册回调,能够获得每个 Token 的处理;
获取的 Token 进行文本匹配,不是 Path-Sensitive,不是 Context-Sensitive;
2、AST Matcher(Clang Tidy)抽象语法树分析
使用 RecursiveASTVisitor 对抽象语法树(AST)进行遍历,对每个 Stmt 或者 Decl 进行分析;
适合检查一些编码规范类,如:在头文件里面导入全局 namespace 等,不是 Path-Sensitive,不是 Context-Sensitive;
3、Symbolic Execution 符号执行
沿着控制流图(CFG)的每条边(路径)进行虚拟的符号执行,保存符号的值以及上下文,记录路径跳转的条件;
适合对 TU 内的函数进行资源泄露、重复释放等检查, 是 Path/Context-Sensitive;
符号执行流程示例
根据 AST 构造控制流图 CFG,从 CFG 的根节点开始,沿着图的各条边进行语句的虚拟执行
对所有可能的 Path 都需要进行遍历,使用符号来表示结果,而不是像运行时记录实际的值。
在遍历每条路径时,在每个点(Program
State)上收集所有在这个点上可见的变量符号值(Symbolic
Value),对 “资源泄露” 问题转化为图的可达性判断问题。
符号执行流程
在路径的遍历分析中用记录变量的 Symbolic Value
FILE* f 在所有路径都可见,路径的所有节点(语句)上均记录 f 的 Symbolic
Value,达到 Sink 节点时,根据 f 的 Symbolic Value 来判断是否残留句柄未关闭。
在分析过程中的节点生成的图称之为 “ExplodedGraph”,本例的问题转化为一个图的可达性问题。
Exploded Node 中的核心概念
在 BasicBlock 里面有很多结点,每个结点里面可以有两个状态,一个状态就是 Program
Point 即程序所处的位置信息,即当前指令的位置分析。另一个状态是 Program
State 即用来记录当前环境、约束、变量范围及用户定义范围等信息。
Analysis Checker
Clang 机制使用过程中,Clang 框架提供了许多挂钩子方式,在控制流图遍历过程中,每个语句访问时触发,是一种典型的控制反转(IoC)模式,开发者可以在不同的触发点上注册自己的检查,Checker 会参与并影响到 Exploded
Graph 的创建,为了减少路径爆炸,Checker 可以通过创建 Sink Node 来做路径支剪。
上图中依据 Clang 框架在 Wakelock 检查过程中,开发者可根据不同条件注册 Check1 以及 Check2 两个检查环节,Checker 可以通过创建 Sink
Node 来对注册的 Check 做路径支剪。
Checker Point
checkPreStmt (const ReturnStmt *S, CheckerContext &C) const
在函数中的 Return 语句准备执行之前调用(每个 Return 语句都会触发);
evalCall (const CallExpr* CE, CheckerContext &C) const
在遇到函数调用语句时触发执行;
checkEndFunction (CheckerContext &C) const
在整个函数的路径执行完成后触发调用;
checkBind (Sval L, Sval R, const Stmt* S, CheckerContext &C) const
当语句中把一个值绑定到一个地址上触发调用(如 C 语言中的赋值操作 “=” 执行后),可以将具体的值获取;
蓝牙持锁检查过程举例:
int bluesleepproc(struct bluetoothsleepdata *bsdata)
{
intretval = 0;
wake_lock(&bs_data->wake_lock); //增加evalCall检查点,记录当前已经持锁的信息
retval =request_irq(bs_data->host_wake_irq, bluesleep_hostwake_isr,
IRQF_DISABLED | IRQF_TRIGGER_RISING |
IRQF_NO_SUSPEND, "bluetoothhostwake", bs_data);
if (retval < 0) {
pr_err("%s: couldn't acquire BT_HOST_WAKE IRQ",__func__);
goto err_request_irq;
}
if (!test_bit(BT_PROTO,&bs_data->flags)) {
up(&bs_data->bt_seam);
pr_err("%s: bluesleep already stop\n", __func__);
return -1; //增加checkEndFunction检查点,因为没释放持锁,所以report warning
}
errrequestirq:
wake_unlock(&bs_data->wake_lock); //增加evalCall检查点,正常释放持锁信息
pr_info("%s, bluetooth power manage unitstop -.\n", __func__);
return retval;
} //函数的每个分支结束时,都会触发调用checkEndFunction