1. 程式人生 > >Android效能優化策略

Android效能優化策略

本篇主要是對 google推出的效能優化典範 進行一個通篇的整理… 主要在於一些具體的優化技巧、至於 60fps、掉幀、gc、記憶體抖動、閾值…等等這些效能術語的概念裡面不做多概括,請自行查閱…

本篇從以下幾點延伸擴充套件…

這裡寫圖片描述

繪製/渲染

這裡寫圖片描述

優化佈局的結構

  • 避免複雜的View層級。佈局越複雜就越臃腫,就越容易出現效能問題,尋找最節省資源的方式去展示巢狀的內容;
  • 儘量避免在檢視層級的頂層使用相對佈局 RelativeLayout 。相對佈局 RelativeLayout 比較耗資源,因為一個相對佈局 RelativeLayout 需要兩次度量來確保自己處理了所有的佈局關係,而且這個問題會伴隨著檢視層級中的相對佈局 RelativeLayout 的增多,而變得更嚴重;
  • 佈局層級一樣的情況建議使用線性佈局 LinearLayout 代替相對佈局 RelativeLayout,因為線性佈局 LinearLayout 效能要更高一些;確實需要對分支進行相對佈局 RelativeLayout 的時候,可以考慮更優化的網格佈局 GridLayout ,它已經預處理了分支檢視的關係,可以避免兩次度量的問題;
  • 相對複雜的佈局建議採用相對佈局 RelativeLayout ,相對佈局 RelativeLayout 可以簡單實現線性佈局 LinearLayout 巢狀才能實現的佈局;
  • 將可重複使用的元件抽取出來並用 include 標籤進行重用。如果應用多個地方的 UI 用到某個佈局,就將其寫成一個佈局部件,便於各個 UI 重用;
  • 使用 merge 標籤減少佈局的巢狀層次;
  • 去掉多餘的不可見背景。有多層背景顏色的佈局,只留最上層的對使用者可見的顏色即可,其他使用者不可見的底層顏色可以去掉,減少無效的繪製操作;
  • 儘量避免使用 layout_weight 屬性。使用包含 layout_weight 屬性的線性佈局 LinearLayout 每一個子元件都需要被測量兩次,會消耗過多的系統資源;
  • 合理的介面的佈局結構應是寬而淺,而不是窄而深;

優化處理邏輯

  • 按需載入檢視。某些不怎麼重用的耗資源檢視,可以等到需要的時候再載入,提高UI渲染速度;
  • 使用 ViewStub 標籤來載入一些不常用的佈局;
  • 動態地 inflation view 效能要比用 ViewStub 標籤的 setVisiblity 效能要好,當然某些功能的實現採用 ViewStub 標籤更合適;
  • 儘量避免不必要的耗資源操作,節省寶貴的運算時間;
  • 避免在 UI 執行緒進行繁重的操作。耗資源的操作(比如 IO 操作、網路操作、SQL 操作、列表重新整理等)耗資源的操作應用後臺程序去實現,不能佔用 UI 執行緒,UI 執行緒是主執行緒,主執行緒是保持程式流暢的關鍵,應該只操作那些核心的 UI 操作,比如處理檢視的屬性和繪製;
  • 最小化喚醒機制。我們常用廣播來接收那些期望響應的訊息和事件,但過多的響應超過本身需求的話,會消耗多餘的 Android 裝置效能和資源。所以應該最小化喚醒機制,當應用不關心這些消失和事件時,就關閉廣播,並慎重選擇那些要響應的 Intent ;
  • 為低端裝置考慮,比如 512M 記憶體、雙核 CPU 、低解析度,確保你的應用可以滿足不同水平的裝置;
  • 優化應用的啟動速度。當應用啟動一個應用時,介面的儘快反饋顯示可以給使用者一個良好的體驗。為了啟動更快,可以延遲載入一些 UI 以及避免在應用 Application 層級初始化程式碼。

