Sao lưu các cặp khoá-giá trị bằng Android Backup Service

Android Backup Service cung cấp tính năng sao lưu và khôi phục bộ nhớ trên đám mây cho dữ liệu khoá-giá trị trong ứng dụng Android của bạn. Trong hoạt động sao lưu khoá-giá trị, dữ liệu sao lưu của ứng dụng sẽ được chuyển đến công cụ sao lưu của thiết bị. Nếu thiết bị đang sử dụng công cụ sao lưu mặc định của Google, thì dữ liệu sẽ được chuyển vào Android Backup Service để lưu trữ.

Giới hạn dữ liệu là 5 MB cho mỗi người dùng ứng dụng của bạn. Bạn sẽ không phải trả phí cho việc lưu trữ dữ liệu sao lưu.

Để biết tổng quan về các tuỳ chọn sao lưu của Android và hướng dẫn về dữ liệu bạn cần sao lưu và khôi phục, hãy xem Tổng quan về sao lưu dữ liệu.

Triển khai sao lưu khoá-giá trị

Để sao lưu dữ liệu ứng dụng, bạn cần triển khai một tác nhân sao lưu. Trình quản lý sao lưu sẽ gọi tác nhân sao lưu của bạn trong cả quá trình sao lưu và khôi phục.

Để triển khai một tác nhân sao lưu, bạn phải:

  1. Khai báo tác nhân sao lưu trong tệp kê khai với thuộc tính android:backupAgent.

  2. Xác định một tác nhân sao lưu bằng cách thực hiện một trong những thao tác sau:

    • Mở rộng BackupAgent

      Lớp BackupAgent cung cấp giao diện trung tâm mà ứng dụng của bạn sử dụng để giao tiếp với Trình quản lý sao lưu. Nếu trực tiếp mở rộng lớp này, bạn phải ghi đè onBackup()onRestore() để xử lý bản sao lưu và khôi phục hoạt động cho dữ liệu của bạn.

    • Mở rộng BackupAgentHelper

      Lớp BackupAgentHelper cung cấp một trình bao bọc tiện lợi xung quanh lớp BackupAgent, nhằm giảm thiểu số lượng mã bạn cần viết. Trong BackupAgentHelper, bạn phải sử dụng một hoặc nhiều đối tượng trợ giúp. Các đối tượng này tự động sao lưu và khôi phục một số loại dữ liệu nhất định để bạn không cần triển khai onBackup()onRestore(). Bạn nên sử dụng BackupAgentHelper để xử lý các bản sao lưu của ứng dụng, trừ phi cần có toàn quyền kiểm soát các bản sao lưu của ứng dụng.

      Android hiện cung cấp các trình trợ giúp sao lưu để sao lưu và khôi phục các tệp hoàn chỉnh từ SharedPreferencesbộ nhớ trong.

Khai báo tác nhân sao lưu trong tệp kê khai

Sau khi bạn quyết định tên lớp cho tác nhân sao lưu, hãy khai báo tên đó trong tệp kê khai sử dụng thuộc tính android:backupAgent trong thẻ <application>.

Ví dụ:

<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>

Để hỗ trợ các thiết bị cũ, bạn nên thêm khoá API <meta-data> vào tệp kê khai Android của mình. Dịch vụ sao lưu của Android không còn yêu cầu khoá dịch vụ nữa, nhưng một số thiết bị cũ vẫn có thể kiểm tra khoá khi sao lưu. Hãy đặt android:name thành com.google.android.backup.api_keyandroid:value thành unused.

Thuộc tính android:restoreAnyVersion nhận giá trị boolean cho biết bạn có muốn khôi phục dữ liệu ứng dụng hay không bất kể phiên bản ứng dụng hiện tại so với phiên bản đã tạo dữ liệu sao lưu. Giá trị mặc định là false. Hãy xem Kiểm tra phiên bản khôi phục dữ liệu để biết thêm thông tin.

Mở rộng BackupAgentHelper

Bạn nên tạo tác nhân sao lưu sử dụng BackupAgentHelper nếu muốn sao lưu các tệp hoàn chỉnh từ SharedPreferences hoặc bộ nhớ trong. Việc tạo tác nhân sao lưu bằng BackupAgentHelper yêu cầu ít mã hơn so với việc mở rộng BackupAgent vì bạn không phải triển khai onBackup()onRestore().

