ImageDownloadServiceで、大きな画像を非同期でダウンロード

AndroidでWebにある画像をダウンロードするサービスクラス ImageDownloadService を作りました。ソースはgithubで管理してます。
https://github.com/sharakova/ImageDownloadService
MIT Lisenceで、ご自由におつかいください。

説明

  • DownloadManagerが利用できない、Android 2.1などで画像をダウンロードするプログラムです。
  • 大きな画像をバックグランドでダウンロードします。
  • ダウンロードした画像は、ギャラリーやコンテンツマネージャーでも表示されるようになります。
  • ダウンロードを開始すると、Notificationでダウンロードの状態を確認できます。
  • ダウンロードは、URLと保存したいタイトルをIntentで渡すだけです。
  • 保存する画像は、他のアプリからも閲覧できる場所に保存されます。
  • SDカードに画像を保存するため、SDカードが挿入されていない場合を想定してません。
  • Notificationには、デフォルトのiconを設定していますが、他のICONも設定可能です。

利用方法
Android マニフェストに必要となる設定

  • Serviceクラスを設定する jp.sharakova.android.service.ImageDownloadService
<service android:name="jp.sharakova.android.service.ImageDownloadService" />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

Activityから呼び出し

  • Actionに、ImageDownloadService.IMAGE_DOWNLOAD_SERVICE を設定する。
  • image_urlに、画像のURLを渡す
  • image_titleには、画像のタイトルを設定する
Intent intent = new Intent(ImageDownloadService.IMAGE_DOWNLOAD_SERVICE);
intent.setClass(getApplicationContext(), ImageDownloadService.class);
intent.putExtra("image_url", "画像URL");
intent.putExtra("image_title", "画像の名前");
startService(intent);

ImageDownloadService.java

package jp.sharakova.android.service;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;

import jp.sharakova.android.imagedownload.R;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Intent;
import android.net.Uri;
import android.os.Binder;
import android.os.Environment;
import android.os.IBinder;
import android.provider.MediaStore.Images.Media;
import android.util.Log;

public class ImageDownloadService extends Service {
	
    public final static String IMAGE_DOWNLOAD_SERVICE = "jp.sharakova.android.service.ImageDownloadService";
	private static ArrayList<String> downloadTask = new ArrayList<String>();
    private NotificationManager mNM;
    private String imageUrl; 
    private String imageTitle;
    private final IBinder mBinder = new LocalBinder();
    
    public class LocalBinder extends Binder {
    	ImageDownloadService getService() {
            return ImageDownloadService.this;
        }
    }
    
    @Override
    public void onCreate() {
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
    }
    
