Handlerのディレイ処理で追い越しを抑止する方法について

1,481 views
Skip to first unread message

otyabito

unread,
Apr 10, 2015, 3:50:02 AM4/10/15
to android-g...@googlegroups.com
いつもお世話になっております。
初めまして、初心者です。

掲題について質問させて下さい。

Handlerのディレイ処理、
例えばsendEmptyMessageDelayedやpostDelayedを呼び出すと、ディレイ処理が掛かってない処理が先に呼び出されてしまうように見えます。

Handlerに独自LooperとCallbackを登録し、お手軽にキューイングが出来るものと勝手に思っていたのですが、実際は上記のようにディレイ処理に限り追い越しがかかるため、対応に苦慮しております。

そこで質問なのですが、
1. ディレイ処理を含む処理でもキューイングが働くようにhandlerにオプションを設定することは可能でしょうか。
2. オプションのような形で設定できない場合、コールバックの中でSystemClock.sleepでディレイしようと思うのですが、設計上懸念点はございますか。
3. 上記方式が取れない場合、お手軽に猿でも分かるようなキューイングの実装方法をご教授願えませんか。

以上、よろしくお願い致します。

Shigeo Mutoh

unread,
Apr 10, 2015, 5:31:52 AM4/10/15
to android-g...@googlegroups.com
武藤です。

私ならどうするか考えてみました。

まず、queを一個自前で用意します。データ構造は純粋なqueとします。

HandlerにpostMessageか何かして、callbackしてもらいます。
このcallbackの中で、上記queのcarが発火条件を満たしていたら、
発火してqueから削除します。そうでなければqueをそのままにして
処理を抜けます。
このcallbackの中で再度HandlerにpostMessageします。これで永久に
このcallbackは呼ばれ続けます。

これでqueされた順番を頑なに守る(car側にdelay指示があった場合は
そこで詰まり続ける)ことができるのではないでしょうか。

※queのcarとはqueの先頭のことです。

> 2. オプションのような形で設定できない場合、コールバックの中で
SystemClock.sleepでディレイしようと思うのですが、設計上懸念点はございま
すか。

これは最悪だと思います。
mainスレッドであったならもちろん最悪ですが、別のスレッドだったと
してもqueの管理がめんどくさくなります。たとえばsleepしている間に
その要素そのものが削除されるのを考えるのは苦痛です。

では。

otyabito

unread,
Apr 22, 2015, 11:28:39 PM4/22/15
to android-g...@googlegroups.com
ご回答ありがとうございます。

最初なるほど!と思い、
自分なりに理解を進めたのですが、
最終的に自分の技量だとこの処理はバグが発生しても分からなそうで嫌だなぁと思い、実際レビュアーにも説明できなかったので
結局当初のコールバック内でsleepするという
手段を取ってしまいました。
貴重なご意見をいただいたのに、申し訳ありません……

嫌だなぁと思ったのは、
発火するキューをいつ積むかというタイミングです。
ご回答には暗黙の了解として書いてあったと思うのですが、sendMessageDelayedなどでメッセージを遅送する直前だという認識でおります。
送り出し側(最低7つあります)とコールバック先は違うスレッドにいますが、銘々が同じキューを参照、取得する場合共有地?的な問題が発生しそうとパッと見、思いました。
調べを進めた結果、プロデューサ・コンシューマーデザインパターン用のBlockingQueueを使えば良さそうと一定の解は得ました。
しかし、並行スレッドによるプログラミングは経験がなく、周囲に有識者もいない環境のため、使いたいけど自分でも何でこれならいいのかレビュアーに十分説明出来ず、
自分でもスレッドスリープでいいではないか、removecallback動くし、Looper止められるのはアプリが終了するときだけだし、業務利用を考えたらメモリリークはぶっちゃけ心配ないしとの心の声に押されて、当初案に何となく流された次第です。

申し訳ありません。。。

Shigeo Mutoh

unread,
Apr 23, 2015, 2:35:22 AM4/23/15
to android-g...@googlegroups.com
武藤です。

繰り返しになりますが、通常、mainスレッドでsleepするのは、本当に最悪です。
この理由について理解する必要があります。それは適当に調べればすぐ分かると
思います。
少なくとも、もし10秒ほどsleepすることがあれば、OSから殺されるぐらい最悪
です。

いずれにせよ、スレッドの知識がないのでしたら、勉強したり実験したりしなければ
なりません。だれでも最初は初心者です。
スレッドの概念と操作(排他制御含む)はpureなjavaレベルで確立していて、なおかつ
様々な言語、アーキテクチャで通用する、応用範囲の広い概念です。
ぜひ乗り越えましょう。

