现如今市面上各种大型手机游戏层出不穷,这对手机的续航与功耗提出了更高的要求。许多市面上流行的爆款手游早在设计与开发之初就已将用户体验与手机耗电进行完美兼顾。
那么,这些爆款手游在设计与开发过程中解决了哪些高耗电场景呢?
一、高耗电场景举例
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复制代码