Việc triển khai BackupAgentHelper phải sử dụng một hoặc nhiều trình trợ giúp sao lưu. Trình trợ giúp sao lưu là một thành phần đặc biệt giúp BackupAgentHelper triệu tập để thực hiện các hoạt động sao lưu và khôi phục cho một loại dữ liệu cụ thể. Khung Android hiện cung cấp 2 trình trợ giúp:

Bạn có thể đưa nhiều trình trợ giúp vào BackupAgentHelper, nhưng chỉ cần một trình trợ giúp cho mỗi loại dữ liệu. Nghĩa là nếu có nhiều SharedPreferences tệp, thì bạn chỉ cần một SharedPreferencesBackupHelper.

Đối với mỗi trình trợ giúp mà bạn muốn thêm vào BackupAgentHelper, hãy làm theo các bước sau trong phương thức onCreate():

  1. Tạo bản sao lớp trình trợ giúp mong muốn. Trong hàm khởi tạo lớp, bạn phải chỉ định (các) tệp muốn sao lưu.
  2. Gọi addHelper() để thêm trình trợ giúp vào BackupAgentHelper.

Các phần sau đây mô tả cách tạo tác nhân sao lưu sử dụng từng trình trợ giúp có sẵn.

Sao lưu SharedPreferences

Khi tạo SharedPreferencesBackupHelper, bạn phải bao gồm tên của một hoặc nhiều tệp SharedPreferences.

Ví dụ: để sao lưu tệp SharedPreferences có tên user_preferences, một tác nhân sao lưu hoàn chỉnh sử dụng BackupAgentHelper sẽ có dạng như sau:

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);
    }
}

SharedPreferencesBackupHelper bao gồm tất cả mã cần thiết để sao lưu và khôi phục tệp SharedPreferences.

Khi Trình quản lý sao lưu gọi onBackup()onRestore(), BackupAgentHelper sẽ gọi ra trình trợ giúp sao lưu của bạn để sao lưu và khôi phục các tệp đã chỉ định.

Sao lưu các tệp khác

Khi tạo một FileBackupHelper, bạn phải bao gồm tên của một hoặc nhiều tệp được lưu vào bộ nhớ trong của ứng dụng, như được chỉ định bởi getFilesDir() ở cùng vị trí mà openFileOutput() ghi tệp.

Ví dụ: để sao lưu hai tệp có tên scoresstats, một tác nhân sao lưu sử dụng BackupAgentHelper sẽ có dạng như sau:

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 bao gồm tất cả mã cần thiết để sao lưu và khôi phục các tệp được lưu vào bộ nhớ trong của ứng dụng.

Tuy nhiên, việc đọc và ghi vào tệp trên bộ nhớ trong là không an toàn theo luồng. Để đảm bảo tác nhân sao lưu của bạn không đọc hoặc ghi các tệp cùng một lúc với các hoạt động, bạn phải sử dụng các câu lệnh được đồng bộ hoá mỗi khi thực hiện đọc hoặc ghi. Ví dụ: trong bất kỳ hoạt động nào mà bạn đọc và ghi tệp, bạn cần có đối tượng để sử dụng làm khoá nội tại cho các câu lệnh được đồng bộ hoá:

Kotlin

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

Java

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

Sau đó, tạo một câu lệnh được đồng bộ hoá bằng kiểu khoá này mỗi khi bạn đọc hoặc ghi các tệp. Ví dụ: đây là câu lệnh được đồng bộ hoá để ghi điểm số mới nhất trong một trò chơi vào một tệp:

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");
}

Bạn nên đồng bộ hoá các câu lệnh đã đọc bằng cùng một khoá.

Sau đó, trong BackupAgentHelper, bạn phải ghi đè onBackup()onRestore() để đồng bộ hoá các hoạt động sao lưu và khôi phục với cùng một khoá nội tại. Ví dụ: điểm dữ liệu MyFileBackupAgent ở trên cần các phương thức sau:

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);
    }
}

Mở rộng BackupAgent

