Android Backup Service로 키-값 쌍 백업

Android Backup Service는 Android 앱에서 키-값 데이터의 클라우드 스토리지 백업 및 복원을 제공합니다. 키-값 백업 작업 중 앱의 백업 데이터가 기기의 백업 전송으로 전달됩니다. 기기가 기본 Google 백업 전송을 사용한다면 데이터는 보관처리를 위해 Android Backup Service로 전달됩니다.

데이터는 앱 사용자당 5MB로 제한됩니다. 백업 데이터를 저장하는 데 드는 비용은 없습니다.

Android의 백업 옵션 개요와 백업 및 복원해야 하는 데이터에 관한 안내는 데이터 백업 개요를 참고하세요.

키-값 백업 구현

앱 데이터를 백업하려면 백업 에이전트를 구현해야 합니다. 백업 관리자는 백업 및 복원 중에 백업 에이전트를 호출합니다.

백업 에이전트를 구현하려면 다음을 실행해야 합니다.

  1. android:backupAgent 속성을 사용하여 매니페스트 파일에 백업 에이전트를 선언합니다.

  2. 다음 중 하나를 진행하여 백업 에이전트를 정의합니다.

    • BackupAgent 확장

      BackupAgent 클래스는 앱이 백업 관리자와 통신하는 데 사용하는 중앙 인터페이스를 제공합니다. 이 클래스를 직접 확장하려면 onBackup()onRestore()를 재정의하여 데이터 백업 및 복원 작업을 처리해야 합니다.

    • BackupAgentHelper 확장

      BackupAgentHelper 클래스는 BackupAgent 클래스의 간편한 래퍼를 제공하고 개발자가 작성해야 하는 코드의 양을 최소화합니다. BackupAgentHelper에서는 특정 데이터 유형을 자동으로 백업 및 복원하는 도우미 객체를 하나 이상 사용해야 하므로 onBackup()onRestore()를 구현할 필요가 없습니다. 앱의 백업을 완전히 제어해야 하는 경우가 아니라면 BackupAgentHelper를 사용하여 앱 백업을 처리하는 것이 좋습니다.

      Android는 현재 SharedPreferences내부 저장소의 전체 파일을 백업 및 복원하는 백업 도우미를 제공합니다.

매니페스트에 백업 에이전트 선언

백업 에이전트의 클래스 이름을 정했으면 <application> 태그의 android:backupAgent 속성을 사용하여 매니페스트에 클래스 이름을 선언합니다.

예:

<manifest ... >
    ...
    <application android:label="MyApplication"
                 android:backupAgent="MyBackupAgent">
        <meta-data android:name="com.google.android.backup.api_key"
            android:value="unused" />
        <activity ... >
            ...
        </activity>
    </application>
</manifest>

이전 기기를 지원하려면 API 키 <meta-data>를 Android 매니페스트 파일에 추가하는 것이 좋습니다. Android Backup Service에서는 더 이상 서비스 키가 필요하지 않지만 일부 이전 기기에서는 아직도 백업 시 키를 확인할 수 있습니다. android:namecom.google.android.backup.api_key로, android:valueunused로 설정합니다.

android:restoreAnyVersion 속성은 현재 앱 버전과 관계없이 백업 데이터를 생성한 버전과 비교하여 앱 데이터를 복원할지를 나타내는 부울 값을 사용합니다. 기본값은 false입니다. 자세한 내용은 복원 데이터 버전 확인을 참고하세요.

BackupAgentHelper 확장

SharedPreferences 또는 내부 저장소 둘 중 하나에서 받은 전체 파일을 백업하려면 BackupAgentHelper를 사용하여 백업 에이전트를 빌드해야 합니다. BackupAgentHelper를 사용하여 백업 에이전트를 빌드하면 onBackup()onRestore()를 구현하지 않아도 되므로 BackupAgent를 확장하는 것보다 필요한 코드가 훨씬 적습니다.

