Android 10에서 'Attempt to get length of null array' 오류 원인과 해결 방법 완벽 가이드
Android 10(API 레벨 29)을 사용하다 보면 파일을 조회하거나 접근할 때 'Attempt to get length of null array' 오류를 만나는 경우가 있습니다. 특히 이전 버전에서 잘 작동하던 앱이 Android 10에서 갑자기 이 오류를 표시하며 작동하지 않는다면 매우 당황스러울 것입니다. 이 글에서는 해당 오류의 주요 원인과 실제 개발 현장에서 적용할 수 있는 해결 방법을 상세히 알아보겠습니다.
목차
- 오류의 기본 원인 이해하기
- Android 10의 저장소 정책 변화
- 권한 관련 문제 해결하기
- 파일 경로 및 접근 방식 수정하기
- Storage Access Framework 활용하기
- scoped storage 적응하기
- 자주 발생하는 오류 상황과 해결책
- AndroidManifest.xml 설정 옵션
오류의 기본 원인 이해하기
'Attempt to get length of null array' 오류는 기본적으로 null 객체의 length 속성에 접근하려고 할 때 발생합니다. 자바에서는 null 참조의 메서드나 속성에 접근하면 NullPointerException이 발생하는데, 이것이 안드로이드에서는 위와 같은 오류 메시지로 나타나는 것입니다.
파일 시스템 접근 시 이 오류가 발생하는 주요 상황들은:
- 파일이나 디렉토리가 존재하지 않을 때
- 파일 목록을 가져오는 API가 null을 반환할 때
- 권한이 없어 파일 시스템에 접근할 수 없을 때
특히 Android 10에서는 저장소 접근 정책이 크게 변경되면서 이 문제가 더 빈번하게 발생하게 되었습니다.
Android 10의 저장소 정책 변화
Android 10부터 Google은 사용자 프라이버시 보호를 위해 "Scoped Storage"라는 개념을 도입했습니다. 이전 버전과 비교했을 때 주요 변화는:
- 외부 저장소 직접 접근 제한: 애플리케이션이 더 이상 모든 외부 저장소에 자유롭게 접근할 수 없습니다.
- 샌드박스 저장소 도입: 각 앱은 자신만의 샌드박스 저장소 공간을 가지며, 다른 앱은 이 공간에 접근할 수 없습니다.
- 미디어 파일 접근 방식 변경: 사진, 비디오, 음악 등의 미디어 파일에 접근하려면 MediaStore API를 사용해야 합니다.
- 공유 저장소 접근 제한: 공유 저장소에 접근하려면 Storage Access Framework를 사용하거나 특별한 권한이 필요합니다.
이러한 변화로 인해 기존에 잘 작동하던 파일 접근 코드가 Android 10에서 'Attempt to get length of null array' 오류를 발생시키는 경우가 많아졌습니다.
권한 관련 문제 해결하기
1. 필요한 권한 선언하기
Android 10에서도 파일 시스템에 접근하려면 적절한 권한이 필요합니다. AndroidManifest.xml에 다음 권한을 추가합니다:
<!-- 기본 저장소 접근 권한 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- Android 10에서 모든 파일에 접근하려면 필요 -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"
tools:ignore="ScopedStorage" />
2. 런타임 권한 요청하기
Android 6.0(API 레벨 23) 이상에서는 매니페스트에 권한을 선언하는 것 외에도 런타임에 사용자에게 직접 권한을 요청해야 합니다:
// 권한 요청 코드
private static final int PERMISSION_REQUEST_CODE = 123;
private void requestStoragePermission() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED ||
ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.WRITE_EXTERNAL_STORAGE
},
PERMISSION_REQUEST_CODE);
}
}
// 권한 요청 결과 처리
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == PERMISSION_REQUEST_CODE) {
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED &&
grantResults[1] == PackageManager.PERMISSION_GRANTED) {
// 권한이 승인되었을 때 파일 접근 로직 수행
accessFiles();
} else {
// 권한이 거부되었을 때 처리
Toast.makeText(this, "저장소 접근 권한이 필요합니다", Toast.LENGTH_SHORT).show();
}
}
}
3. Android 10 이상에서 파일 관리 권한 요청하기
Android 10에서 MANAGE_EXTERNAL_STORAGE
권한이 필요한 경우 다음과 같이 구현합니다:
private void requestManageStoragePermission() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
Uri uri = Uri.fromParts("package", getPackageName(), null);
intent.setData(uri);
startActivity(intent);
}
}
}
파일 경로 및 접근 방식 수정하기
1. 앱 전용 디렉토리 사용하기
Android 10에서는 외부 저장소의 앱 전용 디렉토리를 사용하는 것이 가장 안전합니다:
// 앱 전용 외부 저장소 디렉토리 가져오기
File appExternalDir = getExternalFilesDir(null);
if (appExternalDir != null) {
File myFile = new File(appExternalDir, "myfile.txt");
// 파일 작업 수행
}
// 또는 특정 유형의 디렉토리를 가져오기
File appPicturesDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
2. 파일 존재 여부 확인하기
null 배열 오류를 방지하기 위해 항상 파일 존재 여부를 먼저 확인합니다:
File directory = new File(path);
if (directory.exists() && directory.isDirectory()) {
File[] files = directory.listFiles();
if (files != null) {
// 파일 배열 처리
for (File file : files) {
// 파일 작업
}
} else {
// 파일 목록이 null인 경우 처리
Log.e("FileError", "디렉토리가 비어있거나 접근할 수 없습니다.");
}
} else {
// 디렉토리가 존재하지 않는 경우 처리
Log.e("FileError", "디렉토리가 존재하지 않습니다: " + path);
}
3. 안전한 파일 접근 패턴 적용하기
// null 체크를 포함한 안전한 파일 목록 가져오기
public List<File> safeListFiles(File directory) {
List<File> fileList = new ArrayList<>();
if (directory == null || !directory.exists() || !directory.isDirectory()) {
return fileList; // 빈 목록 반환
}
File[] files = directory.listFiles();
if (files != null) {
fileList.addAll(Arrays.asList(files));
}
return fileList;
}
Storage Access Framework 활용하기
Android 10에서는 Storage Access Framework(SAF)를 사용하는 것이 권장됩니다. SAF는 사용자가 시스템 파일 선택기를 통해 파일을 선택하도록 하여 앱에 제한된 접근 권한을 부여합니다.
1. 파일 선택하기
private static final int READ_REQUEST_CODE = 42;
// 파일 선택 창 열기
private void openFileSelector() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*"); // 모든 파일 유형
startActivityForResult(intent, READ_REQUEST_CODE);
}
// 선택 결과 처리
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
Uri uri = resultData.getData();
try {
// 파일 내용 읽기
InputStream inputStream = getContentResolver().openInputStream(uri);
if (inputStream != null) {
// 스트림을 사용하여 파일 처리
inputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2. 디렉토리 접근하기
private static final int DIRECTORY_REQUEST_CODE = 43;
// 디렉토리 선택 창 열기
private void openDirectorySelector() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, DIRECTORY_REQUEST_CODE);
}
// 선택 결과 처리
@Override
public void onActivityResult(int requestCode, int resultCode, Intent resultData) {
super.onActivityResult(requestCode, resultCode, resultData);
if (requestCode == DIRECTORY_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
if (resultData != null) {
Uri treeUri = resultData.getData();
// 지속적인 접근 권한 요청
getContentResolver().takePersistableUriPermission(treeUri,
Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
// DocumentFile을 사용하여 디렉토리 및 파일 접근
DocumentFile documentFile = DocumentFile.fromTreeUri(this, treeUri);
if (documentFile != null && documentFile.exists()) {
// 모든 하위 파일 나열
DocumentFile[] files = documentFile.listFiles();
for (DocumentFile file : files) {
// 파일 작업
String name = file.getName();
boolean isDirectory = file.isDirectory();
// ...
}
}
}
}
}
Scoped Storage 적응하기
1. MediaStore API 사용하기
Android 10에서 미디어 파일에 접근하려면 MediaStore API를 사용해야 합니다:
// 이미지 가져오기
private void loadImages() {
List<String> imageList = new ArrayList<>();
Uri collection;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
collection = MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL);
} else {
collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
}
String[] projection = new String[] {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DISPLAY_NAME,
MediaStore.Images.Media.DATA
};
try (Cursor cursor = getContentResolver().query(
collection,
projection,
null,
null,
MediaStore.Images.Media.DATE_ADDED + " DESC"
)) {
int dataColumn = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
while (cursor.moveToNext()) {
String path = cursor.getString(dataColumn);
imageList.add(path);
}
}
// 이미지 목록 사용
for (String imagePath : imageList) {
// 이미지 처리
}
}
2. 앱 전용 파일 생성하기
// 앱 내부 저장소에 파일 생성
private void createAppFile(String fileName, String content) {
try {
FileOutputStream fos = openFileOutput(fileName, Context.MODE_PRIVATE);
fos.write(content.getBytes());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
// 앱 전용 외부 저장소에 파일 생성
private void createExternalAppFile(String fileName, String content) {
File file = new File(getExternalFilesDir(null), fileName);
try {
FileOutputStream fos = new FileOutputStream(file);
fos.write(content.getBytes());
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
자주 발생하는 오류 상황과 해결책
1. 공유 저장소의 파일 목록을 가져올 때 발생하는 오류
오류 상황:
// 문제가 발생하는 코드
File externalStorage = Environment.getExternalStorageDirectory();
File[] files = externalStorage.listFiles(); // 여기서 null 반환 가능
for (File file : files) { // NullPointerException 발생
// ...
}
해결책:
// 안전한 코드
File externalStorage = Environment.getExternalStorageDirectory();
File[] files = externalStorage.listFiles();
if (files != null) {
for (File file : files) {
// 파일 처리
}
} else {
// 권한 문제 또는 다른 이유로 파일 목록을 가져올 수 없음
Log.e("FileError", "파일 목록을 가져올 수 없습니다.");
}
2. SD 카드 접근 시 발생하는 오류
오류 상황:
Android 10에서는 SD 카드와 같은 이동식 미디어에 대한 접근이 제한됩니다.
해결책:
// SAF를 사용하여 SD 카드 접근
private void accessSDCard() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
startActivityForResult(intent, DIRECTORY_REQUEST_CODE);
}
3. 다운로드 폴더 접근 시 발생하는 오류
오류 상황:
Android 10에서는 Download 폴더에 대한 직접 접근이 제한됩니다.
해결책:
// Android 10 이상에서 다운로드 폴더 접근
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// MediaStore API 사용
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, "myfile.txt");
values.put(MediaStore.Downloads.MIME_TYPE, "text/plain");
Uri uri = getContentResolver().insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
if (uri != null) {
try (OutputStream os = getContentResolver().openOutputStream(uri)) {
if (os != null) {
os.write("파일 내용".getBytes());
}
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
// Android 9 이하에서는 기존 방식 사용
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
File file = new File(downloadsDir, "myfile.txt");
// 파일 작업
}
AndroidManifest.xml 설정 옵션
1. Android 10에서 레거시 저장소 모드 일시적으로 사용하기
Android 10에서 개발 및 마이그레이션 기간 동안 일시적으로 레거시 저장소 모드를 사용할 수 있습니다. 이는 장기적인 해결책이 아니라 임시 방편임을 명심해야 합니다.
<manifest ... >
<application
android:requestLegacyExternalStorage="true"
... >
...
</application>
</manifest>
2. 적절한 targetSdkVersion 설정하기
<manifest ... >
<uses-sdk
android:minSdkVersion="21"
android:targetSdkVersion="29" />
...
</manifest>
결론
Android 10에서 'Attempt to get length of null array' 오류는 주로 Scoped Storage 정책 변화로 인한 파일 시스템 접근 제한 때문에 발생합니다. 이 문제를 해결하기 위해서는:
- 앱 전용 디렉토리를 사용하여 파일을 저장하고 관리합니다.
- 미디어 파일 접근을 위해 MediaStore API를 사용합니다.
- 다른 앱의 파일이나 공유 저장소 접근을 위해 Storage Access Framework를 활용합니다.
- 항상 null 체크를 통해 안전하게 파일 시스템에 접근합니다.
- 필요한 권한을 선언하고 런타임에 요청합니다.
이러한 방법들을 적용하면 Android 10에서도 안정적으로 파일 시스템에 접근할 수 있으며, 'Attempt to get length of null array' 오류를 효과적으로 방지할 수 있습니다.
구글 핵심 키워드
- Android 10 null array 오류 해결
- Attempt to get length of null array 원인
- 안드로이드 파일 접근 권한 설정
- Scoped Storage 대응 방법
- Android 10 파일 시스템 변화
- SAF 사용법 안드로이드
- Android 외부 저장소 접근 문제
- MediaStore API 사용 예제
- 안드로이드 10 저장소 권한
- 앱 전용 디렉토리 사용 방법
'■Development■ > 《Error》' 카테고리의 다른 글
[Error] Invalid file name: must contain only [a-z0-9_.] 원인과 해겳 방법 (0) | 2020.04.08 |
---|---|
[Error] ORACLE 계정이 Lock 걸렸을 때 원인과 해결 방법 완벽 가이드 (0) | 2020.04.08 |
[Error] Data-Scheme 설정 후 앱 서랍에서, 앱 아이콘이 사라지는 문제 원인과 해결 방법 완벽 가이드 (0) | 2020.03.22 |
[Error] 큰 용량의 PDF 읽기 (0) | 2019.10.29 |
[Error] ListView를 드래그 하면 검게 보이는 현상 (0) | 2019.10.01 |