HTTP による画像ファイルのダウンロード

HTTP を利用してウェブサーバー上の画像ファイルを取得して、ImageView に表示する方法を紹介します。

あまり単純化しすぎず、ダウンロードする所のコードだけではなく、 実際に多くの場合に必要になるであろうサービスを利用した非同期のダウンロードとします。

これを理解するには、Java の基本的な I/O であるストリームAndroid でのファイルの保存方法サービスの動作 (IntentService やアクティビティとの通信方法) 等を理解しておく必要があります。

HTTP で画像をダウンロードするサンプルプログラム

ここで作るプログラムは次のようなものです。

HTTP による画像ファイルのダウンロード

画面のように URL の入力ボックスとボタン、それと ImageView が配置されています。ImageView には初期値として Android アイコンが表示されています。

また、初期状態では見えていませんがボタンの下に進捗状況を表示するための TextView もおいてます。

HTTP による画像ファイルのダウンロード

ボタンをクリックするとダウンロードが始まり、ダウンロードが進むにつれてその進捗状況が表示されます。ダウンロードが完了すると、 下部の ImageView に画像が表示されます。

さて、こうしたプログラムをどのように作ったら良いか考えてみましょう。

プログラムの概要

プログラムの全体像はこうです。主な要素としては、アクティビティ、ブロードバンドレシーバ、ダウンロード用サービスの三つあります。

ボタンを押したとき、ダウンロード用サービスを開始します。このとき URL を渡します。

HTTP による画像ファイルのダウンロード

サービスは IntentService として作成して専用のスレッドで実行します。

ダウンロード java.net.HttpURLConnection を利用します。データは HttpURLConnection から取得できる InputStream として受けとります。 これをバイナリデータとしてそのままファイルに保存します。

ダウンロードの進捗状況は、ブロードキャストすることによって行います。この辺は 「ブロードキャストレシーバの実装によるサービスとアクティビティの通信」 を参考にしてください。

ソースコード

アクティビティは次のように実装しました。

package com.keicode.android.test;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;

public class Download1Activity extends Activity
  implements OnClickListener {
  
  static String TAG = "Download1";
  
  EditText urlEditText;
  Button startButton;
  TextView progressTextView;
  ImageView imageView;
  
  DownloadProgressBroadcastReceiver progressReceiver;
  IntentFilter intentFilter;
  
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    findViews();
    setListeners();
    registerDownloadBroadcastReceiver();
  }
  
  protected void findViews(){
    urlEditText = (EditText)findViewById(R.id.urlEditText);
    startButton = (Button)findViewById(R.id.startButton);
    progressTextView = (TextView)findViewById(R.id.progressTextView);
    imageView = (ImageView)findViewById(R.id.imageView1);
  }
  
  protected void setListeners(){
    startButton.setOnClickListener(this);
  }
  
  protected void registerDownloadBroadcastReceiver(){
    progressReceiver = new DownloadProgressBroadcastReceiver();
    intentFilter = new IntentFilter();
    intentFilter.addAction("DOWNLOAD_PROGRESS_ACTION");
    registerReceiver(progressReceiver, intentFilter);
  }

  protected void startDownload(){
    Intent intent = new Intent(getBaseContext(), DownloadService.class);
    intent.putExtra("url", urlEditText.getText().toString());
    startService(intent);
  }
  
  @Override
  public void onClick(View v) {
    switch(v.getId()){
    case R.id.startButton:
      startDownload();
      break;
    }
  }
  
  class DownloadProgressBroadcastReceiver 
    extends BroadcastReceiver {
  
    @Override
    public void onReceive(Context context, Intent intent) {
      // Show Progress
      Bundle bundle = intent.getExtras();
      int completePercent = bundle.getInt("completePercent");
      int totalByte = bundle.getInt("totalByte");
      String progressString = totalByte + " byte read.";
      if(0 < completePercent){
        progressString += "[" + completePercent + "%]";
      }
      progressTextView.setText(progressString);
      
      // If completed, show the picture.
      if(completePercent == 100){
        String fileName = bundle.getString("filename");
        Bitmap bitmap = BitmapFactory.decodeFile(
          "/data/data/com.keicode.android.test/files/"
          + fileName);
        if(bitmap != null){
          imageView.setImageBitmap(bitmap);
        }
      }
    }
  
  }
  
}