BackupAgentHelper를 구현하려면 백업 도우미를 하나 이상 사용해야 합니다. 백업 도우미는 특정 데이터 유형의 백업 및 복원 작업을 실행하기 위해 BackupAgentHelper에서 호출하는 특화된 구성요소입니다. Android 프레임워크는 현재 다음의 두 가지 도우미를 제공합니다.

BackupAgentHelper에 도우미를 여러 개 포함할 수 있지만, 데이터 유형별로 하나의 도우미만 필요합니다. 즉, SharedPreferences 파일이 여러 개 있더라도 하나의 SharedPreferencesBackupHelper만 필요합니다.

BackupAgentHelper에 추가하려는 각 도우미별로 onCreate() 메서드 내에서 다음을 실행해야 합니다.

  1. 원하는 도우미 클래스의 인스턴스를 인스턴스화합니다. 클래스 생성자에서 백업할 파일을 지정해야 합니다.
  2. addHelper()를 호출하여 BackupAgentHelper에 도우미를 추가합니다.

다음 섹션에서는 사용 가능한 각 도우미를 사용하여 백업 에이전트를 만드는 방법을 설명합니다.

SharedPreferences 백업

SharedPreferencesBackupHelper를 인스턴스화할 때 하나 이상의 SharedPreferences 파일 이름을 포함해야 합니다.

예를 들어, user_preferences라는 이름의 SharedPreferences 파일을 백업하기 위해 BackupAgentHelper를 사용하는 백업 에이전트의 완성된 모습은 다음과 같습니다.

Kotlin

// The name of the SharedPreferences file
const val PREFS = "user_preferences"

// A key to uniquely identify the set of backup data
const val PREFS_BACKUP_KEY = "prefs"

class MyPrefsBackupAgent : BackupAgentHelper() {
    override fun onCreate() {
        // Allocate a helper and add it to the backup agent
        SharedPreferencesBackupHelper(this, PREFS).also {
            addHelper(PREFS_BACKUP_KEY, it)
        }
    }
}

Java

public class MyPrefsBackupAgent extends BackupAgentHelper {
    // The name of the SharedPreferences file
    static final String PREFS = "user_preferences";

    // A key to uniquely identify the set of backup data
    static final String PREFS_BACKUP_KEY = "prefs";

    // Allocate a helper and add it to the backup agent
    @Override
    public void onCreate() {
        SharedPreferencesBackupHelper helper =
                new SharedPreferencesBackupHelper(this, PREFS);
        addHelper(PREFS_BACKUP_KEY, helper);
    }
}

SharedPreferencesBackupHelperSharedPreferences 파일을 백업하고 복원하는 데 필요한 모든 코드를 포함합니다.

백업 관리자가 onBackup()onRestore()를 호출할 때 BackupAgentHelper는 백업 도우미를 호출하여 지정된 파일을 백업 및 복원합니다.

다른 파일 백업

FileBackupHelper를 인스턴스화할 때 앱의 내부 저장소(getFilesDir()에 의해 지정된 위치이며 openFileOutput()이 파일을 쓰는 위치와 동일함)에 저장되는 파일 이름을 하나 이상 포함해야 합니다.

예를 들어, scoresstats라는 이름의 파일 두 개를 백업하기 위해 BackupAgentHelper를 사용하는 백업 에이전트는 다음과 같습니다.

Kotlin

// The name of the file
const val TOP_SCORES = "scores"
const val PLAYER_STATS = "stats"
// A key to uniquely identify the set of backup data
const val FILES_BACKUP_KEY = "myfiles"

class MyFileBackupAgent : BackupAgentHelper() {
    override fun onCreate() {
        // Allocate a helper and add it to the backup agent
        FileBackupHelper(this, TOP_SCORES, PLAYER_STATS).also {
            addHelper(FILES_BACKUP_KEY, it)
        }
    }
}

Java

public class MyFileBackupAgent extends BackupAgentHelper {
    // The name of the file
    static final String TOP_SCORES = "scores";
    static final String PLAYER_STATS = "stats";

    // A key to uniquely identify the set of backup data
    static final String FILES_BACKUP_KEY = "myfiles";

