UIスレッド(メインスレッド)とは別のスレッドで処理を行う必要があります。
メインスレッドでネットワーク接続しようとすると
例外吐いてアプリが落ちやがります。
※どうでもいいけど Android ってビルドでは何も言われないのに
実行すると落ちるみたいなパターンけっこう多いような。。。
で、非同期でのネットワーク処理には AsyncTask クラスを使用するのが
最もポピュラーなやり方かと思います。
具体的な処理はGoogle先生にでもお尋ねすればいくらでも出てくるので
ざっくり省略しますが、だいたい↓こんな感じの処理となります。
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button button = (Button)findViewById(R.id.button1); //ボタンを押したら非同期処理実行 button.setOnClickListener(new OnClickListener(){ @Override public void onClick(View arg0) { //非同期処理を実行させる new MyAsyncTask().execute(); } }); } //非同期処理を行うためのクラス private class MyAsyncTask extends AsyncTask<Void, Void, Void>{ @Override protected Void doInBackground(Void... params) { //別スレッドで実行される非同期処理 return null; } @Override protected void onPostExecute(Void result){ //メインスレッドで実行される処理 //(非同期処理の結果をUIに反映する等) } } }
で本題。
タイトルにある通り、特定の条件において
非同期処理が終わった後に onPostExecute が呼ばれない
とかいう状況が発生します。
参考)onPostExecute not being called in AsyncTask
要は AsyncTask クラスのスタティックイニシャライザがメインスレッド以外で
実行された場合、結果的に onPostExecute がそのスレッド(非メインスレッド)に
紐付けられるようです。
※当然といえば当然ですが onPreExecute も同様。
という書き方をすると
「そもそも非メインスレッドから AsyncTask なんか呼ばねーよ m9(^Д^)プギャー」
とか思われるかもしれませんが、
↑のリンクにもある通り、
C2DM(プッシュ通知機能)なんかではわりとポピュラーに発生する、
てかサンプルコードがそれを実際にやってやがるみたいです。
てか私自身もGCM(C2DMの後継)で見事にハマったワケで。
※GCMIntentService の onRegistered で受け取った regId を Web サービスに
登録しようとして AsyncTask 使ったら見事にこの状態になったっぽい。
その AsyncTask 自体はUIいじってるワケでもないのに。。。
回避策としてはアクティビティの onCreate メソッドとか、
・確実にUIスレッドで実行される箇所
・なるべく早い段階
で、例えば以下のようにして AsyncTask クラスに触れてやればいいみたいです。
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); //とりあえず一度 AsyncTask に触れてやる try{ Class.forName("android.os.AsyncTask"); }catch(ClassNotFoundException e){} //以下略 }
…それにしても Android って標準クラスのバグがまだまだ多いみたいで。
1つのアプリ作るのに既に4つくらい標準クラスのバグに遭遇してるんですが
何すかこの大当たりっぷり。。。
一応↑の StackOverFlow の解説もざっと折りたたみで載せときますか。
----- 以下 StackOverFlow の解説 -----
これは Android フレームワークの AsyncTask のバグによるものです。
AsyncTask.java に以下のようなコードがあります。
private static final InternalHandler sHandler = new InternalHandler();
これは AsyncTask クラスがメインスレッドで初期化されることを
想定しているものですが、非メインスレッドに関しても AsyncTask の
スタティックイニシャライザを走らせる可能性がある以上、必ずしも
メインスレッドで初期化が行われることは保証されません。
※Handler がワーカースレッド(非メインスレッド)を参照するような環境で
この問題が再現することを確認済みです。
この現象が一般的に発生するパターンとして IntentService クラスを使用した
場合が挙げられます。
(C2DM のサンプルコードが実際にそれをやっています。)
この問題については、以下のコードをアプリケーションの onCreate メソッドに
追加することで回避可能です。
Class.forName("android.os.AsyncTask");
これにより AsyncTask クラスを強制的にメインスレッドで初期化させる
ことができます。
この件については Android バグデータベースに登録しましたので
以下ご参照ください。
http://code.google.com/p/android/issues/detail?id=20915
0 件のコメント:
コメントを投稿