深入探索 Android 電量優化

前言

成爲一名優秀的Android開發,需要一份完備的知識體系,在這裏,讓我們一起成長爲自己所想的那樣~。

本文思維導圖

一、正確認識

1、爲什麼要做電量優化?

在 Android 應用開發中,我們需要考慮的是如何優化電量使用,讓我們的 App 不會因爲電量消耗過高被用戶排斥,或者被其他安全應用報告,以此確保用戶黏性。

2、電量重視度不夠

開發中一直連接手機,不知道電量消耗有多快。

3、電量消耗線上難以量化

我們沒有辦法拿到每一個用戶手機的組件能耗,其中不同的硬件模塊使用了不同的參數,然後使用了不同的算法來進行估算。但是,具體的參數值根據手機所使用的硬件來說是不一樣的。

二、電池技術

1、電池容量

現在一般手機的電池容量會佔用內部組件將近一半的空間。

2、充電時間

1)、OPPO VOOC 閃充技術

  • 1、適配器中加入 MCU 智能芯片,得益於 MCU 對電流的精準調節,VOOC 實現了分段恆流和分檔技術,起步時,VOOC 會掛上高速檔,中間時會自動掛上中速檔,讓你快速前行,結尾時又會切換成低速擋,讓你平穩到站。
  • 2、從適配器到接口再到手機內部的全端式五重防護技術。
    • 1)、適配器過載保護
      電流進入適配器時,其中的傳感器會實時檢測電壓電流,安全時, MOSFET(保護)開關會自動打開閃充。
    • 2)、閃充條件鑑定保護
      電流通過適配器時,MCU 芯片會識別設備是否支持閃充,只有支持纔開啓閃充與第二級過載保護。
    • 3)、接口過載保護
      電流進入手機時,在特別定製的 7pinUSB 接口處,手機內的 MCU 會控制第三個 MOSFET(保護)開關,實行第三級過載保護。
    • 4)、電池過載保護
      電池內的特殊 IC 和 MOSFET(保護)開關負責對進入電池的電壓電流實行過載保護。
    • 5)、電池熔絲保護
      出現異常時,電池內的保險絲會立即熔斷,物理性斷絕電流輸入。
  • 3、將充電安全指數從 PPM(百萬分之一)提升至航天級別 DPM(十億分之一)。

2)、快充技術

P=UI(電功率=電壓 * 電流)

普通充電過程

  • 1)、先將 220V 電壓通過充電頭降至 5V。
  • 2)、然後,手機內部電路再把 5V 電壓降至 4.2V。
  • 3)、最後,把電量輸送給電池,而整個降壓的過程中會產生熱能。

分類

  • 1)、高壓低電流快充方案:在充電過程中國提升充電電壓(7-20V)來提升充電功率。
  • 2)、低壓大電流快充方案:在電壓一定情況下,增加電流,通常使用並聯電路的方式進行分流。

3)、鋁-石墨烯超級電池

  • 超高耐用性和安全性,快充充電1.1秒就能充滿電。
  • 實驗階段。

3、壽命

通常使用充電循環次數衡量。

4、安全性

嚴格控制電池容量,例如 VOOC 就使用了各種安全檢測技術。

5、電量和硬件

  • 手機耗電是通過使用相應的硬件模塊來消耗電能。
  • CPU、屏幕、WIFI、數據網絡、GPS、音視頻通話在日常耗電量中佔比最大。

6、Android 耗電演進

KITKAT

批處理傳感器

分批有效地收集和傳遞傳感器事件。

Alarm 對齊

批處理在合理的相似時間內的所有應用的鬧鈴,以便系統僅喚醒一次。

Lollipop

  • 開啓 Volta 項目
  • Job Scheduler
  • dumpsys batterystats
  • Battery Historian
  • 修復 native fork 進程保活的 bug

Marshmallow

  • 省電功能
  • Doze 低功耗模式
  • App Standby 應用待機摸手機

