(API レベル4以上で再現する現象・・のはず)
staticメソッドを持った AsyncTaskを継承したクラス(ExtendsAsyncTask)を作成。
public class ExtendsAsyncTask extends AsyncTask<Void, Void, Void> {上のクラスの staticメソッドに別スレッドでアクセスすると
public static void method() {
}
@Override
protected Void doInBackground(Void... params) {
return null;
}
}
public class TestActivity extends Activity{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() { @Override
public void run() {
ExtendsAsyncTask.method();
}
};
new Thread(r).start();
}
}
なんと
ExtendsAsyncTask.method(); の呼び出しで
java.lang.ExceptionInInitializerError が発生してアプリが落ちてしまいます。
java.lang.ExceptionInInitializerError が発生してアプリが落ちてしまいます。
ちょっと原因をさぐってみました。
公開されてるAndroidのソースコード(今回は4.0.4)のAsyncTaskをみて
607行目
private static class InternalHandler extends Handler {
・・・
}
さらに
190行目
private static final InternalHandler sHandler = new InternalHandler();
が怪しいと判断 (判断するまでに至った理由は省略)
ちなみに static フィールドはClassがロードされた時に実行される (参考:static イニシャライザ)
public class ExtendsAsyncTask {
public static void method() {
}
private static InternalHandler handler = new InternalHandler();これでもおなじく
public static class InternalHandler extends Handler {
@Override
public void handleMessage(Message msg) {
}
}
}
java.lang.ExceptionInInitializerError が発生しました。
さらにコンパクトに下記の様にしても同様です。
public class ExtendsAsyncTask {
public static void method() {
}
private static Handler handler = new Handler();
}
つまりHandler 周りが怪しい?
( Handler さんには昔、かなりお世話になっておりました。。)
そして次にActivity自体を以下のようにしてみます。
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() {
@Override
public void run() {
new Handler();
}
};
new Thread(r).start();
}
}
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare()
という例外が発生します。
ほほう。。
ちょっとググったところ、以下のページがとても参考になったので掲載しておきます。
つまり内部動作を理解すると
各スレッドで Looper.prepare() を呼び出す前にnew Hander()を呼び出すな ということです。
(この記事の発展にもあるように UIスレッド場合は、フレームワーク側が呼び出している)
つまり
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() {
@Override
public void run() {
Looper.prepare();
new Handler();
}
};
new Thread(r).start();
}
}
こうすると落ちなくなります。
つまり、
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
Runnable r = new Runnable() {
@Override
public void run() {
Looper.prepare();
ExtendsAsyncTask.method();
}
};
new Thread(r).start();
}
}
元のコードはこうしても落ちなくなります。
そして、注目したいのは
public class TestActivity extends Activity
{
@Override
protected void onCreate(Bundle arg0) {
super.onCreate(arg0);
new ExtendsAsyncTask();
Runnable r = new Runnable() {
@Override
public void run() {
ExtendsAsyncTask.method();
}
};
new Thread(r).start();
}
}
というように UIスレッド上でExtendsAsyncTaskのインスタンスを生成すると、
アプリが落ちることはありません。
アプリが落ちることはありません。
これらが(Looper.prepare()をされたあとの)UIスレッド上で実行されるためです。
private static final InternalHandler sHandler = new InternalHandler();
private static class InternalHandler extends Handler {
・・・
}
筆者の場合
ExtendsAsyncTask.method();を呼び出す前に
ほとんどUIスレッド上でnew ExtendsAsyncTask();
でインスタンスを生成していたのですが、
ある処理の時、ExtendsAsyncTaskのインスタンスを一度も生成せずに
(ExtendsAsyncTaskをクラスをロードせずに)
直接 ExtendsAsyncTask.method();を別スレッドで実行していたことが判明し、
問題が発覚し、原因を追求しました。
他の状態に依存しない public static メソッドは
どこで定義しても大丈夫だろうという思い込みがあったのは
確かです。
どこで定義しても大丈夫だろうという思い込みがあったのは
確かです。
まぁAndroidのバグではないと思いますが、つまづきやすい仕様というか
なんというかという感じですが。
何かの参考になっていただければ幸いです。
まとめ
AsyncTaskを継承してるクラスのpublic staticメソッド呼び出しはUIスレッド
で行いましょう。
(というまとめでいいのかわかりませんが?)