いつも大変参考にさせていただいております! ゆぅかです。
早速で恐縮ですが、BroadcastReceiver 周りの、考え方についてわからない点が出てきました。
どなたかご助言いただけましたらとても助かります。
今回やりたい事は、ある Service の任意、特定の状態、また定期的なタイミングでの実行です。
現状、以下のような感じで実装をしています。
・処理の定期的な実行には、AlarmManager を利用し、処理の実行後に次のタイマーをセットします。
・また、電源ON(BOOT_COMPLETED)で実行し、処理の実行後に次のタイマーをセットします。
・また、スクリーンON(SCREEN_ON)で実行し、処理の実行後に次のタイマーをセットします。
・また、ユーザーの任意の操作によって実行し、処理の実行後に次のタイマーをセットします。
・上記の全ての処理は、BroadcastReceiver が各種のブロードキャストを受け取って startService し、
実際の処理は Service の onStart の中で行っています。
処理実行後の次のタイマーのセットも、onStart の処理の最後で行っています。
ほとんどの処理は想定どおりうまくいっていますが、SCREEN_ON のブロードキャスト受信関連の
処理・設定に問題があることに気付きました。
SCREEN_ON のブロードキャストは、プログラムの中で Context.registerReceiver しないと
上手く行かないとのことで、現状は BroadcastReceiver の onReceive 内で登録しています。
それは、registerReceiver での登録は端末の電源OFFや再起動でクリアされると認識して
いるからで、伴って BOOT_COMPLETED の際に(再)登録したいと思っているからです。
ところが、onReceive 内で毎回 SCREEN_ON の IntentFilter を registerReceiver していると、
その回数分フィルタが登録がされてしまいます。
今回は、定期的な実行や、任意タイミングの実行など、色々なタイミングで BroadcastReceiver
の onReceive が走るため、時間がたつにつれて、数多く登録されてしまって、困っています。
対策としてまず考えたのは、ブロードキャストが ACTION_BOOT_COMPLETED のときだけ、
SCREEN_ON の IntentFilter を registerReceiver することですが、これだとユーザーが任意
に処理を実行させた時にフィルタを登録することが出来ません。
そこで、ユーザーが任意に処理を実行させた時にもフィルタを登録しようと考えたのですが、
任意の実行は何度でも出来るため、任意実行させるたびに、余分なフィルタが登録されてしま
うという問題が残ってしまいます。
既に登録されている IntentFilter を取得、判定できれば良いのですが、私が調べた限りでは、
そういう方法は無かったように思っています。
ではIntentFilter を registerReceiver を登録する前に、一様に unregisterReceiver してやれ!
と思ったのですが、なぜかクリアできません。処理は onReceive 内で行っていますが、
おそらく IntentFilter の設定は同じだけど、実体(インスタンス)が異なる為だと推測しています。
現状、以上のような感じなのですが、おそらくどこかで考え方がズレているものと思っています。
どこがズレているか程度で結構ですので、どなたか変な部分があればご指摘いただけますと
大変助かります。
どうぞよろしくお願いいたします。
-- ゆぅか。
こんにちは。yagiです。
AndroidManifest.xmlで登録したBroadcastReceiverはonReceive毎に生成され、実行が終わると破棄されます。
動的にBroadcastReceiverを登録する場合はService等の中で行うといいと思います。
おはようございます。ありがとうございます!
気になるポイントをありがとうございます。
アラームのセットのように、同じ内容だったらリセットされると良いのですが、
IntentFilter の登録はそうでは無いように感じています。
ただ、もしかすると、コードの書き方が悪いのかも知れません。
現状、以下のようなコードになっています。
-------
public class TepcoServiceRunner extends BroadcastReceiver
{
final static private IntentFilter mIntentFilterForScreenOn = new
IntentFilter(Intent.ACTION_SCREEN_ON);
final static private IntentFilter mIntentFilterForScreenOff = new
IntentFilter(Intent.ACTION_SCREEN_OFF);
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
context = context.getApplicationContext();
Intent service = new Intent(context, TepcoService.class);
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
service.setAction(TepcoService.ACTION_CANCEL);
} else {
service.setAction(TepcoService.ACTION_CHECK_AND_NOTIFY);
}
context.startService(service);
//★ 次の処理が何度も実行されると、その回数分フィルタが登録されるようで、
//★ 一度の画面 ON で、onReceive が何度も呼ばれてしまいます。。。
context.registerReceiver(this, mIntentFilterForScreenOn);
context.registerReceiver(this, mIntentFilterForScreenOff);
}
}
-------
よろしくお願いいたします。
-- ゆぅか
2011年5月9日8:31 大垣憲俊 <noritos...@gmail.com>:
> --
> このメールは Google グループのグループ「日本Androidの会」の登録者に送られています。
> このグループに投稿するには、android-g...@googlegroups.com にメールを送信してください。
> このグループから退会するには、android-group-j...@googlegroups.com にメールを送信してください。
> 詳細については、http://groups.google.com/group/android-group-japan?hl=ja からこのグループにアクセスしてください。
>
>
ご返信をありがとうございました!
> AndroidManifest.xmlで登録したBroadcastReceiverはonReceive毎に生成され、
> 実行が終わると破棄されます。
はい。そのようですが、SCREEN_ON のブロードキャストの受信は、
プログラム内で registerReceiver する必要があると認識しています。
もしかして、考え違いでしょうか?
> 動的にBroadcastReceiverを登録する場合はService等の中で行うといいと思います。
registerReceiver の処理の他の場所への移設も検討したのですが、
あまり上手く行く解決策を思いつくことが出来ませんでした。
今のところ、以下のように考えています。
・現状のフローにおいては、Service の onStart でフィルタを登録するのは、
本質的に同じことのように思っています。
・Service の onCreate でのフィルタ登録は、「お、良いかも?」と思ったのですが、
Service が kill された場合、onCreate が再び走る為、やはり多重の登録になりそうに思っています。
どこか考えのおかしな部分があればご指摘いただけましたら嬉しいです。
よろしくお願いいたします!
-- ゆぅか
2011年5月9日8:43 sys1yagi <sylc...@gmail.com>:
> こんにちは。yagiです。
>
> AndroidManifest.xmlで登録したBroadcastReceiverはonReceive毎に生成され、実行が終わると破棄されます。
>
> 動的にBroadcastReceiverを登録する場合はService等の中で行うといいと思います。
>
こんにちは。yagiです。
Serviceでやる場合ならonCreateに対応するonDestroyがあるのでそこでunregisterしたらいいと思いますよ。
ありがとうございます。
そうですね。onDestroy がありましたね!
これまで、onDestroy の確実な実行は期待できないという情報もあり、
検討の対象に入れていなかったのですが、
そもそも Service が onDestroy せずに落ちることは稀(だと思っています)ので、
今回の想定の要件だと、実用範囲かも知れませんです。
ありがとうございました!
なお、私的に今回不安に思っているのは、そもそもの私の実装方針や実装そのものが、
フレームワークの想定から大きく外れているのではないか?という危惧だったりしますので、
もし最初の質問に変なところございましたら、引き続き、ご指摘やアドバイスなど
いただければ嬉しいです。
よろしくお願いいたします。
-- ゆぅか
2011年5月9日11:17 sys1yagi <sylc...@gmail.com>:
> こんにちは。yagiです。
>
> Serviceでやる場合ならonCreateに対応するonDestroyがあるのでそこでunregisterしたらいいと思いますよ。
上から。
・onCreateでregisterReceiverを二回やってますが一回(一個のIntentFilter)でもいけます。
IntentFilter#addAction等で複数のActionやCategory等に対応できます。
・AndroidManifest.xmlにMyReceiverの定義は必要ありません。
BroadcastReceiverの静的、動的設定の違いは
■静的
・AndroidManifest.xmlに定義する事で利用出来る。
・受信出来ないActionがある。(ACTION_BATTERY_CHANGEDとか)
・onReceive時にインスタンスが生成され、実行後に破棄される。つまり関連するメンバ変数、staticな変数等は毎回クリアされる。
■動的
・Activity,Serviceの中でContext#registerReceiverして登録する事で利用出来る。
・多分全部のActionを受信できる
・Context#registerReceiverかそれ以前にインスタンスが生成され、Context#registerReceiverしたActivityやServiceが生きている内はインスタンスが存在する。
大体こんな感じです。
----------------------------------------
name: yagi
blog: http://visible-true.blogspot.com/
android: https://market.android.com/developer?pub=yagi
おはようございます。遅い時間までありがとうございます。
サンプルソースを拝見させて頂きました。
ほぼほぼ同じ実装となっております。
少しだけ質問ですが、MyReceiver 内で startService が無いようですが、これは必要ですね?
また、マニュフェストで、android.permission.RECEIVE_BOOT_COMPLETED の指定は不要でしょうか?
お恥ずかしながら BROADCAST_STICKY は使ったことが無いのですが、
もしかしてこちらで対応できている感じなのでしょうか?
§
皆さんのご助言のお陰様で、とりあえず目的の動作は実現できるようになりました。
システムによる Service の kill でレシーバーの登録が残ってしまいそうな懸念はありますが、
しばらく検証してみて実際のところどうなのか考えてみたいと思います。
とりあえず Service.onLowMemory() で stopSelf() も入れておこうかと思っていますが、
効果があるのか無いのかも確認しておきたいなと思っています。
§
下記は、私の手元のコードを少々整理したものになります。
お恥ずかしいかぎりですが、定期起動の部分も入っていますので、参考までに送信いたします。
大垣さんのサンプルと違って、Service.onStart での処理後に、stopSelf() していませんが、
これはこのアプリの要件で、比較的ゆるめの状態保存が必要な為です。
完全な状態保存となると永続化のコードが必要かと思いますが、
今回は1回前の処理結果を利用できたら再利用するという程度の、比較的ゆるめの要件なので、
Service のフィールドに状態を保存しています。
----
/**
* 各種ブロードキャストのレシーバー
* ブロードキャスト受信後、MyService を startService する。
* ・定期起動用のアラームインテントの受信
* ・スクリーンON/OFFのブロードキャストの受信
* ・デバイス起動完了のブロードキャストの受信
*/
public class MyServiceRunner extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent) {
Log.i(MyServiceRunner.class.getSimpleName(), "onReceive: " +
intent.toString());
String action = intent.getAction();
context = context.getApplicationContext();
Intent service = new Intent(context, MyService.class);
if (Intent.ACTION_SCREEN_OFF.equals(action)) {
service.setAction(MyService.ACTION_CANCEL);
} else {
service.setAction(MyService.ACTION_CHECK_AND_NOTIFY);
}
context.startService(service);
}
}
----
/**
* 各種ブロードキャストのレシーバー
* ブロードキャスト受信後、MyService を startService する。
* ・onCreate/onDestroy で ACTION_SCREEN_ON/OFF のブロードキャストを
MyServiceRunner が受信できるように設定/解除
* ★ACTION_SCREEN_ON/OFF はマニュフェストファイルの記述だけでは受信できない為
* ・スクリーンON/OFFのブロードキャストの受信
* ・onStart で目的の処理を行い、実行後に定期起動のための Alarm をセット
*/
public class MyService extends Service
{
/**
* スクリーンON/OFF受信用のブロードキャストレシーバー
*/
final static private MyServiceRunner mReceiver = new MyServiceRunner();
/**
* 定期起動の間隔(秒)
*/
static final int MONITORING_INTERVAL = 60 * 60; //seconds
/**
* モニタリングを開始する Intent アクション
*/
static final String ACTION_CHECK_AND_NOTIFY = "check_and_notify";
/**
* モニタリングを終了させる Intent アクション
*/
static final String ACTION_CANCEL = "cancel";
/**
* 定期起動用の PendingIntent
*/
static protected PendingIntent mPendingIntentForLunchToCheckAndNotify;
/**
* 対象の確認と通知の処理
*/
public synchronized void checkAndNotify() {
//……目的の処理……
}
/**
* 今回はバインド想定なし
*/
@Override
public IBinder onBind(Intent intent) {
return null;
}
/**
* ・スクリーンON/OFFのレシーバー登録
* ・定期起動用のインテントの準備
*/
@Override
public void onCreate() {
super.onCreate();
Context context = this.getApplicationContext();
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.registerReceiver(mReceiver, filter);
//モニタリング起動用の PendingIntent の準備
if (mPendingIntentForLunchToCheckAndNotify == null) {
Intent operation = new Intent(context, MyServiceRunner.class);
operation.setAction(MyService.ACTION_CHECK_AND_NOTIFY);
mPendingIntentForLunchToCheckAndNotify =
PendingIntent.getBroadcast(context, 0, operation, 0);
}
}
/**
* ・スクリーンON/OFFのレシーバー解除
*/
@Override
public void onDestroy() {
super.onDestroy();
Context context = this.getApplicationContext();
context.unregisterReceiver(mReceiver);
}
/**
* 目的の処理の実行と定期起動関連の処理
* ・ACTION_CHECK_AND_NOTIFY: 対象を確認し、問題があれば通知を出す。
* ・ACTION_CANCEL: 定期起動を解除する(不要っぽいですが、念の為)
*/
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
final String action = intent.getAction();
final AlarmManager alermManager = (AlarmManager)
this.getSystemService(Context.ALARM_SERVICE);
if (ACTION_CHECK_AND_NOTIFY.equals(action)) {
Log.i(MyService.class.getSimpleName(), "Start ACTION_CHECK_AND_NOTIFY");
Thread task = new Thread() {
@Override
public void run() {
checkAndNotify();
long triggerAtTimeMillis = System.currentTimeMillis() +
MONITORING_INTERVAL * 1000;
alermManager.set(AlarmManager.RTC, triggerAtTimeMillis,
mPendingIntentForLunchToCheckAndNotify);
}
};
task.start();
}
else if (ACTION_CANCEL.equals(action)) {
alermManager.cancel(mPendingIntentForLunchToCheckAndNotify);
}
}
}
----
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sample.xxx"
<uses-sdk android:minSdkVersion="4" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application android:icon="@drawable/icon" android:label="@string/app_name">
<activity android:name=".MyActivity" android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyService" android:process=":remote">
</service>
<receiver android:name=".MyServiceRunner" android:process=":remote">
<intent-filter>
<action android:name="android.intent.action.SCREEN_ON"/>
<action android:name="android.intent.action.SCREEN_OFF"/>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
</intent-filter>
</receiver>
</application>
</manifest>
----
以上です。
ありがとうございました!
-- ゆぅか。
おはようございます。ありがとうございます!
> ・onCreateでregisterReceiverを二回やってますが一回(一個のIntentFilter)でもいけます。
> IntentFilter#addAction等で複数のActionやCategory等に対応できます。
なるほど。↓こんな感じですね。
IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_SCREEN_ON);
filter.addAction(Intent.ACTION_SCREEN_OFF);
context.registerReceiver(mReceiver, filter);
> ・AndroidManifest.xmlにMyReceiverの定義は必要ありません。
そうなんですか?!
では、今回の場合は↓次で良かった感じですかね。
(同じレシーバーでBOOT_COMPLETEDも拾うので、それだけ指定)
<receiver android:name=".MyReceiver">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
</intent-filter>
</receiver>
> BroadcastReceiverの静的、動的設定の違いは
> ...以下略...
↑こちらもとても参考になりました。
特に 「onReceive時にインスタンスが生成され、実行後に破棄される」というのは、
今回私が行って失敗したアプローチにおける、失敗要因の一つだったと思います。
§
大垣さん、yagiさん、
色々とコメントをありがとうございました。
もともとは「何か根本的に間違っていないか?」と不安に思っての質問でしたが、
それ以上に勉強になりました。ありがとうございました!
(で、そもそもの間違って無いか?という点は、だいたい大丈夫だったんだろうと、
思って安心しています♪)
このあとは自分で、onDestroy の有効性と、もし問題があるなら対策などを考えて、
リリースに向けてアプリをブラッシュアップしたいと思います!
ありがとうございました!
-- ゆぅか。
おはようございます。
ちびっきな疑問にお答えいただいてお手数をおかけいたしました!
参考にあげていただいた2つのログは、今回わたしが実装する際にも参照させて
頂いておりました。皆さんの情報、大変助かっております。
§
メールついでにひとつ今回思ったことがあるので、書いてみたいと思います。
結構「いまさら?」な話ですが、私自身が今回ちょっとハマッたポイントなので、
もしかするとどなたかの参考になればと思って書いてみます。
検索で Android の Service 関連の情報を探していると、しばしば「常駐」という
キーワードが出てきます。そしてそれはどちらかというと「Serviceは常駐する
ものだろうに、よく落ちる。ちゃんと落ちないようにする(落ちないように見せかける)
には工夫が必要だ」という雰囲気のものが多いように感じています。
私も最初はそう思っていたのですが、今回 Service 周りをちゃんと使ってみて、
今は次のようの考えが正しいのではと思っています。
「Android の Service は何らかの常駐的な機能を提供するコンポーネントでは無い」
その上で、何らかの常駐的な機能を実装するとき ― 、
例えば、定期的なプログラムの実行であれば、AlarmManager を利用し、
また外部からの何らかのアクションに対応するには、BroadcastReceicer などで
インターフェースを設ける、という感じの理解がグッドなのではないかなー、と。
で、それでそう考えると、例えば次のような、Service の仕様もしっくり納得できました。
・startService は別スレッドで実行されるわけじゃない
・stopSelf しなくても、いつの間にか kill されて落ちてたりすることもあったりする
・Service が kill されて再起動した時、onCreate は呼ばれるが、onStart は呼ばれない
また、そう思ってから↓改めてリファレンスを見ると、なんとなくそういうことが書いて
あったりして、ちょっとお恥ずかしいです。。。(*^ ^*;
http://developer.android.com/intl/ja/reference/android/app/Service.html#WhatIsAService
(↓What is a Service? の部分の翻訳が含まれている記事がありました)
http://d.hatena.ne.jp/adsaria/20100914/1284435095
(でもちょっと遠まわしな記述ですよね?(^ ^;
私、最初は意味がわからずに読み飛ばしてたと思います)
で、そんなことに気付いたところで、あんまり意味の無いものかも知れませんが、
私個人的には、最初にどう作ろうか考えている時点で、早めにただしい方向性を
見出せるような気がいたします。…というか、今回は最初にそこを勘違い?していて、
ちょっぴり苦労いたしました。。。
以上ですが、間違った理解の箇所などありましたら、ご指摘いただけましたら嬉しいです。
-- ゆぅか。
2011年5月10日23:29 大垣憲俊 <noritos...@gmail.com>: