Development record of developer who study hard everyday.

, , , , ,

안드로이드 10이상 범위저장소 사용하기

 


안드로이드 범위 저장소(Scoped Storage) 사용하기

안드로이드 10부터 Scoped Storage가 도입되었다. 


안드로이드 범위저장소

안드로이드 10 이상부터 범위저장소에 접근할 때 어떻게 해야되는지 알아보자.

내부저장소 접근하기


안드로이드 10 이전이랑 똑같다.

getFilesDir() : 내부저장소의 앱 개별공간

getCacheDir(): 내부저장소의 임시 캐시파일 저장소 

상황에 따라 유연하게 사용하면 된다.

외부저장소 앱 개별공간 접근하기

따로 권한요청이 필요 없고 Context.getExternalFilesDir()를 통해 접근하면 된다.

안드로이드 10 이전에는 EXTERNAL_STORAGE 권한으로 다른 앱의 외부저장소 개별공간에 접근 가능했지만 지금은 불가능합니다.

외부저장소 공용공간 접근하기

- 미디어 파일

외부저장소 공용공간에서 미디어파일을 다루는 경우 MediaStore api를 사용하시면 됩니다.

//예시 보여주기

- 기타 파일

미디어 파일 외의 기타파일들을 다루는 경우에는 Storage Access Framework을 사용하여 접근가능합니다.


public class UIImport extends UICommonTitle {

private static final String TAG = UIImport.class.getSimpleName();

private ListView listView;
private TextView btnLoad;
private LinearLayout baseEmpty;

private LocalDao localDao;

private ListAdapter adapter;

private int selectPos = -1;
private List<File> list = new ArrayList<File>();
private ArrayList<SiteBean> siteList = new ArrayList<SiteBean>();
private ArrayList<HoleBean> holeList = new ArrayList<HoleBean>();

ActivityResultLauncher<Intent> importLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(),
new ActivityResultCallback<ActivityResult>() {
@Override
public void onActivityResult(ActivityResult result) {
if (result.getResultCode() == Activity.RESULT_OK) {
Uri fileUri;
if (result.getData() != null) {
//임포트할 파일 uri 얻기
fileUri = result.getData().getData();
try {
//get mimeType
String type = null;
String extension = MimeTypeMap.getFileExtensionFromUrl(String.valueOf(fileUri));
Log.d(TAG, "extension: " + extension);
/*if (!Objects.equals(extension, "acf")) {
ActivityUtil.showToast(UIImport.this, getString(R.string.inaccessible_extension));
return;
}*/

//파일 내용 읽기
String fileContent = readTextFromUri(fileUri);
Log.d(TAG, "fileContent: " + fileContent);

//에이스 디렉터리에 같은 파일 만들기
String fileName = extractFileName(fileUri);

File dir = new File(getFilesDir().getAbsolutePath() + Constants.FILE_LOAD_PATH);
String destFilePath = getFilesDir().getAbsolutePath() + Constants.FILE_LOAD_PATH + "/" + fileName;
File destFile = new File(destFilePath);
FileUtils.makeFile(dir, destFilePath);

//내용 그대로 옮겨
boolean writeResult = FileUtils.writeFile(destFile, fileContent.getBytes());
Log.d(TAG, "writeResult: " + writeResult);

list.add(destFile);
adapter.notifyDataSetChanged();
baseEmpty.setVisibility(View.GONE);
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
ActivityUtil.showToast(UIImport.this, getString(R.string.cannot_find_file));
}
}
});

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContent(R.layout.ui_import);

init();
}

private String readTextFromUri(Uri uri) throws IOException {
StringBuilder stringBuilder = new StringBuilder();
InputStream inputStream = getContentResolver().openInputStream(uri);

BufferedReader reader = new BufferedReader(new InputStreamReader(Objects.requireNonNull(inputStream)));
String line;
while ((line = reader.readLine()) != null) {
stringBuilder.append(line).append("\r").append("\n");
}
return stringBuilder.toString();
}

private String extractFileName(Uri uri) {
Cursor cursor = getContentResolver()
.query(uri, null, null, null, null, null);
try {
if (cursor != null) {
int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
String displayName = null;
while (cursor.moveToNext()) {
displayName = cursor.getString(index);
Log.i(TAG, "Display Name: " + displayName);
}
return displayName;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
cursor.close();
}
return null;
}

private void init() {
//파일관리자로 임포트할 파일 선택하기
if (osVersionCheck10()) startFileManager();
}

private void startFileManager() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
importLauncher.launch(intent);
}

}

}
위의 코드는 내가 회사 프로젝트를 할 때 사용한 코드이다.
startFileManager 함수에서 Sotrage Access Framework를 사용하여 파일관리자를 인텐트로 띄운다.
이때 setType함수에서 mimeType을 정해주는데 내가 다루는 acf 파일은 mimeType이 없어서 그냥 따로 특정하지는 않았다.

그리고 importLauncher에서 선택한 파일의 uri를 받는다.
나 같은 경우에는 선택한 파일을 앱에서 보여주어야하기 때문에 내부저장소로 같은 내용의 파일을 만들어주었다.

이때, 반드시 Content Provider를 사용해서 파일을 읽어줘야한다.
그렇지않으면 permission denied로 exception이 발생한다.
다른 블로그를 보다가 파일을 카피하면 된다고해서 카피도 해봤지만 마찬가지로 permission denied로 exception이 발생했다.

나 같은 실수는 절대로 하지말자.

readTextUri함수가 uri를 통해 파일내용을 읽는 부분이다.
안드로이드 공식문서를 그대로 활용해서 구현한 내용이다.

파일의 metaData(파일이름, 최근 수정날짜 등)를 확인할 때는 MediaStore를 활용하는게 좋다.
예를들어, extractFileName 함수를 보면 MediaStore를 사용하여 파일의 이름을 추출한다.

더 자세한 내용은 안드로이드 공식문서(Storage Access Framework)를 참조하기를 바란다.


Share:

댓글 없음:

댓글 쓰기