SDK 29부터 적용딘 Scoped Stroage에 대응하여 제작
1. Retrofit2를 사용한 파일 업로드
Retrofit에서 파일 업로드를 구현하는 경우 Multipart를 사용합니다.
기존 인터넷에 있는 소스의 경우 File Picker에서 가져온 Uri를 가지고 파일의 경로를 직접 알아내어 구현하는 방식을 사용합니다.
...
File file = FileUtils.getFile(this, fileUri);
// create RequestBody instance from file
RequestBody requestFile =
RequestBody.create(
MediaType.parse(getContentResolver().getType(fileUri)),
file
);
// MultipartBody.Part is used to send also the actual file name
MultipartBody.Part body =
MultipartBody.Part.createFormData("picture", file.getName(), requestFile);
...
하지만, Scoped Storage가 적용되면서 위 코드가 막힐 것으로 예상됩니다.
developer.android.com/preview/privacy/storage?hl=ko
2. Cursor에 '_data' column이 없는 경우
IllegalArgumentException: column '_data' does not exist
보통 uri를 가지고 파일의 경로를 가져올 때 contentResolver에서 통해 가져온 Cursor에서 _data column의 값을 가져오는게 보통입니다.
하지만, 몇 몇 기기의 File Picker에서 주는 Uri를 가지고 얻은 Cursor에는 _data column이 없는 경우가 있습니다.
MediaStore를 통해 생성된 Uri에서는 _data column이 존재하지만 다른 방법으로 생성된 Uri의 경우 없을 가능성이 높습니다.
SDK 29부터 MediaStore.MediaColumns의 DATA 상수가 deprecated가 된 것이 이유가 될 수 있을 듯 합니다.
developer.android.com/reference/android/provider/MediaStore.MediaColumns#DATA
3. ContentResolver의 openInputStream 활용
Scoped Storage 업데이트에 대비하여 만들어 본 kotlin Extension입니다.
// kotlin
fun Uri.asMultipart(name: String, contentResolver: ContentResolver): MultipartBody.Part? {
return contentResolver.query(this, null, null, null, null)?.let {
if (it.moveToNext()) {
val displayName = it.it.getString(it.getColumnIndex(OpenableColumns.DISPLAY_NAME));
val requestBody = object : RequestBody() {
override fun contentType(): MediaType? {
return contentResolver.getType(this@asMultipart)?.toMediaType()
}
override fun writeTo(sink: BufferedSink) {
sink.writeAll(contentResolver.openInputStream(this@asMultipart)?.source()!!)
}
}
it.close()
MultipartBody.Part.createFormData(name, displayName, requestBody)
} else {
it.close()
null
}
}
}
Extension이 따로 없는 자바를 위한 코드입니다.
// Java
public static MultipartBody.Part uriToMultipart(final Uri uri, String name, final ContentResolver contentResolver) {
final Cursor c = contentResolver.query(uri, null, null, null, null);
if (c != null) {
if(c.moveToNext()) {
final String displayName = c.getString(c.getColumnIndex(OpenableColumns.DISPLAY_NAME));
RequestBody requestBody = new RequestBody() {
@Override
public MediaType contentType() {
return MediaType.parse(contentResolver.getType(uri));
}
@Override
public void writeTo(BufferedSink sink) {
try {
sink.writeAll(Okio.source(contentResolver.openInputStream(uri)));
} catch (IOException e) {
e.printStackTrace();
}
}
};
c.close();
return MultipartBody.Part.createFormData(name, displayName, requestBody);
} else {
c.close();
return null;
}
} else {
return null;
}
}
가볍게 만들어본 코드라 오류가 발생할 수 있습니다. 오류 발생시 댓글로 남겨주시면 감사하겠습니다.
'Android' 카테고리의 다른 글
[Android] isEnabled 로 ScrollView 스크롤 On / Off 시키기 (0) | 2021.07.08 |
---|---|
[Android] Collapse와 Expand 상태에서 힌트 문자가 다른 TextInputLayout (0) | 2021.02.18 |
[Android] SQLiteDatabase 트랜잭션 사용법 (0) | 2020.09.23 |
[Android] 개발, 디자인 및 보안 관련 사이트 북마크 (0) | 2020.09.11 |
[Android] 삼성 파일 브라우저에서 파일 오픈시 intent-filter가 동작하지 않을 때 (0) | 2020.09.08 |