Hầu hết các ứng dụng không cần phải mở rộng trực tiếp lớp BackupAgent, mà thay vào đó nên mở rộng BackupAgentHelper để tận dụng các lớp trình trợ giúp tích hợp sẵn sẽ tự động sao lưu và khôi phục các tệp của bạn. Tuy nhiên, bạn có thể trực tiếp mở rộng BackupAgent để thực hiện những việc sau:

  • Định phiên bản định dạng dữ liệu của bạn. Chẳng hạn, nếu dự đoán nhu cầu sửa đổi định dạng mà bạn ghi dữ liệu ứng dụng, bạn có thể tạo tác nhân sao lưu để kiểm tra chéo phiên bản ứng dụng trong suốt hoạt động khôi phục và thực hiện mọi công việc có khả năng tương thích cần thiết nếu phiên bản trên thiết bị khác với phiên bản của dữ liệu sao lưu. Để biết thêm thông tin, hãy xem Kiểm tra phiên bản dữ liệu khôi phục.
  • Chỉ định các phần dữ liệu cần sao lưu. Thay vì sao lưu toàn bộ tệp, bạn có thể chỉ định các phần dữ liệu để sao lưu và cách mỗi phần sau đó được khôi phục vào thiết bị. Điều này cũng có thể giúp bạn quản lý nhiều phiên bản vì bạn đọc và ghi dữ liệu của mình dưới dạng các thực thể duy nhất, thay vì hoàn thiện các tệp.
  • Sao lưu dữ liệu trong cơ sở dữ liệu. Nếu bạn có cơ sở dữ liệu SQLite muốn khôi phục khi người dùng cài đặt lại ứng dụng của bạn, hãy tạo BackupAgent tuỳ chỉnh để đọc dữ liệu thích hợp trong hoạt động sao lưu, sau đó tạo bảng và chèn dữ liệu trong hoạt động khôi phục.

Nếu bạn không cần thực hiện bất kỳ thao tác nào ở trên và muốn sao lưu các tệp hoàn chỉnh từ SharedPreferences hoặc bộ nhớ trong, hãy xem Mở rộng BackupAgentHelper.

Phương thức bắt buộc

Khi tạo BackupAgent, bạn phải triển khai các phương pháp gọi lại sau:

onBackup()
Trình quản lý sao lưu gọi phương thức này sau khi bạn yêu cầu sao lưu. Trong phương thức này, bạn sẽ đọc dữ liệu ứng dụng của mình từ thiết bị và chuyển dữ liệu bạn muốn sao lưu vào Trình quản lý sao lưu, như mô tả trong phần Sao lưu.
onRestore()

Trình quản lý sao lưu gọi phương thức này trong hoạt động khôi phục. Phương thức này cung cấp dữ liệu sao lưu của bạn. Ứng dụng của bạn có thể sử dụng dữ liệu này để khôi phục trạng thái trước đây, như mô tả trong phần Khôi phục.

Hệ thống gọi phương thức này để khôi phục mọi dữ liệu sao lưu khi người dùng cài đặt lại ứng dụng, nhưng ứng dụng của bạn cũng có thể yêu cầu khôi phục.

Sao lưu

Yêu cầu sao lưu không dẫn đến một lệnh gọi ngay đến phương thức onBackup(). Thay vào đó, Trình quản lý sao lưu sẽ chờ một thời gian thích hợp, sau đó sao lưu dữ liệu cho tất cả các ứng dụng đã yêu cầu sao lưu kể từ lần sao lưu gần đây nhất. Đây là thời điểm bạn phải cung cấp dữ liệu ứng dụng của mình cho Trình quản lý sao lưu để có thể lưu dữ liệu vào bộ nhớ trên đám mây.

Chỉ Trình quản lý sao lưu mới có thể gọi phương thức onBackup() của tác nhân sao lưu. Mỗi khi dữ liệu ứng dụng của bạn thay đổi và bạn muốn sao lưu, bạn phải yêu cầu hoạt động sao lưu bằng cách gọi dataChanged(). Hãy xem phần Yêu cầu sao lưu để biết thêm thông tin.

Mẹo: Trong khi phát triển ứng dụng, bạn có thể bắt đầu hoạt động sao lưu ngay lập tức từ Trình quản lý sao lưu bằng công cụ bmgr.

Khi gọi phương thức onBackup(), Trình quản lý sao lưu sẽ chuyển 3 thông số:

