2014年4月5日土曜日

[Android] 非同期処理はAsyncTaskで十分じゃないの? Loader、AsyncTaskLoader徹底解剖 番外編 Part2


おそらくそもそもLoader、AsyncTaskLoaderとは?から来た人が多いと思うので説明は不要でしょうが、
この記事はLoader、AsyncTaskLoaderについての記事の番外編です。


非同期処理はAsyncTaskで十分じゃないの?


さて、Androidで非同期処理をする方法の1つに
Androidが用意しているAsyncTaskを使用する
というものがあります。
すでに非同期処理の仕組みを用意してるのに
なぜわざわざLoader、AsyncTaskLoaderを後から用意したのでしょう?
いくつか理由があるようですが、私が思う1番の理由は

ActivityやFragmentのライフサイクルに対応していない

ということだと思います。

他にもAsyncTaskのonPostExecuteメソッドでUIやDialogをいじることが多く、
ActivityやFragmentの構成に左右されやすいというのも問題としてあげられることもありますが、
これに関しては上手に継承を使ったり自前でインターフェイスを定義してあげるなりすれば、
解決できると思うので私は問題というほどではないと思ってます。

では、ActivityやFragmentのライフサイクルに対応していないと何が問題なのでしょうか?


ActivityやFragmentのライフサイクルに対応してないことで発生する問題


ActivityやFragmentが破棄された後もonPostExecuteが呼ばれる


例えば以下のパターンを見てみいきましょう。

Activityのコード

protected final static String _TAG="AsyncTaskBadCase1";

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    new BadAsyncTask().execute();
    finish();
}

@Override
protected void onDestroy()
{
    super.onDestroy();
    Log.d(_TAG,"onDestroy");
}

onCreateメソッド内でAsyncTaskを実行し、そのまますぐActivityを終了させてます。
また、onDestroyメソッド内でログを出力させています。

AsyncTaskのコード

protected final static String _TAG="AsyncTaskBadCase1";

@Override
protected Void doInBackground(Void...params)
{
    Log.d(_TAG,"doInBackground");
    try
    {
        Thread.sleep(3000);
    }
    catch(InterruptedException e)
    {
        //サンプルなので例外処理は省略
    }
    return null;
}

@Override
protected void onPostExecute(Void result)
{
    super.onPostExecute(result);
    Log.d(_TAG,"onPostExecute");
}

doInBackgroundメソッド内、つまり非同期で処理をするところでは、
ログを出力し、今のスレッドを3秒間ストップさせてます。
そしてonPostExecuteメソッド内、つまり非同期処理が終了した時に実行するメソッドでも
ログを出力しています。

これを実際に実行してみると下記のようなログが出力されます。

doInBackground
onDestroy
onPostExecute

これを見るとonDestroy・・・つまりActivityが破棄された後も処理が続き、
onPostExecuteが呼ばれているということがわかります。

例えば下記コードのようにonPostExecuteでDialog.dismissメソッドを呼んでいるとします。

@Override
protected void onPostExecute(Void result)
{
    super.onPostExecute(result);
    dialog.dissmiss();
}

すると、Activityが破棄された後にonPostExecuteが呼ばれた場合、
下記エラーでアプリケーションがクラッシュします。

java.lang.IllegalArgumentException: View not attached to window manager

onDestroyの時にAsyncTaskのcancelメソッドを呼ぶとonPostExecuteは実行されなくなる仕様ですが、
Androidのバージョン(Android 2.3未満)によってはonDestroyの時に
AsyncTaskのcancelメソッドを呼んでもonPostExecuteが実行されてしまうというバグがあります。


Configuration Changeが起きた時にバグが発生しやすい


Configuration Changeが発生した(例えば端末を傾けて画面を回転させた)場合、
Activityは一旦破棄された後、再度生成されます。
つまり、onDestroyが呼ばれるわけですがそうなるとさっき言ったように
Dialogなどの操作をonPostExecuteで行っているとクラッシュする可能性があります。

また、onCreateに書いてある非同期処理を何度もされるのは都合が悪い時があります。
例えば、サーバにデータを登録するための通信などが端末を傾けるたびに、
何度も送信されると2重3重にデータが登録されたりします。

じゃ、画面回転をさせなければいい。ということで回転を抑制する場合もありますが、
Configuration Changeは画面回転以外にも標準フォントのサイズを変えたり、
外部キーボードをつないだり、ユーザーインターフェースモードを変えたり、
新しいSIMを装着したりしても発生します。
標準フォントサイズは一応抑制できますが、外部キーボードや新しいSIMに関しては
ソフトウェアから抑制はできません。


まとめ


AsyncTaskはシンプルなコードで非同期処理を行うことができます。
しかし、ActivityやFragmentのライフサイクルにきちんと対応したコードを書くのは結構大変です。
非同期処理の最中にユーザがBackボタンを押したり、Homeボタンを押したり、端末を回転させたりと
様々なイベントが起きる可能性があります。
それら全てを自前でキャッチして対処するのはかなりAndroidに精通してないとなかなか大変でしょう。

0 件のコメント:

コメントを投稿