もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

AndroidでAsyncTaskLoaderを使ってネットワーク上の画像をImageViewに表示してみる

今回は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に上げた写真を表示してみます。

f:id:Rei19:20140803142403p:plain

おーちゃんと画像が表示されていますね。実際のプロダクトでは最初にくるくる回るローディング画像を表示しておいて、lodeFinishedで画像を差し替えるような動きをした方が違和感ないインターフェースになると思います。間違ってるとこなどあればコメントください。

リポジトリはこちら
https://github.com/rei-m/androidSample