저장소는 누구나 잘 알고 있으면서도 늘 당연하게 여기는 존재입니다. 얼마 전까지만 해도 저장 용량이 조금씩 늘어나는 것조차 불가능에 가까운 도약처럼 여겨졌습니다. 하지만 요즘은 기기에 저장 공간이 얼마나 있는지 일일이 따져보지 않을 뿐 아니라, 그 차이조차 거의 신경 쓰지 않습니다.
더 흥미로운 변화는 저장소에 담기는 데이터의 종류에서 찾을 수 있습니다. 스마트폰 이전 시대에는 사진 한두 장과 몇몇 게임, 그리고 수많은 문자 메시지만 저장하면 충분했습니다. 그러나 지금은 어떤 스마트폰에도 애플리케이션, 문서, 사진, 동영상, 음악 파일 등 다양한 데이터가 뒤섞여 들어 있습니다. 이번 글에서는 우리가 만드는 애플리케이션을 위해 기기의 저장 공간을 어떻게 활용할 수 있는지 살펴보겠습니다.
이 글에서 다룰 내용은 다음과 같습니다:
- 안드로이드 폰에 존재하는 다양한 저장소 유형
- 저장소 유형 간의 차이점
- 애플리케이션에서 저장소를 사용하는 방법
모든 애플리케이션은 내부 저장소(internal)와 외부 저장소(external), 두 가지 유형의 저장소에 접근할 수 있습니다. 이 둘 사이에는 중요한 차이점들이 있으며, 이를 제대로 이해하면 새로운 애플리케이션을 설계할 때 큰 도움이 됩니다.
본격적으로 시작하기 전에 저장소와 캐시에 대해 한 가지 짚고 넘어가겠습니다. 저장소는 영구적으로 보존하고 싶은 데이터를 위한 것이며, 캐시는 데이터를 임시로 저장하기 위한 것입니다. 이 구분을 명확히 하는 것이 올바른 데이터 관리의 첫걸음입니다.

내부 저장소(Internal Storage)
애플리케이션이 운영체제에서 실행되면 자신만의 내부 저장소를 할당받습니다. 이 저장소는 해당 앱 전용의 비공개(private) 공간으로, 다른 애플리케이션도 사용자도 직접 접근할 수 없습니다. 또 하나 기억해야 할 특징은 항상 사용 가능하다는 점입니다. 외부 저장소와 달리 내부 저장소는 언제든지 앱에서 안정적으로 사용할 수 있습니다.
다만 내부 저장소에는 분명한 단점이 있습니다. 사용자가 애플리케이션을 삭제하면 내부 저장소에 저장된 모든 데이터도 함께 삭제된다는 점입니다. 게임을 설치한 뒤 어느 시점에 삭제했다고 상상해 보세요. 나중에 다시 게임을 설치했을 때는 진행 상황이 그대로 보존되기를 바랄 것입니다. 이런 경우에는 내부 저장소만으로는 부족합니다.
그렇다면 파일을 내부 저장소에 저장하려면 어떻게 해야 할까요?
public void saveFileInternalStorage() {
String FILENAME = "hello_world_file";
String inputToFile = "Hello From Internal Storage!";
try {
FileOutputStream fileOutputStream = openFileOutput(FILENAME, Context.MODE_PRIVATE);
fileOutputStream.write(inputToFile.getBytes());
fileOutputStream.close();
Toast.makeText(getApplicationContext(),
"File " + FILENAME + " has been saved successfully",
Toast.LENGTH_SHORT).show();
} catch (FileNotFoundException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(),
"File " + FILENAME + " has not been saved successfully due to an exception " + e.getLocalizedMessage(),
Toast.LENGTH_SHORT).show();
} catch (IOException e) {
e.printStackTrace();
Toast.makeText(getApplicationContext(),
"File " + FILENAME + " has not been saved successfully due to an exception " + e.getLocalizedMessage(),
Toast.LENGTH_SHORT).show();
}
}위 코드 예제에서 볼 수 있듯이, "Hello From Internal Storage!"라는 텍스트를 담은 hello_world_file이라는 파일을 저장하고 있습니다. 예외 상황을 보여주기 위해 catch 절을 두 개로 나누어 작성했지만, 실제로는 일반 Exception 객체 하나로 통합하여 더 간결하게 처리할 수도 있습니다.
주목할 메서드는 openFileOutput입니다. 이 메서드는 파일이 이미 존재하면 해당 파일을 열고, 없으면 새로 생성합니다. 두 번째 매개변수는 파일 모드(file mode)로, 파일의 접근 범위를 지정합니다. 기본값인 MODE_PRIVATE는 오직 해당 애플리케이션만 파일에 접근할 수 있도록 설정합니다.
이 매개변수에 사용할 수 있는 다른 값으로 MODE_WORLD_READABLE과 MODE_WORLD_WRITEABLE이 있었지만, API 17부터 deprecated되었습니다. 현재는 ContentProvider 등 별도의 로직을 통해 비공개 파일을 다른 앱과 공유하는 것이 권장됩니다. 마지막으로 파일에 데이터를 쓸 때는 문자열을 바이트 배열로 변환하고, 작업이 끝나면 반드시 파일을 닫아(close) 리소스를 반환해야 합니다.

