iOS/iPhone/iPad/watchOS/tvOS/MacOSX/Android プログラミング, Objective-C, Cocoa, Swiftなど
AsyncTaskの仕様をよく読むと気になることが説明されている。UIスレッドで使用するという箇所だ。
Threading rulesでスレッド関連の説明がされているが、クラスのロードはUIスレッド上で。ただし、JELLY_BEANからは自動で対応している。インスタンスはUIスレッドで生成すること。
ソースを確認してみよう。Android SDKが置かれているディレクトリ配下で以下のコマンドを実行。
$ cd /Applications/Development/android-sdk-macosx
$ find . -name AsyncTask.java -print
./samples/android-19/ui/DisplayingBitmaps/DisplayingBitmapsSample/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java
./samples/android-21/ui/DisplayingBitmaps/Application/src/main/java/com/example/android/displayingbitmaps/util/AsyncTask.java
./sources/android-19/android/os/AsyncTask.java
./sources/android-21/android/os/AsyncTask.java
Android 5.0 Lollipopのソースを確認してみよう。
public abstract class AsyncTask<Params, Progress, Result> {
:
private static final InternalHandler sHandler = new InternalHandler();
:
/** @hide Used to force static handler to be created. */
public static void init() {
sHandler.getLooper();
}
:
}
AsyncTaskのHandlerはクラス変数になっている。そして、HandlerのLooperはクラスメソッドで設定されている。つまり、最初にAsyncTaskのインスタンスを生成する際にHandlerとLooperが保持されることになる。
なぜ、HandlerとLooperが必要か?それは、onCancelledなど、UIスレッドで呼び出すことになっているメソッドがUIスレッドで呼ばれるようにする為だろう。こういうわけだ、AsyncTaskはUIスレッドで生成されることになるので、最初の生成時にUIスレッドのLooperを保持しておいて、別スレッドで処理を終えた後に、このLooperを使って、UIスレッドで結果を返す。
なので、最初のAsyncTaskの生成をUIスレッド以外で行うと、そのスレッドにLooperがない場合は、myLooperがnullの例外が発生することになる。Looperが存在しても、onCencelledなどがUIスレッドとは異なるスレッドで呼ば出されることになってしまう。
気をつけよう。著者はこれでハマった。