今回はAsyncTaskLoaderを使って外部リソースを表示するImageViewを作ってみます。単にResource内の画像を表示するだけならImageViewのsrc属性にResource内のidを指定すれば終わるのですが、ネットワーク上の画像を表示する場合は指定したURLを開いてInputStreamをBitmapなりDrawableに変換してViewにセットしてあげる必要があるみたいです。AsyncTaskLoaderを使う理由はアプリのメインスレッドで呼ぶとNetworkOnMainThreadExceptionが発生する(Android3.x 系からっぽい)ので別スレッドで非同期で読み込ませてやる必要があるためです。んでは実装してみます。
前提
開発環境はAndroid Studio + Genymotion。SDKのターゲットは4.0以上とします。
作ってみよう
では作って行きます。まずはAsyncTaskLoaderを継承した読み込み用のクラスを作ります。指定したURLを読み込んでBItmapに変換する役目を持ちます。
HttpAsyncImageLoader.java
package me.rei_m.androidsample.utils; import android.content.AsyncTaskLoader; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import java.io.IOException; import java.net.HttpURLConnection; import java.net.URL; /** * Created by rei_m on 2014/08/01. */ public class HttpAsyncImageLoader extends AsyncTaskLoader<Bitmap> { private String mUrl; public HttpAsyncImageLoader(Context context, String url) { super(context); this.mUrl = url; } @Override public Bitmap loadInBackground() { try { URL url = new URL(mUrl); HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoInput(true); connection.connect(); return BitmapFactory.decodeStream(connection.getInputStream()); } catch (IOException e) { // 404はここでキャッチする e.printStackTrace(); } return null; } }
次にImageViewを継承したカスタムImageViewクラスを作ります。このビューはLoaderManager.LoaderCallbacksを実装して各ロード時のコールバックメソッドの処理を定義します。createLoader時に上で作ったローダーのインスタンスを生成し、loadFinish時にローダーが取得してきたBItmapを自身にセットして表示します。
HttpAsyncImageLoader.java
package me.rei_m.androidsample.views; import android.app.LoaderManager; import android.content.Context; import android.content.Loader; import android.graphics.Bitmap; import android.os.Bundle; import android.util.AttributeSet; import android.widget.ImageView; import me.rei_m.androidsample.utils.HttpAsyncImageLoader; /** * Created by rei_m on 2014/08/01. */ public class LoaderImageView extends ImageView implements LoaderManager.LoaderCallbacks<Bitmap>{ private String mUrl; public LoaderImageView(Context context) { super(context); } public LoaderImageView(Context context, AttributeSet attrs) { super(context, attrs); } public LoaderImageView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } // 表示する画像のURLをセット public void setUrl(String url){ this.mUrl = url; } @Override public Loader<Bitmap> onCreateLoader(int id, Bundle args) { return new HttpAsyncImageLoader(getContext(), this.mUrl); } @Override public void onLoadFinished(Loader<Bitmap> loader, Bitmap data) { setImageBitmap(data); } @Override public void onLoaderReset(Loader<Bitmap> loader) { } }
これで準備はOKです。ActivityとFragmentを作成し、Fragment側のlayoutにImageViewを追加します。レイアウトのデザイナを開き、パレットの一番下のCustomViewを選択すると上で作ったLoaderImageViewが選択出来るはずなので、それをえらんで画面に配置します。
次にフラグメント側に戻り、カスタムビューに画像のURLをセットしてローダーを起動する処理を実装します。
@Override public void onStart() { super.onStart(); // カスタムイメージビューを作成 LoaderImageView liv = (LoaderImageView) getActivity().findViewById(R.id.imageView); // 取得するURLをセット liv.setUrl("https://fbcdn-sphotos-f-a.akamaihd.net/hphotos-ak-xfp1/t1.0-9/10307218_714125065291821_116728579375456989_n.jpg"); // ローダーを作成しロード開始 Loader loader = getActivity().getLoaderManager().initLoader(0, null, liv); loader.forceLoad(); }
これでOKです。
動かしてみよう
アプリを起動してみます。今回は僕がFacebookに上げた写真を表示してみます。
おーちゃんと画像が表示されていますね。実際のプロダクトでは最初にくるくる回るローディング画像を表示しておいて、lodeFinishedで画像を差し替えるような動きをした方が違和感ないインターフェースになると思います。間違ってるとこなどあればコメントください。