デザインパターンは知らなくてもとりあえず生きて行けます。生きていけなくな
るのは
それを誰かが説明として、いきがって使いやがった時、です。

では。
Message has been deleted

otyabito

unread,
Apr 23, 2015, 11:08:06 AM4/23/15
to android-g...@googlegroups.com
ご回答ありがとうございます。

コールバックにスリープ処理を入れた場合、メインスレッドではなく、Handlerに登録した独自Looperがスリープに入ると思っていたのですが間違っていたのでしょうか…

仰るようにメインスレッドでスリープした場合はANRが発生するし、明らかに画面がもっさりするので注意していたつもりでした。

画面のメインスレッドではなく、独自Looperがスリープすることの危険性がいかほどなのか、知りたい次第です。

お忙しい中、誠に申し訳ありません。
恐れいりますが、よろしくお願いします。

Shigeo Mutoh

unread,
Apr 23, 2015, 11:53:19 AM4/23/15
to android-g...@googlegroups.com
武藤です。

えーと、どうしようかなあと思ってますが...。

たぶん、やりたいことは別スレッド(非mainスレッド)でqueを順番に処理
したい、ただそれだけなのではないですか?

だとしたらLooperとかHandlerとか出さなくても、世の中にいくらでも
簡単なサンプルはあると思うのですよ。

というのがまず第一。

で、仮にLooperとHandlerが大好きで、それを使ってうまくque処理が
「動いているように見える」としましょう。(別にそれが悪いなんてことはなく、
むしろ良いことかも知れません)
そして今それが本当に正しく動作していることを確かめたいとしましょう。

そしたらデバッガでque処理しているところでbreakしてみて、そのスレッド
が何かを確かめれば良いのです。

なのでどのみちスレッドのプログラミングをするのだから、スレッドを
分からないまま完遂する魔法は起こらないのです。

でで、オマケ情報としては、Looperは文字通り、メッセージループを回すだけ
のことなので、どのスレッドで実行されるかは作成者の設計と実装に依存
します。つまりLooperは回したいスレッドで回せると。
たぶんnewしたスレッドで回ると思います。(要確認)

というわけで、sleepするスレッドがmainスレッドじゃなければ問題ないし、
mainスレッドだったらおっしゃるとおり問題ありです。

では。

otyabito

unread,
Apr 23, 2015, 11:38:41 PM4/23/15
to android-g...@googlegroups.com
回答ありがとうございます。

既存処理において、別スレッドで起動した独自Looperにメッセージを投げる処理を元々しており、呼び出し順番などは意識した設計となっておりませんでした。

その改修作業において、1. 呼び出し順番を守ること 2. ディレイ処理が一部において実施することの二点が新規要件で追加になったため、既存ロジックをなるべく壊さずに要件を満たす設計を考慮した上でHandlerによる順列処理に固執した次第です。質問の背景を十分に説明せず、申し訳ありません。

長々とお時間をお借りして、申し訳ありませんでした。ご質問にのっていただき、大変ありがとうございます。

スリープ中のルーパーにメッセージが乗った時、消えることは本当にないかなど不安という名の疑問はまだまだ尽きないのですが、一旦頭を冷やします。

ありがとうございました。

Shigeo Mutoh

unread,
Apr 24, 2015, 5:03:26 AM4/24/15
to android-g...@googlegroups.com
武藤です。

謝らなくても良いですよ。お互い様です。

ちょっと自分でも気になって調べてしまったのと、ここまで読まれている
他の方々に誤解を与えたくないと思い、さらに難しいことを書きます。
ごめんなさい。興味なければ読まないでください。

気になったのは「どこでqueの追い越しが起きるのか」です。

最初Looperのソースを眺めていて、そこにはなかったです。
追い越しをやってたのはMessageQueueクラスのnext()の中でした。

ではMessageQueはどこで作られるかというと、Looperのprivateなコンストラクタ
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
...
}
の中だけでした。こいつは全くMessageQueを挿げ替えさせる気がないことが
わかりました。

それどころか、Looperはpublic final classだと知りました。拡張できません。

だとするとLooperをnewしてHandlerにセットする意味が分かりませんでした。
しかしLooperには
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
というメンバー変数があって、これはthread local strageと呼ばれるスレッド
固有のデータ保管領域に自分自身を保管するという小憎たらしいことを
やってました。だからスレッドごとにLooperは一個しか存在できなくて、
当然MessageQueueも一個だけど、Handlerはどのスレッドに何個あっても
良いという設計だと知りました。

ここまで調べてみて、LooperやMessageQueueというクラスがAndroidの
メッセージポンプの設計そのものの実装であって、あまり汎用性がない
ということに気が付きました。

間違ってたらだれか教えてください。

では。

Shigeo Mutoh