    // Allocate a helper and add it to the backup agent
    @Override
    public void onCreate() {
        FileBackupHelper helper = new FileBackupHelper(this,
                TOP_SCORES, PLAYER_STATS);
        addHelper(FILES_BACKUP_KEY, helper);
    }
}

FileBackupHelper는 앱의 내부 저장소에 저장되는 파일을 백업 및 복원하는 데 필요한 모든 코드를 포함합니다.

하지만 내부 저장소의 파일을 읽고 쓰는 것은 스레드로부터 안전하지 않습니다. 백업 에이전트가 활동과 동시에 파일을 읽거나 쓰지 않도록 보장하려면 읽기 또는 쓰기를 실행할 때마다 동기화된 문을 사용해야 합니다. 예를 들어 파일을 읽고 쓰는 모든 활동에서 다음과 같이 동기화된 문의 고유 잠금으로 사용할 객체가 필요합니다.

Kotlin

// Object for intrinsic lock
companion object {
    val sDataLock = Any()
}

Java

// Object for intrinsic lock
static final Object sDataLock = new Object();

그런 다음 파일을 읽거나 쓸 때마다 이 잠금을 사용하여 동기화된 문을 만듭니다. 예를 들어 게임에서 파일에 가장 최근의 점수를 기록하는 동기화된 문은 다음과 같습니다.

Kotlin

try {
    synchronized(MyActivity.sDataLock) {
        val dataFile = File(filesDir, TOP_SCORES)
        RandomAccessFile(dataFile, "rw").apply {
            writeInt(score)
        }
    }
} catch (e: IOException) {
    Log.e(TAG, "Unable to write to file")
}

Java

try {
    synchronized (MyActivity.sDataLock) {
        File dataFile = new File(getFilesDir(), TOP_SCORES);
        RandomAccessFile raFile = new RandomAccessFile(dataFile, "rw");
        raFile.writeInt(score);
    }
} catch (IOException e) {
    Log.e(TAG, "Unable to write to file");
}

read 문은 동일한 잠금으로 동기화해야 합니다.

그런 다음, BackupAgentHelper에서 onBackup()onRestore()를 재정의하여 동일한 고유 잠금으로 백업 및 복원 작업을 동기화해야 합니다. 예를 들어, 위의 MyFileBackupAgent 예는 다음의 메서드가 필요합니다.

Kotlin

@Throws(IOException::class)
override fun onBackup(
        oldState: ParcelFileDescriptor,
        data: BackupDataOutput,
        newState: ParcelFileDescriptor
) {
    // Hold the lock while the FileBackupHelper performs back up
    synchronized(MyActivity.sDataLock) {
        super.onBackup(oldState, data, newState)
    }
}

@Throws(IOException::class)
override fun onRestore(
        data: BackupDataInput,
        appVersionCode: Int,
        newState: ParcelFileDescriptor
) {
    // Hold the lock while the FileBackupHelper restores the file
    synchronized(MyActivity.sDataLock) {
        super.onRestore(data, appVersionCode, newState)
    }
}

Java

@Override
public void onBackup(ParcelFileDescriptor oldState, BackupDataOutput data,
          ParcelFileDescriptor newState) throws IOException {
    // Hold the lock while the FileBackupHelper performs back up
    synchronized (MyActivity.sDataLock) {
        super.onBackup(oldState, data, newState);
    }
}

@Override
public void onRestore(BackupDataInput data, int appVersionCode,
        ParcelFileDescriptor newState) throws IOException {
    // Hold the lock while the FileBackupHelper restores the file
    synchronized (MyActivity.sDataLock) {
        super.onRestore(data, appVersionCode, newState);
    }
}

BackupAgent 확장

