본문 바로가기

카테고리 없음

[Android] api 30 이상에서 File.listFiles() 의 특정 파일들이 보이지 않는 문제

현재 유지보수 중인 앱이 있는데, 이 앱 내에 storage 파일 리스트를 보여주는 외부 라이브러리를 사용중이다.

 

https://github.com/bartwell/ExFilePicker

 

GitHub - bartwell/ExFilePicker: Open source Android library. Implement choosing files and directories in your application.

Open source Android library. Implement choosing files and directories in your application. - GitHub - bartwell/ExFilePicker: Open source Android library. Implement choosing files and directories in...

github.com

 

근데 파일 리스트 중에 특정 파일들이 보여지지 않는 다는 문제가 보고 되었다.

재현을 해보니 100% 재현이 되었고 위 라이브러리 코드를 살펴보다가 원인을 발견하였다.

 

private void readDirectory(@NonNull File directory) {
    ...
    File[] files = directory.listFiles();
    ...
    
}

확인결과 files 로 return 되는 파일 목록이 android 10 과 android 11 버전이 달랐다!

 

android 는 최근 storage 권한에 많은 변화가 있었는데 manifest 에 아래 코드를 추가하면 android 10 까지는 무시하고 사용할 수 있었다.

android:requestLegacyExternalStorage="true"

근데 앱의 targetSDK 버전을 30(android 11) 으로 올리면서 더 이상 저 무시하는 코드를 사용할 수 없게 된 것이다.

 

 

어쨌든 귀찮게 되었지만.. 버그는 버그니까 해결을 해야했다.

찾아보니 크게 2가지 방법으로 해결이 되었다.

 

Soulution 1

1. manifest 에 권한사용 코드를 넣어준다.

<uses-permission
    android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
    android:minSdkVersion="30" />

2. 시스템 -> 설정 -> 개인정보보호(권한..어쩌구) -> 권한관리자 -> 파일 및 미디어 에서 해당 앱의 파일 및 미디어 액세스 권한을 모두 허용으로 체크해준다.

 

3. 수동으로 하기 귀찮다면 아래 코드를 실행해 바로 권한을 체크하게끔 유도할 수 있다.

@RequiresApi(30)
fun requestStoragePermissionApi30(activity: AppCompatActivity) {
    val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)

    activity.startActivityForResult(intent, MANAGE_EXTERNAL_STORAGE_PERMISSION_REQUEST)
}

4. 이렇게 권한을 부여해주고 나면 listFiles() 에서도 정상적으로 모든 파일을 return 하게 된다.

 

Solution 2

1. Android system 에서 제공하는 저장소 액세스 프레임워크(SAF) 를 사용한다.

2. 아래 코드로 SAF 를 실행한다. 

fun openDocument() {
    // 개별파일을 선택해서 가져오고 싶은 경우 (api 19 이상)
    val intent = Intent(Intent.ACTION_OPEN_DOCUMENT);
    // 폴더내의 파일을 모두 가져오고 싶은 경우 (api 21 이상)
    // Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
    intent.addCategory(Intent.CATEGORY_OPENABLE);
    intent.type = "*/*";
    startActivityForResult(intent, 99)
}

2. SAF 를 실행하게 되면 아까처럼 파일이 몇개 빠져서 보인다거나 그런일은 없다.

그냥 system 에서 파일관리하는 앱을 실행시켜 파일을 가져온다고 생각하면 된다.

3. 받아온 데이터는 아래와 같이 처리가 가능하다.

override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) {
    super.onActivityResult(requestCode, resultCode, resultData)

    if (requestCode == 99 && resultCode == RESULT_OK) {
        // 1개의 파일만 선택했을 경우 data 로 내려온다.
        resultData?.data?.let { uri ->
            Log.d("TEST", "onActivityResult: $uri")
        }

        // 여러개의 파일을 선택했을 경우 clipData 로 내려온다.
        resultData?.clipData.let { clipData ->
            if (clipData?.itemCount!! > 0) {
                for (index in 0 until clipData.itemCount) {
                    val item = clipData.getItemAt(index)
                    Log.d("TEST", "onActivityResult $index: ${item.uri}")
                }
            }
        }
    }
}

4. 참고로 android 19 미만에서는 ACTION_PICK 또는 ACTION_GET_CONTENT 를 사용해야 한다고 한다.

 

 

출처 

https://developer.android.com/guide/topics/providers/document-provider?hl=ko 

 

저장소 액세스 프레임워크를 사용하여 파일 열기  |  Android 개발자  |  Android Developers

저장소 액세스 프레임워크를 사용하여 파일 열기 Android 4.4(API 수준 19)에는 저장소 액세스 프레임워크(SAF)가 도입되었습니다. SAF는 사용자가 선호하는 문서 저장소 제공자 전체에서 문서, 이미지

developer.android.com

https://github.com/android/storage-samples/tree/master/StorageProvider

 

GitHub - android/storage-samples: Multiple samples showing the best practices in storage APIs on Android.

Multiple samples showing the best practices in storage APIs on Android. - GitHub - android/storage-samples: Multiple samples showing the best practices in storage APIs on Android.

github.com