LoginSignup
10
10

More than 5 years have passed since last update.

RetrofitでMediaStoreから取得したオブジェクトをアップロードする

Last updated at Posted at 2015-10-23

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);
}
10
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
10