본문 바로가기
Android

[깡쌤의 안드로이드 프로그래밍 with 자바 - 2022 - 쌤즈] 정리 23 - 파일 읽고 쓰기

by 들풀민들레 2022. 5. 9.

책의 모든 내용을 저자 직강으로 진행한 강의는 ssamz.com 에서 들으실 수 있습니다.

 

 

본 글은 [깡쌤의 안드로이드 프로그래밍 with 자바 - 2022 - 쌤즈] 의 내용을 발췌한 것입니다.
좀더 자세한 내용은 책 혹은 인강(www.ssamz.com)을 통해 확인해 주세요.

 

13.2. 파일 읽고 쓰기


이번 절에서는 안드로이드에서 파일을 읽고 쓰는 방법을 살펴보겠습니다. 안드로이드에서 파일 관련
프로그램은 대부분 자바 API를 그대로 사용하므로 java.io 패키지의 클래스들을 이용해서 작성합니다.

  • File: 파일 및 디렉터리를 지칭하는 클래스
  • FileInputStream: 파일에서 바이트 데이터를 읽기 위한 함수 제공
  • FileOutputStream: 파일에 바이트 데이터를 쓰기 위한 함수 제공
  • FileReader: 파일에서 문자열 데이터를 읽기 위한 함수 제공
  • FileWriter: 파일에 문자열 데이터를 쓰기 위한 함수 제공


안드로이드 파일 저장 공간은 내장 메모리와 외장 메모리 공간으로 구분됩니다. 또한 외장 메모리
공간은 다시 앱 저장 공간과 공용 저장 공간으로 구분됩니다. 앱 저장 공간은 다른 앱에서 접근할 수
없고, 공용 저장 공간은 모든 앱이 접근할 수 있습니다.

 

안드로이드에서 저장소와 관련된 각종 정보는 Environment 클래스로 얻을 수 있습니다.

  • Environment.getExternalStorageState(): 외부 저장 공간 상태
  • Environment.getExternalStorageDirectory().getAbsolutePath(): 외부 저장 공간 경로
  • Environment.getDataDirectory().getAbsolutePath(): 내부 저장 공간 경로


메모리 공간의 경로는 스마트폰마다 다를 수 있습니다. 따라서 파일을 이용할 때 경로를 문자열로
지정하는 게 아니라, 위의 함수를 이용하여 스마트폰에 따라 다르게 대응해야 합니다.

 

13.2.1. 외부 저장 공간 이용


외부 저장 공간을 이용하려면, 우선 스마트폰에서 이를 제공하고 있는지 판단해야 합니다. Environme
nt.getExternalStorageState () 함수로 외부 저장 공간에 대한 정보를 얻습니다.

 

String state = Environment.getExternalStorageState();
if (state.equals(Environment.MEDIA_MOUNTED)) {
	if (state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
		externalStorageReadable = true;
		externalStorageWritable = false;
	} else {
		externalStorageReadable = true;
		externalStorageWritable = true;
	}
}else {
	externalStorageReadable = externalStorageWritable = false;
}

Environment.getExternalStorageState ( ) 함수를 통해 얻은 상태 값이 Environment.
MEDIA_MOUNTED이 면 외 부 저 장 공 간 이 제 공 된 다 는 의 미 입 니 다 . 상 태 값 이
Environment.MEDIA_ MOUNTED_READ_ONLY인지를 판단해 파일을 읽거나 쓸 수 있는지
확인합니다.


이제 실제로 외부 저장 공간에 파일을 읽거나 쓰는 작업을 살펴보겠습니다. 외부 저장 공간에 접근할
때 ContentResolver에서 제공하는 InputStream 등을 이용하면 필요하지 않지만, 그렇지 않다면
매니페스트 파일에서 android.permission.READ_EXTERNAL_STORAG 또는 android.permissi
on.WRITE_EXTERNAL_STORAG 퍼미션 설정을 해야 합니다.


만약 F i l e API를 사용한다면 Android 10 버전부터는 2 가지의 퍼미션 설정과 함께
requestLegacyExternalStorage값 설정도 해주어야 합니다. 결국 파일을 이용하는 방식은 안드로이드
버전에 따라 다르며, API 호환성까지 고려하면 외장 메모리를 사용할 때 다음처럼 선언하는 것이
좋습니다.

 

<manifest ............ >
	<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
	<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
	<application
		..............
		android:requestLegacyExternalStorage="true">
		..........
	</application>
</manifest>

앱 저장소 이용


외장 메모리 공간은 앱 저장소와 공용 저장소로 이루어져 있습니다. 앱 저장소에는 각 앱의 개별 공간이
할당되어 있으며, 기본적으로 각각의 앱에서 할당받은 공간에만 접근할 수 있습니다.
외장 메모리의 앱 저장소 위치는 getExternalFilesDir () 함수로 구합니다.

 

File file = getExternalFilesDir(null);
Log.d("kkang", file.getAbsolutePath());

기기에 따라 다를 수 있지만, getExternalFilesDir (null) 함수가 반환하는 위치는 다음과 같습니다.

  • /storage/emulated/0/Android/data/패키지명/files

외장 메모리의 Android 아래에 패키지명으로 디렉터리가 생기고, 그 아래에 있는 files 디렉터리에
각각의 앱별 파일이 저장됩니다. getExternalFilesDir () 함수의 매개변수로 파일의 종류를 지정하며,
null이 아닌 다음과 같은 Environment 상수를 전달할 수도 있습니다.

 

  • Environment.DIRECTORY_PICTURES: 이미지 파일 저장 폴더
  • Environment.DIRECTORY_MUSIC: 음악 파일 저장 폴더
  • Environment.DIRECTORY_MOVIES: 영상 파일 저장 폴더
  • Environment.DIRECTORY_DOWNLOADS: 다운로드한 파일 저장 폴더
  • Environment.DIRECTORY_DCIM: 카메라로 촬영한 사진 저장 폴더
  • Environment.DIRECTORY_ALARMS: 알람으로 사용할 오디오 파일 저장 폴더
  • Environment.DIRECTORY_NOTIFICATIONS: 알림음으로 사용할 오디오 파일 저장 폴더

 

