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に精通してないとなかなか大変でしょう。

[Android] なぜJava標準Threadクラスを基本的にAndroidで使用しないほうがいいのか? Loader、AsyncTaskLoader徹底解剖 番外編 Part1


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


なぜJava標準Threadクラスを基本的にAndroidで使用しないほうがいいのか?


さて、Androidで非同期処理をする方法の1つに
Java標準のThreadクラスを使用する
というものがありますが、なぜ使用しないほうがいいのでしょう?
この方法には以下の欠点があります。
  1. AndroidにはViewを操作したい場合はUIスレッドからしか行えないという制限がある
  2. Threadクラスにはデフォルトで安全にキャンセルする方法がない
  3. Threadクラスにはデフォルトでプールがない
詳しく説明していきましょう。


AndroidにはViewを操作したい場合はUIスレッドからしか行えないという制限がある


まず、1.ですが、これはAndroid自体の制限で、Viewを操作するにはUIスレッドからしか行えない。
というルールがあります。
故に自分でスレッドを生成し、処理結果を元に自分で生成したスレッド内でViewを操作しようとすると
下記のようなエラーになります。

android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.

しかし、この問題に関して言えばHanderというものを使えばその問題を解決できます。

new Handler(Looper.getMainLooper()).post(new Runnable()
{
    @Override
    public void run()
    {
        //View操作のコード
    }
});

というコードを自分で生成したスレッド内に書けば
そのスレッドからUIスレッドに「こういう処理をしてね」とお願いすることができます。
ただ、2.や3.に関しては自前で頑張るしかありません。


Threadクラスにはデフォルトで安全にキャンセルする方法がない


さて、2.のキャンセルに関してですが、
Threadクラスにはぱっと見stopやsuspend、resumeというメソッドがあるので、一見できそうです。
・・・がこれらのメソッドは非推奨になっています。
これに関しては正式にわざわざアナウンスされております。

Why Are Thread.stop, Thread.suspend, Thread.resume and Runtime.runFinalizersOnExit Deprecated?

スレッドを止める方法がないとActivityなどが破棄されスレッドの結果を受け取る先がなくなった際に、
すでに結果を返却する場所がないのに処理を続けてしまうという事になったりします。


Threadクラスにはデフォルトでプールがない


また、3.に関しては何度も何度も短いスパンで非同期処理を行うときの話ですが、
非同期処理のたびに「スレッドを生成し、処理が終了すると破棄する」という動作をさせると、
スレッドの生成や破棄のコスト(時間的にもCPUやメモリのリソース)もなかなか高くなります。
それが短い時間で何度も行われるとかなりの負荷になり得ます。
そこでうまくスレッドを再利用して無駄を減らすという仕組みをプールといいますが、
これを自前で作って管理するのもなかなか大変です。


まとめ


上記などの理由によってAndroidではThreadで非同期処理はしないほうがいいと思います。
場合によってはThreadクラスを使った方がいいケースも存在しますが、
ある程度Androidやスレッドに深い理解がないと
誤った使い方や非効率な使い方をする可能性が高いため、
基本的にはThreadクラスの使用は避けたほうが無難でしょう。

[Android] そもそもLoader、AsyncTaskLoaderとは? Loader、AsyncTaskLoader徹底解剖 Part1


皆さんLoader、AsyncTaskLoaderをご存知ですか?
知らない人のために簡単にだけ説明すると、
Androidで非同期処理を行うための仕組みです。


Androidで非同期処理をする方法


Androidで非同期処理をする方法はいくつかあります。
  1. Java標準のThreadクラスを使用する
  2. Androidが用意しているAsyncTaskを使用する
  3. Androidが用意しているLoader、AsyncTaskLoaderを使用する
他にも特殊な方法としてServiceやBroadcastを使用する方法などもありますが、
通常ActivityやFragmentなどで非同期処理をするときは主に上記の3つだと思います。
しかし何故その中でわざわざLoader、AsyncTaskLoaderを使用しなければいけないのでしょう・・・?
Androidが用意した最新の仕組みだからそっちのほうがなんとなくいい・・・
ただそれだけの理由で使ってる人も案外いらっしゃるんじゃないでしょうか?
実際1.と2.にはいくつか問題があります。


Java標準のThreadクラスやAsyncTaskの問題点


それぞれを詳しく解説すると結構長くなってしまうので別途下記リンクにまとめました。
ThreadクラスやAsyncTaskの問題を解決し、更に便利な機能をつけた非同期処理の仕組みとして
Loader、AsyncTaskLoaderが提供されました。


Loader、AsyncTaskLoaderのメリット


それではLoader、AsyncTaskLoaderのメリットは何でしょうか?
  1. 基本的にJava標準のThreadクラスやAsyncTaskの問題点を解決している
  2. 処理結果をキャッシュとして持っているため、次回からは再処理することなく結果を取得できる
  3. Configuration Change発生後、Loaderを再設定すれば再処理することなく最終結果を取得できる
  4. Loaderはデータソースを監視してるため、内容が変更された際に新しい結果を取得できる
まず1.に関してはJava標準のThreadクラスやAsyncTaskの問題点のところにあった
リンクを参照するとどういう問題があるのかわかるでしょう。
基本的にLoader、AsyncTaskLoaderはこれらの問題を解決しています。


Loader、AsyncTaskLoaderってほんとうに便利なの?


これらだけ見ると何だかメリットばっかりで是が非でも使うべきみたいに見えますよね?
でも、実際に使ってみると・・・非常に使いづらい・・・。
  • メソッド名が紛らわしい、わかりづらい
  • ドキュメントの説明が紛らわしい、わかりづらい
  • 意外と詳しく解説しているサイトがない(特に日本語)
という情報面で色々苦労します。

さらに
  • 処理中にHomeボタンをおした時どういう動作するのか?
  • 処理中にBackボタン押した時はどういう動作をするのか?
  • 処理中にConfiguration Changeが発生(画面回転など)した時はどういう動作をするのか?
  • 処理後にConfiguration Changeが発生(画面回転など)した時はどういう動作をするのか?
など、どんな時にどんな挙動するのかを把握し正しく使用しないと
特定の場合のみ起こるバグに繋がったりします。


まとめ


使いづらいし、どういう動作をするかきちんと把握しないとバグにつながる。
しかし、きちんと使いこなせれば他の手法にはないメリットもあります。
つまり正しい使用方法を理解すればこの上ない武器になります。
ゆえにLoader、AsyncTaskLoaderを徹底解剖したいと思います!