Development record of developer who study hard everyday.

레이블이 scopedstorage인 게시물을 표시합니다. 모든 게시물 표시
레이블이 scopedstorage인 게시물을 표시합니다. 모든 게시물 표시
, , , , ,

안드로이드 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:
Read More
, , , , , , , , ,

안드로이드 10 이전 legacy storage 개념 총 정리

 


안드로이드 10이전 Legacy Storage 활용

내부저장소 사용하기

안드로이드 10 이전 LegacyStorage를 사용할 때 내부저장소에 접근할 때는 권한이 필요 없습니다.

그리고 앞의 글에서 이야기했듯이 안드로이드 10 이전과 이후, 내부저장소의 개념은 달라진게 없습니다.

따라서 해왔던 것처럼 

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

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

위 2개의 함수를 사용하여 앱의 내부저장소에 접근하면 됩니다.


외부저장소 공유공간 사용하기 

외부저장소의 공유공간에 접근해봅시다.

Legacy Storage에서는 외부저장소에 접근하기 위해서 권한이 필요합니다.


<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

위와 같이 manifest.xml 파일에 권한을 선언해줍니다.

참고로 Legacy Storage에서는 WRITE_EXTERNAL_STORAGE 권한만 있으면 외부저장소에 접근 가능합니다.

한 마디로 보안이 취약하다는 뜻입니다.

그 다음 외부저장소가 사용가능한지 getExternalStorageState() 함수를 호출하여 확인합니다.

public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}

/* Checks if external storage is available to at least read */
public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
return true;
}
return false;
}

이제 외부저장소 공유공간 경로를 가져오겠습니다.

getExternalStorageDirectory() 또는 getExternalStoragePublicDirectory(String type)  함수를 사용하면 됩니다.

getExternalStorageDirectory() 는 외부저장소 SD카드(빌트인)의 최상위 경로를 반환합니다.

getExternalStoragePublicDirectory(String type) 는 외부저장소의 공유디렉터리 경로를 반환합니다.

아래는 예시입니다.

//폴더 저장 경로
public static final String STRSAVEPATH = Environment.getExternalStorageDirectory() + "/MyDirectory/";
public static final String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath();

getExternalStoragePublicDirectory() 함수의 매개변수로 던져줄 String타입의 type 변수의 종류는 아래와 같습니다. null을 넘겨주는 것도 가능합니다.

안드로이드 Legacy Strorage

외부저장소 앱 개별공간 사용하기

외부저장소의 앱 개별공간을 사용하고 싶을 때는 getExternalStorageDirectory() 와 getExternalStoragePublicDirectory(String type) 함수대신 getExternalFilesDir(String type) 함수를 사용하면 됩니다.

나머지는 똑같습니다.

이 경우 앱 개별공간이기 때문에 앱을 삭제하면 외부저장소의 앱 개별공간에 있던 파일들도 사라지게 됩니다.


Share:
Read More
, , , , , , , , ,

안드로이드 10 이상 저장소 개념 총 정리


안드로이드 10 이상 저장소 개념 총 정리

회사에서 프로젝트를 하나 수정하게 되었다.

BLE로 데이터를 측정 후에 저장하고 이메일로 보내고 측정 데이터를 핸드폰으로 옮겨서 import해서 볼 수 있는 앱이다.

근데 앱이 워낙 오래되어서 측정 데이터를 활용하는 모든 기능에 수정이 필요했다. ㅜㅜ

평소에도 파일 입출력을 많이 다뤄보지 않아서 걱정이 많았는데 이번에 빡세게 공부했다.

우선, 안드로이드10(Q)이전의 저장소 개념부터 알아보자.

1. 안드로이드 10 이전의 저장소

안드로이드에서는 내부저장소와 외부저장소가 있다.

내부저장소: 말 그대로 기기 내부의 저장소이다.

외부저장소: 단어 말 뜻 자체만 보면 기기 외부의 저장소일 것 같지만!! 그렇지않다.

SD카드 같은 기기 외부의 저장소일 수도 있고 내부저장소가 아닌 기기 내부에 있는 저장소를 외부저장소라고도 한다.

헷갈린다면 그냥 내부저장소가 아닌 것은 다 외부저장소라고 생각해도 큰 문제 없다.

그렇다면 안드로이드 스튜디오에서 외부저장소를 살펴보자

/mnt/sdcard

/sdcard

/storage/self/primary

/mnt/sdcard, /sdcard, /storage/self/primary 3가지의 경로를 캡쳐화면으로 확인했다.

전부 다 같은 공간을 나타내고 빌트인 외부저장소에 해당한다.

그리고 캡쳐화면에 /Pictures, /DCIM, /Movies 처럼 우리가 흔히 접하는 폴더들이 보인다.

이런 폴더들은 안드로이드에서 제공하는 빌트인 외부저장소의 공유공간에 해당하는 곳이다.

그리고 외부저장소에도 내부저장소 처럼 앱 개별 공간이 별도로 존재한다.

(내부저장소보다 외부저장소가 볼륨이 크기 때문에 용량이 큰 컨텐츠는 외부저장소에 따로 보관할 수 있게 만들었다고 한다.)

이번엔 내부저장소를 살펴보자

/data/data
/data/data 경로로 들어가보면 내부저장소가 나온다.

안드로이드 10 이전 저장소
안드로이드 10 이전의 저장소

안드로이드 10 이전의 저장소 개념을 한눈에 정리한 그림이다.
보통 이것을 Legacy Storage라고 부른다.

2. 안드로이드 10 이상의 저장소 

안드로이드 10부터는 Scoped Storage라는 개념이 생겼습니다.

Scoped Storage

Scoped Storage에서는 내부저장소에 앱별 저장소를 제공합니다.

다른 앱이 액세스해서는 안되는 민감한 정보를 여기에 저장하면 됩니다.

안드로이드 10 이전과 동일합니다.

외부저장소가 조금 달라졌는데요.


외부저장소 구조의 변화

안드로이드 10 이전에는 EXTERNAL_STORAGE 권한으로 다른 앱의 개별공간을 접근했지만 이제는 접근 자체가 불가능합니다.

그리고 개별 앱 공간을 이용할 때는 어떠한 권한도 필요가 없습니다.

오늘은 여기까지 정리하고 다음 글에서는 각 저장소의 공간에 어떻게 접근하는지에 대해서 정리해보도록 하겠습니다.


Share:
Read More