通信前の事前チェック処理に関する考察

はじめに

<Retryしたい状況を考えてみる>

で圏外時の、通信の再トライに関して書いてみましたが、

それ以外でも必要なケースがあるかなと思い考察してみます

懸念点を洗い出してみると

圏外以外のケースだと

  • Battery Saver
  • Soft Doze/Deep Doze モード
    • 上記モード時に通信遮断
  • DataSaver
    • アプリがバックグラウンド時に通信遮断

この条件が適応された時、IOException が飛ぶわけですが、*1

通常の通信失敗と判別かつかない わけです。

本来なら それ専用のExceptionをG様が飛ばしてくれれば対処が可能なんですけどね。。

上記新しめOSのエコモード的な新機能の情報の洗い出し

Battery Saver

Android5から入った、標準のエコモード?

バッテリが15%以下になると、有効にするかどうか聞いてくる感じ

Nexus系とかだと

  • CPUクロックが低下する
  • 画面が非常に暗くなる
  • アカウントの同期機能等が無効になる
  • Activity起動時などにアニメーションが行われないようになる
  • バイブレーションが無効になる
  • ステータス・バーとナビゲーション・バーがオレンジになる
  • 通信も基本は遮断される*2
    • 基本電話待受とか出来るモード?になる

らしいんだけど、

其のタイミングで、現在の状態を一時保存するのが多分良かとおもう

PowerManager powerManager = (PowerManager)
    getActivity().getSystemService(Context.POWER_SERVICE);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
        && powerManager.isPowerSaveMode()) {
    //処理
}

通信遮断時の対処案的な考察

Dozeモードの対処も含め

Firebaseのrealtime保存みたいな仕組みが一番いいパターンになるんだろうなと

  1. Firebase JobDispatcher にジョブ登録
    1. GooglePlay開発者サービスの方にJob登録されるそう
    2. 端末の電源が完全に落ちないかぎりは大丈夫
  2. ローカルに通信予定情報を保存(Jsonとかで)
  3. サーバー通信ができた時点で、通信済みのチェックをいれるか消すかする

Data Saver

Android7から入った、「(3G/LTEの)バックグラウンド通信を制限する」*3の強化版

  • アプリをフロント表示しないと通信自体をしない
    • Chrome for Android の Data Saverとは別物*4
    • Homeボタン等で画面最小化した時点でNG

iOSチックな状態になる対応ですね。*5

Services等で、バックグラウンド通信が常時必須ならホワイトリストに入れるしかない

これウイジェットとかでもホワイトリスト対応は必要じゃないんだろうか?

一応ホワイトリストに入れる場合は

Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS, Uri.parse("package:" + getPackageName()));
startActivity(intent);
  • 判定は下記のコード。
    • ConnectivityManagerCompat::getRestrictBackgroundStatus がsuppot-libraryに追加されてはいますが、これ 25.X のバージョンから
//DataSaverが有効か否か
private boolean isDataSaver(Context context){
    ConnectivityManager connectivityManager = (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE);

    //現在モバイル通信で、かつバックグラウンドデータが制限されているか否か ◎
    boolean bgData = false;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1){
        bgData = connMgr.isActiveNetworkMetered();
    }
    else{
        bgData = connMgr.getBackgroundDataSetting();
    }
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && bgData) {//★
        //アプリ単位の DataSavar ON/OFFチェック
        int restrictBackgroundStatus = connectivityManager.getRestrictBackgroundStatus();
        switch (restrictBackgroundStatus) {
            case ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED:
                Log.v("TAG", "RESTRICT_BACKGROUND_STATUS_DISABLED");
                return false;
            case ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED:
                Log.v("TAG", "RESTRICT_BACKGROUND_STATUS_ENABLED");
                return true;
            case ConnectivityManager.RESTRICT_BACKGROUND_STATUS_WHITELISTED:
                Log.v("TAG", "RESTRICT_BACKGROUND_STATUS_WHITELISTED");
                return false;
        }
    }
    return false;
}

