Androidバッテリーの最適化AlarmManager-WakeLockロックメカニズムの包括的な分析



Android Battery Optimization Alarmmanager Wakelock Lock Mechanism Comprehensive Analysis



まず、AlarmManagerの概要

AlarmManager Androidシステムのシステムレベルのプロンプトサービスです。時間内または定期的にインテントを設定できます。このインテントは、サービスサービスの開始、ブロードキャストの送信、アクティビティのジャンプ、これが考慮されないかどうかの確認、タイマータイマーです。 、タイマーは確かに一般的なタイミング要件を達成するための最も便利な方法ですが、電話が暗くなり、画面が暗くなり、最後にCPUが動作を停止することを想像してください。これにより、バッテリーの急速な低下を防ぐことができます。長いスリープの場合、カスタムタイマー、ハンドラー、スレッド、サービスなどはCPUをウェイクアップする機能がないため中断されますが、 AlarmManager CPUをウェイクアップすることができ、指定された時間に達すると、「小さいU、眠らないで、起きて仕事をする」と叫びます。CPUが起動して仕事をします。 AlarmManager 最も重要な機能は、電話機がスリープしているときにCPUをウェイクアップして動作させる機能です。



次に、AlarmManagerキーAPIの説明

AlarmManagerを使用すると、最初に取得します AlarmManager システムサービスは次のとおりです。



PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE)

非常にシンプルで、特別に説明する必要はありません。

API 19より前のAlarmManagerの一般的なメソッド:

(1)set(int type、long startTime、PendingIntent pi)/ /このメソッドは、ワンタイムタイマーを設定するために使用され、到着時間の完了が終了します。



(2)setRepeating(int type、long startTime、long intervalTime、PendingIntent pi)/ /このメソッドは、繰り返し可能なタイマーを設定するために使用されます。

(3)setInexactRepeating(int type、long startTime、long intervalTime、PendingIntent pi)//このメソッドは、繰り返し可能なタイマーを設定するために使用されます。 setRepeatingと比較すると、この方法ではシステムの能力が考慮されます。たとえば、バッテリー残量が少ない場合、システムはアラームの配信時間を調整して同時にトリガーできるため、設定された間隔に厳密に従って目覚まし時計を実行できない場合があります。必要なデバイスをウェイクアップします。

パラメータの説明:

int型 :アラームタイプ。一般的に使用されるタイプは次の5つです。

AlarmManager.ELAPSED_REALTIME 電話機がスリープ状態にあるとき、つまり、スリープ状態でCPUをウェイクアップする機能がないときにアラームが使用できないことを示します(通常のタイマーと同様)。この状態では、アラームはシステムの起動から始まる相対時間を使用します。
AlarmManager.ELAPSED_REALTIME_WAKEUP スリープ状態のときに、アラームがシステムをウェイクアップし、プロンプト機能を実行することを示します。この状態では、アラームは相対時間を使用します。
AlarmManager.RTC スリープ状態ではアラームが使用できないことを示します。この状態では、アラームは絶対時間、つまり現在のシステム時間を使用します。
AlarmManager.RTC_WAKEUP アラームがシステムをウェイクアップし、スリープ状態のときにプロンプ​​ト機能を実行することを示します。この状態では、アラームは絶対時間を使用します。
AlarmManager.POWER_OFF_WAKEUP これは、携帯電話の電源がオフのときにアラームがプロンプト機能を正常に実行できることを示しており、5つの状態の中で最も使用されている状態の1つです。この状態では、目覚まし時計も絶対時間を使用します。

長いstartTim e:アラームの最初の実行時間(ミリ秒単位)。この属性は最初の属性と密接に関連していることに注意してください。最初のパラメーターに対応するアラームが相対時間(ELAPSED_REALTIMEおよびELAPSED_REALTIME_WAKEUP)を使用する場合、この属性は現在の時間などの相対時間を使用する必要があります。 SystemClock.elapsedRealtime()最初のパラメーターに対応するアラームが絶対時間(RTC、RTC_WAKEUP、POWER_OFF_WAKEUP)を使用する場合、このプロパティは絶対時間を使用する必要があり、現在の時間はSystem.currentTimeMillis()として表されます。

