AsyncTaskLoaderを使ってみる
AsyncTaskLoader
こんにちは。こむろです。最近のクールでナウな非同期処理を行うためには、Loaderクラスというものが使われるようです。Android3.0(API Level 11)から導入されている非同期処理を行うクラスです。サブクラスとしてAsyncTaskLoaderやCursorLoaderが定義されています
Loaderクラスは、SupportPackageにも入っていますのでどのVersionでも利用が可能です。今回は今までAsyncTaskで行っていた処理をAsyncTaskLoaderで実装しなおし、その違いを見てみます
リニューアルされたリファレンスページには、Loader関係の情報はここに集約されています。ここの説明をざっと見てみると
Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. Loaders have these characteristics:
・They are available to every Activity and Fragment.
・They provide asynchronous loading of data.
・They monitor the source of their data and deliver new results when the content changes.
・They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data.
非同期処理で行うための機構のようです。Fragmentでも非同期処理が利用できるように、新たに設計し直されたクラスでしょうか
以前は、AsyncTaskを利用して非同期処理を実現していたと思います。AsyncTaskの説明には次のように記述されています
AsyncTask enables proper and easy use of the UI thread. This class allows to perform background operations and publish results on the UI thread without having to manipulate threads and/or handlers
AsyncTaskは、HandlerやThreadを利用することなく、UI Threadを利用することでBackgroundの処理を行っていたようです。Loaderクラスの説明を見ると、UI Threadと言った単語は見受けられないので(実際やってみましたが、UI処理をすると例外を吐いて落ちます)、実行できることは同じですが、根本的に設計が異なるクラスのようです
AsyncTaskの動作イメージ
同じ画面のUIを制御するコードのはずですが、複数のクラスにまたがって埋め込まれてしまっています
AsyncTaskLoaderの動作イメージ
同じ画面で表示するProgressや非同期処理完了後のViewの更新処理など、UIの制御に関するコードは一箇所に固めることができました。AsyncTaskLoaderからは結果のみLoaderCallbackを通じて通知されています
次にAsyncTaskLoaderを継承して実装した場合、具体的にどのような違いがあるかを見ていきます
実装例(基本の基)
URLを指定し、Web上にある画像を取得し、それをImageViewに表示するという処理を考えてみます
AsyncTaskを用いる場合
public class DownloadImageAsyncTaskHelper extends AsyncTask<String, String, Bitmap> { @Override protected Bitmap doInBackground(String... urls) { String url = urls[0]; // Progress Dialogを表示する publishProgress("Loading from " + url); return HttpUtil.getBitmapHttpService(context, url); } @Override protected void onProgressUpdate(String... progress) { // ProgressDialogに文字列を設定する setProgressMessage(progress[0]); } @Override protected void onPostExecute(Bitmap result) { // 非同期で読み込みが完了した画像をViewに表示する showImage(result); } }
非同期処理に必要な初期パラメーター、途中経過を示すProgressのパラメーター、結果の型、など、なかなか指定するものが多いですね
さらに、doInBackground()で実行される非同期の画像読み込み処理とshowImage(), setProgerssMessage()等のUIの更新処理も含んでいます。これは、AsyncTaskはUI Thread上で実行されているため、UIの変更も出来てしまうのが原因です
AsyncTaskLoaderを用いる場合
同じようにURLを指定し、Web上の画像を取得し、ImageViewに表示する処理を実装してみます
public class DownloadImageAsyncTaskLoaderHelper extends AsyncTaskLoader<Bitmap> { private String imageUrl = ""; private Context context = null; public DownloadImageAsyncTaskLoaderHelper(Context context, String url) { super(context); this.imageUrl = url; this.context = context; } @Override public Bitmap loadInBackground() { return HttpUtil.getBitmapHttpService(context, url); } @Override protected void onStartLoading() { forceLoad(); } }
Progressの表示、非同期処理終了時のViewの更新などのUI制御のコードがなくなり、かなりすっきりしました。UI Threadが絡んでいないため、UIの変更はここでは行えません。もしAsyncTaskのようにUIの変更を行うとUI Thread以外からView変更を行った時にThrowされるいつもの例外が発生します
Caused by: android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
さらにAsyncTaskに比べて異なるのは、結果の型のみの指定になっています。では、どこで終了時のViewの更新などのUIの更新を行えば良いのでしょうか?
AsyncTaskLoaderの結果を受け取るには
LoaderCallbacksというInterfaceを実装してやることで、非同期処理の結果を受け取ることができます
一点注意すべきところがあります。SupportPakcageを利用する場合、Loaderを利用するためのメソッド(getSupportLoaderManager )が「FragmentActivity」に用意されているので、Activityで実行したい場合は「FragmentActivity」を継承してやる必要があります
public class MainActivity extends FragmentActivity implements LoaderCallbacks<Bitmap> { // snip // Loaderの初期化から起動までを行います public void startAsyncLoadImage(String url) { Bundle args = new Bundle(); args.putString("url", url); getSupportLoaderManager().initLoader(0, args, this); // onCreateLoaderが呼ばれます // 複数のLoaderを同時に動かす場合は、第一引数を一意のIDにしてやる必要があります。 // GridViewなどに表示する画像を非同期で一気に取得する場合とか } @Override public Loader<Bitmap> onCreateLoader(int id, Bundle args) { // 非同期で処理を実行するLoaderを生成します. // ここを切り替えてあげるだけで様々な非同期処理に対応できます. if(args != null) { String url = args.getString("url"); return new DownloadImageAsyncTaskLoaderHelper(this, url); } } @Override public void onLoadFinished(Loader<Bitmap> arg0, Bitmap arg1) { // 非同期処理が終了したら呼ばれます. // 今回はDownloadが完了した画像をImageViewに表示します. ImageView imageView = (ImageView)findViewById(R.id.imageview); Drawable iconImage = new BitmapDrawable(getResources(), bmp); imageView.setImageDrawable(iconImage); imageView.invalidate(); } @Override public void onLoaderReset(Loader<Bitmap> arg0) { } }
このように、非同期処理の部分とUI更新の部分を明確に分けることができます。UIの制御に関わる部分が一点に集中できコードが分散しないため、より役割分担が明確になりました。かなり見通しが良くなると思います
まとめ
- AsyncTaskLoaderを使うと、UI制御と非同期処理の役割分担を明確に分けることができる
- SupportPackageに入っているので、今すぐにでも使える
- 何より最新の非同期処理実装なのでクールである(多分)
次回は、GridViewなどで一気に複数の画像を非同期で取得するための、AsyncTaskLoaderの実装例を見ながら、もう少し実用的な解説をしたいと思います