UI除錯工具

  • Hierarchy View :Hierarchy View 在Android SDK裡自帶,常用來檢視介面的檢視結構是否過於複雜,用於瞭解哪些檢視過度繪製,又該如何進行改進;
  • Lint:Lint 是 ADT 自帶的靜態程式碼掃描工具,可以給 XML 佈局檔案和 專案程式碼中不合理的或存在風險的模組提出改善性建議。官方關於 Lint 的實際使用的提示,列舉幾點如下: 
    • 包含無用的分支,建議去除;
    • 包含無用的父控制元件,建議去除;
    • 警告該佈局深度過深;
    • 建議使用 compound drawables ;
    • 建議使用 merge 標籤; 
      ……
  • Systrace:Systrace 在Android DDMS 裡自帶,可以用來跟蹤 graphics 、view 和 window 的資訊,發現一些深層次的問題。很麻煩,限制大,實際除錯中我基本用不到;
  • Track:Track 在 Android DDMS裡自帶,是個很棒的用來跟蹤構造檢視的時候哪些方法費時,精確到每一個函式,無論是應用函式還是系統函式,我們可以很容易地看到掉幀的地方以及那一幀所有函式的呼叫情況,找出問題點進行優化。
  • OverDraw:通過在 Android 裝置的設定 APP 的開發者選項裡開啟 “ 除錯 GPU 過度繪製 ” ,來檢視應用所有介面及分支介面下的過度繪製情況,方便進行優化;
  • GPU 呈現模式分析:通過在 Android 裝置的設定 APP 的開發者選項裡啟動 “ GPU 呈現模式分析 ” ,可以得到最近 128 幀 每一幀渲染的時間,分析效能渲染的效能及效能瓶頸;
  • StrictMode:通過在 Android 裝置的設定 APP 的開發者選項裡啟動 “ 嚴格模式 ” ,來檢視應用哪些操作在主執行緒上執行時間過長。當一些操作違背了嚴格模式時螢幕的四周邊界會閃爍紅色,同時輸出 StrictMode 的相關資訊到 LOGCAT 日誌中;
  • Animator duration scale:通過在 Android 裝置的設定 APP 的開發者選項裡開啟 “ 視窗動畫縮放 ” / “ 過渡動畫縮放 ” / “ 動畫程式時長縮放 ”,來加速或減慢動畫的時間,以檢視加速或減慢狀態下的動畫是否會有問題;
  • Show hardware layer updates:通過在 Android 裝置的設定 APP 的開發者選項裡啟動 “ 顯示硬體層更新 ”,當 Flash 硬體層在進行更新時會顯示為綠色。使用這個工具可以讓你檢視在動畫期間哪些不期望更新的佈局有更新,方便你進行優化,以獲得應用更好的效能。

記憶體/CPU

這裡寫圖片描述