長いintervalTime :2つのアラーム間の間隔をミリ秒単位で示します。

保留中のインテントパイ :時間の経過後に実行する意図。保留中のインテントは、インテントのラッパークラスです。アラームがスタートアップサービスによってプロンプトされた場合、PendingIntentオブジェクトは、アラームプロンプトを実現するためにブロードキャストされた場合、Pending.getService(Context c、int i、Intent intent、int j)によって取得される必要があることに注意してください。保留中のIntentオブジェクトを取得するには、PendingIntent.getBroadcast(Context c、int i、Intent intent、int j)メソッドを使用する必要があります。アクティビティを使用してアラームプロンプトを実装する場合は、PendingIntent.getActivity(Context c、int i、Intent intent、int j)メソッドを使用してPendingIntentオブジェクトを取得する必要があります。この記事の焦点では​​ないPendingIntenについては、使用方法を参照してください。

使用例:要件、CPUがスリープ状態の場合でも実行できるアラームを定義します。 5秒ごとにブロードキャストを送信します。コードは次のとおりです。

画像

WakeLock wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,

画像

第三に、AlarmManagerのバージョン適応

上記の説明は、API = 19およびAPIの場合に正常に実行できます。<=23 mobile phones will find out how Nima is not working well. For example, if we set it to execute once in 1 minute, it will actually change when it runs. It’s done in 3 minutes, isn’t it a pothole? Why is this? Check out the Google documentation and find out that the 4.4 version has the following description:

私はそれを見ました、4.4以上のグーグルは最適化されました、それを最適化する方法は? 2つのアプリケーションA、Bをインストールした携帯電話などの以前のバージョンがAlarmManagerを使用しているとしましょう。アプリケーションは、タスクを実行するためにCPUをウェイクアップするために5秒を設定し、Bアプリケーションはタスクを実行するためにCPUをウェイクアップするために7秒を設定しました。 APIで<19 mobile phone This operation is no problem, 5 seconds once, 7 seconds once to wake up the CPU to work, but to 4.4 and above, this will not work, Google thinks that this function of Laozi has been broken by you, so go down this way Liu once every 5 seconds, Xiao Xu 6 seconds once, Xiaojiang 7 seconds once the CPU is constantly being woken up, the user's power is consumed (wake up the CPU is very power-hungry), well, Laozi directly optimizes, for this situation I have unified the batch processing, you have given me 7 seconds to wake up the CPU, this time you have all three jobs. The general optimization logic is like this.

しかし、すべてがそこにありますが、API> = 19およびAPIで正常に実行したい場合<=23, Google still provides an extra API, using setExact(int type, long triggerAtMillis, PendingIntent operation ) It will be fine. (The specific use of the code article below, do not worry)

変更後も実行を続けました。

しかし、6.0以降の携帯電話には問題があります。携帯電話がしばらく休止状態になると、AlarmManagerが動作しなくなります。本当にサービスです。問題を見つけ続けます。 Googleは6.0にあることがわかります。

低電力モードとアプリケーションスタンバイモード(6.0で導入)は、以下に説明するように最適化されています(元のリンク:https://developer.android.google.cn/training/monitoring-device-state/doze-standby.html) :

説明は非常に明確です。注意深く読んでください。また、対応するAPIソリューションsetExactAndAllowWhileIdle(int type、long triggerAtMillis、PendingIntent操作)も提供してください。この時点で、AlarmManagerのバージョンの適応は完了していますが、setExact(int type、long triggerAtMillis、PendingIntent操作)およびsetExactAndAllowWhileIdle(int type、long triggerAtMillis、PendingIntent操作)メソッドがリマインダー設定を繰り返さず、setRepeatingがないという問題があります。同様のAPIは、すべて1回限りの目覚まし時計ですが、タスクを時々実行する必要性をどのように認識しますか?非常に簡単で、登録を繰り返すことができますので、わからない場合は、以下を読み続けてください。デモで説明します。

第四に、AlarmManagerインスタンスのデモの説明(バージョンの適応と高バージョン設定の繰り返しアラームを含む)

さて、上記の説明の後、あなたは特定のコードを見なかったので、あなたは理解し理解しているようだと思います。単純で、小さなデモはすべて理解するでしょう。

実装機能:CPUスリープの場合でも、5秒ごとにブロードキャストを送信でき、対応するロジックがブロードキャストレシーバーで実行され(デモではログのみが印刷されます)、バージョンが調整されます。

コアAlarmManagerUtilsクラスを見てください。

画像

'MyWakelockTag'

画像

AlarmManagerUtilsは、AlarmManagerに関連する操作をカプセル化して、簡単に分離できるようにします。非常に単純で、主にバージョンの適応です。上記は十分に注意深く説明されています。ここでは、さまざまなAPIのさまざまなバージョンを判別します。

MainActivityコード:

画像

)

