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

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

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

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

ウィジェット

ウィジェットとはユーザインタフェイスの要素(GUI widget)であるか、もしくは、独立した比較的サイズの小さいソフトウェアアプリケーション(desktop widget)のことを指します。

Q&A

解決済

2回答

6874閲覧

[Android]AppWidgetProviderでAlarmManager#setRepeatingがリピートしない

Daiki-Kawanuma

総合スコア29

Android

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

ウィジェット

ウィジェットとはユーザインタフェイスの要素(GUI widget)であるか、もしくは、独立した比較的サイズの小さいソフトウェアアプリケーション(desktop widget)のことを指します。

2グッド

1クリップ

投稿2015/12/07 05:12

編集2015/12/09 15:42

自作ウィジェットを作っております。1秒おきにカウントダウンをしたいのでAppWidgetProvider内でAlarmManager#setRepeatingを呼び出し更新させています。テスト環境はNexus 5X Android 6.0です。以下コードになります。

AppWidgetProvider

public class MyWidget extends AppWidgetProvider { public static final String IMAGE_CLICKED = "ImageClicked"; public static final String COUNTDOWN_OPERATION = "CountdownOperation"; public static boolean DOES_RUN_COUNTDOWN; public static final String UPDATE_TIMETABLE_OPERATION = "UpdateTimeTableOperation"; private final int INTERVAL_COUNTDOWN = 1000; private final int INTERVAL_UPDATE_TIMETABLE = 1000 * 60 * 10; @Override public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { DOES_RUN_COUNTDOWN = false; RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.bustimetable_widget); ComponentName watchWidget = new ComponentName(context, BusTimetableWidget.class); remoteViews.setOnClickPendingIntent( R.id.imageView_bus, getPendingIntentBroadcast(context, IMAGE_CLICKED) ); appWidgetManager.updateAppWidget(watchWidget, remoteViews); } @Override public void onReceive(Context context, Intent intent) { // TODO Auto-generated method stub AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); RemoteViews remoteViews = new RemoteViews(context.getPackageName(), R.layout.bustimetable_widget); ComponentName watchWidget = new ComponentName(context, BusTimetableWidget.class); if(intent.getAction().equals(IMAGE_CLICKED)){ if(DOES_RUN_COUNTDOWN){ DOES_RUN_COUNTDOWN = false; Toast.makeText(context, "IMAGE_CLICKED: " + DOES_RUN_COUNTDOWN, Toast.LENGTH_LONG).show(); cancelCountdownAlarm(context); } else { DOES_RUN_COUNTDOWN = true; Toast.makeText(context, "IMAGE_CLICKED: " + DOES_RUN_COUNTDOWN, Toast.LENGTH_LONG).show(); setCountdownAlarm(context); } } else if(intent.getAction().equals(UPDATE_TIMETABLE_OPERATION)){ Toast.makeText(context, "UPDATE_TIMETABLE_OPERATION: " + Calendar.getInstance().getTime(), Toast.LENGTH_LONG).show(); } else if(intent.getAction().equals(COUNTDOWN_OPERATION)){ remoteViews.setTextViewText(R.id.textView_fore_count, "n: " + count++); // Toast.makeText(context, "COUNTDOWN_OPERATION: " + Calendar.getInstance().getTime(), Toast.LENGTH_SHORT).show(); } appWidgetManager.updateAppWidget(watchWidget, remoteViews); super.onReceive(context, intent); } private void setCountdownAlarm(Context context) { PendingIntent operation = getPendingIntentService(context, COUNTDOWN_OPERATION); AlarmManager alarmManager = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE); long firstTime = System.currentTimeMillis() + 1000 * 3; alarmManager.setRepeating(AlarmManager.RTC, firstTime, INTERVAL_COUNTDOWN, operation); } private void cancelCountdownAlarm(Context context){ AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); PendingIntent operation = getPendingIntentService(context, COUNTDOWN_OPERATION); alarmManager.cancel(operation); } protected PendingIntent getPendingIntentService(Context context, String action) { Intent intent = new Intent(context, getClass()); intent.setAction(action); return PendingIntent.getService(context, 0, intent, 0); } }

widget_info.xml

<?xml version="1.0" encoding="utf-8"?> <appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" android:initialKeyguardLayout="@layout/bustimetable_widget" android:initialLayout="@layout/bustimetable_widget" android:minHeight="40dp" android:minWidth="40dp" android:previewImage="@drawable/bus_image" android:resizeMode="horizontal|vertical" android:updatePeriodMillis="0" android:widgetCategory="home_screen"/>

Manifest

<intent-filter> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="CountdownOperation" /> <action android:name="UpdateTimeTableOperation" /> <action android:name="ImageClicked"/> </intent-filter>

ImageViewのクリックに応じてカウントダウンをスタート、キャンセルする形を取っています。
現状、ImageViewのクリックには即時反応するのですが、カウントダウンは更新が呼ばれていません。おそらくAlarmManagerの使い方が間違っているのではないかと思うのですが、ご指南よろしくお願いします。

KiyoshiMotoki👍を押しています

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

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

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

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

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

guest

回答2

0

テスト環境はNexus 5X Android 6.0です。

ということは、API Levelは19以上でしょうか?

公式のリファレンスによると、
[http://developer.android.com/intl/ja/reference/android/app/AlarmManager.html#setRepeating(int, long, long, android.app.PendingIntent)](http://developer.android.com/intl/ja/reference/android/app/AlarmManager.html#setRepeating(int, long, long, android.app.PendingIntent))

API Level 19以降は、全ての繰り返し系アラームは不正確だそうです。

Note: as of API 19, all repeating alarms are inexact.

代替策として
「ワンタイムのアラームを使用し、アラームが起動した時に、次のアラームをセットするように」
と書いてあります(と、思います(^^;))ので、この方法を試してみてはいかがでしょうか?

If your application wants to allow the delivery times to drift in order to guarantee that at least a certain time interval always elapses between alarms, then the approach to take is to use one-time alarms, scheduling the next one yourself when handling each alarm delivery.

投稿2015/12/09 14:56

KiyoshiMotoki

総合スコア4791

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

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

Daiki-Kawanuma

2015/12/09 16:00

ご回答頂きありがとうございます! ご指摘のように、AlarmManager#setに直し(引数はSystem.currentTimeMillis()をそのまま突っ込みました)onReceive内で再度setCountdownAlarmを呼び出したところ、更新が比較的短い単位で継続されました! ただ、それでも1秒単位とは程遠く、数秒おきという感じです。 不正確のレベルがms単位だと思ってたのですが、こんなにも大きなレベルで不正確なものなんでしょうか・・・?
KiyoshiMotoki

2015/12/09 16:58 編集

Daiki-Kawanuma様、返信ありがとうございます。 > ただ、それでも1秒単位とは程遠く、数秒おきという感じです。 改めて調べてみたところ、以下のリンクを見つけました。 https://akira-watson.com/android/setexact-setwindow.html set よりは setExact の方が、比較的マシなようですね。 > 不正確のレベルがms単位だと思ってたのですが、こんなにも大きなレベルで不正確なものなんでしょうか・・・? 私もよく分かりません。 デバイスの性能やCPUの負荷などによっても変わってくるかと思いますし。 setExact で誤差を数十ms程度まで抑えることができれば、例えば 1. PendingIntent#putExtra に次回の起動予測時刻 (System.currentTimeMillis() + INTERVAL_COUNTDOWN)をセットしておき、 2. 次回起動時にその値と現在のSystem.currentTimeMillis() の誤差を比較して、 3. 次々回の起動時刻を調整する などとすればさらに精度を上げることができるかもしれませんが、 「精確に1秒ごと」 は、なかなか難しいかと思います。
Daiki-Kawanuma

2015/12/12 20:11

KiyoshiMotoki 様 詳細な情報をありがとうございます。AlarmManager#setExactや#setWindowなど色々試したのですが、結局平均して5秒間隔程度までしか間隔を縮められませんでした。最終的にjava.util.Timerとandroid.app.handlerを併用して1秒周期の更新を実現できました! 現時点での自分の見解になりますが、AlarmMangerは1min単位など比較的長い間隔の更新向けなのではないかという感じです。 KiyoshiMotoki様にはAlarmManagerの詳細な説明を頂き本当に助かりました。 ありがとうございました!
KiyoshiMotoki

2015/12/13 04:41

Daiki-Kawanuma様、ご報告ありがとうございます。 > AlarmMangerは1min単位など比較的長い間隔の更新向けなのではないかという感じです。 確かに、これはあり得るかもしれませんね。 改めてAlarmManagerのAPIリファレンスを見てみると、 "Class Overview"の項に以下のような記載を見つけることができましたので。 > For normal timing operations (ticks, timeouts, etc) it is easier and much more efficient to use Handler. http://developer.android.com/intl/ja/reference/android/app/AlarmManager.html こちらこそ、勉強させていただきました。
guest

0

ベストアンサー

getPendingIntentService()内の下記のコードですが、Intenを送る先が
AppWidgetProviderなので、PendingIntent.getBroadcastに返ると動きそうな気がします。

java

1return PendingIntent.getService(context, 0, intent, 0);

投稿2015/12/07 06:53

編集2015/12/07 06:54
退会済みユーザー

退会済みユーザー

総合スコア0

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

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

Daiki-Kawanuma

2015/12/07 12:54

ご回答頂きありがとうございます。 PendingIntent.getBroadcastに直したところ更新されました! しかしながら、1000[ms]で更新されて欲しいところが数分単位で更新されている感じです。引数などが間違っていますでしょうか・・・?;
退会済みユーザー

退会済みユーザー

2015/12/08 01:08

AlarmManager#setRepeatingの実装方法は間違ってなさそうに思いますが、onReceive内にログを実装しやはり数分単位でログが表示されるようであれば、setRepeatingの実装になにかミスがあると思います。ちゃんとログが1000ms置きに表示されるようであれば、ウィジェットの更新方法に問題があると思います。
退会済みユーザー

退会済みユーザー

2015/12/08 13:43

setRepeatingですが、コピペしテストしたところ、毎秒broadcastが送られてる事を確認しましたのでそちらは、問題ないみたいです。
Daiki-Kawanuma

2015/12/08 16:50

わざわざテスト頂きありがとうございます! Log.d()でログを出してみたのですが、毎秒送られて来ない状態です。再描画のタイミングではログが出ます。 端末やOSバージョン固有の問題な気もしてきました・・・ またlogcatに、 W/System﹕ ClassLoader referenced unknown path: /data/app/***package name ***-1/lib/arm64 というログがあり、関係しているのかもしれないと思っています。検索したところヒットするのですが、これについては何かお心あたりはありますか?
退会済みユーザー

退会済みユーザー

2015/12/09 01:02

logと端末依存に関しては、わかりませんでした。ただ、AlarmManagerが端末によって機能しないというのは考えにくいです。 一つ気になるのですが、 上記のコードを見る限りウィジェットをタップした際の処理が実装されておりませんが、それだとsetCountdownAlarmが呼ばれないと思うのですが、別のクラスで実装しているのでしょうか。
Daiki-Kawanuma

2015/12/09 15:55

タップのリスナー登録とwidget_info.xmlを追記致しました。 KiyoshiMotoki様のご回答より、AlarmManagerのAPI Level依存の仕様があるそうです; 実現したいことは1秒ごとカウントダウンしたいだけなのですが、他に実現手段に心当たりはありますでしょうか?
退会済みユーザー

退会済みユーザー

2015/12/10 01:14

ご返信ありがとうございます。 結局AlarmManagerAPIレベルでの不具合があったのですね。 単純にストップウォッチみたいな機能でいいならJavaのTimerを使うのはどうでしょう。 Timer#schedule(TimerTask task, Date firstTime, long period) 参考サイト https://docs.oracle.com/javase/jp/6/api/java/util/Timer.html
KiyoshiMotoki

2015/12/10 02:37

morisakisan様、横から失礼します。 > 結局AlarmManagerAPIレベルでの不具合があったのですね。 不具合ではなく、仕様の変更ですね。 公式のリファレンスで 「API Level 19 以降では不正確です」 と、明言していますので。
退会済みユーザー

退会済みユーザー

2015/12/11 04:19

KiyoshiMotoki様、ご指摘ありがとうございました。 不具合は不適切でした。気をつけます。
Daiki-Kawanuma

2015/12/12 20:18

morisakisan 様 >>単純にストップウォッチみたいな機能でいいならJavaのTimerを使うのはどうでしょう。 ドンピシャでした!AppWidgetProviderとTimerの併用はあまり例を見つけられず、AlarmManagerを使うのが定石と思っておりましたが、カウントダウンはTimerで実現できました。 今回はAlarmManagerの仕様に大分振り回されて大変でしたが、Timerという身近な機能で解決できたということで勉強になりました。 morisakisan様には最初からお世話になったことに加え、実際の解決法を頂きましたのでベストアンサーにさせて頂きます。ありがとうございました!
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問