減小物件的記憶體佔用

  • 使用更加輕量的資料結構:例如考慮使用ArrayMap/SparseArray(SparseBoolMap,SparseIntMap,SparseLongMap,LongSparseMap)代替HashMap等傳統資料結構。(http://hukai.me/android-performance-patterns-season-3/);
  • 傳輸經過壓縮 gzip的資料;
  • 資原始檔需要選擇合適的資料夾進行存放:我們知道hdpi/xhdpi/xxhdpi等等不同dpi的資料夾下的圖片在不同的裝置上會經過scale的處理。例如我們只在hdpi的目錄下放置了一張100100的圖片,那麼根據換算關係,xxhdpi的手機去引用那張圖片就會被拉伸到200200。需要注意到在這種情況下,記憶體佔用是會顯著提高的。對於不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下。
  • 對圖片進行有失真壓縮以減小記憶體:如 使用 tinyPNG;
  • 減小創建出來的Bitmap的記憶體佔用:對Bitmap等比縮放 或者選擇解碼格式(有透明度:ARGB_4444,無透明度:RGB_565)。

複用物件

  • 複用系統自帶的資源:Android系統本身內建了很多的資源,例如字串/顏色/圖片/動畫/樣式以及簡單佈局等等,這些資源都可以在應用程式中直接引用。這樣做不僅僅可以減少應用程式的自身負重,減小APK的大小,另外還可以一定程度上減少記憶體的開銷,複用性更好。但是也有必要留意Android系統的版本差異性,對那些不同系統版本上表現存在很大差異,不符合需求的情況,還是需要應用程式自身內建進去。
  • listView 內子控制元件的複用
  • Bitmap物件的複用: 
    • 用LRU的機制來快取處理好的Bitmap;
    • 利用inBitmap的高階特性提高Android系統在Bitmap分配與釋放執行效率上的提升(3.0以及4.4以後存在一些使用限制上的差異)。使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經存在的記憶體區域,新解碼的bitmap會嘗試去使用之前那張bitmap在heap中所佔據的pixel data記憶體區域,而不是去問記憶體重新申請一塊區域來存放bitmap。利用這種特性,即使是上千張的圖片,也只會僅僅只需要佔用螢幕所能夠顯示的圖片數量的記憶體大小。
  • 避免在onDraw方法裡面執行物件的建立:類似onDraw等頻繁呼叫的方法,一定需要注意避免在這裡做建立物件的操作,因為他會迅速增加記憶體的使用,而且很容易引起頻繁的gc,甚至是記憶體抖動;
  • StringBuilder:在有些時候,程式碼中會需要使用到大量的字串拼接的操作,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”;
  • 避免在for語句內建立物件

避免物件的記憶體洩露

記憶體物件的洩漏,會導致一些不再使用的物件無法及時釋放,這樣一方面佔用了寶貴的記憶體空間,很容易導致後續需要分配記憶體的時候,空閒空間不足而出現OOM。 
顯然,這還使得每級Generation的記憶體區域可用空間變小,gc就會更容易被觸發,容易出現記憶體抖動,從而引起效能問題。 
最新的LeakCanary開源控制元件,可以很好的幫助我們發現記憶體洩露的情況,更多關於LeakCanary的介紹,請看這裡https://github.com/square/leakcanary(中文使用說明。另外也可以使用傳統的MAT工具查詢記憶體洩露,請參考這裡便捷的中文資料

  • 注意Activity的洩漏

    • 內部類引用導致Activity的洩漏:

      • 最典型的場景是Handler導致的Activity洩漏,如果Handler中有延遲的任務或者是等待執行的任務佇列過長,都有可能因為Handler繼續執行而導致Activity發生洩漏。此時的引用關係鏈是Looper -> MessageQueue -> Message -> Handler -> Activity。為了解決這個問題,可以在UI退出之前,執行remove Handler訊息佇列中的訊息與runnable物件。或者是使用Static + WeakReference的方式來達到斷開Handler與Activity之間存在引用關係的目的。
    • Activity Context被傳遞到其他例項中,這可能導致自身被引用而發生洩漏:

      • 內部類引起的洩漏不僅僅會發生在Activity上,其他任何內部類出現的地方,都需要特別留意!我們可以考慮儘量使用static型別的內部類,同時使用WeakReference的機制來避免因為互相引用而出現的洩露。
  • 考慮使用Application Context而不是Activity Context;
  • 注意臨時Bitmap物件的及時回收 
    • 雖然在大多數情況下,我們會對Bitmap增加快取機制,但是在某些時候,部分Bitmap是需要及時回收的。例如臨時建立的某個相對比較大的bitmap物件,在經過變換得到新的bitmap物件之後,應該儘快回收原始的bitmap,這樣能夠更快釋放原始bitmap所佔用的空間。 
      需要特別留意的是Bitmap類裡面提供的createBitmap()方法: 
      這個函式返回的bitmap有可能和source bitmap是同一個,在回收的時候,需要特別檢查source bitmap與return bitmap的引用是否相同,只有在不等的情況下,才能夠執行source bitmap的recycle方法。
  • 注意監聽器的登出: 
    • 在Android程式裡面存在很多需要register與unregister的監聽器,我們需要確保在合適的時候及時unregister那些監聽器。自己手動add的listener,需要記得及時remove這個listener。
  • 注意快取容器中的物件洩漏:

    • 有時候,我們為了提高物件的複用性把某些物件放到快取容器中,可是如果這些物件沒有及時從容器中清除,也是有可能導致記憶體洩漏的。例如,針對2.3的系統,如果把drawable新增到快取容器,因為drawable與View的強應用,很容易導致activity發生洩漏。而從4.0開始,就不存在這個問題。解決這個問題,需要對2.3系統上的快取drawable做特殊封裝,處理引用解綁的問題,避免洩漏的情況。
  • 注意WebView的洩漏:

    • Android中的WebView存在很大的相容性問題,不僅僅是Android系統版本的不同對WebView產生很大的差異,另外不同的廠商出貨的ROM裡面WebView也存在著很大的差異。更嚴重的是標準的WebView存在記憶體洩露的問題,看這裡WebView causes memory leak - leaks the parent Activity。所以通常根治這個問題的辦法是為WebView開啟另外一個程序,通過AIDL與主程序進行通訊,WebView所在的程序可以根據業務的需要選擇合適的時機進行銷燬,從而達到記憶體的完整釋放。
  • 注意Cursor物件是否及時關閉:

    • 在程式中我們經常會進行查詢資料庫的操作,但時常會存在不小心使用Cursor之後沒有及時關閉的情況。這些Cursor的洩露,反覆多次出現的話會對記憶體管理產生很大的負面影響,我們需要謹記對Cursor物件的及時關閉。

優化策略

  • 謹慎使用large heap:

    • Android裝置根據硬體與軟體的設定差異而存在不同大小的記憶體空間,他們為應用程式設定了不同大小的Heap限制閾值。你可以通過呼叫getMemoryClass()來獲取應用的可用Heap大小。在一些特殊的情景下,你可以通過在manifest的application標籤下新增largeHeap=true的屬性來為應用宣告一個更大的heap空間。然後,你可以通過getLargeMemoryClass()來獲取到這個更大的heap size閾值。然而,宣告得到更大Heap閾值的本意是為了一小部分會消耗大量RAM的應用(例如一個大圖片的編輯應用)。不要輕易的因為你需要使用更多的記憶體而去請求一個大的Heap Size。只有當你清楚的知道哪裡會使用大量的記憶體並且知道為什麼這些記憶體必須被保留時才去使用large heap。因此請謹慎使用large heap屬性。使用額外的記憶體空間會影響系統整體的使用者體驗,並且會使得每次gc的執行時間更長。在任務切換時,系統的效能會大打折扣。另外, large heap並不一定能夠獲取到更大的heap。在某些有嚴格限制的機器上,large heap的大小和通常的heap size是一樣的。因此即使你申請了large heap,你還是應該通過執行getMemoryClass()來檢查實際獲取到的heap大小。
  • 綜合考慮裝置記憶體閾值與其他因素設計合適的快取大小:

    • 例如,在設計ListView或者GridView的Bitmap LRU快取的時候,需要考慮的點有: 
      • 應用程式剩下了多少可用的記憶體空間?
      • 有多少圖片會被一次呈現到螢幕上?有多少圖片需要事先快取好以便快速滑動時能夠立即顯示到螢幕?
      • 裝置的螢幕大小與密度是多少? 一個xhdpi的裝置會比hdpi需要一個更大的Cache來hold住同樣數量的圖片。
      • 不同的頁面針對Bitmap的設計的尺寸與配置是什麼,大概會花費多少記憶體?
      • 頁面圖片被訪問的頻率?是否存在其中的一部分比其他的圖片具有更高的訪問頻繁?如果是,也許你想要儲存那些最常訪問的到記憶體中,或者為不同組別的點陣圖(按訪問頻率分組)設定多個LruCache容器。
  • onLowMemory()與onTrimMemory(): 
    Android使用者可以隨意在不同的應用之間進行快速切換。為了讓background的應用能夠迅速的切換到forground,每一個background的應用都會佔用一定的記憶體。Android系統會根據當前的系統的記憶體使用情況,決定回收部分background的應用記憶體。如果background的應用從暫停狀態直接被恢復到forground,能夠獲得較快的恢復體驗,如果background應用是從Kill的狀態進行恢復,相比之下就顯得稍微有點慢。

    • onLowMemory():Android系統提供了一些回撥來通知當前應用的記憶體使用情況,通常來說,當所有的background應用都被kill掉的時候,forground應用會收到onLowMemory()的回撥。在這種情況下,需要儘快釋放當前應用的非必須的記憶體資源,從而確保系統能夠繼續穩定執行。
    • onTrimMemory(int):Android系統從4.0開始還提供了onTrimMemory()的回撥,當系統記憶體達到某些條件的時候,所有正在執行的應用都會收到這個回撥,同時在這個回撥裡面會傳遞以下的引數,代表不同的記憶體使用情況,收到onTrimMemory()回撥的時候,需要根據傳遞的引數型別進行判斷,合理的選擇釋放自身的一些記憶體佔用,一方面可以提高系統的整體執行流暢度,另外也可以避免自己被系統判斷為優先需要殺掉的應用。下圖介紹了各種不同的回撥引數:

      • TRIM_MEMORY_UI_HIDDEN:你的應用程式的所有UI介面被隱藏了,即使用者點選了Home鍵或者Back鍵退出應用,導致應用的UI介面完全不可見。這個時候應該釋放一些不可見的時候非必須的資源
      • 當程式正在前臺執行的時候,可能會接收到從onTrimMemory()中返回的下面的值之一: 
        • TRIM_MEMORY_RUNNING_MODERATE:你的應用正在執行並且不會被列為可殺死的。但是裝置此時正運行於低記憶體狀態下,系統開始觸發殺死LRU Cache中的Process的機制。
        • TRIM_MEMORY_RUNNING_LOW:你的應用正在執行且沒有被列為可殺死的。但是裝置正運行於更低記憶體的狀態下,你應該釋放不用的資源用來提升系統性能。
        • TRIM_MEMORY_RUNNING_CRITICAL:你的應用仍在執行,但是系統已經把LRU Cache中的大多數程序都已經殺死,因此你應該立即釋放所有非必須的資源。如果系統不能回收到足夠的RAM數量,系統將會清除所有的LRU快取中的程序,並且開始殺死那些之前被認為不應該殺死的程序,例如那個包含了一個執行態Service的程序。
    • 當應用程序退到後臺正在被Cached的時候,可能會接收到從onTrimMemory()中返回的下面的值之一:

      • TRIM_MEMORY_BACKGROUND: 系統正運行於低記憶體狀態並且你的程序正處於LRU快取名單中最不容易殺掉的位置。儘管你的應用程序並不是處於被殺掉的高危險狀態,系統可能已經開始殺掉LRU快取中的其他程序了。你應該釋放那些容易恢復的資源,以便於你的程序可以保留下來,這樣當用戶回退到你的應用的時候才能夠迅速恢復。
      • TRIM_MEMORY_MODERATE: 系統正運行於低記憶體狀態並且你的程序已經已經接近LRU名單的中部位置。如果系統開始變得更加記憶體緊張,你的程序是有可能被殺死的。
      • TRIM_MEMORY_COMPLETE: 系統正運行於低記憶體的狀態並且你的程序正處於LRU名單中最容易被殺掉的位置。你應該釋放任何不影響你的應用恢復狀態的資源。

        • 因為onTrimMemory()的回撥是在API 14才被加進來的,對於老的版本,你可以使用onLowMemory)回撥來進行相容。onLowMemory相當與TRIM_MEMORY_COMPLETE。
      • 請注意:當系統開始清除LRU快取中的程序時,雖然它首先按照LRU的順序來執行操作,但是它同樣會考慮程序的記憶體使用量以及其他因素。佔用越少的程序越容易被留下來。

  • Try catch某些大記憶體分配的操作:

    • 在某些情況下,我們需要事先評估那些可能發生OOM的程式碼,對於這些可能發生OOM的程式碼,加入catch機制,可以考慮在catch裡面嘗試一次降級的記憶體分配操作。例如decode bitmap的時候,catch到OOM,可以嘗試把取樣比例再增加一倍之後,再次嘗試decode。
  • 資原始檔需要選擇合適的資料夾進行存放

  • 謹慎使用static物件: 
    • 因為static的生命週期過長,和應用的程序保持一致,使用不當很可能導致物件洩漏,在Android中應該謹慎使用static物件。
  • 特別留意單例物件中不合理的持有: 
    • 雖然單例模式簡單實用,提供了很多便利性,但是因為單例的生命週期和應用保持一致,使用不合理很容易出現持有物件的洩漏。
  • 珍惜Services資源 
    • 如果你的應用需要在後臺使用service,除非它被觸發並執行一個任務,否則其他時候Service都應該是停止狀態。另外需要注意當這個service完成任務之後因為停止service失敗而引起的記憶體洩漏。 當你啟動一個Service,系統會傾向為了保留這個Service而一直保留Service所在的程序。這使得程序的執行代價很高,因為系統沒有辦法把Service所佔用的RAM空間騰出來讓給其他元件,另外Service還不能被Paged out。這減少了系統能夠存放到LRU快取當中的程序數量,它會影響應用之間的切換效率,甚至會導致系統記憶體使用不穩定,從而無法繼續保持住所有目前正在執行的service。 建議使用IntentService,它會在處理完交代給它的任務之後儘快結束自己。更多資訊,請閱讀Running in a Background Service。
  • 優化佈局層次,減少記憶體消耗 
    • 越扁平化的檢視佈局,佔用的記憶體就越少,效率越高。我們需要儘量保證佈局足夠扁平化,當使用系統提供的View無法實現足夠扁平的時候考慮使用自定義View來達到目的。
  • 謹慎使用“抽象”程式設計

    • 很多時候,開發者會使用抽象類作為”好的程式設計實踐”,因為抽象能夠提升程式碼的靈活性與可維護性。然而,抽象會導致一個顯著的額外記憶體開銷:他們需要同等量的程式碼用於可執行,那些程式碼會被mapping到記憶體中,因此如果你的抽象沒有顯著的提升效率,應該儘量避免他們。
  • 謹慎使用依賴注入框架

    • 使用類似Guice或者RoboGuice等框架注入程式碼,在某種程度上可以簡化你的程式碼。 
      程式碼是簡化了不少。然而,那些注入框架會通過掃描你的程式碼執行許多初始化的操作,這會導致你的程式碼需要大量的記憶體空間來mapping程式碼,而且mapped pages會長時間的被保留在記憶體中。除非真的很有必要,建議謹慎使用這種技術。
  • 謹慎使用多程序:

    • 使用多程序可以把應用中的部分元件執行在單獨的程序當中,這樣可以擴大應用的記憶體佔用範圍,但是這個技術必須謹慎使用,絕大多數應用都不應該貿然使用多程序,一方面是因為使用多程序會使得程式碼邏輯更加複雜,另外如果使用不當,它可能反而會導致顯著增加記憶體。當你的應用需要執行一個常駐後臺的任務,而且這個任務並不輕量,可以考慮使用這個技術。
    • 一個典型的例子是建立一個可以長時間後臺播放的Music Player。如果整個應用都執行在一個程序中,當後臺播放的時候,前臺的那些UI資源也沒有辦法得到釋放。類似這樣的應用可以切分成2個程序:一個用來操作UI,另外一個給後臺的Service。
  • 使用ProGuard來剔除不需要的程式碼

    • ProGuard能夠通過移除不需要的程式碼,重新命名類,域與方法等等對程式碼進行壓縮,優化與混淆。使用ProGuard可以使得你的程式碼更加緊湊,這樣能夠減少mapping程式碼所需要的記憶體空間。
  • 謹慎使用第三方libraries:

    • 很多開源的library程式碼都不是為行動網路環境而編寫的,如果運用在移動裝置上,並不一定適合。即使是針對Android而設計的library,也需要特別謹慎,特別是在你不知道引入的library具體做了什麼事情的時候。例如,其中一個library使用的是nano protobufs, 而另外一個使用的是micro protobufs。這樣一來,在你的應用裡面就有2種protobuf的實現方式。這樣類似的衝突還可能發生在輸出日誌,載入圖片,快取等等模組裡面。另外不要為了1個或者2個功能而匯入整個library,如果沒有一個合適的庫與你的需求相吻合,你應該考慮自己去實現,而不是匯入一個大而全的解決方案。

