本文由字節跳動技術團隊高原、湯中峰分享,原題“抖音功耗優化實踐”,本文有修訂和改動。
一、引言
功耗優化是應用體驗優化的一個重要課題,高功耗會引發用戶的電量焦慮,也會導致糟糕的發熱體驗,從而降低了用戶的使用意愿。而功耗又是涉及整機的長時間多場景的綜合性復雜指標,影響因素很多。不論是功耗的量化拆解,還是異常問題的監控,以及主動的功耗優化對于開發人員來說都是很有挑戰性的。
本文結合抖音的功耗優化實踐中產出了一些實驗結論,優化思路,從功耗的基礎知識,功耗組成,功耗分析,功耗優化等幾個方面,對 Android 應用的功耗優化做一個總結沉淀。

* 相關文章推薦閱讀:
- 移動端IM開發入門文章:《新手入門一篇就夠:從零開發移動端IM》
- 開源IM框架源碼:https://github.com/JackJiang2011/MobileIMSDK(備用地址點此)
(本文已同步發布于:http://www.52im.net/thread-4540-1-1.html)
二、功耗基礎知識介紹
首先我們回顧一下功耗的概念,這里比較容易和能耗搞混。解釋一下為什么手機上用mA(電流值)來表征功耗水平,用 mAh(物理意義上是電荷值)來表征能耗水平。
我們先來看幾個物理公式:
P = I × U, E = P × T
能耗(E):即能量損耗,指計算機系統一段時間內總的能量消耗,單位是焦耳(J)。
功耗(P):即功率損耗,指單位時間內的能量消耗,反映消耗能量的速率,單位是瓦特(W)。
電流(I):指手機電池放電的電流值,手機常用 mA 為單位。
電壓(U):指手機電池放電的電壓值,標準放電電壓 3.7V,充電截止電壓 4.35V,放電截止電壓 2.75V(以典型值舉例,不同設備的電池電壓數值有差異)。
電池容量 :常用單位 mAh,從單位意義上看是電荷數,實際表征的是電池以典型電壓放電的時長。
如下面的功耗測試圖所示,手機通常以恒定的典型電壓工作,為了計算方便,就把電壓恒定為 3.7V,那么 P = I × 3.7, E = I × 3.7 × T,即用 mA 表征功耗,mAh 表征能耗。
總結:對同一機型,我們用電池容量(mAh)變化的來表征一段時間總能耗,用平均電流(mA)來表征功耗水平。如 4000mAh 電池的手機刷抖音 1 小時耗電 11%,耗電量(能耗)440mAh,平均電流 440mA

三、為什么要做功耗優化
從引言里我們已經了解到高功耗會引發用戶的電量焦慮,也會導致糟糕的發熱體驗,從而降低了用戶的使用意愿。
優化功耗除了可以我們帶來更好的用戶體驗,提升用戶使用時長外,降低應用耗電還具有很明顯的社會價值。
用一個當前比較火的詞,就是可以為碳中和事業貢獻一份力量。
四、如何來做功耗優化
不同于 Crash、ANR 等常見的 APM 指標,功耗是一個綜合性的課題,分析起來很容易讓人無從下手。
用戶反饋了耗電問題,可能是 CPU 出現高負載,又或者是后臺頻繁的網絡訪問,也可能是動畫泄漏導致高功耗。或者我們自己的業務沒什么變化,單純就是環境因素影響,導致用戶覺得耗電,比如低溫導致的鋰電池放電衰減。
我們的思路是從器件出發,應用的耗電最終都可以分解為手機器件的耗電,所以我們先對抖音做器件耗電的拆解,看主要耗電的是哪些器件,再看如何減少器件的使用,這樣就做到有的放矢。
下面我們先從功耗組成,功耗分析,以及功耗優化等方面來講述如何開展功耗優化。
五、功耗組成
5.1、概述

這里列舉了手機硬件的基本形態,每個模塊又是由復雜的器件構成。
如我們常說的耗電大頭 SoC 里就包含 CPU 的超大核、大核、小核、GPU、DDRC(內存接口),以及外設區的各種小 IP 核等。所以整機的功耗最終就可以拆解為各個器件的功耗,而應用的功耗就是計算其使用的器件產生的功耗。
以抖音的 Feed 流場景為例:亮度固定 120nit、7 格音量、WiFi 網絡下,我們對抖音做了器件級的功耗拆解。可以看到抖音的 feed 功耗主要集中在 SOC(CPU,GPU,DDR),Display,Audio,WIFI 等四個模塊。

5.2、器件功耗計算
那這些器件功耗是如何被拆解出來的呢?
原理是:先對器件進行耗電因子拆解,建立器件功耗模型,得到一個器件耗電的計算公式。通過運行時統計器件的使用數據,代入功耗模型,就可以計算出器件的功耗。應用的功耗則是從器件的總功耗里按應用使用的比較進行分配,這樣就得到了應用的器件耗電。
由于影響器件功耗的耗電因子眾多,這里復雜的就是如何對耗電因子進行拆解以及建模。有了精準的建模,后面就是廠商適配校準參數的過程了。
谷歌提供了一套通用的器件耗電模型和配置方案,OEM 廠商可以按通用方案對自己的產品進行參數校準和配置。
如下圖里 AOSP 里的耗電配置里,以 Wifi 的耗電計算為例:

(來源:https://source.android.com/devices/tech/power/values)
谷歌提供的建模方案是對 WIFI 分狀態計算耗電,WIFI 不同狀態下的耗電差異非常明顯。這里分為了 wifi.on(對應 wifi 打開的基準電流), wifi.active(對應 wifi 傳輸數據時的基準電流), wifi.scan(對應 wifi 單次掃描的基準耗電), wifi 數據傳輸的耗電(controller.rx,controller.tx, controller.idle)。根據 wifi 收發數據的那計算 wifi 的耗電,通過統計這幾個狀態的時長或次數,乘以對應的電流,就得到 wifi 器件的耗電了。
由于谷歌是按照通用性來設計的器件耗電模型,通常只能大致計算出器件的耗電水平,具體到某個產品上可能誤差很大。各 OEM 廠商通常有基于自身硬件的耗電統計方案,可以對耗電做更加精細準確的計算。
這里還用 wifi 舉例:如 OEM 廠商可以分別按照 2.4G,5GWIFI 單獨建模,并引入天線信號的變化對應的基準電流變化,以及統計 wifi 芯片所工作的頻點時長,按頻點細化模型等等,OEM 廠商可以設計出更符合自己設備的精準功耗模型,計算出更精準的 wifi 耗電。這就要根據具體產品的硬件方案來確定了。
六、功耗分析
通過上面的功耗組成的介紹,我們可以看到功耗影響因素是多種多樣。在做應用功耗分析時,我們既要有方法準確評估應用的耗電水平,又要有方法來分解出耗電的組成,以找到優化點。下面就分為功耗評估和功耗歸因分析這兩部分來介紹。
6.1、功耗評估
如前文功耗基礎知識里所說,我們使用電流值來評估應用的功耗水平。
在線下場景:我們通過控制測試條件(如固定測試機型版本,清理后臺,固定亮度,音量,穩定的網絡信號條件等)來測得可信的準確電流值來評估應用的前后臺功耗。
在線上場景:由于應用退后臺時,用戶使用場景的復雜性(指用戶運行的前臺應用不同),我們只采集前臺整機電流來做線上版本監控,使用其他指標,如后臺 CPU 使用率來監控后臺功耗。
下面我們介紹一些常用功耗評估的手段。
6.1.1PowerMonitor
目前業界最通用的整機耗電評估方式是通過 PowerMonitor 外接電量計的方式,高頻率高精度采集電流進行評估。常用需要精細化確認耗電情況,尤其是后臺靜置,滅屏等狀態下的電流輸出,廠商的準入測試等。
常用的 Mosoon 公司的 PowerMonitorAAA10F,電流量程在 1uA ~ 6A 之間,電流精度 50uA,采樣周期 200us (5KHZ)。

6.1.2電池電量計
PowerMonitor 雖然測量結果最準確。但是需要拆機比較麻煩。我們還可以通過谷歌 BatteryManager 提供的接口直接讀取電池電量計的統計結果來獲得電流值。
電池電量計負責估計電池容量。其基本功能為監測電壓,充電/放電電流和電池溫度,并估計電池荷電狀態(SOC)及電池的完全充電容量(FCC)。
有兩種典型的電量計:
- 1)電壓型電量計:簡單講就是檢測當前電壓,然后查詢電壓-電池容量對應表,獲得電量估算;
- 2)電流型電量計:也叫庫侖計,原理是在電池的充電/放電路徑上的連接一個檢測電阻。ADC 量測在檢測電阻上的電壓,轉換成電池正在充電或放電的電流值。實時計數器(RTC)則提供把該電流值對時間作積分,從而得知流過多少庫倫。
目前手機上使用的電量計主要是電流型電量計。