만약 Environment.DIRECTORY_ALARMS 상수를 getExternalFilesDir () 함수의 매개변수에
전달했다면, 파일이 저장되는 위치는 다음과 같습니다.

  • /storage/emulated/0/Android/data/패키지명/files/Alarms


이제 파일을 읽거나 쓰는 코드를 작성해보겠습니다. 우선, 외부 저장 공간에 파일을 쓰는 방법에 대해
살펴봅시다. 다음은 Environment.DIRECTORY_DOCUMENTS 타입의 앱 저장소에 text.txt라는
파일을 만들어 문자열 데이터를 저장하는 코드입니다.

 

try {
	File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "test.txt");
	//파일이 없다면 새로 만들어 준다
	if (!file.exists()) {
		file.createNewFile();
	}
	FileWriter writer = new FileWriter(file, true);
	writer.write("hello world");
	writer.flush();
	writer.close();
}catch (Exception e){
	e.printStackTrace();
}

문자열 데이터를 저장하기 위해 FileWriter를 사용했습니다. 이미지 등의 바이트 데이터 저장하려면
OutputStream 클래스를 사용합니다.

 

이번에는 외부 저장 공간의 파일을 읽기 위한 코드를 살펴보겠습니다. Environment.DIRECTORY_
DOCUMENTS 타입의 앱 저장소에 text.txt라는 파일을 찾아 읽고, StringBuffer 객체에 저장합니다

 

try {
	File file = new File(getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS), "test.txt")
	BufferedReader reader= new BufferedReader(new FileReader(file));
	StringBuffer buffer=new StringBuffer();
	String line;
	while ((line=reader.readLine()) != null){
		buffer.append(line);
	}
	reader.close();
	Log.d("kkang", buffer.toString());
}catch (Exception e){
	e.printStackTrace();
}

만약, 각 앱 저장소에 있는 파일에 외부 앱이 접근되도록 하려면 12장에서 살펴본 FileProvider를
이용하면 됩니다.


공용 저장소 이용
외장 메모리의 앱 저장소에는 개별 앱에서 만든 파일을 저장합니다. 그런데 해당 파일을 모든 앱에서
이용할 수 있게 해야 하는 경우도 있습니다. 이때 공용 저장소를 이용합니다.


또한, 내장 메모리나 외장 메모리의 앱 저장소는 개별 앱을 위한 공간이므로 앱이 삭제되면 파일도
모두 삭제됩니다. 하지만 공용 저장소는 모든 앱을 위한 공간이므로 파일을 만든 앱을 삭제해도 파일은
삭제되지 않습니다.


안드로이드 시스템에서 공용 저장소의 폴더는 파일 종류로 구분되어 있습니다. 사진이나 음원, 또는
문서 등 그 종류에 따라 해당 파일이 저장되는 폴더가 지정되어 있습니다. 이 공용 저장소에는 파일
경로로 직접 접근하지 않고 시스템이 제공하는 API를 이용합니다.

 

try{
	String[] projection = {
		MediaStore.Images.Media._ID,
		MediaStore.Images.Media.DISPLAY_NAME
	};
	Cursor cursor = getContentResolver().query(
		MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
		projection,
		null,
		null,
		null
	);
	while (cursor.moveToNext()){
		Log.d("kkang", "_id : "+cursor.getLong(0)+", name : "+cursor.getString(1));
	}
}catch (Exception e){
	e.printStackTrace();
}

위 코드는 공용 저장소에 저장된 이미지 파일의 정보를 가져와 로그로 출력하는 코드입니다. 외장
메모리의 파일 정보를 이용하지만 파일 경로를 직접 사용하지는 않았습니다.


getContentResolver ( ).query ( ) 함수의 첫 번째 매개변수에 Uri 값을 지정할 때
MediaStore.Images를 이용했는데, 이는 안드로이드폰의 이미지 파일이 저장되는 공용 저장소인
DCIM과 Pictures 디렉터리를 의미합니다. MediaStore.Video는 DCIM과 Movies, 그리고 Pictures
디렉터리입니다. 그리고 MediaStore.Audio는 Alarms, Audiobooks, Music, Notifications,
Podcasts, Ringtones 디렉터리입니다.


아래의 코드는 공용 저장소의 이미지 파일 정보를 이용해 이미지 데이터를 가져오고, 화면에 출력하는
코드입니다.

 

Uri contentUri = ContentUris.withAppendedId(
	MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
	cursor.getLong(0)
);
InputStream inputStream = getContentResolver().openInputStream(contentUri);
// stream 객체에서 작업 수행
BitmapFactory.Options option = new BitmapFactory.Options();
option.inSampleSize = 10;
Bitmap bitmap = BitmapFactory.decodeStream(inputStream, null, option);
binding.resultImageView.setImageBitmap(bitmap);

ContentUris.withAppendedId() 함수의 두 번째 매개변수가 가져온 이미지의 식별자입니다. 이렇게
하면 해당 이미지 파일을 이용할 수 있는 Uri 값이 반환되고, Uri 값으로 이미지를 읽는 InputStream
객체를 얻습니다.


getContentResolver ().openInputStream (contentUri)로 파일을 읽는 Stream 객체를 얻고, 이
객체로 이미지 데이터를 획득합니다.

 

책의 모든 내용을 저자 직강으로 진행한 강의는 ssamz.com 에서 들으실 수 있습니다.