測量工具

  • Memory Monitor:跟蹤整個app的記憶體變化情況。
  • Heap Viewer:檢視當前記憶體快照,便於對比分析哪些物件有可能發生了洩漏。
  • Allocation Tracker:追蹤記憶體物件的來源。

電量

這裡寫圖片描述

網路 and 電量消耗

一個處於完全工作狀態的無線電會大量消耗電量,因此需要學習如何在不同能量狀態下進行過渡,當無線電沒有工作時,節省電量,當需要時嘗試最小化與無線電波供電有關的延遲。

典型的 3G 無線電網路有三種能量狀態:

  • Full power:當無線連線被啟用的時候,允許裝置以最大的傳輸速率進行操作。
  • Low power:一種中間狀態,對電量的消耗差不多是 Full power 狀態下的50%。
  • Standby:最小的能量狀態,沒有被啟用或者需求的網路連線。

    low power 到 Full power 大概需要花費1.5秒; 
    Standby 到 full power 大概需要花費2秒 ; 
    Full power到 low power 大概需要花費5秒; 
    low power 到 Standby 大概需要花費12秒;

策略

  • 優先在無線網路狀態下做耗時操作(視訊、檔案下載…):

    • 使用無線電量消耗低於使用移動流量
  • 使用預取(prefetching)與捆綁(bundle)的方式進行資料的傳輸,這些操作都是為了最小化電量的消耗。