oldState
ParcelFileDescriptor mở, ở chế độ chỉ đọc, trỏ đến trạng thái sao lưu gần nhất do ứng dụng cung cấp. Đây không phải là dữ liệu sao lưu từ bộ nhớ trên đám mây, mà là biểu diễn cục bộ về dữ liệu được sao lưu vào lần trước onBackup() được gọi, như được newState xác định hoặc từ onRestore(). onRestore() được đề cập trong phần tiếp theo. Do onBackup() không cho phép bạn đọc dữ liệu sao lưu hiện có trong bộ nhớ trên đám mây, nên bạn có thể sử dụng biểu diễn cục bộ này để xác định xem dữ liệu của mình đã thay đổi kể từ lần sao lưu gần đây nhất hay chưa.
data
Đối tượng BackupDataOutput mà bạn sử dụng để gửi dữ liệu sao lưu đến Trình quản lý sao lưu.
newState
ParcelFileDescriptor mở, ở chế độ đọc/ghi, trỏ đến tệp mà bạn phải ghi nội dung biểu diễn cho dữ liệu mà bạn đã cung cấp cho data. Nội dung biểu diễn có thể đơn giản như dấu thời gian sửa đổi gần đây nhất của tệp. Đối tượng này được trả về dưới dạng oldState vào lần tiếp theo mà Trình quản lý sao lưu gọi phương thức onBackup(). Nếu bạn không ghi dữ liệu sao lưu của mình vào newState, thì oldState sẽ trỏ đến một tệp trống vào lần tiếp theo Trình quản lý sao lưu gọi onBackup().

Bằng cách sử dụng các thông số này, hãy triển khai phương thức onBackup() của bạn để thực hiện những việc sau:

  1. Kiểm tra xem dữ liệu của bạn đã thay đổi kể từ lần sao lưu gần đây nhất hay chưa bằng cách so sánh oldState với dữ liệu hiện tại. Cách đọc dữ liệu trong oldState tuỳ thuộc vào cách ban đầu bạn ghi dữ liệu vào newState (xem bước 3). Cách dễ nhất để ghi lại trạng thái của tệp là sử dụng dấu thời gian được sửa đổi lần cuối. Ví dụ: dưới đây là cách bạn có thể đọc và so sánh dấu thời gian từ 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
    }
    

    Nếu không có gì thay đổi và bạn không cần sao lưu, hãy chuyển sang bước 3.

  2. Nếu dữ liệu của bạn đã thay đổi, so với oldState, hãy ghi dữ liệu hiện tại vào data để sao lưu dữ liệu vào bộ nhớ trên đám mây.

    Bạn phải viết từng phần dữ liệu dưới dạng thực thể trong BackupDataOutput. Thực thể là một bản ghi dữ liệu nhị phân phẳng được xác định bởi một chuỗi khoá duy nhất. Do đó, về mặt nhận thức, tập dữ liệu mà bạn sao lưu là một tập hợp các cặp khoá-giá trị.

    Để thêm một thực thể vào tập dữ liệu sao lưu, bạn phải:

    1. Gọi writeEntityHeader(), chuyển khoá chuỗi duy nhất cho dữ liệu bạn sắp ghi và kích thước dữ liệu.

    2. Gọi writeEntityData(), chuyển vùng đệm byte chứa dữ liệu của bạn và số byte cần ghi từ vùng đệm và cần khớp với kích thước được chuyển đến writeEntityHeader().

    Ví dụ: mã sau đây làm phẳng một số dữ liệu thành luồng byte và ghi dữ liệu đó vào một thực thể duy nhất:

    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);
    

    Thực hiện thao tác này cho từng phần dữ liệu mà bạn muốn sao lưu. Việc bạn phân chia dữ liệu thành các thực thể như thế nào là do bạn quyết định. Thậm chí, bạn chỉ có thể sử dụng một thực thể.

  3. Dù bạn có thực hiện sao lưu hay không (ở bước 2), hãy ghi nội dung biểu diễn dữ liệu hiện tại vào newState ParcelFileDescriptor. Trình quản lý sao lưu sẽ giữ lại đối tượng này trong cục bộ dưới dạng nội dung biểu diễn dữ liệu hiện đã được sao lưu. Trình này sẽ chuyển lại cho bạn dưới dạng oldState vào lần tới gọi onBackup() để bạn có thể xác định xem liệu một bản sao lưu khác có cần thiết hay không, như đã xử lý ở bước 1. Nếu bạn không ghi trạng thái dữ liệu hiện tại vào tệp này, thì oldState sẽ trống trong lần gọi lại tiếp theo.

    Ví dụ sau đây sẽ lưu nội dung biểu diễn dữ liệu hiện tại vào newState sử dụng dấu thời gian sửa đổi gần đây nhất của tệp:

    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);
    

Khôi phục