Nougat

  • 優化省電功能
  • Doze 加強版
  • implicit broadcasts 顯示
  • 混合編譯

Oreo

  • 更多優化省電功能
  • 後臺執行限制
  • 後臺位置限制

P(電壓管理嚴格限制)

應用待機分組(App Standby Bueckets)

  • 從應用安裝開始。
  • 分組決定後臺被限制的程度。
  • 不常用的應用將被限制地更加嚴格。

應用後臺限制(Background Restrictions)

  • 用戶開啓。
  • 停止後臺運行。
  • 提示用戶後臺耗電嚴重的應用,用戶可選擇停止它們的後臺運行。

省電模式(Battery Saver)

  • 用戶開啓。
  • 所有應用進入待機模式。
  • 更加嚴格的後臺限制,而且無視應用的 Target API。

三、電量檢測方案

對於電量的統計有一個公式,如下所示:

模塊電量(mAh) = 模塊電流(mA)* 模塊耗時(h)

Android 系統要求 ROM 廠商必須在 /frameworks/base/core/res/res/xml/power_profile.xml 提供組件的電源配置文件。而 Android 系統的電量計算 PowerProfile 正是通過讀取 power_profile.xml 的數據。

1、設置—耗電排行

  • 1)、直觀,但沒有詳細數據,對解決問題幫助不大。
  • 2)、需要找特定場景專項測試,比如在某一個界面操作一段時間,然後來判斷這個頁面是否耗電。

2、使用廣播監聽電量變化—ACTION_BATTERY_CHANGED

獲取電池電量、充電狀態、電池狀態等信息。

實戰案例

IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_BATTERY_CHANGED);
Intent intent = registerReceiver(null, filter);
LogUtils.i("battery " + intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1));

缺點

  • 1)、價值不大:針對手機整體的耗電量,而非單個 App。
  • 2)、實時性差、精度較低,被動通知。

3、dumpsys batterystats

batterystats 是 Android 5.0 提供的工具,它可以獲取各個 App 的 WakeLock、CPU 時間佔用等信息,同時增加了一個 Estimated power use(mAh)功能,預估耗電量。

作用

將電量測量轉化爲功能模塊的使用時間或者次數。

adb shell dumpsys batterystats > battery.txt

在 battery.txt 搜索 ‘Estimated power use’ 關鍵字,下面粗略統計了各個 Uid 的總耗電量。

Estimated power use (mAh):
Capacity: 3350, Computed drain: 2767, actual drain: 3752-3853
Uid 1000: 1014 ( cpu=999 wake=1.36 radio=11.4 wifi=1.24 gps=0.435 sensor=0.808 ) Excluded from smearing
Unaccounted: 985 ( ) Including smearing: 0 ( ) Excluded from smearing
Uid 0: 416 ( cpu=157 wake=210 radio=38.8 wifi=9.51 ) Excluded from smearing
...

batterystats 所記錄的電量統計數據源自於 BatteryStatsService-電量統計服務,其實現類爲 BatteryStatsImpl,內部正是使用的 PowerProfile 。

BatteryStatsImpl 爲每一個應用創建與之對應的 UID 來監控器系統資源的使用情況,其統計了 12 大模塊的電量消耗,如下所示:

  • Camera、Audio、Video
  • Bluetooth、Network、Wakelock
  • Sensor、Radio、Screen
  • WIFI、CPU、GPS

4、Battery Historian

特點

  • 1)、查看自設備上次充電以來各種彙總統計信息,而且可以選擇對應的 App 查看詳細信息。
  • 2)、可視化展示指標:
    • 耗電比例。
    • 執行時間、次數。
  • 3)、僅適合線下使用。

安裝

  • 1)、安裝 Docker
  • 2)、docker – run -p :9999 gcr.io/android-battery-historian/stable:3.0 --port 9999 (需要外網)

