Android 功耗與內存優化

主要內容

  • 功耗優化:關於一些對功耗的檢測及優化
  • 內存優化:關於一些對內存的常見優化手段及檢測工具

功耗優化

耗電緣由

  • CPU:wakelocks
  • 網絡、無線、藍牙等
  • 屏幕

CPU 與網絡等是屬於 Background Process,若要優化,須要設法減小、延遲、合併 Background Process。html

Doze 與 App Standby

從 API 23 開始, Android 提供了這兩種模式以延長電池壽命,是 Android 系統的大方針。Doze 就是將一些 Wakelocks、網絡、Jobs/Syncs、Alarms、GPS/Wifi 禁止/延遲,僅在須要喚醒這些東西時即到維護窗口時才喚醒。App Standby 是應用級別的,細分到每個應用中的,應用未使用一段時間後(即沒有 foreground process 時)就會進入 App Standby,進入此狀態後 App 就不能訪問網絡而且 Jobs/Syncs 都會受到延遲。java

固然,啓用 Foreground Service 就不會受到 Doze 和 App Standby 的影響。同時,在 AOSP 中這兩個模式是關閉的,具體須要看看手機廠商有沒有開啓,提起這個只是給到你們一個官方的方案來引導咱們作本身的方案。android

developer.android.google.cn/training/mo…git

常見優化方案

若要進行功耗優化,則須要設法減小、延遲、合併 Background Process。經過 Doze 則是對 Background Process 進行合併,即合併到維護窗口時;經過 App Standby 則是對 Backgroud Process 進行延遲,即延遲到"使用"狀態下或充電狀態下。這些是系統級別的優化,而應用級別的優化則是在平時的積累中進行的,好比對耗電功能進行優化,原則上與上面一致,即減小、延遲、合併這些功耗大的功能:github

  • 屏幕:屏幕亮度對功耗影響是比較大的,在一些場景(如二維碼展現、視頻播放、遊戲)中會對屏幕亮度進行調整或是保持屏幕常亮,在這種狀況下是比較耗電的,注意對此方面的控制可以對功耗作到必定的優化。
  • 充電狀態下進行耗電操做:經過監聽電量廣播(Intent.ACTION_BATTERY_CHANGED)便可獲取當前充電狀態,在充電狀態下進行耗電操做(好比自動備份等高網絡請求的功能)。獲取充電狀態的代碼以下:
/** * This method checks for power by comparing the current battery state against all possible * plugged in states. In this case, a device may be considered plugged in either by USB, AC, or * wireless charge. (Wireless charge was introduced in API Level 17.) */
private boolean checkForPower() {
    // It is very easy to subscribe to changes to the battery state, but you can get the current
    // state by simply passing null in as your receiver. Nifty, isn't that?
    IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
    Intent batteryStatus = this.registerReceiver(null, filter);

    // There are currently three ways a device can be plugged in. We should check them all.
    int chargePlug = batteryStatus.getIntExtra(BatteryManager.EXTRA_PLUGGED, -1);
    boolean usbCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_USB);
    boolean acCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_AC);
    boolean wirelessCharge = false;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
        wirelessCharge = (chargePlug == BatteryManager.BATTERY_PLUGGED_WIRELESS);
    }
    return (usbCharge || acCharge || wirelessCharge);
}
複製代碼
  • 延遲、合併高功耗操做:在網絡請求中,頻繁請求會比集中請求功耗大,蜂窩數據請求會比無線網絡請求功耗大。這種狀況下可經過 JobScheduler 將一些操做延遲、合併在一些合適的狀況下進行。
  • 傳感器、GPS:儘可能複用、及時註銷、選擇正確的參數
  • 及時釋放 WakeLock

檢測工具 Battery Historian

Battery Historian 是 Android 5.0 以後引入的一個獲取設備電量消耗信息的圖形化工具,可以直觀展現手機電量消耗。網絡

img

Battery_Historian_Tool使用說明數據結構

推薦使用 API

JobScheduler:系統利用這些觸發的設置,合併相同的 background process,從而優化內存和電池性能。併發

內存優化

內存優化緣由

  • 內存泄漏 -> 內存佔用高 -> OOM