대부분의 앱에서 직접 BackupAgent 클래스를 확장할 필요는 없지만 대신 BackupAgentHelper를 확장하여 자동으로 파일을 백업 및 복원하는 내장된 도우미 클래스를 활용해야 합니다. 하지만 BackupAgent를 직접 확장하여 다음 작업을 진행할 수 있습니다.

  • 데이터 형식의 버전을 관리합니다. 예를 들어, 앱 데이터를 쓰는 형식을 수정할 필요가 있을 것으로 예상된다면 백업 에이전트를 빌드하여 복원 작업 동안 앱 버전을 교차 확인하고 기기의 버전이 백업 데이터의 버전과 다른 경우 필요한 모든 호환성 작업을 실행할 수 있습니다. 자세한 내용은 복원 데이터 버전 확인을 참고하세요.
  • 백업할 데이터의 일부를 지정합니다. 전체 파일을 백업하는 대신, 백업할 데이터의 일부를 지정하고 그 부분을 기기에 복원할 방법을 지정할 수 있습니다. 이는 완성된 파일이라기보다는 고유한 항목으로 데이터를 읽고 쓰기 때문에 서로 다른 버전을 관리하는 데도 도움이 됩니다.
  • 데이터베이스의 데이터를 백업합니다. 사용자가 앱을 다시 설치할 때 복원할 SQLite 데이터베이스가 있다면 백업 작업 동안 적절한 데이터를 읽도록 맞춤 BackupAgent를 빌드해야 하고 그런 다음 복원 작업 중에 표를 생성하여 데이터를 삽입해야 합니다.

위의 작업을 실행할 필요가 없고 SharedPreferences 또는 내부 저장소에서 전체 파일을 백업하려면 BackupAgentHelper 확장을 참고하세요.

필수 메서드

BackupAgent를 생성하려면 다음 콜백 메서드를 구현해야 합니다.

onBackup()
백업을 요청하면 백업 관리자가 이 메서드를 호출합니다. 이 메서드에서는 백업 실행에 설명한 대로 기기에서 앱 데이터를 읽고 백업하려는 데이터를 백업 관리자에 전달합니다.
onRestore()

백업 관리자는 복원 작업 중에 이 메서드를 호출합니다. 이 메서드는 복원 실행에 설명한 대로 앱이 데이터의 이전 상태를 복원하는 데 사용할 수 있는 백업 데이터를 전달합니다.

시스템은 사용자가 앱을 다시 설치할 때 이 메서드를 호출하여 모든 백업 데이터를 복원하지만 앱이 복원을 요청할 수도 있습니다.

백업 실행

백업 요청을 한다고 onBackup() 메서드가 즉시 호출되는 것은 아닙니다. 대신, 백업 관리자는 적당한 시간을 기다린 다음 마지막 백업이 실행된 이후로 백업을 요청한 모든 앱의 백업을 실행합니다. 이때 앱 데이터를 백업 관리자에 제공해야 하므로 앱 데이터가 클라우드 스토리지에 저장될 수 있습니다.

백업 관리자만 백업 에이전트의 onBackup() 메서드를 호출할 수 있습니다. 앱 데이터가 변경되고 백업을 실행하려고 할 때마다 dataChanged()를 호출하여 백업 작업을 요청해야 합니다. 자세한 내용은 백업 요청을 참고하세요.

: 앱을 개발할 때 백업 관리자에서 bmgr 도구를 사용하여 즉시 백업 작업을 시작할 수 있습니다.

백업 관리자가 onBackup() 메서드를 호출하면 세 개의 매개변수가 전달됩니다.

oldState
열기 및 읽기 전용 ParcelFileDescriptor로, 앱에서 제공한 마지막 백업 상태를 가리킵니다. 이 데이터는 클라우드 스토리지의 백업 데이터가 아니라 마지막으로 onBackup()이 호출되었을 때 백업된 데이터의 로컬 표현(newState 또는 onRestore()에서 정의됨)입니다. onRestore()은 다음 섹션에서 다룹니다. onBackup()을 사용하면 클라우드 스토리지의 기존 백업 데이터를 읽을 수 없기 때문에 이 로컬 표현을 사용하여 마지막 백업 이후 데이터가 변경되었는지 판단할 수 있습니다.
data
BackupDataOutput 객체. 백업 관리자로 백업 데이터를 전달하는 데 사용합니다.
newState
열기 및 읽기/쓰기 ParcelFileDescriptor로, data에 전달되는 데이터의 표현을 써야 하는 파일을 가리킵니다. 표현은 파일의 최종 수정된 타임스탬프만큼 간단하면 됩니다. 이 객체는 다음에 백업 관리자가 onBackup() 메서드를 호출할 때 oldState로 반환됩니다. newState에 백업 데이터를 쓰지 않는다면 oldState는 다음에 백업 관리자가 onBackup()을 호출할 때 빈 파일을 가리킵니다.

