[Android] Multithreading For Performance パフォーマンス向上のためのマルチスレッド化の方法

A good practice in creating responsive applications is to make sure your main UI thread does the minimum amount of work. Any potentially long task that may hang your application should be handled in a different thread. Typical examples of such tasks are network operations, which involve unpredictable delays. Users will tolerate some pauses, especially if you provide feedback that something is in progress, but a frozen application gives them no clue.

In this article, we will create a simple image downloader that illustrates this pattern. We will populate a ListView with thumbnail images downloaded from the internet. Creating an asynchronous task that downloads in the background will keep our application fast.

上記の"Multithreading For Performance"という記事では、Androidアプリケーションのマルチスレッド化の方法が解説されています。画面の描画処理やユーザー操作のための処理以外を別スレッドで行うことで、ユーザーインタフェースの応答性を向上することができます。

この記事のサンプルコードは、以下のからダウンロードできます。

記事の中では簡単にしか解説されていませんが、サンプルコードの中では、一度ダウンロードした画像を、メモリ上にキャッシュするためのコードも盛り込まれています。とても実用的なサンプルだと感じたので、サンプルコードを理解を促進するために、クラス図とシーケンス図を作成してみました。


詳細はオリジナルの記事を参照していただくとして、以下に、簡単に解説してみたいと思います。

ImageListActivity

アプリケーションのメインのアクティビティです。
ListActivityを継承して、ImageAdapterをアダプターとして登録しています。
記事の中で解説してる3つのモードを切り替えるためのラジオボタンのリスナーを実装しています。

ImageAdapter

表示する画像のURLを50個、固定の配列で保持しています。
ImageListActivityから呼び出されるgetViewメソッドでは、指定されたindexに応じてImageViewを生成して返します。
ここで注意する点は、引数のviewメソッドでviewが指定されている場合には、新たにViewを生成するのではなく、引数のViewを使用するという点です。これは、ListActivityが、すでに使われていないViewを再利用する場合に発生します。
引数で渡されたviewか、生成したイメージビューを引数として、ImageDownloaderのdownloadメソッドを呼び出します。

ImageDownloader

イメージのダウンロードを制御するメインのクラスです。
downloadメソッドの中では、モードに応じて、3つの動作に切り替えます。

  • NO_ASYNC_TASK: シングルスレッドで画像をダウンロードします。downloadメソッドから戻るときには、ダウンロードされた画像がViewに設定されています。
  • NO_DOWNLOADED_DRAWABLE: Viewの再利用を考慮しないで、別タスクで画像をダウンロードします
  • CORRECT: DownloadedDrawableでダウンロード中の状態を表示して、別タスクで画像をダウンロードします。

DownloadedDrawable

画像がダウンロード中に、画像の代わりに表示するDrawableのクラスです。サンプルでは真っ黒なビューですが、「ダウンロード中」などを示すアイコンなどを表示すると、より分かりやすくなります。
また、どのタスクがそのビューに関連づいているかを記憶するために、このクラスにタスクへの参照を保持させています。ビューを再利用可能にするために、WeakReferenceを用いて、ビューへの参照を保持しています。

FlushedInputStream

BitmapFactory.decodeStreamのバグをワークアラウンドするためのクラスです。
現在のBitmapFactory.decodeStreamは、すべてのデータが読み出し可能な状態で使用することを前提にしているために、ストリームのskipメソッドが、常に指定したバイト数を読み飛ばすことを期待しています。ネットワークなどのストリームを使用すると、この前提が成り立たないため、エラーが発生してしまうようです。
詳細は、以下のIssue 6066を参照してください。
http://code.google.com/p/android/issues/detail?id=6066

BitmapDownloaderTask

データを別スレッドでダウンロードするためのクラスです。
AsyncTaskを継承するとこで、Threadクラスなどを直接使用することなく、容易にマルチスレッドの処理を記述することができます。
基本的な使用方法は、以下のメソッドを実装することです。

  • doInBackground() : バックグラウンドで別スレッドで実行したい処理を記述します。このメソッドの戻り値を、onPostExecuteに渡すことができます。
  • onPostExecute : バックグランドの処理が終了後、UIを制御するメインスレッドで実行したい処理を記述します。Androidでは、すべてのViewは、メインスレッドで操作する必要があるので、Viewを操作する処理はこのメソッドから呼び出す必要があります。

ハードキャッシュとソフトキャッシュ

ダウンロードした画像をメモリ上にキャッシュしておくための処理が、二つのマップを使用して実装されています。

  • HashMap sHardBitmapCache : URLとBitmapを保持するハードキャッシュです。LinkedHashMapの機能を使用することで、最後にアクセスされた要素を先頭に配置し、古い要素を削除する動作を実現しています。古い要素は、removeEldestEntryメソッドで、ソフトキャッシュに移動しています。
  • ConcurrentHashMap> : URLとSoftReferenceでラップしたBitmapクラスを保持しています。メモリが不足するとガーベジコレクタによってSoftReferenceによって保持されているBitmapが解放されます。

DefaultHttpClientとAndroidHttpClient

DefaultHttpClientは、HTTPの要求応答を簡単に実装することができるヘルパークラスです。
API Level 8(SDK2.2)から、DefaultHttpClientを継承したAndroidHttpClientクラスも使用することが可能になりました。このクラスは、かなり初期から実装には含まれていましたが、実装依存ということで公開されていなかったクラスです。この実装では、Android用の適切なデフォルト値と、Andriod用のスキーマの設定が行われています。

全体的に、いろいろなノウハウ的な手法が使われているサンプルです。アプリのマルチスレッド化や、画像のハンドリング、キャッシュの実装方法などで迷った時には、とても参考になると思います。