    @Override
    public void onStart(Intent intent, int startId)
    {
    	super.onStart(intent, startId);
    	
    	// とりあえず、アクションもチェックする
        if (intent != null && IMAGE_DOWNLOAD_SERVICE.equals(intent.getAction())) {
        	
        	// URLとタイトルを設定する
        	imageUrl = intent.getStringExtra("image_url");
        	imageTitle = intent.getStringExtra("image_title");
        	
        	// ダウンロードタスクにaddする
        	downloadTask.add(imageUrl);

        	// ダウンロード開始するメッセージをタスクトレイで表示
        	showNotification(imageTitle, imageUrl);
        	
            Thread downloadThread = new Thread(null, new Runnable() {
                public void run() {
                	String path = imageUrl;
                	String title = imageTitle;
                	String message = null;
                	try {
                		// HTTPで画像をダウンロード
                		DefaultHttpClient httpClient = new DefaultHttpClient();
        	        	HttpResponse response = httpClient.execute(new HttpGet(path));
        	        	InputStream stream = response.getEntity().getContent();
        	        	ContentResolver contentResolver = getContentResolver();
        	        	
        	        	// ファイルへ保存
        	        	saveImage(contentResolver, getFileName(path), imageTitle, stream);
        	        	
        	        	// タスクトレイで表示するメッセージ
        	        	message = title + " download complete.";
        	        	
        	        	// コンテンツマネージャーなどのアプリでは、キャッシュをクリアしてやらないと
        	        	// すぐに、画像が表示されないので、メディアマウントをしてやる。
        	        	sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED,
                                Uri.parse("file://" + Environment.getExternalStorageDirectory()))); 
                	} catch (Exception e) {
                		message = "Error.";
                	} finally {
                		// ダウンロードのタスクから削除
       	        		downloadTask.remove(path);
                		mNM.cancel(path.hashCode());
                		// メッセージをタスクトレイに表示
                		showNotification(message, path);
                	}
                }
            }, "ImageDownloadService");
            downloadThread.start();
        }
    }

    @Override
    public void onDestroy() {
        mNM.cancelAll();
    }

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }


    private void showNotification(String message, String filePath) {
        Notification notification = new Notification(R.drawable.icon, message, System.currentTimeMillis());
        
        PendingIntent contentIntent = PendingIntent.getActivity(getApplicationContext(), 0, null, 0);
        notification.setLatestEventInfo(this, "download picture.", message, contentIntent);
        mNM.notify(filePath.hashCode(), notification);
    }
    
    private static String saveImage(ContentResolver contentResolver, String filename, String title, InputStream inputStream) {
		OutputStream outputStream = null;
		File file = null;
		try {
			makeDownloadDir();
			file = new File(getDownloadPath(), filename);
			outputStream = new FileOutputStream(file);
			int DEFAULT_BUFFER_SIZE = 1024 * 4;
			  byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
			  int n = 0;
			  while (-1 != (n = inputStream.read(buffer))) {
				  outputStream.write(buffer, 0, n);
			  }
			  inputStream.close();
			  outputStream.close();
		} catch (Exception e) {
			Log.w("save", e);
			if (file != null) {
				file.delete();
			}
		} finally {
			if (outputStream != null) {
				try {
					outputStream.close();
				} catch (Throwable t) {
					Log.w("save", "finally");
				}
			}
			if (inputStream != null) {
				try {
					inputStream.close();
				} catch (Throwable t) {
					Log.w("save", "finally");
				}
			}
		}
		
		// ContentResolver にファイル情報の書き込み。
		// これやらないと、ギャラリーなどのアプリでダウンロードした画像が表示されないです。
		String filePath = getDownloadPath() + "/" + filename;
	        ContentValues values = new ContentValues(7);
	        values.put(Media.TITLE, title);  
	        values.put(Media.DISPLAY_NAME, filename);  
	        values.put(Media.DATE_TAKEN, System.currentTimeMillis());  
	        values.put(Media.MIME_TYPE, getMimeType(filename));
	        values.put(Media.DATA, filePath);
	        contentResolver.insert(Media.EXTERNAL_CONTENT_URI, values);
	    
		return filePath;
	}
    
	private static String getSdCardPath() {
		return Environment.getExternalStorageDirectory().toString();
	}

	private static String getDownloadPath() {
		// 別のフォルダを指定する場合は、下記のフォルダ名を変更してください。
		return getSdCardPath() + "/download";
	}
	
	private static void makeDownloadDir() {
		makeDir(new File(getDownloadPath()));
	}
	
	private static void makeDir(File dir) {
		if (!dir.exists()) {
			dir.mkdir();
		}		
	}

	private static String getFileName(String url) {
		int point = url.lastIndexOf("/");
	    if (point != -1) {
	    	return url.substring(point + 1, url.length());
	    } 
		int hash = url.hashCode();
		return String.valueOf(hash);
	}

	
	// mime-typeの判定が微妙です。すみません。
	private static String getMimeType(String filename) {
		int point = filename.lastIndexOf(".");
	    if (point != -1) {
	    	String extension = filename.substring(point + 1, filename.length());
	    	if(extension.equals("jpeg") || extension.equals("jpg")) {
	    		return "image/jpeg";
	    	}
	    	if(extension.equals("png")) {
	    		return "image/png";
	    	}
	    	if(extension.equals("gif")) {
	    		return "image/gif";
	    	}
	    } 
		return "image/jpeg";
	}
}


(実行サンプルソース
AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
      package="jp.sharakova.android.imagedownload"
      android:versionCode="1"
      android:versionName="1.0">
    <uses-sdk android:minSdkVersion="7" />
    <application android:icon="@drawable/icon" android:label="@string/app_name">
        <activity android:name="jp.sharakova.android.service.ImageDownloadSampleActivity"
                  android:label="@string/app_name">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <service android:name="jp.sharakova.android.service.ImageDownloadService" />
    </application>
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
</manifest>

ImageDownloadSampleActivity

package jp.sharakova.android.service;

import jp.sharakova.android.imagedownload.R;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;

public class ImageDownloadSampleActivity extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        // ダウンロードのOnClickで、画像のダウンロードを開始する
        findViewById(R.id.download_btn).setOnClickListener(downloadBtnListener);
    }
    
    OnClickListener downloadBtnListener = new OnClickListener() {
		@Override
		public void onClick(View arg0) {
                        Intent intent = new Intent(ImageDownloadService.IMAGE_DOWNLOAD_SERVICE);
                        intent.setClass(getApplicationContext(), ImageDownloadService.class);
                        intent.putExtra("image_url", "http://lh5.googleusercontent.com/-U_ZrYg86EMc/S9uxmpuT6hI/AAAAAAAACUs/-zOKvjJKH8E/s640/golf.png");
                        intent.putExtra("image_title", "golf");
                        startService(intent);
            
                        // Toastの表示
			Toast.makeText(getApplicationContext(), "download start", Toast.LENGTH_SHORT).show();
		}
	};
}