이러한 매개변수로 onBackup() 메서드를 구현하여 다음을 진행합니다.

  1. oldState와 현재 데이터를 비교하여 마지막 백업 이후로 데이터가 변경되었는지 확인합니다. oldState에 있는 데이터를 읽는 방법은 처음에 newState에 데이터를 쓴 방법에 따라 다릅니다(3단계 참고). 파일의 상태를 기록하는 가장 쉬운 방법은 최종 수정한 타임스탬프를 사용하는 것입니다. 예를 들어, 다음과 같이 oldState의 타임스탬프를 읽고 비교할 수 있습니다.

    Kotlin

    val instream = FileInputStream(oldState.fileDescriptor)
    val dataInputStream = DataInputStream(instream)
    try {
       // Get the last modified timestamp from the state file and data file
       val stateModified = dataInputStream.readLong()
       val fileModified: Long = dataFile.lastModified()
       if (stateModified != fileModified) {
           // The file has been modified, so do a backup
           // Or the time on the device changed, so be safe and do a backup
       } else {
           // Don't back up because the file hasn't changed
           return
       }
    } catch (e: IOException) {
       // Unable to read state file... be safe and do a backup
    }
    

    Java

    // Get the oldState input stream
    FileInputStream instream = new FileInputStream(oldState.getFileDescriptor());
    DataInputStream in = new DataInputStream(instream);
    
    try {
       // Get the last modified timestamp from the state file and data file
       long stateModified = in.readLong();
       long fileModified = dataFile.lastModified();
    
       if (stateModified != fileModified) {
           // The file has been modified, so do a backup
           // Or the time on the device changed, so be safe and do a backup
       } else {
           // Don't back up because the file hasn't changed
           return;
       }
    } catch (IOException e) {
       // Unable to read state file... be safe and do a backup
    }
    

    변경사항이 없고 백업할 필요가 없는 경우 3단계로 건너뜁니다.

  2. oldState와 비교하여 데이터가 변경되었다면 현재 데이터를 data에 써서 클라우드 스토리지에 백업합니다.

    데이터의 각 청크를 BackupDataOutput에 항목으로 써야 합니다. 항목은 고유 키 문자열로 식별되는 평면화된 바이너리 데이터 레코드입니다. 따라서 백업하는 데이터 세트는 개념적으로 키-값 쌍의 조합입니다.

    백업 데이터 세트에 항목을 추가하려면 다음을 진행해야 합니다.

    1. writeEntityHeader()를 호출하여 쓰려는 데이터의 고유 문자열 키와 데이터 크기를 전달합니다.

    2. writeEntityData()를 호출하여 데이터를 포함하는 바이트 버퍼와 버퍼에서 쓰려는 바이트 수(writeEntityHeader()에 전달된 크기와 일치해야 함)를 전달합니다.

    예를 들어, 다음의 코드는 일부 데이터를 바이트 스트림으로 평면화하고 단일 항목에 씁니다.

    Kotlin

    val buffer: ByteArray = ByteArrayOutputStream().run {
       DataOutputStream(this).apply {
           writeInt(playerName)
           writeInt(playerScore)
       }
       toByteArray()
    }
    val len: Int = buffer.size
    data.apply {
       writeEntityHeader(TOPSCORE_BACKUP_KEY, len)
       writeEntityData(buffer, len)
    }
    

    Java

    // Create buffer stream and data output stream for our data
    ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
    DataOutputStream outWriter = new DataOutputStream(bufStream);
    // Write structured data
    outWriter.writeUTF(playerName);
    outWriter.writeInt(playerScore);
    // Send the data to the Backup Manager via the BackupDataOutput
    byte[] buffer = bufStream.toByteArray();
    int len = buffer.length;
    data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len);
    data.writeEntityData(buffer, len);
    

    백업할 각 데이터에 이 작업을 진행합니다. 데이터를 항목으로 나누는 방법은 개발자가 결정하게 됩니다. 개발자는 항목을 하나만 사용할 수도 있습니다.

  3. 2단계에서 백업을 실행했는지와 관계없이 현재 데이터의 표현을 newState ParcelFileDescriptor에 씁니다. 백업 관리자는 이 객체를 현재 백업된 데이터의 표현으로 로컬에 유지합니다. 백업 관리자는 다음에 onBackup()을 호출할 때 oldState로 이 표현을 다시 전달하므로 1단계에서 처리한 것처럼 다른 백업이 필요한지 판단할 수 있습니다. 현재 데이터 상태를 이 파일에 쓰지 않으면 다음 콜백이 호출될 때 oldState는 비어있게 됩니다.

    다음 예에서는 파일의 최종 수정된 타임스탬프를 사용하여 현재 데이터의 표현을 newState에 저장합니다.

    Kotlin

    val modified = dataFile.lastModified()
    FileOutputStream(newState.fileDescriptor).also {
       DataOutputStream(it).apply {
           writeLong(modified)
       }
    }
    

    Java

    FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
    DataOutputStream out = new DataOutputStream(outstream);
    
    long modified = dataFile.lastModified();
    out.writeLong(modified);
    

