ナカザンドットネット

それって私の感想ですよね

AsyncTaskLoaderに手を出してみる

※2012/5/14 手を出してから4ヶ月後の使い方を d:id:Nkzn:20120514:1336979844 で紹介しています。



お仕事でHTTPレスポンスを非同期で受け取ってゴニョゴニョするよくある処理をしようとしています。
で、当初はHandler使って非同期処理を実現していたのですが、どうやらJavaの実務経験が乏しい僕では、Threadを直接触るのはまだ不安が残ることが身にしみて分かって来ました。
ということで、今まで敬遠していたAsyncTaskに触ってみようとしたのですが。。。

時代は AsyncTask より AsyncTaskLoader
http://archive.guma.jp/2011/11/-asynctask-asynctaskloader.html

どうやら時代はAsyncTaskLoaderのようです。ヽ(゚∀゚)ノ
コンパチに入っているのでAndroid 1.6以上で利用できますし、どうやらお手軽らしいですし、使ってみることにしましょう。

と、ここで、残念なお知らせが。

↑以外にAsyncTaskLoaderを詳しく説明している日本語サイトがない。
というか英語も公式以外にあんまりない。

AsyncTaskLoader | Android Developers
http://developer.android.com/reference/android/content/AsyncTaskLoader.html

某ましゅいさんには↑がシンプルだろと言われてしまいましたが、ちょっとパッと見ではどこからどこまでがAsyncTaskLoaderの使い方として本質的な処理なのか見えてきませんでした。僕のどしろうとめ(´;ω;`)ブワッ

まあ見つからないものは仕方ないので

作ることにしました。
AsyncTaskの超シンプルな例として、@vvakameさんのJsonPullParserのAndroid版サンプルに目をつけてみます。

jsonpullparser-android-sample
https://github.com/vvakame/JsonPullParser/tree/master/jsonpullparser-android-sample

HTTPで入手してきたJSONを整形してListViewに表示するだけ。分かりやすい。AsyncTaskもよく分かってない僕にも、何となく挙動が掴めます。
これを参考にしつつ強引に手を入れてAsyncTaskLoader化したものが以下になります。
今回はList型でデータを読み込んでくるイメージで作りました。

MainActivity.java

package net.nkzn.android.sample.asynctaskloader;

import java.util.List;

import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.widget.ArrayAdapter;
import android.widget.ListView;

public class MainActivity extends FragmentActivity implements LoaderCallbacks<List<String>>{
	
	ArrayAdapter<String> mAdapter;

	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // 結果表示用
		ListView listView = (ListView)findViewById(R.id.listview);
		mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1);
		listView.setAdapter(mAdapter);

		// ローダーを初期化
		Bundle args = new Bundle(1); //onCreateLoaderに渡したい値はここへ
		getSupportLoaderManager().initLoader(0, args, this);
    }

	/**
	 * ローダーを初期化した際に呼ばれる。
	 * (今回の例だとonCreate内での初期化)
	 */
	@Override
	public Loader<List<String>> onCreateLoader(int id, Bundle args) {
		// オブジェクト作って返すだけ
		StringsDownloadTaskLoader loader = new StringsDownloadTaskLoader(getApplication());
		loader.forceLoad(); //これでロードが始まる。AsyncTaskLoader#onStartLoading内に実装するのも可。
		return loader;
	}

	/**
	 * データ読み込みが完了したときに呼ばれる
	 * @param data AsyncTaskLoader#loadInBackgroundで返した値
	 */
	@Override
	public void onLoadFinished(Loader<List<String>> loader, List<String> data) {
		for(String s: data){
			mAdapter.add(s);
		}
		mAdapter.notifyDataSetChanged();
	}

	/**
	 * よくわかんね
	 */
	@Override
	public void onLoaderReset(Loader<List<String>> loader) {
		// TODO Auto-generated method stub
		
	}
}

StringsDownloadTaskLoader.java

package net.nkzn.android.sample.asynctaskloader;

import java.util.ArrayList;
import java.util.List;

import android.content.Context;
import android.support.v4.content.AsyncTaskLoader;

public class StringsDownloadTaskLoader extends AsyncTaskLoader<List<String>> {

	/**
	 * コンストラクタ。
	 * super(Context)を実行できる分には、コンストラクタの引数は何でもOK。
	 * この例ではMainActivity#onCreateLoaderから呼び出されます。
	 */
	public StringsDownloadTaskLoader(Context context) {
		super(context);
	}

	/**
	 * バックグラウンド処理したいもの。
	 * パラメータ指定した型でデータを返す必要がある。
	 */
	@Override
	public List<String> loadInBackground() {
		
		List<String> strings = new ArrayList<String>();

		try {
			Thread.sleep(500); //データ読み込みの代わり
			strings.add("hogehoge");
			strings.add("hugahuga");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return strings;
	}
	
}

JPPのサンプルを参考にしたといっておきながらサラっと逐次処理を消してしまいましたが、作者自身が


とか元も子もないことを言っていたので問題ないでしょう。

解説もどき

たぶんこれが最小構成に近いんじゃないかなあと思っています。
これが理解できれば@mocelさんのとか夜子ままのスライド*1も理解できるのでは。
↑は夜子ままのスライドっぽくAsyncTaskLoader#forceLoadを直接呼び出していますが、たぶん@mocelさんの真似してAsyncTaskLoader#onStartLoadingの中にforceLoadを実装したほうが幸せになれる気がする。というか、@mocelさんが記事の中で仰っている

キモはコンストラクタと loadInBackground() の辺りですかね。
その他のメソッドはわりと定型的な感じで、よくわからなくてもこう書いておけばうまく動く、程度に把握しといても当面は困らないです。

が正しいのがサンプルを作りながら実感できました。

個人的においしいところ

  1. Web上のちょっとしたファイルが欲しい時のスレッド管理の手間から解放される
  2. Activity/Fragmentから切り離せるのでコードがスッキリする
  3. AsyncTaskやThread管理を覚える負担から一時的に逃げ出せる
  4. コンパチ対応しているので、実質的に現存するほとんどのAndroid端末で使える
  5. 僕コンパチ活用してますよ!(ドヤァ という顔ができる


そんなわけで、もうちょっと使い込んでみようと思います。
プログレスバーの進捗状況表示処理とか、できるのかしら。

*1:22〜33P