Android 앱 데이터 백업과 복원 완벽 가이드 — Auto Backup, SQLite·DataStore, 기기 변경과 데이터 마이그레이션

Android 앱을 개발할 때 데이터 저장 기능만큼 중요한 것이 데이터 백업과 복원입니다.

앱이 정상적으로 실행되고 SQLite에 데이터가 잘 저장된다고 해서 사용자의 데이터가 항상 안전한 것은 아닙니다.

사용자는 다음과 같은 상황을 경험할 수 있습니다.

  • 새로운 스마트폰으로 기기 변경

  • 앱 삭제 후 재설치

  • Android 운영체제 업데이트

  • 앱 업데이트 과정에서 데이터베이스 구조 변경

  • 스마트폰 고장 또는 초기화

  • 앱 데이터 손상

  • 실수로 중요한 메모 삭제

특히 사용자가 직접 작성한 메모나 좋아요, 즐겨찾기, 기념일처럼 다시 만들기 어려운 데이터는 더욱 중요합니다.

이번 글에서는 Android의 Auto Backup과 기기 간 데이터 전송 기능을 살펴보고, SQLite와 DataStore 데이터를 안전하게 백업하고 복원하는 방법을 알아보겠습니다.

또한 Android 12 이상에서 사용하는 dataExtractionRules, SQLite 데이터베이스 백업 시 주의사항, 앱 업데이트 마이그레이션, 사용자 직접 백업 파일 내보내기까지 실무 관점에서 정리하겠습니다.

작성 기준: 2026년 10월. 예제는 Kotlin, SQLiteOpenHelper, Preferences DataStore, AndroidX Activity Result API를 기준으로 작성했습니다. 백업 동작은 Android 버전, 제조사, 사용자 백업 설정, 전송 방식, 앱의 targetSdk에 따라 달라질 수 있습니다.


1. Android 앱에서 백업해야 하는 데이터는 무엇일까?

모든 앱 데이터를 백업할 필요는 없습니다.

먼저 데이터를 종류별로 구분해야 합니다.

예를 들어 조류 정보 앱에서는 다음과 같은 데이터가 존재할 수 있습니다.

데이터저장 위치백업 필요성
조류 기본 정보SQLite원본 재배포 가능하면 선택적
사용자 메모SQLite매우 높음
좋아요·즐겨찾기SQLite높음
검색 기록SQLite사용자 설정에 따라 결정
다크 모드DataStore권장
알림 설정DataStore권장
이미지 캐시Glide 캐시일반적으로 불필요
임시 다운로드cache 디렉터리불필요
인증 토큰앱 내부 저장소별도 보안 정책 필요

핵심은 사용자가 직접 생성한 데이터와 앱이 다시 다운로드하거나 생성할 수 있는 데이터를 구분하는 것입니다.

예를 들어 서버에서 다시 받을 수 있는 조류 기본 데이터는 백업 대상에서 제외할 수 있습니다.

반면 사용자가 작성한 메모는 앱이 다시 생성할 수 없으므로 백업 우선순위가 높습니다.

2. Android 데이터 백업 방식 비교

Android 앱에서 사용할 수 있는 백업 방식은 크게 네 가지입니다.

방식특징적합한 상황
Android Auto BackupAndroid 시스템의 백업·복원 기능 활용일반적인 앱 데이터 보존
기기 간 데이터 전송새 기기로 앱 데이터 이동스마트폰 교체
사용자 직접 백업 파일파일 내보내기·가져오기수동 백업, 장기 보관
서버 동기화계정 기반 데이터 저장·복원여러 기기 사용, 지속적인 동기화

각 방식에는 장단점이 있습니다.

특히 Auto Backup은 사용자 설정과 기기 환경에 영향을 받으므로 반드시 복원된다고 보장할 수 없습니다.

중요한 사용자 데이터라면 수동 내보내기나 서버 동기화 같은 보완 수단을 제공하는 것이 좋습니다.


3. Android Auto Backup이란?

Auto Backup은 Android 시스템이 앱 데이터를 백업하고, 지원되는 상황에서 복원할 수 있도록 제공하는 기능입니다.

일반적으로 앱 내부 저장소의 파일, 데이터베이스, SharedPreferences 등이 백업 대상에 포함될 수 있습니다.

하지만 모든 파일이 무조건 백업되는 것은 아닙니다.

백업 규칙, 저장 위치, Android 버전 등에 따라 제외되는 파일이 있습니다.

또한 실제 백업 수행 시점과 복원 여부는 Android 시스템 및 백업 전송 서비스에 의해 결정됩니다.

4. Auto Backup의 기본 동작

개념적으로 다음과 같은 흐름입니다.

기존 스마트폰
      │
      ▼
Android 백업 시스템
      │
      ▼
백업 전송 서비스
      │
      ▼
새 스마트폰
      │
      ▼
앱 설치 및 복원
      │
      ▼
SQLite / DataStore 데이터 사용

이때 개발자가 고려해야 할 사항은 다음과 같습니다.

  1. 백업에 포함할 파일

  2. 백업에서 제외할 파일

  3. 데이터베이스 스키마 호환성

  4. 복원 후 사용자 인증 상태

  5. 기기 변경 시 데이터 일관성