복원 실행

앱 데이터를 복원할 때가 되면 백업 관리자는 백업 에이전트의 onRestore() 메서드를 호출합니다. 이 메서드를 호출할 때 백업 관리자는 백업 데이터를 전달하므로 데이터를 기기에 복원할 수 있습니다.

백업 관리자만 onRestore()를 호출할 수 있습니다. 이 메서드는 시스템이 앱을 설치하고 기존의 백업 데이터를 찾을 때 자동으로 호출됩니다.

백업 관리자가 onRestore() 메서드를 호출하면 세 개의 매개변수가 전달됩니다.

data
백업 데이터를 읽을 수 있는 BackupDataInput 객체입니다.
appVersionCode
앱의 android:versionCode 매니페스트 속성 값을 나타내는 정수이며 이 데이터가 백업됐을 때와 동일합니다. 이를 사용하여 현재 앱 버전을 교차 확인하고 데이터 형식이 호환되는지 판단할 수 있습니다. 이 값을 사용하여 서로 다른 버전의 복원 데이터를 처리하는 방법에 관한 자세한 내용은 복원 데이터 버전 확인 섹션을 참고하세요.
newState
열기, 읽기/쓰기 ParcelFileDescriptor로, data에서 제공된 최종 백업 상태를 써야 하는 파일을 가리킵니다. 이 객체는 다음에 onBackup()이 호출될 때 oldState로 반환됩니다. 또한 onBackup() 콜백에 동일한 newState 객체를 써야 하는 것에 유의해야 하며 이렇게 하는 것은 기기가 복원된 후 onBackup()이 호출된 것이 처음이라고 해도 onBackup()에 주어진 oldState 객체가 유효하다는 것을 보장합니다.

onRestore()를 구현할 때 readNextHeader()를 호출하여 data에 따라 데이터 세트에서 모든 항목을 반복해야 합니다. 발견된 항목별로 다음을 실행합니다.

  1. getKey()를 사용하여 항목 키를 가져옵니다.
  2. 항목 키와 BackupAgent 클래스 내에 static final 문자열로 선언했어야 하는 알려진 키 값의 목록을 비교합니다. 키가 알려진 키 문자열 중에 하나와 일치하면 다음과 같이 구문에 입력하여 항목 데이터를 추출하고 기기에 저장합니다.

    1. getDataSize()를 사용하여 항목 데이터 크기를 가져오고 크기에 맞는 바이트 배열을 생성합니다.
    2. readEntityData()를 호출하여 데이터가 전송될 바이트 배열을 전달하고 시작 오프셋과 읽어야 하는 크기를 지정합니다.
    3. 이제 바이트 배열은 가득 찬 상태입니다. 원하는 대로 데이터를 읽고 기기에 씁니다.
  3. 데이터를 읽고 기기에 다시 쓴 후 onBackup() 중에 한 것과 마찬가지로 데이터의 상태를 newState 매개변수에 씁니다.