画像

MainActivityでは、AlarmManagerUtilsにカプセル化されたコードが呼び出されて初期化され、ボタンがクリックされると、getUpAlarmManagerStartWorkメソッドが呼び出されてAlarmManagerが初めて完了します。

最後に、放送受信機が何をしたかを見てみましょう。

MyBroadcastReceiverクラス:

画像

1 am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE)

画像

低バージョンのsetRepeatingと同じ効果を実現するには、onReceiveメソッドにAlarmManagerを再度登録します。

さて、デモの中核はAlarmManagerUtilsクラスです。あなたがそれを理解すれば、あなたはそれを理解するでしょう。ゆっくり勉強する必要があります。

第四に、AlarmManagerの問題の要約

1:プロセスが強制終了されます。 AlarmManager 仕事をやめる

デモの実行中に、プロセスAlarmManagerをアクティブに強制終了し、実行を停止したことがわかりました。ログの印刷が停止しました。アプリケーションが開いているとき、またはアプリケーション内にある場合にのみ、AlarmManagerを再登録できます。サービスを再起動したとき、他に良い方法が見つかりませんでした。良い解決策があれば、メッセージを残してアドバイスを求めてください。

二: 電話の再起動 AlarmManager 仕事をやめる

実際、この問題は上記のプロセスの強制終了に似ています。この場合、電話機の再起動を監視するブロードキャストを登録し、ブロードキャストを受信したときにAlarmManagerを再登録できます。

3: 各メーカーの「ハートビートアラインメント」

Xiaomi、Huawei、およびその他の携帯電話メーカーには、「ハートビート調整」メカニズムがあります。たとえば、APPを開発する場合、CPUをウェイクアップして、バックグラウンド2秒でタスクを実行します。

前述のように、CPUのウェイクアップには電力が消費されます(2sは1回ウェイクアップしますが、製品マネージャーに問題があり、実装者が必要とするプログラムの方が問題が多いと言えます)。

主要メーカーは、アプリがCPUを頻繁にウェイクアップするため、ユーザーにとって非常に電力を消費することを検出しました。そのため、システムはアプリケーションを「ハートビートアライメント」に強制し、アプリがCPUをウェイクアップする頻度を減らします。たとえば、アプリの設定

2秒は1回実行されますが、実際には大手メーカーでは10秒です。

V.まとめ

わかりました、この記事は終わりました。上記の説明を読んだ後は、AlarmManagerについての理解が深まったと思います。使用時に乱用しないでください。ユーザーの力を考慮して、アプリを最適化してみてください。

================================================ == ======

まず、WakeLockの概要

Wakelockはロックメカニズムです。アプリケーションがこのロックを保持している限り、CPUはスリープ状態に入ることができず、常に動作しています。たとえば、携帯電話の画面がオフの場合でも、一部のアプリケーションは画面をウェイクアップしてユーザーにメッセージの入力を求めることができます。これがウェイクロックロックメカニズムです。携帯電話の画面は閉じていますが、これらのアプリケーションは引き続き実行されています。携帯電話の消費電力に関する問題のほとんどは、開発者がこのロックを正しく使用しておらず、「スタンバイキラー」になっていることです。