특히 복원된 데이터가 현재 앱 버전과 호환되는지 확인해야 합니다.


5. AndroidManifest.xml 백업 설정

AndroidManifest.xml에는 다음과 같은 백업 관련 속성을 설정할 수 있습니다.

<application
    android:allowBackup="true"
    android:fullBackupContent="@xml/backup_rules"
    android:dataExtractionRules="@xml/data_extraction_rules"
    ... >
</application>

주요 속성은 다음과 같습니다.

속성역할
allowBackup앱의 백업 허용 관련 설정
fullBackupContent기존 백업 규칙 파일
dataExtractionRulesAndroid 12 이상 데이터 추출 규칙

Android 12(API 31) 이상을 대상으로 하는 앱에서는 dataExtractionRules를 통해 클라우드 백업과 기기 간 전송 규칙을 구분할 수 있습니다.

다만 allowBackup="false"만으로 모든 제조사의 기기 간 데이터 전송까지 완전히 차단된다고 가정해서는 안 됩니다.

민감한 데이터를 다루는 앱은 실제 대상 기기와 Android 버전에서 백업·전송 동작을 확인해야 합니다.


6. Android 12 이상 dataExtractionRules 설정

res/xml/data_extraction_rules.xml 파일을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>
<data-extraction-rules>

    <cloud-backup>
        <include
            domain="database"
            path="user_data.db" />

        <include
            domain="file"
            path="datastore/" />
    </cloud-backup>

    <device-transfer>
        <include
            domain="database"
            path="user_data.db" />

        <include
            domain="file"
            path="datastore/" />
    </device-transfer>

</data-extraction-rules>

이 예제에서는 사용자 데이터베이스와 DataStore 파일을 백업 대상으로 지정합니다.

다만 실제 SQLite 파일 이름과 DataStore 저장 경로를 확인해야 합니다.

Preferences DataStore는 일반적으로 앱의 files/datastore/ 아래에 파일을 저장합니다.

백업 규칙의 file 도메인 경로는 앱 내부 파일 디렉터리를 기준으로 해석됩니다.

7. 클라우드 백업과 기기 간 전송 구분

Android 12 이상에서는 다음 두 영역을 별도로 설정할 수 있습니다.

<cloud-backup>
    ...
</cloud-backup>
<device-transfer>
    ...
</device-transfer>

클라우드 백업과 기기 간 전송은 서로 다른 방식으로 동작할 수 있습니다.

예를 들어 클라우드 백업에서는 제외하고, 기기 간 전송에서만 허용할 데이터를 구분하는 것도 가능합니다.

단, 제조사별 전송 도구와 시스템 정책 차이가 있으므로 설정 파일만 보고 실제 동작을 단정하지 않는 것이 좋습니다.


8. Android 11 이하 backup_rules.xml

Android 11 이하 환경에서는 fullBackupContent에 지정한 규칙 파일을 사용할 수 있습니다.

res/xml/backup_rules.xml 파일을 생성합니다.

<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>

    <include
        domain="database"
        path="user_data.db" />

    <include
        domain="file"
        path="datastore/" />

</full-backup-content>

Android 12 이상을 지원하는 앱에서도 이전 Android 버전과의 호환성을 위해 두 규칙 파일을 함께 관리할 수 있습니다.

다만 DataStore가 실제로 저장하는 파일 경로와 백업 도메인 구성이 일치하는지 확인해야 합니다.


9. SQLite 데이터베이스 백업 시 주의사항

SQLite는 파일 기반 데이터베이스입니다.

따라서 단순히 .db 파일을 복사하면 백업이 완료된다고 생각하기 쉽습니다.

하지만 실제로는 주의해야 할 점이 있습니다.

SQLite가 WAL(Write-Ahead Logging) 모드로 동작하는 경우 다음 파일이 존재할 수 있습니다.

user_data.db
user_data.db-wal
user_data.db-shm

이때 데이터베이스가 사용 중인 상태에서 user_data.db 파일만 복사하면 최신 변경사항이 반영되지 않을 수 있습니다.

또한 복사 중 데이터베이스가 변경되면 일관성이 깨질 위험이 있습니다.

따라서 수동 백업에서는 데이터베이스를 안전한 시점에 복사하는 전략이 필요합니다.

10. SQLiteOpenHelper 데이터베이스 구조

예를 들어 다음과 같은 구조를 사용한다고 가정하겠습니다.

앱 내부 저장소
      │
      ├── databases/
      │     └── user_data.db
      │
      ├── files/
      │     └── datastore/
      │           └── app_settings.preferences_pb
      │
      └── cache/
            └── 이미지 캐시

여기서 중요한 데이터는 사용자 데이터베이스와 설정 파일입니다.

캐시 디렉터리는 일반적으로 백업 대상에서 제외하는 것이 적합합니다.

다만 앱에서 실제로 사용하는 파일 이름과 경로는 프로젝트마다 다를 수 있습니다.


11. 사용자 데이터와 기본 데이터 분리하기

도감 앱에서는 기본 데이터와 사용자 데이터를 분리하는 구조가 유리합니다.