Android 提供了 BMS 的接口,通過屬性提供了電池電量計的統計結果:
1)BATTERY_PROPERTY_CHARGE_COUNTER 剩余電池容量,單位為微安時;
2)BATTERY_PROPERTY_CURRENT_NOW 瞬時電池電流,單位為微安;
3)BATTERY_PROPERTY_CURRENT_AVERAGE 平均電池電流,單位為微安;
4)BATTERY_PROPERTY_CAPACITY 剩余電池容量,顯示為整數百分比;
5)BATTERY_PROPERTY_ENERGY_COUNTER 剩余能量,單位為納瓦時。
importandroid.os.BatteryManager;
importandroid.content.Context;
BatteryManager mBatteryManager = (BatteryManager)Context.getSystemService(Context.BATTERY_SERVICE);
Long energy = mBatteryManager.getLongProperty(BatteryManager.BATTERY_PROPERTY_ENERGY_COUNTER);
Slog.i(TAG, "Remaining energy = "+ energy + "nWh");
以下面的 Nexus9 為例,該機型使用了 MAX17050 電流型電量計,解析度 156.25uA,更新周期 175.8ms。

從實踐結果上看:由于不同的手機使用的電量計不同,導致直接讀取出來的電流值單位也不同,需要做數據轉化。為了簡化電池數據的獲取,我們開發了 Thor SDK,只保留電流、電壓、電量等指標的采集過程,針對不同機型做了數據歸一處理,用戶可以不用關心內部實現,只需要提供需要采樣的數據類型、采樣周期就可以定時返回所需要的功耗相關的數據,我們用 Thor 對比 PowerMonitor 進行了數據一致性的校驗,誤差<5mA,滿足線上監控需求。
此外:我們做了 Thor 采集功能本身的功耗影響,可以看到 1s 采集 1 次的情況下,平均電流上漲了 0.59mA,所以說這種方案的功耗影響非常低,適合線上采集電流值。