예를 들어, 이전 섹션의 예에서 백업된 데이터를 복원하는 방법은 다음과 같습니다.

Kotlin

@Throws(IOException::class)
override fun onRestore(data: BackupDataInput, appVersionCode: Int,
                       newState: ParcelFileDescriptor) {
    with(data) {
        // There should be only one entity, but the safest
        // way to consume it is using a while loop
        while (readNextHeader()) {
            when(key) {
                TOPSCORE_BACKUP_KEY -> {
                    val dataBuf = ByteArray(dataSize).also {
                        readEntityData(it, 0, dataSize)
                    }
                    ByteArrayInputStream(dataBuf).also {
                        DataInputStream(it).apply {
                            // Read the player name and score from the backup data
                            playerName = readUTF()
                            playerScore = readInt()
                        }
                        // Record the score on the device (to a file or something)
                        recordScore(playerName, playerScore)
                    }
                }
                else -> skipEntityData()
            }
        }
    }

    // Finally, write to the state blob (newState) that describes the restored data
    FileOutputStream(newState.fileDescriptor).also {
        DataOutputStream(it).apply {
            writeUTF(playerName)
            writeInt(mPlayerScore)
        }
    }
}

Java

@Override
public void onRestore(BackupDataInput data, int appVersionCode,
                      ParcelFileDescriptor newState) throws IOException {
    // There should be only one entity, but the safest
    // way to consume it is using a while loop
    while (data.readNextHeader()) {
        String key = data.getKey();
        int dataSize = data.getDataSize();

        // If the key is ours (for saving top score). Note this key was used when
        // we wrote the backup entity header
        if (TOPSCORE_BACKUP_KEY.equals(key)) {
            // Create an input stream for the BackupDataInput
            byte[] dataBuf = new byte[dataSize];
            data.readEntityData(dataBuf, 0, dataSize);
            ByteArrayInputStream baStream = new ByteArrayInputStream(dataBuf);
            DataInputStream in = new DataInputStream(baStream);

            // Read the player name and score from the backup data
            playerName = in.readUTF();
            playerScore = in.readInt();

            // Record the score on the device (to a file or something)
            recordScore(playerName, playerScore);
        } else {
            // We don't know this entity key. Skip it. (Shouldn't happen.)
            data.skipEntityData();
        }
    }

    // Finally, write to the state blob (newState) that describes the restored data
    FileOutputStream outstream = new FileOutputStream(newState.getFileDescriptor());
    DataOutputStream out = new DataOutputStream(outstream);
    out.writeUTF(playerName);
    out.writeInt(mPlayerScore);
}

이 예에서 onRestore()에 전달된 appVersionCode 매개변수는 사용되지 않습니다. 그러나 사용자의 앱 버전이 실제로 더 낮아진 경우(예: 사용자의 앱 버전이 1.5에서 1.0으로 변경됨) 백업을 실행하기로 선택했다면 이 매개변수를 사용할 수도 있습니다. 자세한 내용은 다음 섹션을 참고하세요.

복원 데이터 버전 확인

백업 관리자가 클라우드 스토리지에 데이터를 저장할 때 매니페스트 파일의 android:versionCode 속성에 정의된 앱 버전을 자동으로 포함합니다. 백업 관리자는 백업 에이전트를 호출하여 데이터를 복원하기 전에 설치된 앱의 android:versionCode를 보고 복원 데이터 세트에 기록된 값과 비교합니다. 복원 데이터 세트에 기록된 버전이 기기의 앱 버전보다 최신이면 사용자가 앱을 다운그레이드한 것입니다. 이 경우 이전 버전에는 복원 세트가 무의미하다고 간주하기 때문에 백업 관리자는 앱의 복원 작업을 취소하고 onRestore() 메서드를 호출하지 않습니다.