導出電量信息

  • 1)、使用 batterystats 命令重置手機電量:adb shell dumpsys batterystats --reset
  • 2)、使用 batterystats 命令獲取電池數據權限並開啓記錄全面的電量信息:adb shell dumpsys batterystats --enable full-wake-history
  • 3)、測試完成後,使用 bugreport 導出電量信息:
    • 7.0和7.0以後:adb bugreport bugreport.zip
    • 6.0和6.0之前:adb bugreport > bugreport.txt
    • 通過 historian 圖形化展示結果:python historian.py -a bugreport.txt > battery.html

上傳分析

  • 1)、打開 http://localhost:

如果打不開,可以使用備用網站 bathist

  • 2)、上傳 bugreport 文件,點 Submit 提交即可。

Battery Historian 數據分析

Hitorian V2 — 電量統計圖表

Add Metrics

在 Add Metrics 中我們可以增加更多的測量項。

CPU running

如果一直處於 running,則表明電量消耗比較高。

JobScheduler

選中 Job Scheduler 的某一個工作時間片,我們可以查看具體的 發生的時間、耗時以及次數,最重要的是它統計出來了是哪一個進程在使用這個 JobScheduler。

App Selection

  • 1)、選擇要分析電量的指定 App。
  • 2)、點擊右邊區域的 System Stats 一欄可以在下方查看各個系統組件的電量百分比消耗詳情,例如 Userspace Wakelocks。

主入口處的 Switch to Bugreport Comparison

選擇多個文件進行上傳對比。

5、電量專項測試

1)、耗電場景測試

  • 複雜計算。
  • 音視頻播放。

2)、傳感器相關

  • 使用時長
  • 耗電量
  • 發熱

3)、後臺靜默測試

四、耗電優化

1、耗電優化的難點

  • 1)、缺乏現場,無法復現
  • 2)、信息不全,難以定位
  • 3)、無法評估結果

在 App 開發中,經常會由於某個需求場景或 代碼 bug 而導致大量耗電。

2、後臺調度任務省電

思考步驟

  • 需要後臺運行
    • 長時間下載:DownloadManager
    • 數據同步:SyncAdapter
    • 本地任務:JobScheduler
  • 特定時間執行:AlarmManager
  • 實時通信:推送服務
  • 立刻執行:Foreground Service

對於耗電優化中,我們最常用的就是 JobScheduler,下面👇,我們來實戰一下。

Job Scheduler 實戰

/**
 * 開啓 JobScheduler
 */
private void startJobScheduler() {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        JobScheduler jobScheduler = (JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE);
        JobInfo.Builder builder = new JobInfo.Builder(1, new ComponentName(getPackageName(), JobSchedulerService.class.getName()));
        // 設置僅在 充電和WIFI 下才使用 JobScheduler 進行批量任務處理
        builder.setRequiresCharging(true)
                .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);
        jobScheduler.schedule(builder.build());
    }
}

其中,JobSchedulerService 就是用於進行批量任務處理的服務,示例代碼如下所示:

/**
 * 用於進行批量任務處理的 JobSchedulerService
 */
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public class JobSchedulerService extends JobService {

    @Override
    public boolean onStartJob(JobParameters params) {
        // 此處執行在主線程
        // 模擬一些處理:批量網絡請求,APM日誌上報
        return false;
    }

    @Override
    public boolean onStopJob(JobParameters params) {
        return false;
    }
}

特點

  • 1)、僅支持 API 21 及之上
  • 2)、在符合某些條件時創建執行在後臺的任務
  • 3)、把不緊急的任務放到更合適的時機批量處理

符合 Android 規則,手機在充電狀態纔去做耗電工作。示例代碼如下所示:

IntentFilter ifilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
Intent batteryStatus = context.registerReceiver(null, ifilter);
//獲取用戶是否在充電的狀態或者已經充滿電了
int status = batteryStatus.getIntExtra(BatteryManager.EXTRA_STATUS, -1);
boolean isCharging = status == BatteryManager.BATTERY_STATUS_CHARGING || status == BatteryManager.BATTERY_STATUS_FULL;

