Retrofit 1.9.0でファイルをアップロードするにはTypedFile
を使うのが手っ取り早い。以下は最小限の定義。
public interface UploadService {
@Multipart
@POST("/upload")
Observable<Response> upload(@Part("file") TypedFile file);
}
TypedFile
が扱えるのはローカルファイルのみであるが、Android開発をしていてMediaStoreから取得したオブジェクトをアップロードしたいことがあったので以下のようなクラスを作成した。
public class Media implements TypedInput, TypedOutput {
private static final int BUFFER_SIZE = 4096;
private Context mContext;
private Uri mUri;
private String mDisplayName;
private String mMimeType;
private long mSize;
public Media(Context context, Uri uri) {
mContext = context;
mUri = uri;
ContentResolver contentResolver = mContext.getContentResolver();
mMimeType = contentResolver.getType(uri);
Cursor cursor = contentResolver.query(uri, null, null, null, null);
int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE);
cursor.moveToFirst();
mDisplayName = cursor.getString(nameIndex);
mSize = cursor.getLong(sizeIndex);
cursor.close();
// OpenableColumns.SIZE で取得したサイズは実際のバイト数より大きいことがあり
// unexpected end of streamになる可能性があるので
// statの値が利用できればそれを使う
long statSize = getStatSize(contentResolver, uri);
if (statSize > 0) {
mSize = statSize;
}
}
/**
* Stack Overflowの記事を参考にした
* @link http://stackoverflow.com/questions/21882322/how-to-correctly-get-the-file-size-of-an-android-net-uri
*/
private long getStatSize(ContentResolver contentResolver, Uri uri) {
long size = -1;
try {
ParcelFileDescriptor fd = contentResolver.openFileDescriptor(uri, "r");
size = fd.getStatSize();
try {
fd.close();
} catch (IOException e) {
e.printStackTrace();
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}
return size;
}
@Override
public String fileName() {
return mDisplayName;
}
@Override
public String mimeType() {
return mMimeType;
}
@Override
public long length() {
return mSize;
}
@Override
public InputStream in() throws IOException {
return mContext.getContentResolver().openInputStream(mUri);
}
@Override
public void writeTo(OutputStream out) throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
InputStream in = in();
try {
int read;
while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read);
}
} finally {
in.close();
}
}
}
嵌まったポイントはコメントにも書いている通りOpenableColumns.SIZE
が正確なサイズを返さない場合があることで、Stack Overflowの How to correctly get the file size of an android.net.Uri? に書いてある実装を使わせていただいた。
あとはメソッドの定義を追加するだけ。
public interface UploadService {
@Multipart
@POST("/upload")
Observable<Response> upload(@Part("file") TypedFile file);
@Multipart
@POST("/upload")
Observable<Response> upload(@Part("file") Media media);
}