Android AsyncTask はバックグラウンドの処理に集中すべし!
Android でネットワークアクセスするときなどは、非同期タスクである、AsyncTask を利用する。
非同期処理はUIスレッドとは別にバックグラウンドスレッドで実行される、doInBackground() をオーバーライドすることで実現する。
で、処理結果はUIスレッドと同期される、onPostExecute() で処理する。
多くの場合は、結果をUI側に反映させたいはず。
こういう場合、一番単純な戦略は
1.Activitiのインナークラスとして、非同期処理を作成しActivityのメンバーを共有する方法
ことだろう(匿名クラスを利用する戦略もあるが、省略)
擬似コードでは、以下のような感じになるか
public class HogeActivity extends Activity { private EditText mEditText = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.hoge_activity); mEditText = (EditText)findViewById(R.id.text1); ((Button)findViewById(R.id.button1)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // 非同期処理の呼び出し String[] params = null; (new HogeTask()).execute(params); } }); } public class HogeTask extends AsyncTask{ @Override protected String doInBackground(String ... parm) { String result = null; // 何らかの処理を行い結果を返す return result; } @Override protected void onPostExecute(String result) { // ここで結果をテキストボックスに書き込む mEditText.setText(result); } } }
いろいろ書いたが、一番重要なのは、mEditTextをAcivity と AsyncTask が共有しているということ。
まぁこれですめばいいのだが、さすがに結合度が高すぎるというか使い回しが効かないので、汎用的な処理なら早晩Activity から脱出させることになるだろう。
そうすると、Activity側のUIウィジェットにどのようにアクセスさせるかという話になるのだが、例えば、AsyncTask側に、Activity の参照を持たせたりしてもおそらく動くだろうが、これではソースファイルを分けただけで結合度が高いまま。
2.AsyncTask にリスナーを登録してActivity をコールバックする方法
というのが、素直に考えると現実的な戦略なのかなと思う。
Activity
public class HogeActivity extends Activity implements AsyncTaskCallBackListener{ private EditText mEditText = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.hoge_activity); mEditText = (EditText)findViewById(R.id.text1); ((Button)findViewById(R.id.button1)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { // 非同期処理の呼び出し String[] params = null; (new HogeTask()).execute(params); } }); } // AsyncTaskCallBackListener のオーバーライド @Override protected void onPostExecute(String result) { mEditText.setText(result); } }
CallbackListener
public interface AsyncTaskCallbackListener { public onPostExecute(String result); }
AsyncTask
public class HogeTask extends AsyncTask{ private AsyncTaskCallbackListener listener = null; public void setAsyncTaskCallbackListener(AsyncTaskCallbackListener listener) { this.listener = listener; } @Override protected String doInBackground(String ... parm) { String result = null; // 何らかの処理を行い結果を返す return result; } @Override protected void onPostExecute(String result) { // ここでリスナーのコールバックを呼び出す if (listener != null) { listener.onPostExecute(result); } } }
ググった事例をしっかり見ていないのだが、おそらくこんな感じになるのではないだろうか(ここまでの例示は擬似コードで動作させていないのであしからず)。
自分も今まではこんな感じで書いてきた。
これで、AsyncTask と Activity はリスナーを介して結合されることになるので、結合度は小さくなるし、AsyncTaskを利用したいところでは、リスナーを利用すればよい。
3.AcyncTask はバックグラウンドの処理だけに集中
確かにリスナーを利用する方法がが堅いとは思うのだが、同じようなTaskをいくつか作ろうとすると、AsyncTask を継承して、独自の抽象クラスを作成して、そこでリスナー登録抽象メソッドを用意して、リスナーはパラメータの型に対応出来るようにして。。。
と、クラス階層を考えたり作成したりというのもまぁ楽しいのだが、この場合汎用的なところに落とそうとするとちょっとしたライブラリみたいにしなきゃいけないし、かといって個別に対応するのは、なにか釈然としないというか、そもそもリスナーがやっていることがトートロジックな感じがするいうか、まぁ要するに面倒くさい事態に陥るのが目に見える。
なにか他の解決策がないものか。。。
と考えることしばし。
AsyncTaskの派生クラスでは、バックグラウンドの処理だけに集中し、UIスレッドをいじくるところは、呼び出し元のUI側でオーバーライドすれば、いい感じになるのでは!?
結合度も低くなるし、凝集度は高くなる。
なによりシンプル。
ということで、以下のような感じになるだろう。
Activity
public class HogeActivity extends Activity { private EditText mEditText = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.hoge_activity); mEditText = (EditText)findViewById(R.id.text1); ((Button)findViewById(R.id.button1)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View arg0) { doHogeTask(); } }); } private void doHogeTask() { // UIスレッドに関わるところはUI側でオーバーライド HogeTask hogeTask = new HogeTask(){ @Override protected void onPostExecute(String result) { mEditText.setText(result); } }; // 非同期処理の呼び出し String[] params = null; hogeTask.execute(params); } }
AsyncTask
// AsyncTaskではバックグラウンド処理に集中 public class HogeTask extends AsyncTask{ @Override protected String doInBackground(String ... parm) { String result = null; // 何らかの処理を行い結果を返す return result; } }
上記例では、onPostExecute()にのみ着目したが、UIスレッドとやりとりするメソッドは上記方法でいけるのではないかな(まだやってないけど)。
これで、ほとんどのケースでは事足りるのではないか?
本当に込み入ったことをしたい場合には、もちろんリスナーを作成すればよいと思うが。
ということで、これから非同期処理を実装するのが、自分の中では楽になる気がする。
ご参考になれば。
UIスレッドうんぬんの話は、
あたりがわかりやすかった。
Effective Java は、ピアソン桐原の書籍で現在絶版みたいだけど、丸善から再出版されるようで。
Effective といえば、先日本屋で立ち読みした、Effective Android めっちゃ面白そう!次のお小遣いで購入だ!