예를 들어 다음과 같습니다.

catalog.db
  └── korean_birds

user_data.db
  └── bird_memos

catalog.db에는 앱에서 제공하는 조류 기본 정보를 저장합니다.

user_data.db에는 사용자가 직접 작성한 데이터를 저장합니다.

이렇게 분리하면 기본 데이터베이스를 업데이트할 때 사용자 메모를 보호하기 쉬워집니다.

또한 백업 시 사용자 데이터베이스만 선택적으로 포함할 수 있습니다.

하지만 기존 앱에서 하나의 데이터베이스를 사용하고 있다면 반드시 두 파일로 분리해야 하는 것은 아닙니다.

기존 구조에서도 사용자 테이블을 안전하게 유지하도록 설계할 수 있습니다.


12. SQLite 사용자 메모 테이블 예제

예를 들어 다음과 같은 테이블이 있다고 가정하겠습니다.

CREATE TABLE bird_memos (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    bird_id INTEGER NOT NULL UNIQUE,
    is_like INTEGER NOT NULL DEFAULT 0,
    memo TEXT,
    created_at TEXT NOT NULL,
    updated_at TEXT NOT NULL
);

각 컬럼의 역할은 다음과 같습니다.

컬럼설명
id내부 식별자
bird_id조류 데이터 식별자
is_like좋아요 여부
memo사용자 메모
created_at생성 시각
updated_at수정 시각

여기서 bird_id는 기본 조류 데이터베이스의 고유 식별자와 연결됩니다.

백업 및 복원 시 이 식별자가 유지되어야 사용자 메모가 올바른 조류 정보와 연결됩니다.


13. 앱 업데이트와 데이터 백업은 다른 문제

앱을 업데이트한다고 해서 일반적으로 앱 내부 데이터가 자동으로 삭제되는 것은 아닙니다.

하지만 다음과 같은 코드가 있으면 데이터가 손실될 수 있습니다.

override fun onUpgrade(
    db: SQLiteDatabase,
    oldVersion: Int,
    newVersion: Int
) {

    db.execSQL(
        "DROP TABLE IF EXISTS bird_memos"
    )

    onCreate(db)
}

이 코드는 기존 사용자 메모 테이블을 삭제합니다.

따라서 앱 업데이트 과정에서 사용자 데이터가 사라질 수 있습니다.

데이터베이스 버전이 변경되었다고 해서 기존 테이블을 무조건 삭제하는 방식은 피해야 합니다.

14. 안전한 SQLite 마이그레이션

예를 들어 데이터베이스 버전 2에서 메모 수정 시각 컬럼을 추가한다고 가정하겠습니다.

override fun onUpgrade(
    db: SQLiteDatabase,
    oldVersion: Int,
    newVersion: Int
) {

    if (oldVersion < 2) {

        db.execSQL(
            """
            ALTER TABLE bird_memos
            ADD COLUMN updated_at TEXT
            """.trimIndent()
        )
    }
}

이렇게 하면 기존 메모를 유지하면서 새로운 컬럼을 추가할 수 있습니다.

단, 기존 데이터에 기본값이 필요한지, 새로운 컬럼을 NULL로 허용할지 등을 함께 검토해야 합니다.

특히 여러 버전에서 최신 버전으로 업데이트하는 경우에는 중간 마이그레이션이 순서대로 실행되도록 구성해야 합니다.


15. 사용자가 직접 백업 파일을 만들 수 있도록 하기

Auto Backup은 시스템이 관리하는 기능입니다.

따라서 사용자가 원하는 시점에 직접 백업 파일을 생성하는 기능도 제공하면 좋습니다.

예를 들어 설정 화면에 다음 기능을 추가할 수 있습니다.

데이터 관리

[사용자 데이터 백업]

[백업 파일에서 복원]

[백업 파일 정보]

이 기능은 사용자가 데이터를 직접 보관하거나 다른 기기로 옮길 때 유용합니다.

다만 백업 파일에 개인정보가 포함될 수 있으므로 파일 내용과 보안 정책을 명확하게 안내해야 합니다.


16. SQLite 백업 파일 내보내기 방식

사용자가 직접 파일을 저장하도록 하려면 Storage Access Framework를 사용할 수 있습니다.

대표적으로 다음 계약을 사용할 수 있습니다.

ActivityResultContracts.CreateDocument(
    "application/octet-stream"
)

이 방식은 사용자가 백업 파일을 저장할 위치를 직접 선택할 수 있도록 합니다.

다만 파일 선택기가 반환하는 것은 파일 경로가 아니라 Uri입니다.

따라서 ContentResolver를 이용해 데이터를 기록해야 합니다.

17. CreateDocument로 백업 파일 저장하기

다음은 백업 파일을 저장할 위치를 선택하는 예제입니다.

private val backupLauncher =
    registerForActivityResult(
        ActivityResultContracts.CreateDocument(
            "application/octet-stream"
        )
    ) { uri ->

        if (uri != null) {
            exportBackup(uri)
        }
    }

백업 버튼 클릭 시 다음과 같이 호출합니다.