ネットのサンプルを見えていると★が入っていないコードが多くて、その状態だと正常に取得できない挙動。

でもまあ ◎の処理自体 Wifi通信だと意味がない判定ではあるんですけどね

DataSaverがONのままでのバックグラウンド判定

ActivityGroup での 判定が非推奨な状況だと 下記な判定は必要になるのかなと

基本ONにしてもらうっていうのは難しいと思うので

RuntimePermission のときのように設定を変更してもらうのではなく

<通信遮断時の対処案的な考察> で対処を逃げるしかないのですかね・・

DataSaver判定は3Gの時のみすべき(2017/7/6追記)

上記の判定を、 * 例えば起動時に入れると、毎回表示が出てしまう・・ * NW接続がない時 * Wifi運用の端末

正直、接続中で3G/LTEのときのみ表示すべき

public static boolean isNotWifiConnected(Context context) {
      ConnectivityManager connMgr = (ConnectivityManager)
                context.getSystemService(Context.CONNECTIVITY_SERVICE);
      NetworkInfo networkInfo = connMgr.getActiveNetworkInfo()

      if(networkInfo != null){
          return false;
      } 
      if(!networkInfo.isConnected()){
          return false;
      }
      if(networkInfo.getType() != ConnectivityManager.TYPE_WIFI){
          return false;
      }
      return true;
}

ただ下記の例みたいにBroadcastReciverまでは必要ないと思うけど

というより OS7より

  • innner BroadcastReciverは動かない
  • 別クラス、Manifest.xmlに記述が前提
  • アプリがフロントのときしか動かない*6

辺りの制限があるので、BroadcastReciver運用自体は、今後中々厳しいかも


Dozeモード

画面スクリーンOFFにしていても、バックグラウンド通信が走っていると電池の消耗が激しいため

  • モード中は
    • バックグラウンドの通信を止めましょう
  • モードから復帰時には
    • ペンディングになっていた SyncAdapter/JobScheduler/AlarmManager の処理はすべて実行

という仕様

正直動かしてみた感じ

  • モードに入る前に登録されて再生されていない予約JOBは再生
  • モード中は予約JOBが登録されていない?

というのが正確な挙動の気がする

一応、Intent経由で無効にはできますが

電池の最適化を無視しますか?

と表示されてイメージが悪くなるので、ユーザー同意はしない機能。詳細は下記ページが参考になる

浅いDoze(Soft Doze)

  • 6系だと放置30分後から
  • 7系だと放置5分後から

  • AlarmManager の Dozeで動く関数でも約10分に1回しか動かせなくなる*7

    • setAndAllowWhileIdle
    • setExactAndAllowWhileIdle

深いDeep(Deep Doze)

  • 7系だと30分後から
  • PCでいう Sleepモード*8

だからモードに入ったときの処理は完全に無理

公式に推奨されている対処法的には

  • Firebase JobDispatcherの利用を推奨
    • Jobの状態がちゃんと保存されているらしい
  • 規程時間に処理させたい場合には
    • 指定時間直前にFCM(high priority)で通知
    • 通常モードにアプリを復帰させる

DOZE無効にするだけなら

id:sakura_bird1 先生が既に調べているんですよね。*9

ただこの権限つけるとマーケットリリース時に弾かれると言う話がTwitterで言及されていたかと

JobScedulerの話

*1:でもメーカ独自のメンテナンスモードとかはG様の管理外なので通信できるケースもあるらしい

*2:Zenfone2のカスタムされたモードとか

*3:バックグラウンド通信を無効にするが正しい?

*4:アッチは圧縮通信?の話

*5:でもiOSの場合は CPU30%モードで通信はできるので挙動的に差異はある

*6:最小化やバックグラウンド時は無視

*7:9分間以上間隔を開けないと無視される

*8:プロセス的に生きているのは Google Play開発者サービス ぐらいみたいな話も。。

*9:何時も背中を追う感じになるのが感じがすごすぎですわ・・