Android Blog の1エントリーを翻訳してみる(Painless threading)
英語が苦手なので、ときどき簡単なエントリーを勝手に翻訳してみることにした。
間違っているかもだけど!
※以下の内容、翻訳したけれどピンと来ない・・・と思っていたら、
こちら(http://www.adamrocker.com/blog/261/what-is-the-handler-in-android.html)
に解説エントリーがあり、助かりました。
以下、Android Developpers Blog 「Painless threading」を勝手に翻訳。
- エントリー原文URL:
http://android-developers.blogspot.com/2009/05/painless-threading.html
破綻の無いスレッド化の方法
Posted by Romain Guy on 06 May 2009 at 9:30 AM
Androidアプリケーションを最初に起動する時はいつでも、main スレッドが自動的に生成されています。mainスレッドはUIスレッドとも呼ばれ、非常に重要です。適切なウィジェットのためのイベントを起動する役割があり、イベントの描画も行います。ユーザが Androidウィジェットとやり取りをする際のスレッドでもあります。
たとえば、あなたが画面のボタンをタッチしたとしましょう。
UIスレッドは、押された状態を設定し、イベントキューに無効リクエストを投げて、タッチイベントを起動します。UIスレッドは、リクエストをキューから外し、ウィジェットに自分自身を再描画するよう伝えます。
シングルスレッドモデルなので、よく考えないで実装すると、Androidアプリケーションのパフォーマンスは低くなります。ネットワークアクセス、DB照会といった、長時間の処理をすべてシングルスレッド上で行うと、ユーザインタフェイスに関するすべてがブロックされてしまうでしょう。
長時間の処理が走っている間は、描画イベントも含め、何もイベントが動かせません。ユーザからすると、アプリケーションがハングしたように見えます。
最悪なことに、UIスレッドは数秒以上ブロックされると(一般に約5秒間)、ユーザは残念な「アプリケーションが応答しません(ANR)」ダイアログを見ることになります。
これを見たいのならば、「Thread.sleep(2000)」を OnClickListener で走らせるボタンを持ったシンプルなアプリケーション書いてみて下さい。
ボタンは、通常状態に戻る前に、押された状態を2秒間キープします。これが起こったとき、ユーザは非常に簡単にアプリケーションが遅くなったと認識できます。
UIスレッド上で長ったらしい処理をしてはいけないということがわかったので、あなたはきっと、こういった処理をこなすには別スレッド(バックグラウンドかワーカースレッド)を使おうと思うでしょう。そのとおり。
クリックリスナーの例を見てみましょう。ネットワーク越しで画像をダウンロードして、ImageView で表示します。
public void onClick(View v) { new Thread(new Runnable() { public void run() { Bitmap b = loadImageFromNetwork(); mImageView.setImageBitmap(b); } }).start(); }
一見、このコードがあなたの問題をうまく解決してくれるように、UIスレッドを邪魔しないように思えるでしょう。残念ながら、アンドロイドUIツールキットのシングルスレッドモデルは、スレッドセーフではないし、UIスレッド上で常に操作するべきなのです。このコードサンプルでは、ImageViewがワーカースレッド上で操作されています。これは実におかしな問題を引き起こしてしまいます。状況を追跡把握し、この種のバグをフィックスするのは、難しく、時間が費やされることになるでしょう。
AndroidはUIスレッドに別スレッドからアクセスするための方法をいくつか提供します。全リストを示しましょう。このうちのいくつかはすでにあなたにとって馴染みのあるものかもしれません。
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
- Handler
これらのクラスやメソッドはどれも、先ほどのコードを正しく直すことができます。
example:
public void onClick(View v) { new Thread(new Runnable() { public void run() { final Bitmap b = loadImageFromNetwork(); mImageView.post(new Runnable() { public void run() { mImageView.setImageBitmap(b); } }); } }).start(); }
あいにく、これらのクラス、メソッドは、コードをより複雑かつ難読化する傾向もあります。頻繁なUI更新が必要となるような複雑な処理の実装をする時は、ひどいことになってしまうでしょう。
この問題を直すために、Android1.5 は AsyncTask と呼ばれる新しいユーティリティクラスを提供します。これは、長時間走り、ユーザインタフェイスとやりとりするタスクの生成を簡素化します。
AsyncTaskは UserTask として Android 1.0 と 1.1 でも利用できます。
これは全く同じAPIを提供しています。ソースをあなたのアプリケーションにコピーすれば良いだけです。
AsyncTaskの目標は、スレッド管理のめんどうををあなたが見られるようにすることです。前の例だと、AsyncTask を使って下記のように簡単に書き換えることができます。
public void onClick(View v) { new DownloadImageTask().execute("http://example.com/image.png"); } private class DownloadImageTask extends AsyncTask { protected Bitmap doInBackground(String... urls) { return loadImageFromNetwork(urls[0]); } protected void onPostExecute(Bitmap result) { mImageView.setImageBitmap(result); } }
このように、AsyncTask はサブクラスを作って使います。AsyncTask インスタンスはUIスレッド上に生成されており、一度だけ実行されるものであることを覚えておくことは、大変重要です。AsyncTask のクラスの使い方を完全に理解できるようなドキュメントはあるのですが、どのように動作するものか、簡単な概略をここで示します。
- 型定義ができ、タスクの進捗値やfinal値といったパラメータのジェネリックが使えます
- doInBackground()メソッドは自動でワーカースレッドを実行します
- onPreExecute()、onPostExecute()、onProgressUpdate() はすべてUIスレッド上で発動します
- doInBackground() は onPostExecute() が送った値を返します
- onProgressUpdate() をUIスレッドで実行するために、doInBackground() でいつでも publishProgress() を呼ぶことができます
- どのスレッドからでも、タスクをいつでもキャンセルすることができます
公式ドキュメントのほかにも、Shelves のソースコードで、複雑な例をいくつか読むことができます。(ShelvesActivity.java and AddBookActivity.java) and Photostream (LoginActivity.java, PhotostreamActivity.java and ViewPhotoActivity.java)。Shelvesのソースコードを読むことを強くおすすめします。構成の変更を通じてどのようにタスクを持続させるのか、アクティビティが destroyed されたときにどのようにそれらを適切にキャンセルさせるのかを理解できるでしょう。
AsyncTask を使うかどうかは別として、シングルスレッドモデルについての2つのルールを常に意識していて下さい。UIスレッドを邪魔してはいけないこと。Android UI ツールキットはUIスレッド上でだけアクセスされるということ。
If you want to learn more cool techniques, come join us at Google I/O. Members of the Android team will be there to give a series of in-depth technical sessions and answer all your questions.
Tags: Android 1.5, How-to, User Interface |