AsyncTask#doInBackgroundの戻り値を考える
AsyncTaskって不親切よね
だってエラー処理がしにくいもの
doInBackgroundの戻り値がResultのみなので、非同期処理中にエラーが発生したとき
どんな理由でエラーが発生したとか、その時のメッセージはどれにするとか
指定することができません。
不親切なら自分でよくする
戻り値を独自のクラスにしてしまえばなんとでもなるよね
サンプルアプリつくる
画像をダウンロードしてくるアプリを作ろうと思います。
ありがちですね。ぐだぐだ設計します。
しばらく箇条書きが続くので、めんどい人は飛ば(ry
設計
処理の流れ
- ネットワークから画像をダウンロードしてくる
- 画像のダウンロードが終わるとUIに表示する
- ネットワークの障害がある場合、その旨をユーザに伝える
クラスの設計とか
- MainActivity
- ユーザへの情報伝達は全てActivityが行う
- DownloadImageTask
- AsyncTaskでネットワークからデータをダウンロードしてくる
- AsyncTask内でユーザへの情報伝達はProgressDialogのみ
- DownloadImageTaskCallback
- AsyncTaskからAcitivtyへの情報の伝達はコールバックを使う
- AsyncTaskResult
MainActivityのUI設計
- btnDownload:Button
- ボタン押下で画像をダウンロードしてくる
- ダウンロードした画像をimgViewerに表示する
- imgViewer:ImageView
- ダウンロードした画像を表示する場所
- :Toast
- メッセージを表示する
DownloadTaskのインターフェイス定義
AsyncTaskResultのインターフェイス定義
- getContent:T
- タスクの実行結果を返す
- getResourceId():int
- エラーメッセージのリソースIDを返す
- isError():boolean
- タスクの実行結果がエラーの場合trueを返す
- static createNormalResult(content:T):AsyncTaskResult
- タスクの実行が成功した時の戻り値を作成する
- static createErrorResult(resId:int):AsyncTaskResult
- タスクの実行が失敗した時の戻り値を作成する
DownloadTaskCallbackのインターフェイス定義
- onSuccessDownloadImage(image:Bitmap):void
- ダウンロードが成功した時に呼ばれる
- onFailedDownloadImage(statusNo:int):void
- ダウンロードが失敗した時に呼ばれる
だいたい
こんな感じでもうコーディングしていいよね。
定義飽きたー!
だいたいこんなん誰が読むんだ!
ソース
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="jp.tomorrowkey.android.downloadimage" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MainActivity" android:label="@string/app_name"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> <uses-sdk android:minSdkVersion="4" /> <uses-permission android:name="android.permission.INTERNET"></uses-permission> </manifest>
string.xml
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">DownloadImage</string> <string name="main.button.download">ダウンロード</string> <string name="dialog.message.downloading">ダウンロード中...</string> <string name="toast.not_found">画像がありません</string> <string name="toast.server_error">サーバーエラー</string> <string name="toast.uri_syntax_error">URLが不正です</string> <string name="toast.network_error">ネットワークエラー</string> <string name="toast.unkown_error">不明なエラー</string> </resources>
main.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent"> <Button android:id="@+id/btnDownload" android:text="@string/main.button.download" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <ImageView android:id="@+id/imgViewer" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
MainActivity.java
package jp.tomorrowkey.android.downloadimage; import android.app.Activity; import android.graphics.Bitmap; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; import android.widget.ImageView; import android.widget.Toast; public class MainActivity extends Activity implements OnClickListener, DownloadImageTaskCallback { /* * 画像のURL * 画像はpng形式でないとうまく動作しません(android1.6で確認) */ private static final String IMAGE_URL = "http://xxx.xxx/image.png"; /* ダウンロードボタン */ private Button btnDownload; /* 画像を表示する部分 */ private ImageView imgViewer; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); btnDownload = (Button) findViewById(R.id.btnDownload); btnDownload.setOnClickListener(this); imgViewer = (ImageView) findViewById(R.id.imgViewer); } @Override public void onClick(View view) { int id = view.getId(); if (id == R.id.btnDownload) { // ダウンロードを開始する DownloadImageTask task = new DownloadImageTask(this, this); task.execute(IMAGE_URL); } } @Override public void onSuccessDownloadImage(Bitmap image) { // ダウンロードした画像を設定する imgViewer.setImageBitmap(image); } @Override public void onFailedDownloadImage(int resId) { // エラーの内容をトーストに表示する Toast.makeText(this, resId, Toast.LENGTH_LONG).show(); } }
DownloadImageTaskCallback.java
package jp.tomorrowkey.android.downloadimage; import android.graphics.Bitmap; /** * DownloadImageTaskのコールバックインターフェイス * * @author tomorrowkey * */ public interface DownloadImageTaskCallback { /** * 画像のダウンロードが成功した時に呼ばれるメソッド * * @param image * ダウンロードした画像 */ void onSuccessDownloadImage(Bitmap image); /** * 画像のダウンロードが失敗した時に呼ばれるメソッド * * @param resId * エラーメッセージのリソースID */ void onFailedDownloadImage(int resId); }
DownloadImageTask.java
package jp.tomorrowkey.android.downloadimage; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import android.app.ProgressDialog; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; /** * ネットワークから画像をダウンロードする非同期タスク * * @author tomorrowkey * */ public class DownloadImageTask extends AsyncTask<String, Void, AsyncTaskResult<Bitmap>> { private DownloadImageTaskCallback callback; private ProgressDialog progressDialog; public DownloadImageTask(Context context, DownloadImageTaskCallback callback) { this.callback = callback; // プログレスを作成する progressDialog = new ProgressDialog(context); progressDialog.setMessage(context.getText(R.string.dialog_message_downloading)); } public void onPreExecute() { // プログレスを表示する progressDialog.show(); } @Override public void onProgressUpdate(Void... values) { } @Override public AsyncTaskResult<Bitmap> doInBackground(String... params) { try { // 画像をダウンロードする HttpClient client = new DefaultHttpClient(); HttpGet get = new HttpGet(new URI(params[0])); HttpResponse response = client.execute(get); int statusCode = response.getStatusLine().getStatusCode(); switch (statusCode) { case HttpStatus.SC_OK: // 画像をダウンロードする InputStream is = response.getEntity().getContent(); Bitmap bitmap = BitmapFactory.decodeStream(is); is.close(); return AsyncTaskResult.createNormalResult(bitmap); case HttpStatus.SC_NOT_FOUND: // 画像がありません return AsyncTaskResult.createErrorResult(R.string.toast_not_found); default: // サーバーエラー return AsyncTaskResult.createErrorResult(R.string.toast_server_error); } } catch (URISyntaxException e) { // URLが不正です return AsyncTaskResult.createErrorResult(R.string.toast_uri_syntax_error); } catch (ClientProtocolException e) { // ネットワークエラー return AsyncTaskResult.createErrorResult(R.string.toast_network_error); } catch (IllegalStateException e) { // 不明なエラー return AsyncTaskResult.createErrorResult(R.string.toast_unkown_error); } catch (IOException e) { // 不明なエラー return AsyncTaskResult.createErrorResult(R.string.toast_unkown_error); } } public void onPostExecute(AsyncTaskResult<Bitmap> result) { // プログレスを閉じる progressDialog.dismiss(); if (result.isError()) { // エラーをコールバックで返す callback.onFailedDownloadImage(result.getResourceId()); } else { // ダウンロードした画像コールバックでを返す callback.onSuccessDownloadImage(result.getContent()); } } }
AsyncTaskResult.java
package jp.tomorrowkey.android.downloadimage; /** * AsyncTaskのdoInBackgroundからonPostExecuteに渡す引数に仕様するクラス * * @author tomorrowkey * * @param <T> */ public class AsyncTaskResult<T> { /** * AsyncTaskで取得したデータ */ private T content; /** * エラーメッセージのリソースID */ private int resId; /** * エラーならtrueが設定されている */ private boolean isError; /** * コンストラクタ * * @param content * AsyncTaskで取得したデータ * @param isError * エラーならtrueを設定する * @param resId * エラーメッセージのリソースIDを指定する */ private AsyncTaskResult(T content, boolean isError, int resId) { this.content = content; this.isError = isError; this.resId = resId; } /** * AsyncTaskで取得したデータを返す * * @return AsyncTaskで取得したデータ */ public T getContent() { return content; } /** * エラーならtrueを返す * * @return エラーならtrueを返す */ public boolean isError() { return isError; } /** * stringリソースのIDを返す * * @return stringリソースのIDを返す */ public int getResourceId() { return resId; } /** * AsyncTaskが正常終了した場合の結果を作る * * @param <T> * @param content * AsyncTaskで取得したデータを指定する * @return AsyncTaskResult */ public static <T> AsyncTaskResult<T> createNormalResult(T content) { return new AsyncTaskResult<T>(content, false, 0); } /** * AsyncTaskが異常終了した場合の結果を作る * * @param <T> * @param resId * エラーメッセージのリソースIDを指定する * @return AsyncTaskResult */ public static <T> AsyncTaskResult<T> createErrorResult(int resId) { return new AsyncTaskResult<T>(null, true, resId); } }
動作
ht-03a(android1.6)で動作を確認しています
IMAGE_URLにpng画像のURLを入れると画像がダウンロードされ、画面に表示されます。
jpgやgifだと画像のダウンロードはできますが、表示はできません。
ネットワークからのデコードに対応してないみたいです(android1.6)
urlに不正な値を入れてみるとエラーメッセージがトーストで表示されます。
まとめ
ここを読んでくれている人はいるのだろうか…。
コードが長すぎてもう読まねえよって言ってる人いるんじゃないだろうか…。
こんなにぺたぺたするならGitHubにあげろよって人いるんじゃないだろうか…。
くじょうはついったーでうけつけてます(´・ω・`)