Androidフォンには2つのプロセッサがあります。1つはアプリケーションプロセッサ(AP)と呼ばれ、もう1つはベースバンドプロセッサ(BP)と呼ばれます。 APは、Linux + Androidシステムを実行するためのARMアーキテクチャプロセッサです。BPはリアルタイムオペレーティングシステム(RTOS)を実行するために使用され、通信プロトコルスタックはBPのRTOSで実行されます。非通話時間の場合、BPのエネルギー消費量は基本的に約5mAであり、APは非スリープ状態である必要があり、エネルギー消費量は少なくとも50mAであり、グラフィックス操作を実行するとさらに高くなります。また、LCDの消費電力は約100mA、WIFIも約100mAです。携帯電話がスタンバイ状態になると、AP、LCD、およびWIFIはすべてスリープ状態になります。このとき、Androidのアプリケーションのコードは実行を停止します。

アプリケーションでキーコードが正しく実行されるようにするために、AndroidはWake Lock APIを提供します。これにより、アプリケーションはAPがスリープ状態になるのを防ぐコードを使用できます。ただし、Androidデザイナーの意図を理解せず、Wake Lock APIを悪用すると、APがバックグラウンドでプログラムの通常の操作のためにスリープ状態になるのを防ぐために、長時間スタンバイバッテリーキラーになります。 。

では、Wake Lock APIの具体的な用途は何ですか?ハートビートパケットを要求から応答、切断の再接続、およびその他の主要なロジックの実行から保護するには、ウェイクロックが必要です。重要なロジックが正常に実行されたら、ウェイクロックをすぐに解放する必要があります。 2つのハートビート要求は5〜10分間隔であり、基本的に電力は消費されません。

第二に、WakeLockの使用

次のようにWakeLockインスタンスコードを取得します。

1