根據電池狀態區別操作

如:

  • 充電狀態或者電量高於20%直接播放視訊
  • 當電池低於20%時 給出提示“請充電…”
  • ….

喚醒工作與電量消耗

高效的保留更多的電量與不斷促使使用者使用你的App會消耗電量,這是矛盾的選擇題。不過我們可以使用一些更好的辦法來平衡兩者。 
假設你的手機裡面裝了大量的社交類應用,即使手機處於待機狀態,也會經常被這些應用喚醒用來檢查同步新的資料資訊。Android會不斷關閉各種硬體來延長手機的待機時間,首先螢幕會逐漸變暗直至關閉,然後CPU進入睡眠,這一切操作都是為了節約寶貴的電量資源。但是即使在這種睡眠狀態下,大多數應用還是會嘗試進行工作,他們將不斷的喚醒手機。一個最簡單的喚醒手機的方法是使用PowerManager.WakeLock的API來保持CPU工作並防止螢幕變暗關閉。這使得手機可以被喚醒,執行工作,然後回到睡眠狀態。知道如何獲取WakeLock是簡單的,可是及時釋放WakeLock也是非常重要的,不恰當的使用WakeLock會導致嚴重錯誤。例如網路請求的資料返回時間不確定,導致本來只需要10s的事情一直等待了1個小時,這樣會使得電量白白浪費了。這也是為何使用帶超時引數的wakelock.acquice()方法是很關鍵的。 
但是僅僅設定超時並不足夠解決問題,例如設定多長的超時比較合適?什麼時候進行重試等等?解決上面的問題,正確的方式可能是使用非精準定時器。通常情況下,我們會設定一個時間進行某個操作,但是動態修改這個時間也許會更好。例如,如果有另外一個程式需要比你設定的時間晚5分鐘喚醒,最好能夠等到那個時候,兩個任務捆綁一起同時進行,這就是非精確定時器的核心工作原理。我們可以定製計劃的任務,可是系統如果檢測到一個更好的時間,它可以推遲你的任務,以節省電量消耗。 
這正是JobScheduler API所做的事情。它會根據當前的情況與任務,組合出理想的喚醒時間,例如等到正在充電或者連線到WiFi的時候,或者集中任務一起執行。我們可以通過這個API實現很多免費的排程演算法。