binding.buttonBackup.setOnClickListener {

    backupLauncher.launch(
        "bird_backup.db"
    )
}

사용자가 저장 위치를 선택하면 exportBackup()이 호출됩니다.

하지만 여기서 데이터베이스 파일을 곧바로 복사하는 것은 주의해야 합니다.

데이터베이스가 사용 중인 상태라면 안전한 스냅샷을 생성한 뒤 내보내는 것이 좋습니다.


18. SQLite 데이터베이스 안전하게 백업하기

SQLite의 WAL 모드를 고려하면 데이터베이스 파일을 복사하기 전에 일관된 백업본을 만드는 것이 중요합니다.

Android에서 사용할 수 있는 방법 중 하나는 데이터베이스의 백업 시점에 쓰기 작업을 제어하고 WAL 체크포인트를 수행하는 것입니다.

예를 들어 SQLiteDatabase의 다음 API를 사용할 수 있습니다.

database.rawQuery(
    "PRAGMA wal_checkpoint(FULL)",
    null
).use { cursor ->

    if (cursor.moveToFirst()) {
        // 체크포인트 결과 확인
    }
}

하지만 WAL 체크포인트만 실행했다고 해서 데이터베이스 파일을 언제든 안전하게 복사할 수 있는 것은 아닙니다.

다른 스레드나 프로세스에서 동시에 쓰기 작업이 진행될 수 있기 때문입니다.

따라서 실제 서비스에서는 다음 조건을 함께 관리해야 합니다.

  1. 백업 중 데이터베이스 쓰기 작업 중지

  2. 진행 중인 트랜잭션 완료

  3. WAL 상태 확인

  4. 일관된 데이터베이스 파일 생성

  5. 백업 완료 후 쓰기 작업 재개

SQLite 버전과 Android API 수준에 따라 지원되는 백업 기능이 다를 수 있으므로, 단순 파일 복사 방식을 적용하기 전에 충분히 검증해야 합니다.


19. SQLite 백업 전용 파일 생성 전략

개인 개발 앱에서는 백업 전용 파일을 별도로 생성하는 방식이 유용할 수 있습니다.

예를 들어 다음과 같은 흐름입니다.

SQLite 원본 DB
      │
      ▼
쓰기 작업 일시 중지
      │
      ▼
일관된 백업 스냅샷 생성
      │
      ▼
임시 백업 파일
      │
      ▼
ContentResolver
      │
      ▼
사용자가 선택한 저장 위치

이렇게 하면 사용자가 선택한 Uri에 원본 데이터베이스를 직접 복사하는 위험을 줄일 수 있습니다.

단, 스냅샷 생성 방식은 앱의 SQLite 사용 구조에 맞게 구현해야 합니다.

또한 백업이 실패했을 때 임시 파일을 정리하고 쓰기 작업을 정상적으로 재개하도록 해야 합니다.


20. JSON 백업 방식도 고려하기

사용자 메모와 좋아요처럼 데이터 양이 많지 않다면 SQLite 파일 자체를 백업하는 대신 JSON으로 내보내는 방법도 있습니다.

예를 들어 다음과 같은 구조입니다.

{
  "backup_version": 1,
  "created_at": "2026-10-30T10:00:00Z",
  "bird_memos": [
    {
      "bird_id": 1001,
      "is_like": true,
      "memo": "공원에서 관찰한 참새"
    },
    {
      "bird_id": 1002,
      "is_like": false,
      "memo": "하천 주변에서 관찰"
    }
  ]
}

JSON 백업 방식의 장점은 다음과 같습니다.

  • SQLite 내부 파일 구조에 덜 의존

  • 백업 파일 버전 관리 용이

  • 다른 기기와 데이터 이전 용이

  • 선택적인 데이터 복원 가능

  • 향후 서버 동기화 기능과 연계 가능

다만 JSON 파일에는 사용자 메모가 평문으로 저장될 수 있습니다.

민감한 정보가 포함된다면 암호화나 파일 보호 정책을 고려해야 합니다.


21. JSON 백업 데이터 모델 정의하기

Kotlin에서는 다음과 같은 데이터 클래스를 사용할 수 있습니다.

data class BirdMemoBackup(
    val birdId: Long,
    val isLike: Boolean,
    val memo: String?
)

백업 전체 모델입니다.

data class AppBackup(
    val backupVersion: Int,
    val createdAt: String,
    val birdMemos: List<BirdMemoBackup>
)

이렇게 하면 Gson 등을 이용해 JSON으로 변환할 수 있습니다.

val json = Gson().toJson(appBackup)

이후 ContentResolver의 OutputStream을 통해 파일에 기록할 수 있습니다.

22. ContentResolver로 JSON 백업 저장하기

suspend fun writeBackup(
    context: Context,
    uri: Uri,
    backup: AppBackup
) = withContext(Dispatchers.IO) {

    val json = Gson().toJson(backup)

    context.contentResolver
        .openOutputStream(uri)
        ?.use { outputStream ->

            outputStream.write(
                json.toByteArray(Charsets.UTF_8)
            )
        }
        ?: throw IOException(
            "백업 파일을 열 수 없습니다."
        )
}