3、電量優化套路總結

1、優化應用的後臺耗電

避免後臺長時間獲取 WakeLock、WIFI 和藍牙的掃描等。

2、符合系統的耗電規則

Android P 使用了 Android Vitals 監控後臺耗電,其規則如下所示:

  • 1)、Alarm Manager wakeup 喚醒過多:當手機不在充電狀態,每小時 wakeup 喚醒次數大於 10 次。
  • 2)、頻繁使用局部喚醒鎖:當手機不在充電狀態,partial wake lock 持有超過1小時。
  • 3)、後臺網絡使用量過高:當手機不在充電狀態而且應用在後臺,每小時網絡使用量超過 50MB。
  • 4)、後臺 WiFi scans 過多:當手機不在充電狀態而且應用在後臺,每小時大於4次 WiFi scans。

3、CPU 時間片

Android 手機保護 AP 和 BP 兩個 CPU。AP 即 Application Processor,所有的用戶界面以及 App 都是運行在 AP 上的。BP 即 Baseband Processor,手機射頻都是運行在這個 CPU 上的。而一般我們所說的耗電,PowerProfile 文件裏面的 CPU,指的是 AP

CPU 耗電通常有兩種情況:

  • 1)、長期頻繁喚醒:原本可以僅僅在 BP 上運行,消耗 5mA 左右,但是因爲喚醒,AP 就會運作,不同手機情況不一樣,至少會導致 20~30 mA 左右的耗電
  • 2)、CPU 長期高負荷:例如 App 退到後臺的時候沒有停止動畫,或者程序有不退出的死循環等等,導致 CPU 滿頻、滿核地跑

常用優化 CPU 時間片的方式有:

  • 1)、獲取運行過程線程 CPU 消耗,定位 CPU 佔用率異常方法
  • 2)、減少後臺應用的主動運行

4、網絡相關

通常情況下,使用 WIFI 連接網絡時的功耗要低於使用移動網絡的功耗。而使用移動網絡傳輸數據,電量的消耗有以下3種狀態:

  • Full power:高功率狀態,移動網絡連接被激活,允許設備以最大的傳輸速率進行操作
  • Low power:低功耗狀態,對電量的消耗差不多是 Full power 狀態下的 50%
  • Standby:空閒態,沒有數據連接需要傳輸,電量消耗最少

因此,爲了避免網絡連接所帶來的電量消耗,我們可以採用如下幾種方案:

  • 1)、儘量在 WIFI 環境下進行數據傳輸,在使用 WIFI 傳輸數據時,應該儘可能增大每個包的大小(不超過 MTU),並降低發包的頻率。
  • 2)、在蜂窩移動網絡下需要對請求時機及次數控制:可以延遲執行的網絡請求稍後一起發送,最好做到批量執行,儘量避免頻繁的間隔網絡請求,以儘量多地保持在 Radio Standby 狀態。
  • 3)、使用 JSON 和 Protobuf 進行數據壓縮,減少時間。
  • 4)、禁止使用輪詢功能:輪詢會導致網絡請求一直處於被激活的狀態,耗電過高。

5、定位相關

  • 1)、根據場景謹慎選擇定位模式:對定位準確度沒那麼高的場景可以選擇低精度模式
  • 2)、可以考慮網絡定位代替 GPS
  • 3)、使用後務必及時關閉,減少更新頻率,例如定位開啓一定時間後超過某個閾值可以執行一個兜底策略:強制關閉 GPS

6、界面相關

  • 1)、離開界面後停止相關活動,例如關閉動畫
  • 2)、耗電操作判斷前後臺,如果是後臺則不執行相關操作

7、WakeLock 相關

WakeLock 常用於後臺播放音視頻、錄製音視頻、下載文件的情況。如果沒有合理使用 WakeLock,則會造成嚴重的耗電問題,爲了避免該問題,我們應該定期針對使用了 WakeLock 的模塊進行重點排查

