Androidで常駐起動するサービスを作ることがあったので、そのときの備忘録。
作るときのポイントを整理してみた。が、いろいろ考えることが多い。。。
ポイント
- WakefulBroadcastReceiverを使ってServiceを起動する
- スリープしてもWifiの接続を維持しておく
- AlarmManagerで定期的に起動する
- ネットワークを使うときは、電源の最適化をOFFにする
もろもろ
- Android6.0(API 23)からDozeが追加された
- DozeモードになるとCPUを使わなくなり、KILLされやすくなる
- Dozeモードになるとネットワークを使わなくなる
WakefulBroadcastReceiverを使って、Wakelockを取得する
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
- AndroidManifest.xmlにRecieverを追加
<receiver android:name=".StartupReceiver"> <intent-filter> <action android:name="android.intent.action.BOOT_COMPLETED"/> <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/> <category android:name="android.intent.category.DEFAULT"/> </intent-filter> </receiver>
- WakefulBroadcastReceiverを継承したRecieverを用意して、そこからサービスを起動
public class StartupReceiver extends WakefulBroadcastReceiver { private static final String TAG = StartupReceiver.class.getSimpleName(); @Override public void onReceive(Context context, Intent intent) { Intent service = new Intent(context, MyService.class); startWakefulService(context, service); } }
スリープ時にWifiの接続を維持する
try { ContentResolver contentResolver = getContentResolver(); // Settings.System.WIFI_SLEEP_POLICY は API level 17 から deprecated なので // Settings.Global.WIFI_SLEEP_POLICY を使用する int policy = Settings.System.getInt(contentResolver, Settings.Global.WIFI_SLEEP_POLICY); if (policy != Settings.Global.WIFI_SLEEP_POLICY_NEVER) { Settings.System.putInt(contentResolver, Settings.Global.WIFI_SLEEP_POLICY, Settings.Global.WIFI_SLEEP_POLICY_NEVER); } } catch (Settings.SettingNotFoundException e) { Log.i(TAG, e.getLocalizedMessage(), e); }
ネットワークが有効化を確認する
ConnectivityManager
を使うと、通信ができているかどうか確認できる。
public boolean isNetworkAvailable() { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); boolean isAvailable = activeNetwork != null && activeNetwork.isConnectedOrConnecting(); if (isAvailable) { Log.i(TAG, String.format("Network is Available: %s %s", activeNetwork.getTypeName(), activeNetwork.getSubtypeName() )); } else { String info = ""; if (activeNetwork != null) { info = String.format(": %s %s %s", activeNetwork.getState().name(), activeNetwork.getTypeName(), activeNetwork.getSubtypeName()); } Log.w(TAG, "Network is Not Working" + info); } return isAvailable; }
AlarmMangerで定期的に起動しておく
private void setupAlarm() { Context context = ...; // AlarmManagerを取得する AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); // Alarmのタイプ int type = AlarmManager.RTC_WAKEUP; // Alarmの開始日時 long triggerAtMillis = Calendar.getInstance().getTimeInMillis(); // Alarmの繰り返し間隔 long intervalMillis = 60 * 1000; // 1min // Alarmの発火時に発行するIntent int requestCode = 0; Intent intent = new Intent(context, StartupReceiver.class); int flags = PendingIntent.FLAG_UPDATE_CURRENT; PendingIntent operation = PendingIntent.getBroadcast(context, requestCode, intent, flags); // AlarmManagerに登録する alarmManager.setRepeating(type, triggerAtMillis, intervalMillis, operation); }
電源の最適化をOFFにする(Dozeのホワイトリストに登録する)
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE); String packageName = getPackageName(); if (pm.isIgnoringBatteryOptimizations(packageName)) return; Intent intent = new Intent(); intent.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); intent.setData(Uri.parse("package:" + packageName)); startActivity(intent, REQUEST_BATTERY); }
Adbでの確認方法
- WakeLockを取得しているか確認する
$ adb shell dumpsys power | grep "Wake Locks:" -A 10
- AlarmManagerが設定できているかの確認
$ adb shell dumpsys alarm | grep <Package Name>
- Dozeのホワイトリストに入っているかの確認
$ adb shell dumpsys deviceidle whitelist | grep <Package Name>
参考にしたサイト様
- Firebase Crash Reporting | Firebase
- ウェイクロック: Android* アプリケーションでスリープしない問題の検出 | iSUS
- skimemo - 日記/2013-08-18/WakeLockじゃなくてAlarmManagerを使って定期的な処理を継続する - skimemo
- 液晶パネルのWakeLockを取得し、Sleep状態からWake状態へ遷移する | TechBooster
- スリープ状態を解除する(コードから解除する) - 寒川のアプリ開発者ブログ
- Android M/NのDozeによる制限とバックグラウンドタスク実行に関するまとめ - Qiita
- networking - Android networkinfo always returns true even if internet is not available - Stack Overflow
- android - networkInfo.isConnected() method return true even there is no internet? - Stack Overflow
- Androidで端末をスリープにするとネットワークに接続できなくなった問題 - Qiita
- チラシの裏的備忘録: Androidで設定を参照, 操作するあれこれ -Wi-fiの小ネタ編-
- android - can't check WIFI_SLEEP_POLICY on some devices - Stack Overflow
- サービス | Android Developers
- Android開発 | Androidで落ちない常駐サービスを作る方法 – ムッシューのIT備忘録
- WakeLock の状態を確認する - Qiita
- Doze と App Standby 用に最適化する | Android Developers
- アプリをAndroid 6.0 (APIレベル23) に対応するための主なポイント – FeeeeeLog
- https://developer.zebra.com/community/android/android-forums/android-blogs/blog/2017/05/04/keeping-your-application-running-when-the-device-wants-to-sleep
- Android 6.0 Marshmallow(API level 23) で導入されたDozeでダイアログから電池の最適化の無視を許可したのに設定画面の「最適化してないアプリ」一覧には反映されない問題 - Androidはワンツーパンチ 三歩進んで二歩下がる