이 코드는 JSON 데이터를 사용자가 선택한 Uri에 저장합니다.

실제 앱에서는 파일 저장 실패, 저장 공간 부족, 권한 문제 등을 처리해야 합니다.

또한 JSON 생성과 파일 기록 중 예외가 발생하면 사용자에게 백업 실패를 명확하게 안내해야 합니다.


23. 백업 파일에서 데이터 복원하기

백업 파일을 불러올 때는 OpenDocument를 사용할 수 있습니다.

private val restoreLauncher =
    registerForActivityResult(
        ActivityResultContracts.OpenDocument()
    ) { uri ->

        if (uri != null) {
            restoreBackup(uri)
        }
    }

복원 버튼 클릭 시 다음과 같이 실행합니다.

binding.buttonRestore.setOnClickListener {

    restoreLauncher.launch(
        arrayOf("application/json")
    )
}

백업 파일을 선택하면 Uri를 통해 내용을 읽을 수 있습니다.

다만 파일 확장자나 MIME 타입만으로 안전한 백업 파일이라고 판단해서는 안 됩니다.

파일 내용을 검증해야 합니다.


24. JSON 백업 파일 읽기

suspend fun readBackup(
    context: Context,
    uri: Uri
): AppBackup = withContext(Dispatchers.IO) {

    val json = context.contentResolver
        .openInputStream(uri)
        ?.bufferedReader(Charsets.UTF_8)
        ?.use { reader ->
            reader.readText()
        }
        ?: throw IOException(
            "백업 파일을 읽을 수 없습니다."
        )

    Gson().fromJson(
        json,
        AppBackup::class.java
    )
}

이 코드는 JSON 파일을 읽어 데이터 클래스로 변환합니다.

실제 서비스에서는 JSON 파싱 결과가 null인지, 필수 필드가 존재하는지, 데이터 형식이 올바른지 확인해야 합니다.

또한 매우 큰 파일을 읽으면 메모리 사용량이 증가할 수 있으므로 파일 크기 제한도 고려해야 합니다.


25. 백업 파일 버전 관리하기

앱이 업데이트되면 백업 데이터 구조도 변경될 수 있습니다.

따라서 백업 파일에는 버전 정보를 포함하는 것이 좋습니다.

예를 들어 다음과 같습니다.

{
  "backup_version": 2,
  "created_at": "2026-10-30T10:00:00Z",
  "bird_memos": []
}

복원할 때는 버전을 확인합니다.

when (backup.backupVersion) {

    1 -> {
        restoreVersion1(backup)
    }

    2 -> {
        restoreVersion2(backup)
    }

    else -> {
        throw IllegalArgumentException(
            "지원하지 않는 백업 버전입니다."
        )
    }
}

다만 이 예제에서는 버전별 백업 모델 변환 로직이 생략되어 있습니다.

실제 프로젝트에서는 구버전 JSON을 최신 데이터 모델로 변환하는 마이그레이션 단계가 필요할 수 있습니다.


26. 백업 복원 시 중복 데이터 처리

사용자가 이미 메모를 작성한 상태에서 백업 파일을 복원하면 같은 조류 ID에 여러 메모가 존재할 수 있습니다.

따라서 복원 정책을 미리 결정해야 합니다.

대표적인 방식은 다음과 같습니다.

방식동작
전체 덮어쓰기기존 데이터를 삭제하고 백업 데이터로 교체
병합기존 데이터와 백업 데이터를 합침
선택적 복원사용자가 복원 항목 선택
충돌 확인중복 항목을 사용자에게 확인

개인 개발 앱에서는 전체 덮어쓰기 또는 병합 방식부터 구현할 수 있습니다.

다만 전체 덮어쓰기는 기존 데이터를 잃을 수 있으므로 복원 전에 반드시 사용자에게 안내해야 합니다.

27. SQLite UPSERT를 이용한 복원

예를 들어 bird_id가 UNIQUE인 테이블에서는 중복 여부를 기준으로 업데이트하거나 삽입할 수 있습니다.

Android에 포함된 SQLite 버전은 OS에 따라 다를 수 있습니다.

따라서 모든 지원 Android 버전에서 최신 SQLite UPSERT 문법을 사용할 수 있다고 가정해서는 안 됩니다.

호환성을 고려한다면 UPDATE 후 INSERT를 수행하는 방식도 사용할 수 있습니다.

fun restoreMemo(
    db: SQLiteDatabase,
    item: BirdMemoBackup
) {

    val values = ContentValues().apply {
        put("is_like", if (item.isLike) 1 else 0)
        put("memo", item.memo)
    }

    val updated = db.update(
        "bird_memos",
        values,
        "bird_id = ?",
        arrayOf(item.birdId.toString())
    )

    if (updated == 0) {

        values.put("bird_id", item.birdId)

        db.insertOrThrow(
            "bird_memos",
            null,
            values
        )
    }
}

이 예제는 기본적인 복원 구조를 보여줍니다.

실제 테이블에서 created_at, updated_at 등이 NOT NULL이라면 해당 컬럼도 함께 처리해야 합니다.

또한 동시 쓰기와 충돌을 방지하려면 복원 작업 전체를 트랜잭션으로 관리해야 합니다.