android:restoreAnyVersion 속성을 사용하여 이 동작을 재정의할 수 있습니다. 앱을 복원 세트 버전과 관계없이 복원하겠다고 나타내려면 이 속성을 true로 설정합니다. 기본값은 false입니다. 이 값을 true로 설정하면 백업 관리자는 android:versionCode를 무시하고 모든 경우에 onRestore() 메서드를 호출합니다. 이렇게 하면 수동으로 onRestore() 메서드에서 버전 차이를 확인하고 버전이 일치하지 않을 경우 데이터를 호환할 수 있게 하는 데 필요한 모든 단계를 실행할 수 있습니다.

복원 작업 중에 다양한 버전을 처리할 수 있도록 onRestore() 메서드는 복원 데이터 세트와 함께 포함된 버전 코드를 appVersionCode 매개변수로 전달합니다. 그런 다음 현재 앱의 버전 코드를 PackageInfo.versionCode 필드를 사용하여 쿼리할 수 있습니다. 예:

Kotlin

val info: PackageInfo? = try {
    packageManager.getPackageInfo(packageName, 0)
} catch (e: PackageManager.NameNotFoundException) {
    null
}

val version: Int = info?.versionCode ?: 0

Java

PackageInfo info;
try {
    String name = getPackageName();
    info = getPackageManager().getPackageInfo(name, 0);
} catch (NameNotFoundException nnfe) {
    info = null;
}

int version;
if (info != null) {
    version = info.versionCode;
}

그런 다음 PackageInfo에서 얻은 versiononRestore()에 전달된 appVersionCode를 비교합니다.

백업 요청

dataChanged()를 호출하여 언제든지 백업 작업을 요청할 수 있습니다. 이 메서드는 백업 에이전트를 사용하여 데이터를 백업하도록 백업 관리자에게 알려줍니다. 그러면 백업 관리자가 미래 어느 시점에 백업 에이전트의 onBackup() 메서드를 호출합니다. 일반적으로 데이터가 변경(예: 백업하려는 앱 환경설정을 사용자가 변경할 때)될 때마다 백업을 요청해야 합니다. 백업 관리자가 에이전트에 백업을 요청하기 전에 dataChanged()를 여러 번 호출해도 에이전트는 onBackup() 호출을 한 번만 수신합니다.

복원 요청

앱이 정상적으로 작동하는 동안에는 복원 작업을 요청하지 않아도 됩니다. 앱이 설치되면 시스템은 자동으로 백업 데이터를 확인하고 복원을 실행합니다.

자동 백업으로 이전

앱을 전체 데이터 백업으로 전환하려면 매니페스트 파일의 <application> 요소에서 android:fullBackupOnlytrue로 설정하면 됩니다. Android 5.1(API 수준 22) 이하를 사용하는 기기에서 앱을 실행하면 앱은 매니페스트에서 이 값을 무시하고 계속 키-값 백업을 실행합니다. Android 6.0(API 수준 23) 이상을 사용하는 기기에서 앱을 실행하면 앱은 키-값 백업 대신 자동 백업을 실행합니다.

사용자 개인 정보 보호

Google은 사용자의 신뢰를 중요하게 생각하며 사용자의 개인 정보를 보호할 책임이 있습니다. Google은 백업 및 복원 기능을 제공하기 위해 Google 서버로/Google 서버에서 백업 데이터를 안전하게 전송합니다. Google은 이 데이터를 Google의 개인정보처리방침에 따라 개인 정보로 취급합니다.

또한 사용자는 Android 시스템의 백업 설정을 통해 데이터 백업 기능을 사용 중지할 수 있습니다. 사용자가 백업을 사용 중지하면 Android Backup Service는 저장된 백업 데이터를 모두 삭제합니다. 사용자는 기기에서 백업을 다시 사용 설정할 수 있지만 Android Backup Service는 이전에 삭제된 데이터를 복원하지 않습니다.