ブロードキャストレシーバも内部クラスとして簡単に実装してある所に注意してください。

サービス側は次の通りです。

package com.keicode.android.test;

import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;

import org.apache.http.HttpException;

import android.app.IntentService;
import android.content.*;

import android.os.Bundle;
import android.util.Log;

public class DownloadService extends IntentService {

  static final String TAG = "Download1";
  
  public DownloadService() {
    super(TAG);
  }

  @Override
  protected void onHandleIntent(Intent intent) {

    try {

      Bundle bundle = intent.getExtras();
      if(bundle == null){
        Log.d(TAG, "bundle == null");
        return;
      }
      String urlString = bundle.getString("url");

      // HTTP Connection

      URL url = new URL(urlString);
      String fileName = getFilenameFromURL(url);
      Log.d(TAG, fileName);
      URLConnection conn = url.openConnection();
      
      HttpURLConnection httpConn = (HttpURLConnection)conn;
      httpConn.setAllowUserInteraction(false);
      httpConn.setInstanceFollowRedirects(true);
      httpConn.setRequestMethod("GET");
      httpConn.connect();
      int response = httpConn.getResponseCode();

      // Check Response
      if(response != HttpURLConnection.HTTP_OK){
        throw new HttpException();
      }
      int contentLength = httpConn.getContentLength();

      InputStream in = httpConn.getInputStream();
      
      FileOutputStream outStream 
        = openFileOutput(fileName, MODE_PRIVATE);
      
      DataInputStream dataInStream = new DataInputStream(in);
      DataOutputStream dataOutStream 
        = new DataOutputStream(
          new BufferedOutputStream(outStream));

      // Read Data
      byte[] b= new byte[4096];
      int readByte = 0, totalByte = 0;

      while(-1 != (readByte = dataInStream.read(b))){
        dataOutStream.write(b, 0, readByte);
        totalByte += readByte;
        sendProgressBroadcast(
          contentLength,
          totalByte,
          fileName);
      }

      dataInStream.close();
      dataOutStream.close();
      
      if(contentLength < 0){
        sendProgressBroadcast(
          totalByte,
          totalByte,
          fileName);
      }

    } catch (IOException e) {
      Log.d(TAG, "IOException");
    } catch (HttpException e) {
      Log.d(TAG, "HttpException");
    }
  }

  protected void sendProgressBroadcast(
    int contentLength, 
    int totalByte, 
    String filename){
    Intent broadcastIntent = new Intent();
    int completePercent = contentLength < 0 ? 
      -1 : ((totalByte*1000)/(contentLength*10));
    Log.d(TAG, "completePercent = " + completePercent);
    Log.d(TAG, "totalByte = " + totalByte);
    Log.d(TAG, "fileName = " + filename);
    
    broadcastIntent.putExtra("completePercent", completePercent);
    broadcastIntent.putExtra("totalByte", totalByte);
    broadcastIntent.putExtra("filename", filename);
    broadcastIntent.setAction("DOWNLOAD_PROGRESS_ACTION");
    getBaseContext().sendBroadcast(broadcastIntent);
  }
  
  protected String getFilenameFromURL(URL url){
    String[] p = url.getFile().split("/");
    String s = p[p.length-1];
    if(s.indexOf("?") > -1){
      return s.substring(0, s.indexOf("?"));
    }
    return s;
  }
}

エラー処理、例外処理はほとんど何もしてません。ネットワーク系のプログラムではいろんな場所でいろんなエラーがでるのが常なので、 本来はちゃんと処理しないといけないんですけどね。

ここまでお読みいただき、誠にありがとうございます。SNS 等でこの記事をシェアしていただけますと、大変励みになります。どうぞよろしくお願いします。

© 2024 Android 開発入門