28. 트랜잭션으로 복원 작업 보호하기

여러 개의 메모를 복원할 때는 중간에 오류가 발생할 수 있습니다.

예를 들어 100개 중 50개를 복원한 뒤 실패하면 일부 데이터만 변경된 상태가 될 수 있습니다.

이 문제를 방지하기 위해 트랜잭션을 사용할 수 있습니다.

db.beginTransaction()

try {

    backup.birdMemos.forEach { item ->

        restoreMemo(
            db,
            item
        )
    }

    db.setTransactionSuccessful()

} finally {

    db.endTransaction()
}

트랜잭션이 성공하면 변경사항이 반영되고, 실패하면 해당 트랜잭션의 변경사항을 롤백할 수 있습니다.

단, 복원 대상 데이터가 많다면 메인 스레드에서 실행하지 않아야 합니다.

또한 복원 도중 다른 화면에서 동일한 데이터베이스에 쓰기 작업을 수행하지 않도록 동시성 제어를 고려해야 합니다.


29. 복원 전에 백업 파일 검증하기

외부 파일을 읽는 기능은 반드시 입력값을 검증해야 합니다.

예를 들어 다음 항목을 확인할 수 있습니다.

  • 백업 버전

  • 파일 크기

  • JSON 구조

  • 필수 필드 존재 여부

  • ID 값의 유효성

  • 문자열 길이 제한

  • 중복 데이터 여부

  • 데이터 타입

  • 지원하지 않는 필드

  • 예상하지 못한 대량 데이터

예를 들어 음수 ID가 들어 있다면 복원을 중단하거나 해당 항목을 제외할 수 있습니다.

if (item.birdId <= 0L) {
    throw IllegalArgumentException(
        "잘못된 조류 ID입니다."
    )
}

외부 파일은 사용자가 직접 선택했더라도 신뢰할 수 없는 입력으로 취급하는 것이 안전합니다.


30. DataStore 설정값 백업하기

SQLite뿐 아니라 DataStore에 저장된 사용자 설정도 백업할 수 있습니다.

예를 들어 다음과 같은 설정입니다.

theme_mode = DARK
notification_enabled = true
wifi_only_download = false

DataStore 파일 자체를 복사하는 방법도 있지만, 사용자 직접 백업 기능에서는 설정값을 JSON 모델에 포함하는 방식이 관리하기 쉬울 수 있습니다.

예를 들어 다음과 같습니다.

data class SettingsBackup(
    val themeMode: String,
    val notificationEnabled: Boolean,
    val wifiOnlyDownload: Boolean
)

백업 전체 모델에 추가합니다.

data class AppBackup(
    val backupVersion: Int,
    val createdAt: String,
    val birdMemos: List<BirdMemoBackup>,
    val settings: SettingsBackup?
)

이렇게 하면 사용자 메모와 설정을 하나의 JSON 파일로 내보낼 수 있습니다.


31. DataStore 설정 복원하기

복원된 설정은 DataStore의 edit()를 이용해 저장합니다.

suspend fun restoreSettings(
    context: Context,
    backup: SettingsBackup
) {

    context.dataStore.edit { preferences ->

        preferences[
            SettingsKeys.THEME_MODE
        ] = backup.themeMode

        preferences[
            SettingsKeys.NOTIFICATION_ENABLED
        ] = backup.notificationEnabled

        preferences[
            SettingsKeys.WIFI_ONLY_DOWNLOAD
        ] = backup.wifiOnlyDownload
    }
}

다만 외부 백업 파일의 문자열을 그대로 저장하기 전에 값이 유효한지 확인해야 합니다.

예를 들어 테마 모드는 SYSTEM, LIGHT, DARK 중 하나인지 검증하는 것이 좋습니다.

또한 SQLite 트랜잭션과 DataStore의 edit()는 하나의 공통 트랜잭션이 아닙니다.

따라서 SQLite 복원에는 성공했지만 DataStore 복원에는 실패하는 상황을 고려해야 합니다.

필요하다면 복원 순서와 실패 시 복구 전략을 별도로 설계해야 합니다.


32. 백업 파일에 민감한 정보 포함하지 않기

백업 파일에는 사용자가 직접 작성한 정보가 포함될 수 있습니다.

예를 들어 다음과 같은 데이터입니다.

  • 개인 메모

  • 위치 기록

  • 검색 기록

  • 사용자 식별 정보

  • 계정 관련 설정

따라서 백업 파일을 생성할 때는 실제로 필요한 데이터만 포함해야 합니다.

특히 다음 정보는 일반적인 JSON 백업 파일에 포함하지 않는 것이 좋습니다.

  • 비밀번호

  • 인증 토큰

  • API Secret

  • 암호화 키

  • 세션 정보

사용자 데이터에 민감한 정보가 포함된다면 암호화된 백업 형식이나 별도의 보호 방법을 검토해야 합니다.


33. Android Keystore와 백업의 관계

Android Keystore를 이용해 암호화 키를 생성하는 경우에는 백업과 복원 시 주의해야 합니다.