1 Intent intent = new Intent('WANG_LEI') 2 intent.putExtra('msg','Get up' ' 3 PendingIntent pi = PendingIntent.getBroadcast(this,0,intent,0) 4 5 AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE) 6 // Send a broadcast via the PendingIntent pi object every 5 seconds 7 am.setRepeating(AlarmManager.RTC_WAKEUP,System.currentTimeMillis(),5*1000,pi) 

 1 public class AlarmManagerUtils { 2 3 private static final long TIME_INTERVAL = 5 * 1000 / / time interval for the alarm to perform the task 4 private Context context 5 public static AlarmManager am 6 public static PendingIntent pendingIntent 7 // 8 private AlarmManagerUtils(Context aContext) { 9 this.context = aContext 10 } 11 12 //Hungry Chinese single case design mode 13 private static AlarmManagerUtils instance = null 14 15 public static AlarmManagerUtils getInstance(Context aContext) { 16 if (instance == null) { 17 synchronized (AlarmManagerUtils.class) { 18 if (instance == null) { 19 instance = new AlarmManagerUtils(aContext) 20 } 21 } 22 } 23 return instance 24 } 25 26 public void createGetUpAlarmManager() { 27 am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE) 28 Intent intent = new Intent('WANG_LEI') 29 intent.putExtra('msg', 'Hurry up') 30 pendingIntent = PendingIntent.getBroadcast(context, 0, intent, 0) / / send a broadcast every 5 seconds 31 } 32 33 @SuppressLint('NewApi') 34 public void getUpAlarmManagerStartWork() { 35 // version adaptation 36 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0 and above 37 am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, 38 System.currentTimeMillis(), pendingIntent) 39 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// 4.4 and above 40 am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), 41 pendingIntent) 42 } else { 43 am.setRepeating(AlarmManager.RTC_WAKEUP, 44 System.currentTimeMillis(), TIME_INTERVAL, pendingIntent) 45 } 46 } 47 48 @SuppressLint('NewApi') 49 public void getUpAlarmManagerWorkOnReceiver() { 50 / / high version repeatedly set the alarm to achieve the same effect of setRepeating in the low version 51 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {// 6.0 and above 52 am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, 53 System.currentTimeMillis() + TIME_INTERVAL, pendingIntent) 54 } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {// 4.4 and above 55 am.setExact(AlarmManager.RTC_WAKEUP, System.currentTimeMillis() 56 + TIME_INTERVAL, pendingIntent) 57 } 58 } 59 }
 1 public class MainActivity extends Activity { 2 3 private AlarmManagerUtils alarmManagerUtils 4 5 @Override 6 protected void onCreate(Bundle savedInstanceState) { 7 super.onCreate(savedInstanceState) 8 setContentView(R.layout.activity_main) 9 // 10 alarmManagerUtils = AlarmManagerUtils.getInstance(this) 11 alarmManagerUtils.createGetUpAlarmManager() 12 // 13 findViewById(R.id.am).setOnClickListener(new OnClickListener() { 14 15 @SuppressLint('NewApi') 16 @Override 17 public void onClick(View v) { 18 // 19 alarmManagerUtils.getUpAlarmManagerStartWork() 20 } 21 }) 22 } 23 }
 1 public class MyBroadcastReceiver extends BroadcastReceiver { 2 3 private static final String TAG = 'MyBroadcastReceiver' 4 5 @SuppressLint('NewApi') 6 @Override 7 public void onReceive(Context context, Intent intent) { 8 / / high version repeatedly set the alarm to achieve the same effect of setRepeating in the low version 9 AlarmManagerUtils.getInstance(context).getUpAlarmManagerWorkOnReceiver() 10 // 11 String extra = intent.getStringExtra('msg') 12 Log.i(TAG, 'extra = ' + extra) 13 } 14 }

NewWakeLock(int levelAndFlags、String tag)では、PowerManager.PARTIIAL_WAKE_LOCKはフラグビットです。フラグは、取得するWakeLockオブジェクトのタイプを制御するために使用されます。これは主に、CPUが動作しているときに画面をオンにする必要があり、キーボードライトをオンにする必要があるかどうかを制御します。次のように:

levelAndFlags CPUは稼働していますか? 画面は点灯していますか? キーボードのライトは点灯していますか?
PARTIAL_WAKE_LOCK はい しない しない
SCREEN_DIM_WAKE_LOCK はい 低輝度 しない
SCREEN_BRIGHT_WAKE_LOCK はい 高輝度 しない
FULL_WAKE_LOCK はい はい はい

特別な指示 :APIレベル17以降、FULL_WAKE_LOCKは非推奨になります。アプリケーションを使用する必要があります FLAG_KEEP_SCREEN_ON

WakeLockクラスを使用して、デバイスの動作状態を制御できます。このクラスのacquireを使用して、CPUを動作させ続けます。 CPUを機能させる必要がない場合は、releaseを呼び出してCPUを閉じます。

(1)、自動リリース

取得(長いタイムアウト)を呼び出す場合、ロックを解放するために手動でrelease()を呼び出す必要はありません。システムは、タイムアウト後に解放するのに役立ちます。

(2)、手動リリース

Acquisition()を呼び出す場合は、手動でrelease()を呼び出してロックを解除する必要があります。

最後に、WakeLockクラスを使用して、次のアクセス許可を追加することを忘れないでください。

1 

注:このクラスを使用するときは、次のことを確認する必要があります 取得する リリース ペアになっています。

三、 画面を点灯させてください

最良の方法はアクティビティで使用することです FLAG_KEEP_SCREEN_ON 国旗。

画像

1 public class MainActivity extends Activity { 2 @Override 3 protected void onCreate(Bundle savedInstanceState) { 4 super.onCreate(savedInstanceState) 5 setContentView(R.layout.activity_main) 6 getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) 7 } 8 }

画像

この方法の利点は、ウェイクロックとは異なり、いくつかの特定のアクセス許可が必要なことです。また、異なるアプリ間の切り替えを適切に管理でき、無駄なリソースのリリースについて心配する必要はありません。
別の方法は、レイアウトファイルで使用することです アンドロイド:keepScreenOn 属性:

画像

1 6 ... 7 

画像

Android:keepScreenOn = 'true'は、FLAG_KEEP_SCREEN_ONと同じ効果があります。コードを使用する利点は、必要な場所で画面をオフにできることです。

注意 :通常、FLAG_KEEP_SCREEN_ONのフラグを手動で削除する必要はありません。 windowManagerは、バックグラウンドに入り、フォアグラウンドに戻るプログラムの操作を管理します。常にオンになっているフラグを手動でクリアする必要がある場合は、 getWindow()。clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)

四、 WakefulBroadcastReceiver + IntentServiceインスタンス

IntentServiceの使用法については、以前のブログを参照してください。 AndroidIntentServiceの紹介とソースコード分析

WakefulBroadcastReceiverは、BroadcastReceiverの特殊なケースです。アプリのタイプPARTIAL_WAKE_LOCKのWakeLockを作成および管理します。 WakefulBroadcastReceiverは、作業をサービス(通常はIntentService)に引き渡し、ハンドオーバー中にデバイスがスリープ状態にならないようにします。 WakeLockがないと、タスクの実行が完了する前にデバイスが簡単に休止状態になります。その結果、アプリはいつ実行されるかわからなくなります。これはあなたが望んでいることではないと思います。

使用する startWakefulService() サービスを開始する方法、 startService() 対照的に、ウェイクアップロックは、サービスの開始と同時に有効になります。

バックグラウンドサービスのタスクが完了すると、呼び出されます WLWakefulReceiver.completeWakefulIntent() ウェイクロックを解除します。

WLWakefulReceiverクラスは次のとおりです。

画像

 1 public class WLWakefulReceiver extends WakefulBroadcastReceiver { 2 3 private static final String TAG = 'myTag' 4 5 @Override 6 public void onReceive(Context context, Intent intent) { 7 // 8 String extra = intent.getStringExtra('msg') 9 Log.i(TAG, 'onReceive:'+extra) 10 Intent serviceIntent = new Intent(context, MyIntentService.class) 11 serviceIntent.putExtra('msg', extra) 12 startWakefulService(context, serviceIntent) 13 } 14 }

画像

非常に簡単です。情報を印刷して電話するだけです。 startWakefulService サービスを開始する方法。

MyIntentServiceクラスは次のとおりです。

画像

 1 public class MyIntentService extends IntentService { 2 3 private static final String TAG = 'myTag' 4 5 public MyIntentService() { 6 super('MyIntentService') 7 } 8 9 @Override 10 protected void onHandleIntent(Intent intent) { 11 //executed in child thread 12 Log.i(TAG, 'onHandleIntent') 13 for (int i = 0 i <10 i++) { 14 try { 15 Thread.sleep(3000) 16 String extra = intent.getStringExtra('msg') 17 Log.i(TAG, 'onHandleIntent:'+extra) 18 } catch (InterruptedException e) { 19 e.printStackTrace() 20 } 21 } 22 // Call completeWakefulIntent to release the wakelock. 23 WLWakefulReceiver.completeWakefulIntent(intent) 24 } 25 }

画像

また、非常にシンプルで、情報を印刷する時間のかかる操作でもありますが、独自のビジネスロジックを実行した後、completeWakefulIntentを呼び出してウェイクアップロックを解除することを忘れないでください。

最後は、ブロードキャストレシーバーを起動し、アクセス許可とステートメントを結合することです。

画像

Intent intent = new Intent('WANG_LEI') intent.putExtra('msg', 'learn WAKE_LOCK...') sendBroadcast(intent) 

画像

さて、プログラムを書いた後、時間内に電源ボタンを押すと画面が閉じていることがわかりました。

この記事の終わりに、ウェイクロックロックは主にシステムのスリープに関連しています。つまり、私のプログラムはCPUにロックを追加し、システムはスリープしません。これの目的は、私たちのプログラムの実行に完全に協力することです。場合によっては、これを行わないと、いくつかの問題が発生します。たとえば、WeChatなどのインスタントメッセージングのハートビートパケットは、画面がオフになった後、ネットワークアクセスを停止します。そのため、WeChatではwake_lockロックが多く使用されています。上記の調査の後、WakeLockを正しく使用できることを願っています。バッテリーキラーにならないでください。