我們可以使用 adb shell dumpsys power 命令查看系統當前的耗電信息,其中我們可以看到 WakeLock 列表,它通常會以 ”mLocks.size“ 或者 ”Wake Locks:size“ 開頭。關於 WakeLock 的使用我們要着重注意以下幾點:

  • 1)、注意成對使用 acquire、release
  • 2)、建議使用帶參數的 acquire,避免沒有及時釋放而導致電量消耗過大
  • 3)、使用 finally 確保 release 一定會被調用
  • 4)、常亮場景使用 keepScreenOn 即可
  • 5)、WakeLock 有一個接口 setReferenceCounted,用來設置 WakeLock 的技術機制,官方默認爲計數。true 爲計數,false 爲不計數。所謂計數即每一個 acquire 必須對應一個 release;不計數則是無論有多少個 acquire,一個 release 就可以釋放。但是問題是有的第三方 ROM 它將默認設置爲了不計數,以爲我們需要在調用 newWakeLock 之後再調用 setReferenceCounted 爲 false

8、計算優化

浮點運算比整數運算更消耗 CPU 時間片,因此耗電也會增加。避開浮點運算的優化方法如下所示:

  • 1)、除法變乘法
  • 2)、充分利用移位
  • 3)、在 native 層開發時,可以利用 ARM neon 指令集做並行運算,注意需要 ARM V7 及以上架構 CPU 才能支持

9、滅屏時停止動畫

我們可以監聽滅屏以及亮屏的廣播,在滅屏的時候停止 surfaceView 的動畫繪製。在亮屏的時候,恢復動畫的繪製

五、耗電監控

以後臺耗電監控爲主,必須監控的模塊有:

  • 1)、Alarm wakeup
  • 2)、WakeLock
  • 3)、WiFi scans
  • 4)、Network

必須監控的現場信息有

  • 1)、堆棧信息
  • 2)、是否充電
  • 3)、電量水平
  • 4)、應用前後臺時間
  • 5)、CPU 狀態信息

最後,我們需要 提煉規則,將監控內容 => 抽象成規則

1、Java Hook

我們可以通過代理對應的 Service 實現,完成收集 Wakelock、Alarm、GPS 的申請堆棧、釋放信息、手機充電狀態等等。

示例項目

2、電量輔助監控實戰

1)、獲取運行時能耗文件

  • 1)、adb pull /system/framework/framework-res.apk
  • 2)、反編譯,xml—》power_profile

2)、電量輔助監控

線下使用 epic 進行 AOP 電量輔助統計

這裏我們就以 WakeLock 的監控爲例,切面代碼如下所示:

public static long sStartTime = 0;
@Insert(value = "acquire")
@TargetClass(value = "com.optimize.performance.wakelock.WakeLockUtils",scope = Scope.SELF)
public static void acquire(Context context){
    trace = Log.getStackTraceString(new Throwable());
    sStartTime = System.currentTimeMillis();
    Origin.callVoid();
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            WakeLockUtils.release();
        }
    },1000);
}
@Insert(value = "release")
@TargetClass(value = "com.optimize.performance.wakelock.WakeLockUtils",scope = Scope.SELF)
public static void release(){
    LogUtils.i("PowerManager "+(System.currentTimeMillis() - sStartTime)+"/n"+trace);

此外,我們也可以利用 epic 來監控每個線程的執行時間,超過閾值則警告,示例代碼如下所示:

public static long runTime = 0;
@Insert(value = "run")
@TargetClass(value = "java.lang.Runnable",scope = Scope.ALL)
public void run(){
    runTime = System.currentTimeMillis();
    Origin.callVoid();
    LogUtils.i("runTime "+(System.currentTimeMillis() - runTime));
}

3、編譯插樁

寫一個基礎類,然後在統一的調用接口中添加監控邏輯。這裏我們可以參考 Facebook Battery-Metrics 獲取、監控數據的方式。其代碼如下所示:

public class WakelockMetrics {

    /**
     * 獲取 WakeLock
     *
     * @param wakeLock WakeLock
     * @param timeout 超時時間
     */
    public static void acquire(PowerManager.WakeLock wakeLock, long timeout) {
        wakeLock.acquire(timeout);
        // 監控 wakelock 相關信息
        Log.e("HOOOOOOOOK", "--acquireWakeLock--");
        Log.e("HOOOOOOOOK", Utils.getStackTrace());
        // 使用 Battery-Metrics 庫統計其它維度的電量信息
        
    }

    /**
     * 釋放 WakeLock
     *
     * @param wakeLock WakeLock
     */
    public static void release(PowerManager.WakeLock wakeLock) {
        wakeLock.release();
        Log.e("HOOOOOOOOK", "--releaseWakeLock--");
        Log.e("HOOOOOOOOK", Utils.getStackTrace());
        // 使用 Battery-Metrics 庫統計其它維度的電量信息
        
    }

}

Gradle 耗電量統計插件中 BatteryCreateMethodVisitor 的核心實現代碼如下所示:

@Override
public void visitMethodInsn(int opcode, String owner, String name, String descriptor, boolean isInterface) {
    // 監控 Wakelock
    String monitorClass = "com/ss/android/ugc/bytex/example/battery_monitor/WakelockMetrics";
    if (!monitorClass.equals(className)
            && "android/os/PowerManager$WakeLock".equals(owner)
            && opcode == Opcodes.INVOKEVIRTUAL
            && "acquire".equals(name)) {
        mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                monitorClass,
                name,
                "(Landroid/os/PowerManager$WakeLock;J)V",
                isInterface
        );
        return;
    }
    if (!monitorClass.equals(className)
            && "android/os/PowerManager$WakeLock".equals(owner)
            && opcode == Opcodes.INVOKEVIRTUAL
            && "release".equals(name)) {
        mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                monitorClass,
                name,
                "(Landroid/os/PowerManager$WakeLock;)V",
                isInterface
        );
        return;
    }
    // 監控 Gps
    monitorClass = "com/ss/android/ugc/bytex/example/battery_monitor/GpsMetrics";
    if (!monitorClass.equals(className)
            && "android/location/LocationManager".equals(owner)
            && opcode == Opcodes.INVOKEVIRTUAL
            && "requestLocationUpdates".equals(name)) {
        mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                monitorClass,
                name,
                "(Landroid/location/LocationManager;Ljava/lang/String;JFLandroid/location/LocationListener;)V",
                isInterface
        );
        return;
    }
    if (!monitorClass.equals(className)
            && "android/location/LocationManager".equals(owner)
            && opcode == Opcodes.INVOKEVIRTUAL
            && "removeUpdates".equals(name)) {
        mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                monitorClass,
                name,
                "(Landroid/location/LocationManager;Landroid/location/LocationListener;)V",
                isInterface
        );
        return;
    }
    // 監控 Alarm Service
    monitorClass = "com/ss/android/ugc/bytex/example/battery_monitor/AlarmMetrics";
    if (!monitorClass.equals(className)
            && "android/app/AlarmManager".equals(owner)
            && opcode == Opcodes.INVOKEVIRTUAL
            && "set".equals(name)) {
        mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                monitorClass,
                name,
                "(Landroid/app/AlarmManager;IJLandroid/app/PendingIntent;)V",
                isInterface
        );
        return;
    }
    if (!monitorClass.equals(className)
            && "android/app/AlarmManager".equals(owner)
            && opcode == Opcodes.INVOKEVIRTUAL
            && "cancel".equals(name)) {
        mv.visitMethodInsn(
                Opcodes.INVOKESTATIC,
                monitorClass,
                name,
                "(Landroid/app/AlarmManager;Landroid/app/PendingIntent;)V",
                isInterface
        );
        return;
    }
    super.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
}

缺點