Keystore의 키는 일반적으로 기기와 앱 환경에 연결되며, 백업을 통해 다른 기기로 그대로 복원할 수 있다고 가정해서는 안 됩니다.

예를 들어 다음과 같은 상황이 발생할 수 있습니다.

기존 스마트폰
   │
   ├── 암호화된 데이터
   └── Android Keystore 키
               │
               ▼
          새 스마트폰
               │
               ▼
      암호화된 데이터만 복원
               │
               ▼
       기존 키로 복호화 불가

따라서 Keystore 키로 암호화한 파일을 백업할 때는 복원 가능성을 별도로 설계해야 합니다.

민감한 데이터를 보호하면서 여러 기기에서 복원하려면 사용자 암호 기반 암호화나 서버 기반 키 관리 등 별도 구조가 필요할 수 있습니다.


34. 앱 삭제 후 재설치 시 데이터는 유지될까?

일반적으로 Android 앱을 삭제하면 해당 앱의 내부 저장소 데이터도 삭제됩니다.

따라서 SQLite와 DataStore 데이터도 사라질 수 있습니다.

다만 Auto Backup이 활성화되어 있고 복원 조건이 충족되면 재설치 시 일부 데이터가 복원될 수 있습니다.

하지만 다음과 같은 이유로 복원되지 않을 수 있습니다.

  • 백업이 수행되지 않음

  • 사용자가 백업 기능을 비활성화

  • 백업 대상에서 제외

  • 백업 데이터가 오래됨

  • 앱 서명 또는 패키지 관련 조건 불일치

  • 백업 전송 서비스의 정책

  • 기기 환경 차이

따라서 앱 삭제 후 재설치 시 데이터가 반드시 유지된다고 안내해서는 안 됩니다.


35. 새 스마트폰으로 기기 변경하기

기기 변경 시에는 Android 시스템의 데이터 전송 기능을 사용할 수 있습니다.

하지만 앱 개발자는 데이터가 실제로 이전되는지 테스트해야 합니다.

예를 들어 다음과 같은 시나리오를 구성할 수 있습니다.

기존 스마트폰

조류 메모 20개
좋아요 50개
다크 모드 ON
       │
       ▼
기기 간 데이터 전송
       │
       ▼
새 스마트폰

메모 20개 확인
좋아요 50개 확인
다크 모드 확인

또한 다음 항목도 확인해야 합니다.

  • SQLite 스키마 버전

  • 데이터베이스 파일 정상 열기

  • DataStore 설정값

  • 사용자 로그인 상태

  • 암호화된 파일 복원 가능 여부

  • 앱 권한 상태

  • 알림 설정

특히 권한은 단순히 앱 설정값을 복원했다고 해서 모두 동일하게 적용되는 것은 아닙니다.


36. 백업과 서버 동기화의 차이

백업과 동기화는 비슷해 보이지만 목적이 다릅니다.

백업은 특정 시점의 데이터를 보관하고 필요할 때 복원하는 기능입니다.

동기화는 여러 기기나 서버 사이에서 데이터 변경사항을 지속적으로 맞추는 기능입니다.

구분백업동기화
목적데이터 복구데이터 일관성
실행 시점주기적 또는 수동변경 시 또는 주기적
데이터 충돌복원 시 발생 가능여러 기기에서 발생 가능
서버 필요필수 아님일반적으로 필요
오프라인 지원가능로컬 저장 구조 필요

예를 들어 메모가 많은 앱에서는 백업 기능만으로도 사용자 데이터 보호에 도움이 됩니다.

하지만 여러 기기에서 같은 메모를 수정해야 한다면 서버 동기화 기능이 필요할 수 있습니다.


37. 백업 기능을 설정 화면에 추가하기

설정 화면에는 다음과 같은 데이터 관리 메뉴를 추가할 수 있습니다.

설정
──────────────────────────

데이터 관리

사용자 데이터 백업
메모와 좋아요를 파일로 저장합니다.

백업 파일에서 복원
이전에 저장한 데이터를 불러옵니다.

백업 파일 정보
백업 형식과 포함 항목을 확인합니다.

──────────────────────────

복원 기능에는 확인 대화상자를 제공하는 것이 좋습니다.

예를 들어 다음과 같은 안내입니다.

백업 파일을 복원하시겠습니까?

복원 방식에 따라 현재 메모와
좋아요가 변경될 수 있습니다.

복원 전에 현재 데이터를
백업하는 것을 권장합니다.

[취소] [복원]

사용자가 복원 결과를 예상할 수 있도록 안내하는 것이 중요합니다.


38. 백업 및 복원 테스트 시나리오

백업 기능은 정상 동작뿐 아니라 실패 상황도 테스트해야 합니다.

테스트확인할 내용
메모 백업모든 메모 포함
좋아요 백업상태 유지
설정 백업DataStore 값 포함
백업 파일 생성파일 정상 저장
빈 데이터 백업오류 없이 처리
백업 파일 복원데이터 정상 복구
중복 데이터 복원충돌 정책 적용
손상된 JSON안전하게 오류 처리
지원하지 않는 버전복원 중단
저장 공간 부족오류 안내
복원 중 앱 종료데이터 일관성 유지
Android 버전 변경호환성 확인
앱 업데이트사용자 데이터 유지
기기 변경백업·복원 동작 확인
암호화 데이터복호화 가능 여부 확인