6.1.3廠商自帶耗電排行
廠商提供的耗電排行也可以用來查看一段時間內的應用耗電情況。
如下面華為的耗電排行里,對硬件和軟件耗電進行了分拆,并給出了應用的具體耗電量。其他廠商 OV 也是支持具體的耗電量,小米則是提供耗電占比,并不會提供具體耗電量。
功能入口:設置->電池->耗電排行

6.2、功耗歸因
從功耗評估我們可以判斷應用的整體耗電情況,但具體到某個 case 高耗電的原因是什么,就要具體問題選擇不同的工具來進行分析了。目前可以直接歸因到業務代碼的主要是 CPU 相關的工具,這也是我們目前分析問題的主要方向,后續我們也會建設流量歸因等能力,下面我列舉了常用的分析工具。
6.2.1Battery Historian
谷歌官方提供的分析工具,需要先進行功耗測試,再通過 adb 抓取 bugreport.zip,再通過網頁工具打開,可提供粗粒度的功耗歸因。
本質上是對 systemserver 里的各種服務統計信息+手機狀態+內核統計信息(kernel 喚醒)的展示,應用耗電的估算依賴廠商配置的 power_profile.xml。比較適合對整機耗電問題做耗電歸因,如歸因到某應用耗電較高。
對于單個應用,由于對 wakelock,alarm,gps,job,syncservice,后臺服務運行時長等統計的比較詳細,比較適合做后臺耗電的歸因。對于網絡異常,CPU 異常,只能看到消耗較多,無法歸因到具體業務。(來源:https://developer.android.com/to ... -historian?hl=zh-cn)

6.2.2AS Profiler
相比于 BatteryHistorian 需要先手動測試,再 adb 抓取的操作繁瑣,AS 自帶的 Profiler 提供了 Energy 的可視化展示。使用 debug 版本的應用,可以直觀的看到功耗的消耗情況,方便了線下測試。需要注意的是這里展示的功耗值是通過 GPS+網絡+CPU 計算的擬合值,并不是真實功耗值,只表征功耗水平。

Profiler 同步展示了 CPU 使用率,網絡耗電,內存信息。支持 CPU 和線程級別的跟蹤。通過主動錄制 Trace,可以分析各線程的 CPU 使用情況,以及耗時函數。對于容易復現的 CPU 高負載問題或者固定場景的耗時問題,這種方式可以很容易看到根因。但 trace 的展示方式并不適合偶現的 CPU 高負載,信息量特別多反而讓人難以抓住重點。
網絡耗電可以很方便抓取到上行下行的網絡請求,可以展示網絡請求的 api 細節,并且劃分到線程上。對于頻繁的網絡訪問,很容易找到問題點。但目前只支持通過 HttpURLConnection 和 OkHttp 的網絡請求,使用其他的網絡庫,Profiler 追蹤不到。
可以看到官方出品的工具,功能比較完善,但只支持 debug 版本的 app 分析,如果要分析 release 版本的 app,需要使用 root 手機。總體而言,Profiler 比較適合于線下固定某個業務場景的分析。(來源:https://developer.android.com/studio/profile/energy-profiler)
6.2.3線程池監控
使用上面的工具監控單個線程的 CPU 異常是可以的。但是對于線程池,Handler,AsyncTask 等異步任務不太容易歸因具體的業務,尤其是網絡庫的線程池,由于執行的網絡請求邏輯是一樣的,只靠抓線程堆棧是不能歸因到具體業務的。需要統計提交任務的源頭代碼才能抓到真正問題點。
我們可以通過多種機制,如改造線程池,java hook 等,對提交任務方進行了詳細記錄和聚合,可以幫忙我們分析線程池里的耗時任務。
6.2.4線上 CPU 異常精準監控
除了線下的 CPU 分析,我們在進行線上 CPU 異常監控的建設時,我們考慮到單純使用 CPU 使用率閾值不能精準的判斷進程是否處于 CPU 異常。比如不同的 CPU 型號本身的性能不同,在某些低端 CPU 上的使用率就是比較高。又比如系統有不同的溫控策略,省電策略,會對手機進行限頻,對任務進行 CPU 核心遷移。在這種情況下,應用也會有更高的 CPU 使用率。
因此我們基于不同的變量因素(如 CPU 型號,進程/線程的 CPU 時長在不同核,不同頻點的分布,充電,電量,內存,網絡狀態等),將 CPU 的使用閾值進行精細判定,針對不同場景、不同設備、不同業務制定精細化的 CPU 異常閾值,從而實現了高精度的 CPU 異常抓取。
此外還有業界的一些歸因框架,在這里不展開介紹了:
- 1)Facebook BatteryMetrics:從 CPU/IO/Location 等多種歸因點采集數據,和系統 BatteryStatsService 的統計行為類似,偏重于線下做 App 的耗電評估和器件分解;
- 2)WeChat BatteryCanary:提供了線程和線程池歸因能力,相對于其他工具,增加前后臺,亮滅屏,充放電,前臺服務統計的統計。
七、功耗優化實踐概述
上面介紹了功耗的組成,以及如何分析我們應用的耗電。這里我們對功耗優化做一個整體性介紹。
我們把優化思路從器件角度展開,列舉我們有哪些優化的思路和措施,可以減少器件的使用情況,進而降低功耗。
此外對于一些用戶可感知的有損業務的降級,我們通過低功耗模式來做,在低電量時通過更激進的降級手段,緩解用戶的電量焦慮,帶來用戶的使用時長的提升。
下圖列舉了各器件上的優化思路,有一些優化思路會對多個器件都有收益,在這里沒有特別詳細的區分,就劃分在主要影響的器件上,如減少刷新區域,對 GPU,CPU,DDR 都有收益,主要收益在 GPU 繪制上,在下圖里就列舉在 GPU 上了。
同時我們列舉了廠商側的一些優化方案,應用通常無需關注,比如降低屏幕刷新率,TP 掃描頻率,整機低分辨率等,這種可以通過廠商合作的方式進行更細致的調優,如分場景動態調整屏幕刷新率,在搜索列表場景使用 90HZ 高刷,在短視頻場景結合幀率對齊進行刷新率降低為 30HZ,以獲得更平衡的功耗和性能體驗。