Khi đến thời điểm khôi phục dữ liệu ứng dụng, Trình quản lý sao lưu sẽ gọi phương thức onRestore() của tác nhân sao lưu. Khi gọi phương thức này, Trình quản lý sao lưu sẽ phân phối dữ liệu sao lưu của bạn để bạn có thể khôi phục dữ liệu đó trên thiết bị.

Chỉ Trình quản lý sao lưu mới có thể gọi onRestore(). Quá trình này tự động diễn ra khi hệ thống cài đặt ứng dụng của bạn và tìm dữ liệu sao lưu hiện có.

Khi gọi phương thức onRestore(), Trình quản lý sao lưu sẽ chuyển 3 thông số:

data
Đối tượng BackupDataInput, cho phép bạn đọc dữ liệu sao lưu.
appVersionCode
Một số nguyên thể hiện giá trị của thuộc tính tệp kê khai android:versionCode của ứng dụng, giống như khi bạn sao lưu dữ liệu này. Bạn có thể sử dụng số này để kiểm tra chéo phiên bản ứng dụng hiện tại và xác định xem định dạng dữ liệu có tương thích hay không. Để biết thêm thông tin về cách sử dụng phiên bản này để xử lý nhiều phiên bản dữ liệu khôi phục, hãy xem Kiểm tra phiên bản khôi phục dữ liệu.
newState
ParcelFileDescriptor mở, ở chế độ đọc/ghi, trỏ đến tệp mà bạn phải ghi trạng thái sao lưu cuối cùng được cung cấp kèm theo data. Đối tượng này được trả về dưới dạng oldState vào lần tiếp theo gọi onBackup(). Xin lưu ý rằng bạn cũng phải ghi cùng đối tượng newState trong lệnh gọi lại onBackup() và thực hiện việc này ở đây đảm bảo rằng đối tượng oldState được cấp cho onBackup() là hợp lệ ngay từ lần đầu tiên gọi onBackup() sau khi thiết bị được khôi phục.

Khi triển khai onRestore(), bạn nên gọi readNextHeader() trên data để lặp lại tất cả các thực thể trong tập dữ liệu. Đối với mỗi thực thể tìm thấy, hãy làm như sau:

  1. Nhận khoá thực thể bằng getKey().
  2. So sánh khoá thực thể với danh sách các khoá đã biết mà bạn nên khai báo là chuỗi cuối cùng tĩnh bên trong lớp BackupAgent. Khi khoá khớp với một trong các chuỗi khoá đã biết, hãy nhập câu lệnh để trích xuất dữ liệu thực thể và lưu vào thiết bị:

    1. Tải kích thước dữ liệu thực thể bằng getDataSize() và tạo một mảng byte có kích thước đó.
    2. Gọi readEntityData(), chuyển nó vào mảng byte (đây là nơi dữ liệu sẽ được chuyển đi) và chỉ định đích bù trừ bắt đầu và kích thước cần đọc.
    3. Mảng byte của bạn hiện đã đầy. Đọc dữ liệu và ghi vào thiết bị theo cách bạn muốn.
  3. Sau khi bạn đọc và ghi dữ liệu trở lại thiết bị, hãy ghi trạng thái của dữ liệu vào thông số newState giống như khi bạn thực hiện trong onBackup().

Ví dụ: dưới đây là cách bạn có thể khôi phục dữ liệu đã được sao lưu theo điểm dữ liệu ở phần trước:

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);
}

Trong ví dụ này, thông số appVersionCode được chuyển đến onRestore() không được sử dụng. Tuy nhiên, bạn có thể muốn sử dụng thông số này nếu bạn đã chọn thực hiện sao lưu khi phiên bản ứng dụng của người dùng đã thực sự di chuyển lùi (ví dụ: người dùng chuyển từ phiên bản 1.5 của ứng dụng sang 1.0). Để biết thêm thông tin, hãy xem phần tiếp theo.

Kiểm tra phiên bản dữ liệu khôi phục

Khi Trình quản lý sao lưu lưu dữ liệu vào bộ nhớ trên đám mây, nó sẽ tự động bao gồm phiên bản ứng dụng của bạn như được xác định bởi thuộc tính android:versionCode của tệp kê khai. Trước khi gọi tác nhân sao lưu để khôi phục dữ liệu, Trình quản lý sao lưu sẽ xem android:versionCode của ứng dụng cần cài đặt và so sánh với giá trị đã ghi trong tập dữ liệu khôi phục. Nếu phiên bản được ghi lại trong tập dữ liệu khôi phục mới hơn phiên bản ứng dụng trên thiết bị, thì người dùng đã hạ cấp ứng dụng của họ. Trong trường hợp này, Trình quản lý sao lưu sẽ huỷ hoạt động khôi phục cho ứng dụng của bạn và không gọi phương thức onRestore(), vì tập dữ liệu khôi phục được xem là không có ý nghĩa đối với phiên bản cũ.

