앱 내부 저장소는 격리된 저장소를 통해 특정 애플리케이션만 액세스할 수 있는 기기의 전용 공간입니다. Android Developers, 2026에 따르면, 각 애플리케이션은 고유한 샌드박스 디렉토리를 받으며 다른 애플리케이션은 직접 액세스할 수 없습니다. 이 접근 방식은 무단 읽기로부터 데이터를 보호하고 모바일 기기의 멀티태스킹 환경에서 안정적인 작동을 보장합니다.
주요 포인트
Context.getFilesDir(), getCacheDir(), getDataDir() 제공NSDocumentDirectory와 NSCachesDirectory 사용앱 내부 저장소는 운영 체제가 설치 시 각 애플리케이션에 할당하는 격리된 디렉토리입니다. 다른 애플리케이션과 사용자는 표준 파일 관리자를 통해 이 디렉토리에 액세스할 수 없습니다. 시스템은 애플리케이션이 제거될 때 이 디렉토리 내의 모든 데이터가 완전히 삭제되도록 보장합니다. 이 접근 방식은 모바일 운영 체제 보안 모델의 기초를 형성하여 프로그램 간 기밀 정보 유출을 방지합니다.
외부 저장소(SD 카드)와 달리 내부 저장소는 항상 사용 가능하며 미디어 존재 여부를 확인할 필요가 없습니다. 최신 기기의 NAND 플래시 메모리 읽기 및 쓰기 속도는 순차 읽기 800~900MB/s, 순차 쓰기 200~300MB/s에 도달하며 SATA SSD와 비슷합니다. 할당된 영역의 크기는 기기의 총 용량과 제조업체 정책에 따라 다릅니다. 64GB 플래시 메모리가 있는 기기에서 앱은 필요에 따라 확장 가능한 16~64MB의 초기 공간을 받습니다.
내부 저장소 아키텍처는 Android와 iOS 간에 다릅니다. Android에서 각 애플리케이션은 /data/data/<package_name>/ 디렉토리를 받으며, 그 안에 시스템이 files/, cache/, databases/ 하위 디렉토리를 만듭니다. iOS에서 애플리케이션은 Documents/, Library/, tmp/ 디렉토리가 있는 샌드박스 컨테이너에서 작동하며, 각각 고유한 목적과 백업 정책이 있습니다.
개발자는 앱 내부 저장소에 데이터를 저장하는 여러 방법을 사용할 수 있습니다. 각 방법은 특정 작업을 해결하고 특정 데이터 유형에 적합합니다. 올바른 방법을 선택하면 애플리케이션 성능, 개발 편의성 및 사용자 데이터 보안에 직접적인 영향을 미칩니다.
가장 낮은 수준의 방법은 파일 디렉토리에 직접 파일을 쓰는 것입니다. 앱은 샌드박스 내에서 모든 파일과 디렉토리를 만들 수 있습니다. 이 방법은 미디어 파일, 사용자 문서 및 구조화된 구성이 필요하지 않은 모든 바이너리 데이터를 저장하는 데 적합합니다. Android에서는 Context.getFilesDir() 호출을 통해 디렉토리에 액세스하며, 앱의 파일 디렉토리 절대 경로를 반환합니다. iOS에서는 NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) 함수가 비슷한 목적을 수행합니다.
키-값 쌍을 저장하기 위해 Android는 SharedPreferences와 Kotlin 코루틴 및 protobuf 프로토콜 기반의 더 현대적인 DataStore를 제공합니다. SharedPreferences는 /data/data/<package>/shared_prefs/ 디렉토리 내의 XML 파일에 데이터를 저장합니다. 단순함에도 불구하고 SharedPreferences에는 단점이 있습니다. 동기 쓰기는 UI 스레드에서 지연을 유발할 수 있으며, 타입 안전성 부족은 오류 위험을 증가시킵니다. DataStore는 Flow 기반 비동기 API와 protobuf 스키마를 통한 완전한 타입 지원을 제공하여 이러한 문제를 해결합니다.
관계형 연결이 있는 구조화된 데이터에는 SQLite 또는 Room 래퍼가 최적의 선택입니다. 데이터베이스는 databases/ 디렉토리 내의 단일 파일에 저장되며 완전한 SQL 구문을 지원합니다. Room은 공식 Jetpack 라이브러리로, 타입 안전 API, 자동 스키마 마이그레이션 및 코루틴 지원을 제공합니다. 적절한 인덱싱을 통해 데이터베이스 크기는 성능 저하 없이 수 기가바이트에 도달할 수 있습니다. 모바일 기기의 SQLite는 최신 플래그십 프로세서에서 초당 최대 50,000회 쓰기 작업을 처리합니다.
인증 토큰 및 암호화 키와 같은 기밀 데이터를 저장하기 위해 Android는 EncryptedSharedPreferences를 제공합니다. 표준 SharedPreferences를 래핑하는 이 기능은 AES256-GCM-None을 사용하여 키와 값을 자동으로 암호화합니다. 암호화는 디스크에 쓰기 전에 파일 수준에서 수행되므로 기기에 물리적으로 액세스하더라도 공격자가 내용을 읽을 수 없습니다. EncryptedSharedPreferences는 AndroidX Security 라이브러리의 일부이며, 전체 파일을 암호화하는 EncryptedFile도 포함합니다.
Android SDK는 Context 클래스를 통해 내부 저장소를 사용하는 다양한 메서드를 제공합니다. 각 메서드는 앱 샌드박스 내의 특정 시스템 디렉토리 경로를 반환합니다. Kotlin 예제를 사용하여 기본 파일 쓰기 및 읽기 작업을 살펴보겠습니다.
내부 파일 디렉토리 경로를 가져오는 주요 메서드는 context.filesDir입니다. /data/data/<package>/files/ 디렉토리를 가리키는 File 객체를 반환합니다. 첫 액세스 시 시스템이 필요한 모든 상위 디렉토리를 자동으로 만듭니다. 내부 저장소의 파일 크기는 명시적으로 제한되지 않지만, 총 데이터 양은 /data 파티션의 사용 가능한 공간을 초과해서는 안 되며, 일반적으로 전체 플래시 메모리 용량의 60~80%입니다.
val context = getApplicationContext()
val file = File(context.filesDir, "notes.txt")
file.writeText("노트 내용")
val content = file.readText()
println("읽음: $content")
writeText 및 readText 메서드는 Kotlin 표준 라이브러리의 확장 함수입니다. 스트림 열기와 닫기를 자동으로 관리하여 메모리 누수를 방지합니다. 바이너리 데이터에는 writeBytes 및 readBytes를 사용합니다. 인코딩이 필요 없으며 ByteArray 배열로 작동합니다. 큰 파일 작업 시 버퍼링된 스트림 사용을 권장합니다. 텍스트에는 BufferedReader 및 BufferedWriter, 바이너리 데이터에는 BufferedInputStream 및 BufferedOutputStream을 사용합니다.
파일을 계층적으로 구성하려면 filesDir 내에 하위 디렉토리를 만드세요. 이미지, 문서, 내보내기 파일 등 유형별로 데이터를 구조화하는 데 도움이 됩니다. mkdirs() 메서드는 중첩된 디렉토리를 포함하여 경로에서 누락된 모든 디렉토리를 만듭니다. 생성 작업이 성공했는지 확인하세요. 메서드는 새 디렉토리가 생성된 경우에만 true를 반환합니다. 생성 실패는 대부분 /data 파티션의 공간 부족 또는 파일 시스템 inode 고갈과 관련됩니다.
val imagesDir = File(context.filesDir, "images")
if (imagesDir.mkdirs()) {
println("디렉토리가 생성되었습니다")
}
val imageFile = File(imagesDir, "photo.jpg")
imageFile.writeBytes(byteArray)
큰 파일을 쓰기 전에 사용 가능한 공간을 확인하려면 File.getFreeSpace() 또는 File.getUsableSpace()를 사용하세요. 두 번째 메서드는 보안 할당량을 고려하여 현재 애플리케이션이 사용 가능한 바이트 수를 반환하며, 다중 사용자 기기 컨텍스트에서 더 정확합니다. 사용 가능한 공간이 예상 파일 크기보다 작으면 사용자에게 메시지를 표시하고 기기 설정에서 공간을 확보하도록 제안합니다.
iOS에서 각 애플리케이션은 격리된 샌드박스 컨테이너에서 작동합니다. 시스템은 특별한 entitlements 없이 경계를 넘는 API를 제공하지 않습니다. 파일 시스템 작업의 주요 도구는 Foundation 프레임워크의 FileManager 클래스입니다. 샌드박스 컨테이너에는 각각 고유한 백업 정책이 있는 여러 표준 디렉토리가 포함됩니다.
Documents 디렉토리는 애플리케이션 실행 간에 유지되고 백업에서 복원되어야 하는 사용자 데이터를 위한 것입니다. iOS는 자동으로 이 디렉토리를 iCloud 및 iTunes 백업에 포함합니다. urls(for:in:) 메서드는 요청된 디렉토리의 URL 배열을 반환하며, 배열의 첫 번째 요소가 기본입니다.
let fm = FileManager.default
let docs = fm.urls(
for: .documentDirectory,
in: .userDomainMask
).first!
let fileURL = docs.appendingPathComponent("data.plist")
try data.write(to: fileURL)
FileManager는 파일 생성, 복사, 이동, 삭제, 이름 변경 등 완전한 파일 작업 세트를 지원합니다. 각 작업은 오류를 던질 수 있으므로 모든 호출을 do-catch 구문으로 감싸야 합니다. 파일 삭제에 특히 주의하세요. 작업은 되돌릴 수 없으며, removeItem(at:) 이후 데이터 복원은 사전 백업 없이 불가능합니다.
샌드박스 컨테이너의 모든 데이터를 iCloud 백업에 포함해서는 안 됩니다. 예를 들어, 다운로드한 캐시된 이미지나 임시 처리 파일은 복원할 필요가 없으며 다음 사용 시 다시 생성됩니다. 디렉토리나 파일을 백업에서 제외하려면 isExcludedFromBackup 속성을 true로 설정합니다. Apple은 원격으로 복원할 수 있는 데이터를 항상 백업에서 제외할 것을 권장합니다. 이렇게 하면 iCloud 저장소 사용을 최소화하고 복구 시간을 단축할 수 있습니다.
var cacheURL = fm.urls(
for: .cachesDirectory,
in: .userDomainMask
).first!
cacheURL.hasExcludedFromBackupKey = true
var values = URLResourceValues()
values.isExcludedFromBackup = true
try cacheURL.setResourceValues(values)
모바일 기기의 각 저장소 유형에는 고유한 목적과 사용 규칙이 있습니다. 이러한 차이점을 이해하면 개발자가 각 데이터 유형에 적합한 위치를 선택하는 데 도움이 됩니다. 아래는 애플리케이션에서 사용 가능한 세 가지 주요 저장소 유형의 비교입니다.
| 특성 | Internal Storage | 캐시 디렉토리 | External Storage |
|---|---|---|---|
| 다른 앱에 대한 가시성 | 숨김 | 숨김 | 액세스 가능 |
| 앱 제거 시 삭제 | 완전 | 완전 | 위치에 따라 다름 |
| 백업 | Android — 없음, iOS — 있음 (Documents) | 없음 | 동기화 시에만 |
| 미디어 없이 사용 가능 | 항상 | 항상 | SD 카드 필요 |
| 데이터 손실 위험 | 최소 | 높음 | 중간 |
| 권장 파일 크기 | 최대 100 MB | 최대 50 MB | 모든 크기 |
내부 저장소는 다른 프로그램이 액세스할 수 없는 앱 설정, 데이터베이스 파일 및 사용자 문서를 저장하는 데 최적입니다. 캐시 디렉토리는 다운로드한 이미지, API 응답, 중간 처리 데이터 등 다음 사용 시 다시 생성할 수 있는 임시 파일을 위한 것입니다. 외부 저장소는 대용량 미디어 파일(사진, 비디오, 음악)과 사용자가 공유 액세스를 통해 다른 애플리케이션과 공유하려는 데이터에 가장 적합합니다.
저장소 유형 선택은 Google Play 및 App Store의 앱 평점에도 영향을 미칩니다. 정리 없이 내부 저장소에 대량의 데이터를 저장하는 앱은 부정적인 리뷰를 받습니다. 사용자는 공간 부족을 불평합니다. App Annie의 연구에 따르면, 사용자의 62%가 정리 옵션 없이 기기 내부 저장소의 500MB 이상을 차지하는 앱을 삭제합니다.
앱 내부 저장소의 적절한 관리는 성능, 보안 및 사용자 경험을 향상시킵니다. 다음 권장 사항은 공식 Android 및 iOS 문서는 물론 수백만 설치 수의 애플리케이션 개발 실무 경험을 기반으로 합니다.
경계 사례 테스트에 특히 주의해야 합니다. 내부 저장소가 가득 찬 경우, 쓰기 작업이 예기치 않게 중단된 경우(앱 충돌, 수신 전화), iOS 백업에서 복원할 때의 애플리케이션 동작을 확인하세요. 이러한 각 시나리오에서 데이터는 일관성을 유지하거나 마지막 안정 상태로 복원되어야 합니다. 트랜잭션 파일을 사용하세요. 임시 파일에 데이터를 쓴 다음 원자적으로 대상 파일로 이름을 변경합니다. 이렇게 하면 쓰기 실패 시 손상된 데이터를 읽는 것을 방지할 수 있습니다.
사용자 제어를 잊지 마세요. 앱 설정에서 임시 데이터를 지우고 점유된 내부 저장소 용량을 표시하는 옵션을 제공하세요. Google Play Console에 따르면, 이 기능이 있는 앱은 “성능” 카테고리에서 18% 더 많은 긍정적인 리뷰를 받습니다.
자주 묻는 질문
앱의 내부 저장소에 있는 모든 데이터가 완전히 삭제됩니다. 운영 체제는 데이터베이스, 설정, 임시 파일을 포함한 잔여 파일이 없음을 보장합니다. 외부 저장소의 데이터는 남을 수 있습니다.
기기에 대한 루트 액세스 없이 다른 앱은 다른 앱의 Internal Storage에서 파일을 읽을 수 없습니다. Android에서는 슈퍼유저 권한이 필요하며, iOS에서는 샌드박스를 통해 커널 수준에서 격리가 적용됩니다.
명시적 제한은 없지만 총량은 /data 파티션의 사용 가능한 공간에 의해 제한됩니다. 앱당 100MB를 초과하지 않는 것이 좋습니다. 더 큰 용량은 외부 저장소나 클라우드에 배치하는 것이 좋습니다.
filesDir은 영구 앱 데이터용이며 시스템이 필요할 때만 삭제합니다. cacheDir은 임시 파일용으로 메모리 부족 시 시스템이 삭제할 수 있습니다. 시스템은 cacheDir의 지속성을 보장하지 않습니다.
Internal Storage에서 SD 카드로 직접 복사는 보안 정책에 의해 금지됩니다. 사용자 동의를 받아 공유 저장소에 데이터 복사본을 만들려면 Android 10+의 MediaStore API 또는 SAF(Storage Access Framework)를 사용하세요.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.