八、功耗優化實踐1:DISPLAY
顯示功耗的優化主要圍繞對屏幕、GPU、CPU、視頻解碼器、TP 等器件降級使用或者減少處理,盡量使用硬件處理等實現的。對于屏幕而言主要是降低亮度,刷新率,TP 掃描頻率等。
8.1、屏幕亮度
8.1.1概述
屏幕亮度是屏幕功耗的最大來源,亮度和功耗幾乎是正比的關系。
參見下圖:

可以看出無論是 IPS 屏幕還是 OLED 屏幕,隨著屏幕亮度增加,功耗幾乎是線性增加。針對 OLED 屏幕則是白色內容的功耗更高,深色內容則功耗相對更低。應用通用的降低亮度的方式有進入應用后主動降低亮度,或者使用深色的 UI 模式,來達到屏幕亮度降低的效果。廠商會通過 FOSS 或者 CABC 的方案,降低屏幕亮度。
8.1.2深色模式
利用 AMOLED 屏幕本身的原理,黑色功耗最低,所以可以盡量采用較暗的主題顏色等,最終獲取較低的功耗,可以保持用戶使用時間更長。
為什么說 AMOLED 屏幕顯示黑色界面會消耗更少的電量呢?這要從它與傳統的 LCD 屏幕之間的發光原理區別上來說。
LCD 背光顯示屏,主要是靠背光層,發光層由大量 LED 燈泡組成,顯示白光,通過液晶層偏振控制,顯示出 RGB 顏色。在這種情況下,黑色與其它顏色的像素并沒有什么不同,雖然看起來并沒有光亮,但是依然還是處于發光的狀態。
AMOLED 屏幕根本就沒有背光一說。相反,每個小的亞像素只是發出微弱的 RGB 光,如果屏幕需要顯示黑色,只需要通過調整電壓使得液晶分子排列旋轉從而遮蔽住背光就可以實現黑色的效果,不會額外點亮任何顏色。