Battery Historian

Battery Historian是Android 5.0開始引入的新API。通過下面的指令,可以得到裝置上的電量消耗資訊:

<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">$ adb shell dumpsys batterystats > xxx<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.txt</span>  //得到整個裝置的電量消耗資訊
$ adb shell dumpsys batterystats > <span class="hljs-keyword" style="color: rgb(0, 0, 136); box-sizing: border-box;">com</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.package</span><span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.name</span> > xxx<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.txt</span> //得到指定app相關的電量消耗資訊</code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li><li style="box-sizing: border-box; padding: 0px 5px;">2</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div>

得到了原始的電量消耗資料之後,我們需要通過Google編寫的一個Python指令碼把資料資訊轉換成可讀性更好的html檔案:

<code class="hljs avrasm has-numbering" style="display: block; padding: 0px; color: inherit; box-sizing: border-box; font-family: 'Source Code Pro', monospace;font-size:undefined; white-space: pre; border-radius: 0px; word-wrap: normal; background: transparent;">$ python historian<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.py</span> xxx<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.txt</span> > xxx<span class="hljs-preprocessor" style="color: rgb(68, 68, 68); box-sizing: border-box;">.html</span></code><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><ul class="pre-numbering" style="box-sizing: border-box; position: absolute; width: 50px; top: 0px; left: 0px; margin: 0px; padding: 6px 0px 40px; border-right-width: 1px; border-right-style: solid; border-right-color: rgb(221, 221, 221); list-style: none; text-align: right; background-color: rgb(238, 238, 238);"><li style="box-sizing: border-box; padding: 0px 5px;">1</li></ul><div class="save_code tracking-ad" data-mod="popu_249"><a target=_blank target="_blank"><img src="http://static.blog.csdn.net/images/save_snippets.png" alt="" /></a></div>

開啟這個轉換過後的html檔案,可以看到類似TraceView生成的列表資料,這裡的資料資訊量很大,這裡就不展開了