unread,
Apr 24, 2015, 5:55:40 AM4/24/15
to android-g...@googlegroups.com
武藤です。

大事なことを書き忘れました。

Looperは融通がきかず汎用性がないことは確かだと思いますが、とはいえ、
大抵これは使えると思います。今後私も積極的に使おうかと思います。

今回のケースのように、messageの追い抜きが起きないようにsleepする
場合は、worker threadにおいてのみ許されるという、普遍的なことなので、
otyabitoさんのやり方で良いと思います。

では。

Akihiko Nakagawa

unread,
Apr 24, 2015, 9:38:20 PM4/24/15
to android-g...@googlegroups.com
お世話になります。中川と申します。

Handler, Looperについてはいろいろなところで使われているので、確認させてください。

追い抜きと言われているのは、postDelayedのあとにpostしたRunnableが先に実行されることだと認識しました。
これはMessageQueueの仕様であり、Androidのバグでも、アプリの問題でもないと思っていますが、この考えは合っていますでしょうか。
(もし追い抜きバグがあれば、コードレビューでバグの回避法や問題が起こらない確証について説明する必要があるので・・・)

なお、自分の理解としては、MessageQueueは優先度付きキューのようなものだと理解しています。
postで追加されたRunnableは優先度が高く、postDelayedで追加されたものよりも早く実行される・・・ということです。
以下のHandlerのリファレンスには、「postは即座に実行され、postDelayedは遅らせて実行され、postAtTimeは指定時刻に実行される」とあるためです。
http://developer.android.com/reference/android/os/Handler.html#Handler%28%29
When posting or sending to a Handler, you can either allow the item to be processed as soon as the message queue is ready to do so, or specify a delay before it gets processed or absolute time for it to be processed.

スレッドに横入りしてしまい申し訳ありませんが、確認いただけますでしょうか。

よろしくお願いします。

2015年4月24日 18:55 Shigeo Mutoh <tmh...@gmail.com>:
--
このメールは Google グループのグループ「日本Androidの会」の登録者に送られています。
このグループから退会し、グループからのメールの配信を停止するには android-group-j...@googlegroups.com にメールを送信してください。
このグループに投稿するには、android-g...@googlegroups.com にメールを送信してください。
http://groups.google.com/group/android-group-japan からこのグループにアクセスしてください。
その他のオプションについては、https://groups.google.com/d/optout にアクセスしてください。

noxi

unread,
Apr 25, 2015, 12:48:57 AM4/25/15
to android-g...@googlegroups.com
noxiです。

こちらを見ると分かりやすいかもしれません。
http://www.adamrocker.com/blog/261/what-is-the-handler-in-android.html

> このMessageQueueはMessageをノードにしたリストを管理しているだけです。
> LinkedListの様な形式です。
> ただし、単純なキューではありません。
> キューにMessageを追加する際、Messageリストの順番が必ず時間順になるようにしています。

postで追加したRunnableが優先度が高くて実行されるのではありません。

postで追加したMessageはディレイ0で追加されるため、Queueから次に取り出されるMessageとなります。
そのために現在実行中の処理が終わり次第追加したRunnableが動き出す、ですね。


###
一度でもpostDelayedを使ってしまうと、そのMessageが処理されるまで他のMessageが動かない
UIスレッドなんて見たくないですね。。
###

2015年4月25日 10:37 Akihiko Nakagawa <freesoft...@gmail.com>:
> このメールは Google グループのグループ「日本Androidの会」に登録しているユーザーに送られています。

Akihiko Nakagawa

unread,
Apr 25, 2015, 5:11:49 AM4/25/15
to android-g...@googlegroups.com

noxiさん

単純なリストで、時間順にソートされてるということですね。
すっきりしました。
ありがとうございます。

2015/04/25 午後1:48 "noxi" <android.w...@gmail.com>:

Shigeo Mutoh

unread,
Apr 28, 2015, 5:11:37 AM4/28/15
to android-g...@googlegroups.com
武藤です。

お返事遅れてしまいすみません。

On 2015/04/25 10:37, Akihiko Nakagawa wrote:
> 追い抜きと言われているのは、postDelayedのあとにpostしたRunnableが先に実行されることだと認識しました。
> これはMessageQueueの仕様であり、Androidのバグでも、アプリの問題でもないと思っていますが、この考えは合っていますでしょうか。

はい、MessageQueueの仕様ということで合っていると思います。
ですので、今回の議論の結論は、LooperとMessageQueueを使って常にqueした順
番通りに
発火させたいというのは、Looper(および内包するMessageQueue)を拡張できない
以上、無理がある、けどもworker threadでsleepするならできなくもないね、という
結論でした。

では。
Reply all
Reply to author
Forward
0 new messages