ART GC 緣由

  • Concurrent: 併發GC,不會使App的線程暫停,該GC是在後臺線程運行的,並不會阻止內存分配。
  • Alloc:當堆內存已滿時,App嘗試分配內存而引發的GC,這個GC會發生在正在分配內存的線程。
  • Explicit:App顯示的請求垃圾收集,例如調用System.gc()。與DVM同樣,最佳作法是應該信任GC並避免顯示的請求GC,顯示的請求GC會阻止分配線程並沒必要要的浪費 CPU 週期。若是顯式的請求GC致使其餘線程被搶佔,那麼有可能會致使 jank(App同一幀畫了屢次)。
  • NativeAlloc:Native內存分配時,好比爲Bitmaps或者RenderScript分配對象, 這會致使Native內存壓力,從而觸發GC。
  • CollectorTransition:由堆轉換引發的回收,這是運行時切換GC而引發的。收集器轉換包括將全部對象從空閒列表空間複製到碰撞指針空間(反之亦然)。當前,收集器轉換僅在如下狀況下出現:在內存較小的設備上,App將進程狀態從可察覺的暫停狀態變動爲可察覺的非暫停狀態(反之亦然)。
  • HomogeneousSpaceCompact:齊性空間壓縮是指空閒列表到壓縮的空閒列表空間,一般發生在當App已經移動到可察覺的暫停進程狀態。這樣作的主要緣由是減小了內存使用並對堆內存進行碎片整理。
  • DisableMovingGc:不是真正的觸發GC緣由,發生併發堆壓縮時,因爲使用了 GetPrimitiveArrayCritical,收集會被阻塞。通常狀況下,強烈建議不要使用 GetPrimitiveArrayCritical,由於它在移動收集器方面具備限制。
  • HeapTrim:不是觸發GC緣由,可是請注意,收集會一直被阻塞,直到堆內存整理完畢。

檢測工具

  • LeakCanary:github.com/square/leak…
  • Memory Profiler:Android Studio 自帶工具https://developer.android.com/studio/profile/memory-profiler.html?hl=zh-cn

常見的內存泄漏場景

  • 非靜態內部類的靜態實例:非靜態內部類會持有外部類的引用。
  • 匿名內部類的靜態實例:匿名內部類會持有外部類的引用。
  • Handler:直接 new Handler(){} 是匿名內部類,也會持有外部類引用,同時 Message Queue 中的部分消息不會及時消費掉,所以:一、在銷燬 Activity 時及時清除 MessageQueue 中的 Message;二、使用靜態的 Handler 內部類;三、持有弱引用的 Activity。
  • Context:避免在非必要的狀況下使用 Activity Context,使用 Application Context 替代。
  • 對有可能持有 Activity 引用的靜態類(如靜態 View)須要在銷燬時置空。
  • 未關閉資源對象(File、Cursor、MediaPlayer等)。
  • Bitmap:Bitmap 的管理是很是麻煩的,使用其都需很是當心,臨時建立需及時回收,避免靜態變量持有 Bitmap 對象。
  • 監聽器及時 remove/unregister(傳感器、定位等)。

其餘內存優化手段

除避免內存泄漏外還有其餘內存優化手段:less

  • 用 JobScheduler 替代 Service。若必須使用 Service,最好用 IntentService 限制服務壽命,全部請求完成後會自動中止。ide

  • 使用 SparseArray、SparseBooleanArray、LongSparseArray 代替 HashMap 等數據結構。

  • 必要時釋放內存:

    import android.content.ComponentCallbacks2;
    
    public class MainActivity extends AppCompatActivity implements ComponentCallbacks2 {
        
        /** * 當UI不可見時或系統資源不足時釋放內存 * @param level 引起的與內存相關的事件 */
        public void onTrimMemory(int level) {
    
            // 肯定引起了哪一個生命週期或系統事件
            switch (level) {
                case ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN:
                    /* 釋放一些不必的內存資源。如今用戶界面已移至後臺 */
                    break;
                case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
                case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
                case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
                    /* 釋放應用不須要的內存。 應用程序運行時,設備內存不足。 引起的事件表示與內存相關的事件的嚴重性。 若是事件是TRIM_MEMORY_RUNNING_CRITICAL,那麼系統將開始殺死後臺進程。 */
                    break;
                case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
                case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
                case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
                    /* 釋放盡量多的內存。 該應用程序位於LRU列表中而且系統內存不足。 引起的事件代表應用程序位於LRU列表中的位置。 若是引起的事件是 TRIM_MEMORY_COMPLETE, 這一進程將是最早終止的進程之一。 */
                    break;
                default:
                    /* 釋聽任何非關鍵數據結構。 應用程序從系統接收到一個沒法識別的內存級別值。 將其視爲通常的低內存消息。 */
                    break;
            }
        }
    }
    複製代碼
  • 檢查當前使用了多少內存,作好保護去釋放一些次要資源以防 OOM:

    public void doSomethingMemoryIntensive() {
        // 在作一些須要大量內存的事情以前檢查設備是否處於低內存狀態
        ActivityManager.MemoryInfo memoryInfo = getAvailableMemory();
        if (!memoryInfo.lowMemory) {
            // 當前處於低內存狀態 ...
        }
    }
    
    // 獲取 MemoryInfo
    private ActivityManager.MemoryInfo getAvailableMemory() {
        ActivityManager activityManager = (ActivityManager) this.getSystemService(ACTIVITY_SERVICE);
        ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
        activityManager.getMemoryInfo(memoryInfo);
        return memoryInfo;
    }
    複製代碼
  • 優化佈局層次、避免自定義組件中 onDraw 建立大量臨時對象。

相關文章
相關標籤/搜索