下面引用測試應用為 Reddit Sync 的不同場景下彩色和黑色模式功耗對比。(參考鏈接:https://m.zol.com.cn/article/4895723.html#p4)

從上面的圖表我們可以很清楚的看到,在黑色背景的情況下,AMOLED 屏幕在能耗上的確要比普通顏色背景少了很多,在 Reddit Sync 的測試中,平均耗電量要降低 40%左右。
應用可以設計自己的深色模式主題,同步手機系統深色模式開關的切換。目前抖音背景設置有兩種模式如下圖,可以看到經典模式就是深色模式,正好對應于深色主題,這個也可以和手機平臺的深色模式也結合起來。

8.1.3FOSS
FOSS (Fidelity Optimized Signal Scaling,保真優化信號縮放)是芯片廠商提供的一種對 AMOLED 屏幕調節的低功耗方案。
LCD 屏幕上對應的是 CABC (Content Adaptive Brightness Control,內容適應背光控制)。
一方面降低屏幕亮度,一方面調節顯示內容灰度值,從而使顯示效果差異不大,由于降低了屏幕亮度,所以獲取的功耗收益較大。一般大約是 0.2 小時左右,即平均可延長手機使用時間 0.2 小時左右。
已知的情況是廠商的 FOSS 方案在某些參數情況下會導致個別場景出現變色或閃爍問題。如果遇到未確認閃爍問題,在內部定位無法確認原因時,可以跟廠商咨詢進行排除。
8.2、降低刷新率
目前市面上部分手機支持 60HZ、90HZ、120HZ、144HZ 等,高的刷新率帶來了流暢度提高,用戶的體驗更好,但是功耗更高。
通常來講在系統應用界面比如桌面、設置、刷新率會跟當前系統設置保持一致,而在具體應用中,刷新率會根據不同場景做調整。比如抖音,即使在高刷屏幕上,平臺系統一般選擇讓抖音運行在 60HZ 刷新率,從而相對功耗較低。
針對不同的刷新率,PhoneArena 就做了一個比較有參考性的數據來驗證這個觀點。他們選取了兩個品牌四款產品,都是高刷新率的機型,在同一條件下進行 60Hz 刷新率和 120Hz 刷新率的測試,結果 120HZ 刷新率下手機續航相比 60HZ 下的確縮短了至少 10%,即便是支持 90Hz 的一加 8 也是比 60HZ 刷新率要差。

8.3、降低 TP 掃描頻率
通常游戲中為了提高點擊響應速度會提高 TP 掃描頻率,其他場景都采用默認的掃描頻率。抖音一般使用默認的 TP 掃描幀率。

九、功耗優化實踐2:GPU
GPU 的優化思路主要在減少不必要的繪制或者降低繪制面積,這體現在更低的分辨率,更低的幀率,更少的繪制圖層等方面。此外視頻應用使用 SurfaceView 替換 TextureView 也有顯著的功耗收益。對于復雜的運算,我們可以選擇更高能效比的器件來進行,比如使用硬件繪制代替軟件繪制,使用 NPU 代替 GPU 執行復雜算法,對整體功耗都有明顯降低。
9.1、降低分辨率
9.1.1應用低分辨率
通常該模式下游戲和特定應用一般以較低分辨率運行。縮小了 GPU 繪制區域和傳輸區域大小,降低了 GPU 和 CPU 以及傳輸 DDR 的功耗。功耗收益在游戲場景下比較大,線下測試特定平臺下1080p->720p約20mA左右,1440p->720p約40mA左右。
其原理如下,應用圖層在低分辨率下繪制,通過 HWC 通道放大到屏幕分辨率并跟其余圖層合成后送顯。

該功能通常平臺側設置,非游戲應用無需關注,游戲應用可以自己選擇設置低分辨率。
部分游戲比如騰訊系游戲(如 QQ 飛車、王者榮耀和和平精英等)內部也有不同分辨率的設置,默認以低分辨率運行,從而可以實現較低功耗。
9.1.2整機低分辨率
所有應用都運行在低分辨率下。同樣也縮小了 GPU 繪制區域和傳輸區域大小,降低了 GPU 和 CPU 以及傳輸 DDR 的功耗。功耗收益跟應用低分辨率相同,普通應用在該模式下也有功耗收益。用戶從系統設置菜單中切換,應用本身通常無需關注。
其原理如下,所有圖層都在低分辨率下繪制,并在低分辨率下進行合成。合成后經過 scaler 一次性放大到屏幕分辨率,然后進行送顯。其中 scaler 是放縮硬件,由芯片平臺提供。

9.2、減少刷新區域
應用布局動畫位置相近,布局出來一個較小的區域,繪制區域最小,刷新區域最小, 從而功耗最低。不同場景,收益不同。
如下圖兩種情況,可以看到左側圖,有 3 個動畫區域(紅色框住區域),最終形成的 Dirty 區域為大的紅框區域,整個面積較大。而對比中間圖,動畫兩個紅色區域,經過運算后形成的 Dirty 大紅框區域就較小,GPU 的繪制區域跟刷新的傳輸區域都較小,從而相對而言,功耗較低。從最右側功耗數據圖中可以看出收益較大。
可以在開發者選項中打開:設置 -> 開發者選項 -> 顯示GPU視圖更新,當刷新范圍與動畫范圍明顯不一致時便是動畫布局不合理。這種情況需要具體到代碼層面分析寫法的問題并修改。

9.3、降低繪制頻率
通常在游戲或應用動畫中使用,可以降低 GPU 繪制頻率和后面的刷新頻率。通過降低動畫繪制頻率,可以降低 GPU,CPU 及 DDR 功耗。
不同幀率功耗情況對比如下,可以看到低幀率下相比高幀率,功耗明顯低了很多。

在抖音應用中,低繪制幀率可以通過在抖音內部主動降低動畫等幀率實現。在抖音推薦界面音樂轉盤動畫和音符動畫中降低幀率,可以顯著的降低功耗。
此外也可以通過廠商側提供 soft vsync 實現 30HZ 繪制,這部分抖音與廠商合作,SurfaceFlinger 控制 APP vsync,降幀時 SurfaceFlinger vsync 輸出降為 30fps,在特定條件下主動降低幀率,以延長使用時長。
9.4、幀率對齊
在抖音推薦頁面中,通過視頻和降低頻率后的動畫達到同步,可以實現整個界面以30HZ 繪制和刷新。
否則,如果視頻30hz和動畫30幀正好交錯,最終形成的繪制/刷新頻率還是60幀,沒有達到最優。
我們通過調節各種動畫的繪制流程,將動畫整體繪制對齊,整體幀率明顯降低。
9.5、減少過度繪制
過度繪制(Overdraw)描述的是屏幕上的某個像素在同一幀的時間內被繪制了多次。在多層次重疊的 UI 結構里面,如果不可見的 UI 也在做繪制的操作,會導致某些像素區域被繪制了多次,同時也會浪費大量的 CPU 以及 GPU 資源。
可以通過如下來調試過度繪制:打開手機,設置 -> 開發者選項 -> 調試 GPU 過度繪制 -> 顯示 GPU 過度繪制。
過度繪制的存在會導致界面顯示時浪費不必要的資源去渲染看不見的背景,或者對某些像素區域多次繪制,就會導致界面加載或者滑動時的不流暢、掉幀,對于用戶體驗來說就是 App 特別的卡頓。為了提升用戶體驗,提升應用的流暢性,優化過度繪制的工作還是很有必要做的。
抖音的 feed 頁的過度繪制非常的嚴重,抖音存在 5 層過度繪制。下圖左側是優化前的過渡繪制情況,右側是優化后的過度繪制情況,可以看出優化后明顯改善。

9.6、使用 SurfaceView 視頻播放
TextureView 和 SurfaceView 是兩個最常用的播放視頻控件。
TextureView 控件位于主圖層上,解碼器將視頻幀傳遞到 TextureView 對象還需要 GPU 做一次繪制才能在屏幕上顯示,所以其功耗更高,消耗內存更大,CPU 占用率也更高。
控件位置差異如下,可以看出 SurfaceView 擁有獨立的 Surface 位于單獨的圖層上,而 TextureView 位于主圖層上。

BufferQueue 是 Android 圖形架構的核心,其一側是生產者,另一側是消費者。從這方面看,SurfaceView 和 TextureView 的差異如下。容易看出,SurfaceView 流程更短,內存使用更少,也沒有 GPU 繪制,功耗更省。

下面是一些 SurfaceView 替換 TextureView 后的收益數據:
- 1)CPU數據上看:SurfaceView 要比 TextureView 優化 8%-13%;
- 2)功耗數據上看:SurfaceView 要比 TextureView 平均功耗低 20mA 左右。
9.7、硬件繪制和軟件繪制
硬件繪制是指通過 GPU 繪制,Android 從 3.0 開始支持硬件加速繪制,它在 UI 顯示和繪制效率方面遠高于軟件繪制,但是 GPU 功耗相對較高。目前是系統默認的繪制方式。
軟件繪制是指通過 CPU 實現繪制,Android 上面使用 Skia 圖形庫來進行繪制。
兩者差異參見下圖:

目前默認是開硬件加速的,可以通過設置 Activity、Application、窗口、View 等方式來指定軟件繪制。如果應用需要單獨指定某些場景的軟件繪制方式,需要對性能、功耗等做好評估。(參考鏈接:https://developer.android.com/gu ... hics/hardware-accel)
9.8、復雜算法用 NPU 代替 GPU
現在的較新的 SoC 平臺都帶有專門進行 AI 運算的 NPU 芯片,使用 NPU 代替 GPU 運行一些復雜算法,可以有效的節省 GPU 功耗。
如視頻的超分算法,可以給用戶帶來很好的體驗。但是超分開啟對 GPU 的耗電影響很大,在某些平臺測試整機功耗可以高出 100mA,選擇用 NPU 替換 GPU 是一種優化方式。
十、功耗優化實踐3:CPU
10.1、概述
CPU 的優化是功耗優化里最常見的,我們遇到的大部分的 CPU 異常都是出現了死循環。
這里使用上面介紹過的功耗歸因工具,都可以很容易的發現死循環問題。此外高頻的耗時函數,效果和死循環類似,很容易讓 CPU 大核跑到高頻點,帶來 CPU 功耗增加。
另外一個典型的 CPU 問題,就是動畫泄漏,泄漏動畫大概能帶來 20mA 的功耗增加。
由于 CPU 工作耗電很高,手機平臺大多會增加各種低功耗的 DSP 來分擔 CPU 的工作,減少耗電,如常見視頻解碼,使用硬解會有更好的功耗表現。
10.2、CPU 高負載優化
10.2.1死循環治理
死循環是我們遇到的最明顯的 CPU 異常,通常表現為某一個線程占滿了一個大核。線程使用率達到了 100%,手機會很容易發熱,卡頓。
這里舉一個實際修復的死循環例子:在一段循環打包日志的代碼邏輯里,所有 log打包完了,才會break跳出循環。當db query出現了異常,異常處理分支并沒有做break,導致出現了死循環。
// 方法邏輯有裁剪,僅貼出主要邏輯
privateJSONArray packMiscLog() {
do{
......
try{
cursor = mDb.query(......);
intn = cursor.getCount();
......
if(start_id >= max_id) {
break;
}
} catch(Exception e) {
} finally{
safeCloseCursor(cursor);
}
} while(true);
returnret;
}
對于死循環治理,我們通過實際解決的問題,總結了幾種常見的死循環套路。
// 邊界條件未滿足,無法break
while(true) {
...
if(shouldExit()) {
break
}
}
// 異常處理不妥當,導致死循環
while(true) {
try{
dosometing;
break;
} catch(e) {
}
}
// 消息處理不當,導致Handler線程死循環
voidhandleMessage(Message msg) {
//do something
handler.sendEmptyMessage(MSG)
}
10.2.2高頻耗時函數治理
除了死循環問題,我們遇到的另外一種常見的就是高頻的耗時函數。
通過線上監控 CPU 異常,我們也找到很多可優化的點。如 md5 壓縮算法的耗時,正則表達式的不合理使用,使用 cmd 執行系統命令的耗時等。這種就 case by case 的修復,就有很不錯的收益。
10.3、后臺資源規范使用:Alarm、Wakelock、JobScheduler 的規范使用
最常見的后臺 CPU 耗電就是對后臺資源的不合理使用。
Alarm 的頻繁喚醒,wakelock 的長時間不釋放,JobScheduler 的頻繁執行,都會使 CPU 保持喚醒狀態,造成后臺耗電。
這種行為很容易讓系統判斷應用為后臺異常耗電,通常會被系統清理,或者發出高耗電提醒。
我們可以通過 dumpsys alarm & dumpsys power & dumpsys jobscheduler 查看相關的統計信息,也可以通過 BH 的后臺統計來分析自身的使用情況。
參考綠盟的功耗標準,滅屏 Alarm 觸發小于過 12 次/h,即 5min 一次,5min 一次在數據業務下可以保證長鏈接存活,廠商的后臺功耗優化也通常會強制對齊 Alarm 為 5min 觸發一次。
后臺的 Partial Wakelock 通常會被重點限制,非可感知的場景(音樂,導航,運動)等會被廠商強制釋放 wakelock。按照綠盟的標準,滅屏下每小時累計持鎖小于 5min,從實際經驗上看,持 Partial 鎖超過 1min 就會被標為 Long 的 wakelock,如果是應用在后臺無可感知業務并且頻繁持鎖,導致系統無法休眠的,系統會觸發 forcestop 清理。

某些定時任務可以使用 JobScheduler 來替代 Alarm,Job 的好處是可以組合多種觸發條件,選擇一個最恰當的時刻讓系統調度自己的后臺任務。這里建議使用充電+網絡可用狀態下處理自己的后臺任務,對功耗體驗是最好的。如果是非充電場景下,設置條件頻繁觸發 job,同樣會帶來耗電問題。值得一提的是 Job 執行完要及時結束。因為 JobScheduler 在執行時會持有一個*job/*開頭的 wakelock,最長執行時間 10min,如果一直在執行狀態不結束,就會導致系統無法休眠。
10.4、視頻硬解替換軟解
硬解碼:通常是用手機平臺自帶的硬件解碼器來做解碼從而實現視頻播放,基于專用芯片的硬解碼速度快、功耗低。
軟解碼:通常使用 FFMPEG 內置的 H.264 和 H.265 的軟件解碼庫來做解碼。
下表是三星手機和蘋果手機分別在軟硬解情況下的功耗,可以看出硬解功耗比軟解功耗顯著降低,目前抖音默認使用硬解。

(圖片來源:http://www.noobyard.com/article/p-eedllxrr-qz.html)
十一、功耗優化實踐4:NETWORK
11.1、概述
網絡耗電是應用耗電的一個重要部分,一個數據包的收發,會同步拉動 CPU 和 Modem/WIFI 兩大系統。
由于 LTE 的 CDRX 特性(即沒有數據包接收,維持一定時間的激活態,再進入睡眠,依賴運營商配置,通常為 10s),所以批量進行網絡訪問,減少頻繁的網絡喚醒對網絡功耗很有幫助。
此外:優化壓縮算法,減少數據傳輸量也從基礎上減少了網絡耗電。
另外:弱信號條件下的網絡請求會提高天線的功率,也會觸發頻繁的搜網,帶來更高的網絡功耗。根據網絡質量進行網絡請求調度,提前預緩存網絡資源,可以減少網絡耗電。
11.2、長鏈接心跳優化
對于應用的后臺 PUSH 來說,使用廠商穩定的 push 鏈路替代自己的長鏈接可以減少功耗。如果不能替換,也可以優化長鏈接保活的心跳,根據不同的網絡條件動態的調整心跳。
根據經驗,數據業務下通常是 5min,WIFI 網絡下通常可以達到 20min 或更久。
抖音對于長鏈接進行了的心跳優化,進入后臺的長鏈接心跳時間間隔 [4min, 28min],初始心跳 4min。采用動態心跳試探策略,每次步進 2min,確定最大心跳間隔。
11.3、Doze 模式適配
由于系統對后臺應用有多種網絡限制策略,最常見的是 Doze 模式,手機滅屏一段時間后會進入 doze,限制非白名單應用訪問網絡,并在窗口期解除限制,窗口期為每 10min 放開 30s。
所以在后臺進行網絡訪問前要特別注意進行網絡可用的判斷,選擇窗口期進行網絡訪問,避免因為被限網而浪費了 CPU 資源。
這里舉一個 Doze 未適配的后臺耗電例:用戶反饋抖音自上次手機充滿電(24h)后,沒有在前臺使用過,耗電占比 31%,分析日志發現:在 Doze 限制網絡期間,會觸發輪詢判斷網絡是否及時恢復,此邏輯在后臺未適配 Doze 的窗口期模式,導致了后臺頻繁嘗試網絡請求帶來的 CPU 耗電。

十二、功耗優化實踐5:AUDIO
12.1、降低音量
音頻的耗電最終體現在 Codec 和 SmartPA(連接喇叭的功率放大器)兩部分。減少 Audio 耗電最明顯的就是減少音頻的音量,這直接反應到喇叭的響度上。
用 0-15 級的音量進行測試,可以看到音量對功耗的影響巨大,尤其是超過 10 之后,整體增幅非常巨大。每一級幾乎與功耗成百分比上漲。

具體是:
- 1)10-15 :1:30ma;
- 2)5-10:1:1.62ma;
- 3)0-5:1:1.36ma。
12.2、調整音頻參數
由于用戶對音量的感受很明顯,直接全局降低音量會帶來不好的體驗。廠商通常會針對不同的場景,設計不同的音頻參數,如電影場景,游戲場景,導航場景,動態調節音頻的高低頻配置參數,兼顧了效果和功耗。
從這個角度出發,可以選擇和廠商合作,根據播放視頻的內容,精細化調整音頻參數,如電影剪輯類型視頻就使用電影場景的參數,游戲視頻就切換為游戲場景的配置參數,從而達到用戶無感調節音量節省功耗的目的。
十三、功耗優化實踐6:CAMERA
Camera 是功耗大戶,尤其是高分辨率高幀率的錄制會帶來快速的功耗消耗和溫升。
經過線下測算,開播場景,Camera 功耗 200mA+,占整機的 25%以上。
優化Camera功耗的思路主要是從業務降級的角度上進行,如降低錄制的分辨率,降低錄制幀率等。之前抖音直播和生產端都是使用30幀,但最終只使用15幀,在開播端主動下調采集幀率,按需設置幀率為15幀,功耗顯著降低了120ma。
十四、功耗優化實踐7:SENSOR
sensor 的典型功耗值很低,如我們常用到的 accelerometer(加速度計)的典型功耗只有 180uA。
但 sensor 的開啟會導致 cpu 的喚醒與負載增加,尤其是在應用退到后臺,sensor 的濫用會顯著增加待機功耗。
可以在低電量時關閉不必要的 sensor,減少耗電。
十五、功耗優化實踐8:GPS
精確度、頻率、間隔是影響 GPS 耗電的三個主要因素。其中精度影響定位的工作模式,頻率和間隔是影響工作時長,我們可以通過優化這三者來減少 GPS 的耗電。
15.1、降低精度
Android 原生定位提供 GPS 定位和網絡定位兩種模式。
GPS 定位支持離線定位,依靠衛星,沒有網絡也能定位,精度高,但功耗大,因需要開啟移動設備中的 GPS 定位模塊,會消耗較多電量。
Network 定位(網絡定位),定位速度快,只要具備網絡或者基站要求,在任何地方都可實現瞬間定位,室內同樣滿足;功耗小,耗電量小。但定位精度差,容易受干擾,在基站或者 WiFi 數量少、信號弱的地方定位質量較差,或者無法定位;必須連接網絡才能實現定位。
我們可以在滿足定位要求的情況下,主動使用低精度的網絡定位,減少定位耗電,抖音在進入低功耗模式時,進行了 GPS 降級為網絡定位,并且擴大了定位間隔。
15.2、降低頻率 & 提高間隔
這里除了業務上主動控制頻率與間隔外,還推薦使用廠商的定位服務。
為了優化定位耗電,海外 gms 以及國內廠商都提供了位置服務 SDK,本質上是通過系統服務統一管理位置請求,根據電量,信號,請求方的延遲精度要求,進行動態調整,達到功耗與定位需求的平衡。提供了諸如被動位置更新,獲取最近一次定位的位置信息,批量后臺位置請求等低功耗定位能力。
比如:
- 1)https://developer.android.com/guide/topics/location/battery;
- 2)https://developer.huawei.com/consumer/cn/doc/development/HMSCore-References/location-description-0000001088559417。
十六、功耗優化實踐9:低功耗模式
上述的優化措施,有些在常規模式下已經實施。但有一部分是有損用戶體驗的,我們選擇在低電量場景下去做,降低功耗,減少用戶的電量焦慮,獲得用戶在低電量下更多使用時長。
在低功耗模式預研中,我們列舉了很多可做的措施,通過 AB 實驗,我們去掉了業務負向的降級手段,比如亮度降低,音量降低等。
此外在功能觸發的策略上,我們通過對比了低電量彈窗提醒,設置里增加開關+Toast 提醒,以及低電量自動進入,最終選擇了對用戶體驗最好的 30%電量無打擾自動進入的觸發方式。

經過實驗發現:一些高發熱機型,通過低功耗模式全程開啟,也可以拿到業務收益。說明部分有損的降級,用戶在易發熱的情況下也是接受的,可以置換出業務收益,目前低功耗模式線下測試功耗收益穩定在 20mA 以上。
十七、本文小結
功耗優化是一個復雜的綜合課題,既包含了利用工具對功耗做拆解評估,對異常的監控治理,也包含了主動挖掘優化點進行優化。
上面列舉的優化思路,我們也只是做了部分,還有部分待開展,包括功耗歸因的工具建設上,我們也還有很多可以優化的點。
我們會持續發力,產出更多的方案,在滿足使用需求的前提下,消耗更少的物理資源,給抖音用戶帶來更好的功耗體驗。
十八、相關資料
[1] 微信團隊分享:詳解iOS版微信視頻號直播中因幀率異常導致的功耗問題
[2] 移動端IM實踐:WhatsApp、Line、微信的心跳策略分析
[3] 移動端IM實踐:實現Android版微信的智能心跳機制
[7] 微信客戶端團隊負責人技術訪談:如何著手客戶端性能監控和優化
(本文已同步發布于:http://www.52im.net/thread-4540-1-1.html)
作者:Jack Jiang (點擊作者姓名進入Github)
出處:http://www.52im.net/space-uid-1.html
交流:歡迎加入即時通訊開發交流群 215891622
討論:http://www.52im.net/
Jack Jiang同時是【原創Java
Swing外觀工程BeautyEye】和【輕量級移動端即時通訊框架MobileIMSDK】的作者,可前往下載交流。
本博文
歡迎轉載,轉載請注明出處(也可前往 我的52im.net 找到我)。