系統的代碼插樁方案無法替換。

六、電量優化常見問題

1、怎麼做電量測試?

電量相關的測試相對來說難度較大,因爲 App 在具體手機上的耗電量無法準確統計,每一個手機所使用的硬件不一樣,那麼它相應的功耗就不一樣。而且這個功耗值我們只能在線下通過導出手機的 power_profile.xml 文件拿到。

由於我們無法獲取準確的耗電量,所以我們只能增加多個維度來輔助判斷 App 是否耗電。

最後,我們可以分場景各個突破。

關於電量測試,我們可以針對各個功能場景進行鍼對性的專項測試。操作一段時間後,我們可以在手機設置—電量消耗裏面,利用其數據作爲判斷依據。這樣雖然直觀,但精確度不行。

介紹 Battery Historian:

  • Google 推出的一款 Android 電量分析工具,它支持 Android 5.0 及以上系統的電量分析。
  • 它獲取到的各個耗電模塊的耗電信息要相對精確、豐富地多。例如 GPS、WaleLock、藍牙 等的工作時間以及耗電量。
  • 此外,它不僅可以針對單個 App 進行選擇,也可以比對不同的電量場景的信息,比如 優化前、優化後 的信息。
  • Battery Historian 的缺點在於它只能在線下使用。因此除了使用其在線下測試之外,我們還需要在線上增加一些電量的輔助監控,統計例如:耗電組件的使用次數、調用堆棧以及訪問時間。這些都是與用戶相關的基礎電量消耗數據,如果有用戶反饋,我們就可以通過這些信息來判斷用戶是不是有耗電的操作。

2、有哪些有效的電量優化手段?

因爲我們不能在線上統計出 App 的電量消耗,因此需要在儘量保證 App 在正常使用下的耗電。對此我們採取了一系列的電量優化措施:

1)、網絡相關

  • 網絡請求的時機以及次數,將可以延遲的網絡請求批量發送,減少網絡被激活的時機與次數。
  • 此外,我們可以對網絡傳輸數據進行壓縮,以降低傳輸的時間與流量。
  • 最後,一定要禁止使用輪詢的方式來做業務操作。

2)、傳感器相關

根據場景謹慎地選擇傳感器使用的模式,比如說在使用 GPS 的時候一般要避免使用高精度的模式,或者是儘量複用上一次的定位結果。

3)、WakeLock

我們在實際項目中使用 WakeLock 有幾個注意事項,第一,acquire、release 要成對地釋放,第二,儘量使用 acquire 的超時方法來設置超時時間,避免因爲異常情況從而導致 WakeLock 而無法釋放的情況,第三,關於 WakeLock 的釋放一定要寫在 try-catch-finally 的 finally 當中,保證 WakeLock 在異常情況下的釋放。

4)、JobScheduler

JobScheduler 可以允許開發者在符合某些條件下創造執行在後臺的任務,我們可以設置執行一些耗電操作的場景,比如說 處於 WIFI 狀態下同時連接電源 的情況下。同時,要注意用戶在離開界面後,要避免耗電的操作,比如說停止播放動畫。通過這些操作,我們的 App 就不會比之前耗電了。

七、總結

對於電量優化來說,最重要的就是 建立監控與自動化報警的一整套體系,只有發現了耗電的問題所在,才能使用針對性的解決措施

公衆號

我的公衆號 JsonChao 開通啦,歡迎關注~

參考鏈接:


Contanct Me

● 微信:

歡迎關注我的微信:bcce5360

● 微信羣:

由於微信羣人數過多,麻煩大家想進微信羣的朋友們,加我微信拉你進羣。

● QQ羣:

2千人QQ羣,Awesome-Android學習交流羣,QQ羣號:959936182, 歡迎大家加入~

About me

很感謝您閱讀這篇文章,希望您能將它分享給您的朋友或技術羣,這對我意義重大。

希望我們能成爲朋友,在 Github掘金上一起分享知識。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章