質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.50%
Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

Q&A

2回答

8545閲覧

Android 画面を切るとTimerの間隔が遅くなる問題

teresa

総合スコア7

Android

Androidは、Google社が開発したスマートフォンやタブレットなど携帯端末向けのプラットフォームです。 カーネル・ミドルウェア・ユーザーインターフェイス・ウェブブラウザ・電話帳などのアプリケーションやソフトウェアをひとつにまとめて構成。 カーネル・ライブラリ・ランタイムはほとんどがC言語/C++、アプリケーションなどはJavaSEのサブセットとAndroid環境で書かれています。

1グッド

0クリップ

投稿2016/02/28 04:30

実現したいこと
AndroidのService内で15秒毎にサーバにリクエストを投げる

Java

1package net.aaa.sample; 2 3import android.app.Service; 4import android.content.Context; 5import android.content.Intent; 6import android.os.Handler; 7import android.os.IBinder; 8import android.util.Log; 9 10import net.aaa.sample.MyLoader; 11 12import org.json.JSONObject; 13 14import java.util.ArrayList; 15import java.util.Date; 16import java.util.List; 17import java.util.Timer; 18import java.util.TimerTask; 19 20/** 21 * Reference: 22 * http://blog.oukasoft.com/%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%A0/%E3%80%90android%E3%80%91%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%EF%BC%88service%EF%BC%89%E3%82%92%E4%BD%BF%E3%81%A3%E3%81%A6%E3%83%90%E3%83%83%E3%82%AF%E3%82%B0%E3%83%A9%E3%82%A6%E3%83%B3%E3%83%89/ 23 */ 24public class MyService extends Service { 25 26 private static boolean running = false; 27 28 private Timer mTimer = null; 29 Handler mHandler = new Handler(); 30 private Context context; 31 32 public MyService() { 33 } 34 35 @Override 36 public void onCreate() { 37 Log.i("MyService", "onCreate"); 38 context = this.getApplicationContext(); 39 } 40 @Override 41 public int onStartCommand(Intent intent, int flags, int startId) { 42 Log.i("MyService", "onStartCommand"); 43 44 if(running){ 45 return START_STICKY; 46 } 47 48 mTimer = new Timer(true); 49 mTimer.schedule( new TimerTask(){ 50 @Override 51 public void run(){ 52 mHandler.post( new Runnable(){ 53 public void run(){ 54 Log.d( "MyService" , "Timer run" ); 55 bgMain(); 56 } 57 }); 58 } 59 }, 1000, 1000*15); 60 61 running = true; 62 63 return START_STICKY; 64 } 65 66 @Override 67 public IBinder onBind(Intent intent) { 68 Log.i("MyService", "onBind"); 69 return null; 70 } 71 72 // 15秒に一度この関数が呼ばれserverにアクセスします 73 private void bgMain() { 74 // get data from server 75 MyLoader asyncJsonLoader = new MyLoader(new MyLoader.AsyncCallback() { 76 // 実行前 77 public void preExecute() { 78 } 79 80 // 実行後 81 public void postExecute(JSONObject result) { 82 // do something 83 //... 84 } 85 86 // 実行中 87 public void progressUpdate(int progress) { 88 } 89 90 // キャンセル 91 public void cancel() { 92 } 93 }); 94 // 処理を実行 95 asyncJsonLoader.executeWithDto(context); 96 } 97}

問題
ある条件下で、指定したはずの15秒毎に実行されず、数分〜数時間毎に実行されてしまいます。
画面を切るとこの問題が起きはじめ、3分以内に30秒毎くらいの間隔に落ちます。時間が立つとさらにこの遅れは拡大してゆき、最終的に1時間以上に広がることもあります。

再現条件
少なくともAndroid 6.0.1(実機)とAndroid 4.1.2(実機)で問題が起こることを確認しています。
・Androidの電源ボタンを一度押し、画面をOffにすると問題が再現します
・ただし画面がOffであっても充電中の場合は問題は起きませんでした(このためDebugが非常に困難です。Timerの中でサーバにアクセスしているので、そのサーバのaccessログを見ることで問題を確認しました)

質問
いかなる状況であってもTimerを必ず15秒毎に実行したいのですが、どのように書き換えればよいでしょうか?

shunsukekato👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

スリープ時には電力消費を抑えるため、様々なことがおきます。
内容は機種によって変わります。

例:
タイマーが遅れる
それ以前に呼ばれない
通信が遅い
それ以前に通信が途切れる
各種センサから取れる値の間隔が広がる
それ以前にセンサ類が止まる

どうしても15秒定期的に実行するのでしたら、

  • AlarmManagerを使ってスケジューリング
  • WakeLockを取得

を行ってください。これで無理なら無理な端末かと……

投稿2016/02/28 10:55

liply

総合スコア150

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teresa

2016/02/28 16:16

ありがとうございます。 まずはAlarmManagerについて試してみました。 その結果、手元にあったAndroid 4.1.2の方は問題が解決しました。 しかし6.0.1の方は解決したかのように見えましたが、1時間ほど放置していると今度は完全にサーバーへのアクセスが途絶えてしまいましたので、完全に眠ってしまうという挙動を見せるようになりました。その後電源ボタンを押してモニターをOnにすると、その途端に処理は再開され、サーバーにもアクセスがやってきました。 一部の端末特有の問題なのか、それともbzcatさんがご指摘されているようにAndroid6以降のDozeに起因する問題なのかは特定できていません。 一旦、AlarmManagerを使った実装でリリースし、ユーザの反応を観察してみます。 一旦ありがとうございました。
liply

2016/02/29 09:12

WakeLockをとる処理はいれましたか?
guest

0

Android 6.0にてDoze modeという省電力のための機能が追加されました。
このモードに入った事によりTimerが意図した間隔で呼び出されないのではないでしょうか?
(Android 4.1.2の方での原因は分かりませんが・・・)

参考:
Optimizing for Doze and App Standby

Doze modeを回避する手段は、

  • アプリをwhitelistに追加する
  • AlarmManager#setAndAllowWhileIdleなどのDoze modeに影響を受けないメソッドを使う

などがあります。

ホワイトリストに追加する場合は
adbコマンドで頭に + をつけてパッケージ名を指定して登録できます

$ adb shell dumpsys deviceidle whitelist +パッケージ名

AlarmManager#setAndAllowWhileIdleを使う場合の例は以下の様になります

Java

1 2public void registMyServiceTimer() { 3 Context context = getApplicationContext(); 4 Intent intent = new Intent(context, MyService.class); 5 intent.setPackage(net.aaa.sample.MyService"); 6 final PendingIntent pendingIntent = PendingIntent.getService(context, 0, intent, 0); 7 AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 8 final long nextExcuteTime = System.currentTimeMillis() + 15000; 9 am.setAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, nextExcuteTime, pendingIntent); 10}

投稿2016/02/28 07:22

bzcat

総合スコア37

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

teresa

2016/02/28 16:25

ありがとうございます。 liplyさんの回答でも触れましたが、AlarmManagerによって一部デバイスまたは一部OSを除き問題を解決できました。 解決しないケースは、手持ちの実機ではNexus5 (Android 6.0.1)で教えていただいたDozeとやらを疑ってみました。 そこで、「設定」→「電池」→「電池の最適化」から、アプリをDozeの対象から外すようにしました。 ところが、それでもなお、1時間近く放置するとアプリが深い眠りに入ってしまったのか、サーバーへのアクセスは途絶えてしまいました。その後画面をONにするとその途端に処理は再開され、サーバーへのアクセスも再開されました。 このことからDozeが原因ではなく、機種依存の原因だが、それがたまたまAndoird6以降のデバイスで起きたためにDozeが原因であるかのように見える・・・と結論付けたいのですが、いかがでしょうか。 一旦、AlarmManagerを使った実装でリリースしましたので、ユーザの反応を見たいと思います。 進展があればコメントいたします。 一旦ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問