くらげになりたい。

くらげのようにふわふわ生きたい日曜プログラマなブログ。趣味の備忘録です。

Androidで常駐サービスを作ろう(Service/WakefulBroadcastReceiver/Doze/AlarmManager)

Androidで常駐起動するサービスを作ることがあったので、そのときの備忘録。
作るときのポイントを整理してみた。が、いろいろ考えることが多い。。。

ポイント

  1. WakefulBroadcastReceiverを使ってServiceを起動する
  2. スリープしてもWifiの接続を維持しておく
  3. AlarmManagerで定期的に起動する
  4. ネットワークを使うときは、電源の最適化をOFFにする

もろもろ

  1. Android6.0(API 23)からDozeが追加された
  2. DozeモードになるとCPUを使わなくなり、KILLされやすくなる
  3. Dozeモードになるとネットワークを使わなくなる

WakefulBroadcastReceiverを使って、Wakelockを取得する

  1. AndroidManifest.xmlパーミッションを追加
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
  1. 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>
  1. 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のホワイトリストに登録する)

  1. AndroidManifest.xmlパーミッションを追加
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS"/>
  1. PowerManagerを使ってホワイトリストに入っているか確認&リクエス
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>
$ adb shell dumpsys deviceidle whitelist | grep <Package Name>

参考にしたサイト様