특히 복원 도중 앱이 종료되는 상황을 테스트하는 것이 중요합니다.

트랜잭션을 적절히 사용했다면 SQLite 내부의 일부 변경만 반영되는 문제를 줄일 수 있습니다.

다만 SQLite와 DataStore를 동시에 복원하는 작업은 별도의 복구 전략이 필요합니다.


39. 개인 개발자에게 추천하는 백업 전략

개인 개발 앱이라면 처음부터 복잡한 클라우드 동기화 시스템을 구축할 필요는 없습니다.

다음과 같은 순서로 구현하는 것이 현실적입니다.

1단계. 데이터 분류

사용자 데이터와 재생성 가능한 데이터를 구분합니다.

2단계. Auto Backup 규칙 설정

SQLite와 DataStore의 백업 범위를 결정합니다.

3단계. 데이터베이스 마이그레이션 검증

앱 업데이트 시 사용자 데이터가 유지되는지 확인합니다.

4단계. JSON 백업 기능 구현

메모, 좋아요, 설정 등을 파일로 내보냅니다.

5단계. 복원 기능 구현

파일 검증, 충돌 처리, 트랜잭션을 적용합니다.

6단계. 사용자 안내

백업 파일에 포함되는 데이터와 복원 정책을 명확하게 설명합니다.

7단계. 실제 기기 테스트

앱 업데이트, 재설치, 기기 변경 상황을 테스트합니다.

8단계. 필요할 때 서버 동기화 도입

여러 기기에서 데이터를 공유해야 한다면 서버 동기화를 추가합니다.


40. Android 데이터 백업 체크리스트

  • 사용자 데이터와 기본 데이터 구분

  • 백업 대상 파일 목록 작성

  • AndroidManifest 백업 설정 확인

  • Android 12 이상 dataExtractionRules 설정

  • Android 11 이하 backup_rules 설정

  • SQLite 데이터베이스 파일 경로 확인

  • WAL 모드 사용 여부 확인

  • 사용 중인 DB 파일 단순 복사 방지

  • 사용자 메모 백업

  • 좋아요 및 즐겨찾기 백업

  • DataStore 설정 백업

  • 이미지 캐시 백업 제외

  • 민감한 인증 정보 백업 제외

  • 백업 파일 버전 관리

  • JSON 필수 필드 검증

  • 파일 크기 제한 검토

  • 복원 충돌 정책 정의

  • SQLite 트랜잭션 적용

  • 복원 실패 시 롤백 확인

  • DataStore 복원 실패 처리

  • 사용자 복원 확인 대화상자 제공

  • 앱 업데이트 마이그레이션 테스트

  • 앱 삭제 후 재설치 테스트

  • 기기 변경 테스트

  • Android Keystore 암호화 데이터 검토

  • 백업 파일 개인정보 보호 검토

41. 마무리

Android 앱에서 데이터 백업과 복원은 사용자의 신뢰와 직결되는 기능입니다.

특히 사용자 메모, 좋아요, 즐겨찾기, 기념일처럼 사용자가 직접 만든 데이터는 앱 업데이트나 기기 변경 과정에서 최대한 안전하게 유지되어야 합니다.

이를 위해 다음 원칙을 기억하는 것이 좋습니다.

  1. 사용자 데이터와 다시 생성할 수 있는 데이터를 구분합니다.

  2. Auto Backup은 편리하지만 복원을 보장하지 않습니다.

  3. Android 12 이상에서는 dataExtractionRules를 확인합니다.

  4. SQLite 데이터베이스 백업 시 WAL과 동시 쓰기를 고려합니다.

  5. 앱 업데이트에서는 기존 사용자 테이블을 삭제하지 않습니다.

  6. 사용자 직접 백업 기능에는 JSON 형식도 고려합니다.

  7. 백업 파일에는 버전 정보를 포함합니다.

  8. 복원 전 파일 구조와 입력값을 검증합니다.

  9. 복원 작업은 가능한 범위에서 트랜잭션으로 보호합니다.

  10. 백업 파일의 개인정보와 암호화 키 관리 문제를 고려합니다.

특히 SQLiteOpenHelper를 사용하는 앱에서는 데이터베이스 구조와 사용자 데이터 보존 정책을 처음부터 분리해 설계하면 유지보수가 쉬워집니다.

또한 DataStore 설정과 SQLite 사용자 데이터를 함께 백업할 수 있도록 구성하면 사용자가 새 기기에서도 기존 환경을 복원하는 데 도움이 됩니다.

백업 기능의 핵심은 파일을 저장하는 것이 아니라, 실제로 데이터가 손실된 상황에서 안전하게 복원할 수 있도록 검증하는 것입니다.


참고 자료

태그: Android, Kotlin, Android Auto Backup, dataExtractionRules, SQLite, SQLiteOpenHelper, DataStore, SharedPreferences, 데이터 백업, 데이터 복원, 기기 변경, JSON 백업, Storage Access Framework, Android Keystore, 데이터베이스 마이그레이션