Bạn có thể ghi đè hành vi này bằng thuộc tính android:restoreAnyVersion. Đặt thuộc tính này thành true để cho biết bạn muốn khôi phục ứng dụng bất kể phiên bản của tập dữ liệu khôi phục. Giá trị mặc định là false. Nếu bạn đặt giá trị này thành true thì Trình quản lý sao lưu sẽ bỏ qua android:versionCode và gọi phương thức onRestore() trong mọi trường hợp. Khi làm vậy, bạn có thể kiểm tra sự khác biệt về phiên bản theo cách thủ công trong phương thức onRestore() và thực hiện mọi bước cần thiết để làm cho dữ liệu tương thích nếu các phiên bản không khớp.

Để giúp bạn xử lý nhiều phiên bản trong hoạt động khôi phục, phương thức onRestore() sẽ chuyển cho bạn mã phiên bản có trong tập dữ liệu khôi phục dưới dạng thông số appVersionCode. Sau đó, bạn có thể truy vấn mã phiên bản của ứng dụng hiện tại bằng trường PackageInfo.versionCode. Ví dụ:

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;
}

Sau đó, so sánh version thu được từ PackageInfo với appVersionCode được chuyển vào onRestore().

Yêu cầu sao lưu

Bạn có thể yêu cầu một hoạt động sao lưu bất cứ lúc nào bằng cách gọi dataChanged(). Phương thức này thông báo cho Trình quản lý sao lưu rằng bạn muốn sao lưu dữ liệu sử dụng tác nhân sao lưu của mình. Sau đó, Trình quản lý sao lưu sẽ gọi phương thức onBackup() của tác nhân sao lưu trong tương lai. Thông thường, bạn nên yêu cầu sao lưu mỗi khi dữ liệu thay đổi (chẳng hạn như khi người dùng thay đổi lựa chọn ưu tiên về ứng dụng mà bạn muốn sao lưu). Nếu bạn gọi dataChanged() nhiều lần trước khi Trình quản lý sao lưu yêu cầu một bản sao lưu từ tác nhân của bạn, thì tác nhân này sẽ chỉ nhận một lệnh gọi đến onBackup().

Yêu cầu khôi phục

Trong thời gian hoạt động bình thường của ứng dụng, bạn không cần yêu cầu hoạt động khôi phục. Hệ thống tự động kiểm tra dữ liệu sao lưu và thực hiện khôi phục khi ứng dụng của bạn được cài đặt.

Chuyển sang tính năng Tự động sao lưu

Bạn có thể chuyển đổi ứng dụng của mình sang bản sao lưu toàn bộ dữ liệu bằng cách đặt android:fullBackupOnly thành true trong phần tử <application> của tệp kê khai. Khi chạy trên thiết bị chạy Android 5.1 (API cấp 22) trở xuống, ứng dụng sẽ bỏ qua giá trị này trong tệp kê khai và tiếp tục thực hiện sao lưu khoá-giá trị. Khi chạy trên thiết bị chạy Android 6.0 (API cấp 23) trở lên, ứng dụng sẽ thực hiện sao lưu Tự động sao lưu thay vì sao lưu khoá-giá trị.

Quyền riêng tư của người dùng

Tại Google, chúng tôi thấu hiểu sự tin tưởng của người dùng đối với chúng tôi và trách nhiệm của chúng tôi trong việc bảo vệ quyền riêng tư của người dùng. Google truyền dữ liệu sao lưu tới và từ các máy chủ của Google một cách an toàn để cung cấp các tính năng sao lưu và khôi phục. Google xử lý dữ liệu này như thông tin cá nhân theo Chính sách quyền riêng tư của Google.

Ngoài ra, người dùng có thể tắt chức năng sao lưu dữ liệu thông qua phần cài đặt sao lưu của hệ thống Android. Khi người dùng tắt tính năng sao lưu, Android Backup Service sẽ xoá tất cả dữ liệu sao lưu đã lưu. Người dùng có thể bật lại tính năng sao lưu trên thiết bị, nhưng Android Backup Service sẽ không khôi phục bất kỳ dữ liệu nào đã bị xoá trước đó.