2013年4月19日金曜日

[Android]Fragmentはバックグラウンドスレッドから操作できる

AndroidはUIを操作をメインスレッドのみで行うシングルスレッドモデルで設計されています。 バックグラウンドスレッドから、UIの操作を行う時は、Handler経由で行うのが定石とされています。

 で、今日、コードレビューしていると、バックグラウンドスレッドの中で、DialogFragmentを表示する処理を書いているのを見つけました。 ってかこれだめだよね。と思って動作させてみると、普通にダイアログが出てくる。 なんで例外でないんだろう?と思って調べてみると、まわりまわってHandler経由でUIの操作をしているのがわかりました。

流れはこんな感じ。
  1. FragmentManager#beginTransaction→(FragmentManagerImpl#beginTransaction)
  2. (Fragmentをいろいろ操作)
  3. FragmentTransaction#commit→(android.app.BackStackRecord#commit)
  4. (FragmentManagerImpl#enqueueAction)→(Activity#mHandlerに処理委譲)
つまり、バックグラウンドスレッドからFragmentを切り替えたとしても、Handlerを経由してメインスレッドで実行される。ということです。

これの何が嬉しいかというと、処理全体をバックグラウンドスレッドにできるという利点があります。
たとえば、定期的にデータを取得して画面を更新するみたいな処理は、デーモンスレッドを1つ起こしておいて動かしっぱなし。というのが可能です。そしてFragmentは画面の一部を更新するのに向いています(たぶん更新はreplaseしかダメかなぁ)。

 それとは別の観点では、Androidは通信処理をバックグラウンドスレッドで実行する必要があります。そのため、
  • 通信はバックグラウンド
  • UI更新はメインスレッド
のようなめんどくさいことをやるため、AsyncTaskやLoaderを使います。
AsyncTaskではコールバックでメインスレッドとバックグラウンドスレッドを呼び分けてくれますが、処理が分断されるため、一連の流れがわかりづらくなるというデメリットがあります。
使ったことがある人はわかると思いますが、実装上のコードのネストがどうしても深くなってしまいます。

しかし、すべてをバックグラウンドで処理できるとなると、たとえばボタン押下以降の処理全体をバックグラウンドスレッドで実行することが可能になります。
私はHandlerTheadを結構使います。このクラスは、以下のような特徴があります。
  • バックグラウンドスレッドである
  • Queueで処理するため、一連の順序性が保たれる(逐次処理ができる)
  • 遅延実行できる
  • Queueをキャンセルすることができる
しかし、ここからUIを操作するのがめんどくさい(Handler経由になる=HandlerのくせにHandler持ってないとダメ)ので完全にバックグラウンドの逐次処理だけに限って使っていましたが、これがありなら、

 通信→データ加工→UI更新

みたいな一連の流れをカプセル化し処理することが可能になります。

しかし、実装上こうなっているだけで、これをやってOKとはどこにも書かれていません。将来的にこの実装のまま。というわけではないので、注意は必要ですね。
OSのバージョンを固定してもいい場合(一品もの)などは、この方式もありかもしれません。。。

2 件のコメント :

賢太 さんのコメント...

初めまして。
現在fragmentの扱いについて調べています。
これは、できることはフラグメントそのものの追加消去であって、フラグメント内に記述されたUIはいじることはできない、ってことでいいんでしょうか?

GENZ0 さんのコメント...

上記の通り、フラグメントそのものの追加です。
しかし、その後のライフサイクルに従ったイベント(onCreateなど)を処理することで、結果的にUIをいじることになりませんか?

すでに存在するフラグメントを操作したいのであれば、Handlerなどを経由して操作する必要がありますね。