외부 저장소(External Storage)
이름과 달리 외부 저장소란 '항상 접근 가능하지 않을 수 있다'는 특징으로 정의되는 저장소입니다. 여기에는 기기에 장착된 SD 카드 같은 보조 외부 저장소(secondary external storage)뿐 아니라, 기기 내부에 있으면서 외부 저장소로 분류되는 주 외부 저장소(primary external storage)도 포함됩니다.
쉽게 말해, USB 케이블로 기기를 컴퓨터에 연결했을 때 접근할 수 있는 저장소가 바로 외부 저장소입니다. 예상할 수 있듯이, 이곳에 저장된 데이터는 기기의 다른 애플리케이션에서도 접근할 수 있으며, 앱을 삭제해도 데이터는 그대로 남아 있습니다.
파일을 외부 저장소에 저장하는 방법을 살펴보기 전에, 먼저 두 가지 작업이 필요합니다:
- 파일을 저장할 공간이 충분한지 확인하기
- 런타임에 사용자에게 권한 요청하기
저장 공간이 충분한지 확인하고 외부 저장소의 상태를 검사하려면 다음 코드를 사용합니다:
//외부 저장소에 읽기/쓰기가 가능한지 확인
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) {
return true;
}
return false;
}외부 저장소에 접근하려면 AndroidManifest.xml에 다음 권한을 추가해야 합니다:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />또한 API 23부터는 위험(dangerous) 권한이 설치 시점이 아닌 런타임에 승인받도록 변경되었습니다. 외부 저장소에 쓰기는 대표적인 위험 권한에 속하므로, 사용자가 앱에 권한을 부여할지 직접 결정할 수 있도록 로직을 추가해야 합니다.
public void saveFileExternalStorage(View view) {
if (isExternalStorageWritable()) {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) ==
PackageManager.PERMISSION_GRANTED) {
writeFileToExternalStorage();
} else{
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case 0:
{
writeFileToExternalStorage();
break;
}
}
}권한이 확인되면 호출되는 writeFileToExternalStorage 메서드는 다음과 같이 구현합니다:
public void writeFileToExternalStorage() {
String root = Environment.getExternalStorageDirectory().toString();
File myDir = new File(root + "/saved_files");
if (!myDir.exists()) {
myDir.mkdirs();
}
try {
File file = new File(myDir, "myfile.txt");
FileOutputStream out = new FileOutputStream(file);
out.write(inputToFile.getBytes());
out.close();
Toast.makeText(getApplicationContext(),
"File myfile.txt" + " has been saved successfully to external storage",
Toast.LENGTH_SHORT).show();
} catch (Exception e) {
e.printStackTrace();
}
}이 글에서 소개한 전체 코드 예제를 확인하고 싶다면 GitHub 저장소를 참고하세요.

알아두면 좋은 팁(Good To Know)
지금까지 애플리케이션에서 서로 다른 저장소 유형을 다루는 간단한 예제 두 가지를 살펴보았습니다. 저장소는 시스템이 관리하는 자원이므로, 이와 관련된 시스템 동작 방식도 함께 이해해야 합니다.
기본적으로 애플리케이션은 내부 저장소에 설치됩니다(internalOnly 설명 참고). 하지만 API 레벨 8부터는 매니페스트에 installLocation 속성을 추가하여 앱을 외부 저장소에 설치할 수 있도록 허용할 수 있습니다. 앱 용량이 매우 커서, 공간이 더 넉넉한 기기의 외부 저장소에 설치되기를 원하는 경우가 대표적인 예입니다.
installLocation 속성에는 세 가지 값을 설정할 수 있습니다:
- auto – 설치 위치에 대한 특별한 선호가 없음을 의미합니다. 앱은 우선적으로 내부 저장소에 설치를 시도하며, 내부 저장소가 가득 찬 경우 외부 저장소에 설치됩니다.
- internalOnly – 앱이 오직 내부 저장소에만 설치됩니다. 내부 저장소에 공간이 부족하면 설치 자체가 이루어지지 않습니다.
- preferExternal – 앱을 외부 저장소에 설치하기를 선호한다는 의미입니다. 외부 저장소에 공간이 부족한 경우에만 내부 저장소에 설치됩니다.
auto와 preferExternal 옵션의 경우, 사용자는 언제든지 앱을 외부 저장소에서 내부 저장소로 옮기거나 그 반대로 이동할 수 있습니다.
단, 주의할 점이 있습니다. 사용자가 기기를 컴퓨터에 연결해 데이터 공유를 활성화하거나 SD 카드를 탈착(unmount)하면, 외부 저장소에서 실행 중인 모든 애플리케이션은 강제로 종료됩니다. 따라서 앱이 다음과 같은 기능을 사용한다면 외부 저장소에 설치되지 않도록 해야 합니다:
각종 서비스(특히 알람 서비스), 입력 방식 엔진(Input Method Engines), 라이브 배경화면(Live Wallpapers), 앱 위젯(Application Widgets), 계정 관리자(Account Managers), 동기화 어댑터(Sync Adapters), 기기 관리자(Device Administrators), 그리고 부팅 완료 브로드캐스트를 수신하는 Broadcast Receiver 등