สำรองข้อมูลคู่คีย์-ค่าด้วยบริการสำรองข้อมูล Android

Android Backup Service มีบริการสำรองและกู้คืนข้อมูลในพื้นที่เก็บข้อมูลระบบคลาวด์สำหรับข้อมูลคีย์-ค่าในแอป Android ของคุณ ในระหว่างการสำรองข้อมูลคีย์-ค่า ระบบจะส่งข้อมูลสำรองของแอปไปยังการส่งข้อมูลสำรองของอุปกรณ์ หากอุปกรณ์ใช้ Google ถ่ายโอนข้อมูลสำรองเริ่มต้น ระบบจะส่งข้อมูลไปยัง Android Backup Service เพื่อเก็บถาวร

ข้อมูลจะจำกัดอยู่ที่ 5 MB ต่อผู้ใช้แอปของคุณ และไม่มีค่าใช้จ่ายในการจัดเก็บข้อมูลสำรอง

ดูภาพรวมของตัวเลือกการสำรองข้อมูลของ Android และคำแนะนำเกี่ยวกับข้อมูลที่คุณควรสำรองและกู้คืนได้ที่ภาพรวมการสำรองข้อมูล

ใช้การสำรองข้อมูลคีย์-ค่า

หากต้องการสำรองข้อมูลแอป คุณต้องติดตั้งใช้งานตัวแทนการสำรองข้อมูล ตัวแทนการสำรองข้อมูลจะถูกเรียกโดย Backup Manager ทั้งในระหว่างการสำรองข้อมูลและการกู้คืน

หากต้องการใช้ Agent สำรอง คุณต้องดำเนินการต่อไปนี้

  1. ประกาศตัวแทนการสำรองข้อมูลในไฟล์ Manifest ด้วยแอตทริบิวต์ android:backupAgent

  2. กำหนดตัวแทนสำรองโดยทำอย่างใดอย่างหนึ่งต่อไปนี้

    • กำลังขยาย BackupAgent

      คลาส BackupAgent มีอินเทอร์เฟซส่วนกลางที่แอปใช้เพื่อสื่อสารกับเครื่องมือจัดการการสำรองข้อมูล หากคุณขยายคลาสนี้โดยตรง คุณต้องลบล้าง onBackup() และ onRestore() เพื่อจัดการการดำเนินการสำรองและกู้คืนข้อมูล

    • กำลังขยาย BackupAgentHelper

      คลาส BackupAgentHelper มี Wrapper ที่สะดวกสำหรับคลาส BackupAgent ซึ่งช่วยลดจำนวนโค้ดที่คุณต้องเขียน ใน BackupAgentHelper คุณต้องใช้ออบเจ็กต์ตัวช่วยอย่างน้อย 1 รายการ ซึ่งจะสำรองและกู้คืนข้อมูลบางประเภทโดยอัตโนมัติ เพื่อให้คุณไม่ต้องใช้ onBackup() และ onRestore() เราขอแนะนำให้ใช้ BackupAgentHelper เพื่อจัดการการสำรองข้อมูลของแอป เว้นแต่คุณต้องการควบคุมการสำรองข้อมูลของแอปอย่างเต็มรูปแบบ

      ปัจจุบัน Android มีตัวช่วยสำรองข้อมูลที่จะสำรองและกู้คืนไฟล์ทั้งหมดจาก SharedPreferences และที่จัดเก็บข้อมูลภายใน

ประกาศตัวแทนการสำรองข้อมูลในไฟล์ Manifest

เมื่อเลือกชื่อคลาสสำหรับตัวแทนการสำรองข้อมูลแล้ว ให้ประกาศชื่อนั้นในไฟล์ Manifest โดยใช้แอตทริบิวต์ android:backupAgent ในแท็ก <application>

เช่น

<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> ลงในไฟล์ Manifest ของ Android Android Backup Service ไม่จำเป็นต้องใช้คีย์บริการอีกต่อไป แต่อุปกรณ์รุ่นเก่าบางรุ่นอาจยังคงตรวจสอบคีย์เมื่อสำรองข้อมูล ตั้งค่า android:name เป็น com.google.android.backup.api_key และตั้งค่า android:value เป็น unused

แอตทริบิวต์ android:restoreAnyVersion จะใช้ค่าบูลีนเพื่อระบุว่าคุณต้องการกู้คืนข้อมูลแอปหรือไม่ โดยไม่คำนึงถึงเวอร์ชันปัจจุบันของแอปเมื่อเทียบกับเวอร์ชันที่สร้างข้อมูลสำรอง ค่าเริ่มต้นคือ false ดูข้อมูลเพิ่มเติมได้ที่หัวข้อตรวจสอบเวอร์ชันของข้อมูลที่กู้คืน

ขยาย BackupAgentHelper

คุณควรสร้างตัวแทนการสำรองข้อมูลโดยใช้ BackupAgentHelper หากต้องการสำรองไฟล์ทั้งหมดจาก SharedPreferences หรือที่จัดเก็บข้อมูลภายใน การสร้างตัวแทนสำรองข้อมูลด้วย BackupAgentHelper ใช้โค้ดน้อยกว่าการขยาย BackupAgent มาก เนื่องจากคุณไม่จำเป็นต้องใช้ onBackup() และ onRestore()

การติดตั้งใช้งาน BackupAgentHelper ต้องใช้ตัวช่วยสำรองอย่างน้อย 1 รายการ ตัวช่วยสำรองข้อมูลเป็นคอมโพเนนต์เฉพาะที่ BackupAgentHelper เรียกใช้เพื่อดำเนินการสำรองและกู้คืนข้อมูลบางประเภท ปัจจุบันเฟรมเวิร์ก Android มีตัวช่วย 2 แบบ ได้แก่

  • SharedPreferencesBackupHelper เพื่อสำรองข้อมูลไฟล์ SharedPreferences รายการ
  • FileBackupHelper เพื่อสำรองไฟล์จากที่จัดเก็บข้อมูลภายใน

คุณสามารถรวมผู้ช่วยหลายคนไว้ใน BackupAgentHelper ได้ แต่จำเป็นต้องมีผู้ช่วยเพียงคนเดียวสำหรับข้อมูลแต่ละประเภท กล่าวคือ หากคุณมีไฟล์ SharedPreferences หลายไฟล์ คุณก็ต้องมี SharedPreferencesBackupHelper เพียงไฟล์เดียว

สำหรับผู้ช่วยแต่ละคนที่ต้องการเพิ่มลงใน BackupAgentHelper คุณต้องทำดังต่อไปนี้ในระหว่างเมธอด onCreate()

  1. สร้างอินสแตนซ์ของคลาสตัวช่วยที่เลือก ในตัวสร้างคลาส คุณต้องระบุไฟล์ที่ต้องการสำรองข้อมูล
  2. โทรหา addHelper() เพื่อเพิ่มผู้ช่วยลงใน BackupAgentHelper

ส่วนต่อไปนี้จะอธิบายวิธีสร้างตัวแทนสำรองโดยใช้ตัวช่วยแต่ละรายการที่มีอยู่

สำรองข้อมูล SharedPreferences

เมื่อสร้างอินสแตนซ์ SharedPreferencesBackupHelper คุณต้องระบุชื่อไฟล์ SharedPreferences อย่างน้อย 1 ไฟล์

ตัวอย่างเช่น หากต้องการสำรองข้อมูลไฟล์ SharedPreferences ชื่อ user_preferences ตัวแทนการสำรองข้อมูลที่สมบูรณ์ซึ่งใช้ 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);
    }
}

SharedPreferencesBackupHelper มีโค้ดทั้งหมดที่จำเป็นในการสำรองข้อมูลและกู้คืนไฟล์ SharedPreferences

เมื่อตัวจัดการการสำรองข้อมูลเรียกใช้ onBackup() และ onRestore() BackupAgentHelper จะเรียกใช้ตัวช่วยสำรองข้อมูลเพื่อสำรองและกู้คืนไฟล์ที่คุณระบุ

สำรองข้อมูลไฟล์อื่นๆ

เมื่อสร้างอินสแตนซ์ FileBackupHelper คุณต้องระบุชื่อไฟล์อย่างน้อย 1 ไฟล์ที่บันทึกไว้ในที่จัดเก็บข้อมูลภายในของแอปตามที่ระบุโดย getFilesDir() ซึ่งเป็นตำแหน่งเดียวกับที่ openFileOutput() เขียนไฟล์

ตัวอย่างเช่น หากต้องการสำรองข้อมูลไฟล์ 2 ไฟล์ชื่อ scores และ stats ตัวแทนการสำรองข้อมูลที่ใช้ 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");
}

คุณควรซิงค์คำสั่งอ่านด้วยการล็อกเดียวกัน

จากนั้นใน 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 คุณต้องใช้เมธอด Callback ต่อไปนี้

onBackup()
เครื่องมือจัดการข้อมูลสำรองจะเรียกใช้เมธอดนี้หลังจากที่คุณขอสำรองข้อมูล ในวิธีนี้ คุณจะอ่านข้อมูลแอปจากอุปกรณ์และส่งข้อมูลที่ต้องการสำรองไปยัง Backup Manager ตามที่อธิบายไว้ในทำการสำรองข้อมูล
onRestore()

เครื่องมือจัดการการสำรองข้อมูลจะเรียกใช้เมธอดนี้ระหว่างการดำเนินการกู้คืน วิธีนี้จะส่งข้อมูลสำรองซึ่งแอปของคุณสามารถใช้เพื่อกู้คืนสถานะเดิมได้ ตามที่อธิบายไว้ในดำเนินการกู้คืน

ระบบจะเรียกใช้เมธอดนี้เพื่อกู้คืนข้อมูลสำรองเมื่อผู้ใช้ติดตั้งแอปอีกครั้ง แต่แอปของคุณก็สามารถขอการกู้คืนได้เช่นกัน

สำรองข้อมูล

คำขอสำรองข้อมูลจะไม่ส่งผลให้มีการเรียกใช้เมธอด onBackup() ของคุณในทันที แต่ตัวจัดการการสำรองข้อมูลจะรอเวลาที่เหมาะสม แล้วจึงสำรองข้อมูลสำหรับแอปทั้งหมดที่ขอสำรองข้อมูลนับตั้งแต่การสำรองข้อมูลครั้งล่าสุด นี่คือจุดที่คุณต้องระบุข้อมูลแอปให้กับเครื่องมือจัดการการสำรองข้อมูลเพื่อให้สามารถบันทึกข้อมูลลงในพื้นที่เก็บข้อมูลระบบคลาวด์ได้

มีเพียง Backup Manager เท่านั้นที่เรียกใช้เมธอด onBackup() ของตัวแทนการสำรองข้อมูลได้ ทุกครั้งที่ข้อมูลแอปมีการเปลี่ยนแปลงและคุณต้องการสำรองข้อมูล คุณต้องส่งคำขอการดำเนินการสำรองข้อมูลโดยเรียกใช้ dataChanged() ดูข้อมูลเพิ่มเติมได้ที่ขอข้อมูลสำรอง

เคล็ดลับ: ขณะพัฒนาแอป คุณสามารถเริ่มการดำเนินการสำรองข้อมูลทันทีจากเครื่องมือจัดการการสำรองข้อมูลด้วยเครื่องมือ bmgr

เมื่อ Backup Manager เรียกใช้เมธอด onBackup() ระบบจะส่งพารามิเตอร์ 3 รายการดังนี้

oldState
ParcelFileDescriptorแบบเปิดและอ่านอย่างเดียวที่ชี้ไปยังสถานะการสำรองข้อมูลล่าสุดที่แอปของคุณระบุไว้ ข้อมูลนี้ไม่ใช่ข้อมูลสำรองจากพื้นที่เก็บข้อมูลระบบคลาวด์ แต่เป็นการแสดงข้อมูลที่สำรองไว้ในเครื่องเมื่อมีการเรียกใช้ onBackup() ครั้งล่าสุดตามที่กำหนดโดย newState หรือจาก onRestore() onRestore() จะอธิบายไว้ในส่วนถัดไป เนื่องจาก onBackup() ไม่อนุญาตให้คุณอ่านข้อมูลสำรองที่มีอยู่ในพื้นที่เก็บข้อมูลระบบคลาวด์ คุณจึงสามารถใช้การแสดงข้อมูลในเครื่องนี้เพื่อระบุว่าข้อมูลมีการเปลี่ยนแปลงหรือไม่นับตั้งแต่การสำรองข้อมูลครั้งล่าสุด
data
ออบเจ็กต์ BackupDataOutput ซึ่งคุณใช้เพื่อส่งข้อมูลสำรองไปยัง Backup Manager
newState
ParcelFileDescriptorแบบเปิดและอ่าน-เขียนที่ชี้ไปยังไฟล์ที่คุณต้องเขียนการนำเสนอข้อมูลที่ส่งไปยัง data การแสดงข้อมูลอาจเป็นการประทับเวลาที่แก้ไขล่าสุดสำหรับไฟล์ของคุณ ระบบจะแสดงผลออบเจ็กต์นี้เป็น oldState ในครั้งถัดไปที่ Backup Manager เรียกใช้เมธอด onBackup() ของคุณ หากคุณไม่เขียนข้อมูลสำรองลงใน 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 using the BackupDataOutput
    byte[] buffer = bufStream.toByteArray();
    int len = buffer.length;
    data.writeEntityHeader(TOPSCORE_BACKUP_KEY, len);
    data.writeEntityData(buffer, len);
    

    ดำเนินการนี้กับข้อมูลแต่ละรายการที่ต้องการสำรองข้อมูล คุณจะแบ่งข้อมูลออกเป็นเอนทิตีอย่างไรก็ได้ตามต้องการ หรืออาจใช้เพียงเอนทิตีเดียวก็ได้

  3. ไม่ว่าคุณจะสำรองข้อมูล (ในขั้นตอนที่ 2) หรือไม่ ให้เขียนการนำเสนอข้อมูลปัจจุบันลงใน newState ParcelFileDescriptor เครื่องมือจัดการการสำรองข้อมูลจะเก็บออบเจ็กต์นี้ไว้ในเครื่องเพื่อแสดงข้อมูลที่สำรองไว้ในปัจจุบัน โดยจะส่งค่านี้กลับมาให้คุณเป็น oldState ในครั้งถัดไปที่เรียกใช้ onBackup() เพื่อให้คุณพิจารณาได้ว่าจำเป็นต้องสำรองข้อมูลอีกหรือไม่ ตามที่จัดการในขั้นตอนที่ 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() ของตัวแทนการสำรองข้อมูล เมื่อเรียกใช้เมธอดนี้ ตัวจัดการการสำรองข้อมูลจะส่งข้อมูลสำรองเพื่อให้คุณกู้คืนข้อมูลลงในอุปกรณ์ได้

มีเพียง Backup Manager เท่านั้นที่เรียกใช้ onRestore() ได้ ซึ่งจะเกิดขึ้นโดยอัตโนมัติเมื่อระบบติดตั้งแอปและพบข้อมูลสำรองที่มีอยู่

เมื่อ Backup Manager เรียกใช้เมธอด onRestore() ระบบจะส่งพารามิเตอร์ 3 รายการดังนี้

data
ออบเจ็กต์ BackupDataInput ซึ่งช่วยให้คุณอ่านข้อมูลสำรองได้
appVersionCode
จำนวนเต็มที่แสดงค่าของแอตทริบิวต์ไฟล์ Manifest android:versionCode ของแอป ณ เวลาที่สำรองข้อมูลนี้ คุณสามารถใช้ข้อมูลนี้เพื่อตรวจสอบเวอร์ชันปัจจุบันของแอปและพิจารณาว่ารูปแบบข้อมูลเข้ากันได้หรือไม่ ดูข้อมูลเพิ่มเติมเกี่ยวกับการใช้ฟังก์ชันนี้เพื่อจัดการข้อมูลการกู้คืนเวอร์ชันต่างๆ ได้ที่ตรวจสอบเวอร์ชันข้อมูลการกู้คืน
newState
ParcelFileDescriptorแบบเปิดที่อ่านและเขียนได้ซึ่งชี้ไปยังไฟล์ที่คุณต้องเขียนสถานะการสำรองข้อมูลขั้นสุดท้ายที่ระบุไว้ด้วย data ระบบจะแสดงผลออบเจ็กต์นี้เป็น oldState ในครั้งถัดไปที่มีการเรียกใช้ onBackup() โปรดทราบว่าคุณต้องเขียนออบเจ็กต์ newState เดียวกันใน Callback onBackup() ด้วย การดำเนินการนี้จะช่วยให้มั่นใจว่าออบเจ็กต์ oldState ที่ระบุให้กับ onBackup() นั้นถูกต้อง แม้ว่าจะเป็นครั้งแรกที่มีการเรียกใช้ onBackup() หลังจากกู้คืนอุปกรณ์แล้วก็ตาม

ในการติดตั้งใช้งาน onRestore() คุณควรเรียกใช้ readNextHeader() ใน data เพื่อทำซ้ำผ่านเอนทิตีทั้งหมดในชุดข้อมูล สำหรับเอนทิตีแต่ละรายการที่พบ ให้ทำดังนี้

  1. รับคีย์เอนทิตีด้วย getKey()
  2. เปรียบเทียบคีย์เอนทิตีกับรายการค่าคีย์ที่รู้จักซึ่งคุณควรประกาศเป็นสตริงแบบคงที่ขั้นสุดท้ายภายในคลาส BackupAgent เมื่อคีย์ตรงกับสตริงคีย์ที่รู้จัก ให้ป้อนคำสั่งเพื่อดึงข้อมูลเอนทิตีและบันทึกลงในอุปกรณ์ ดังนี้

    1. รับขนาดข้อมูลเอนทิตีด้วย getDataSize() และสร้างอาร์เรย์ไบต์ขนาดดังกล่าว
    2. เรียกใช้ readEntityData() และส่งอาร์เรย์ไบต์ซึ่งเป็นตำแหน่งที่ข้อมูลจะไป และระบุออฟเซ็ตเริ่มต้นและขนาดที่จะอ่าน
    3. ตอนนี้อาร์เรย์ไบต์ของคุณเต็มแล้ว อ่านข้อมูลและเขียนลงในอุปกรณ์ตามที่คุณต้องการ
  3. หลังจากอ่านและเขียนข้อมูลกลับไปยังอุปกรณ์แล้ว ให้เขียนสถานะของข้อมูลลงในพารามิเตอร์ newState เช่นเดียวกับที่ทำในระหว่าง onBackup()

ตัวอย่างเช่น วิธีคืนค่าข้อมูลที่สำรองไว้ตามตัวอย่างในส่วนก่อนหน้ามีดังนี้

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

ในตัวอย่างนี้ ไม่ได้ใช้พารามิเตอร์ appVersionCode ที่ส่งไปยัง onRestore() อย่างไรก็ตาม คุณอาจต้องการใช้หากเลือกที่จะสำรองข้อมูลเมื่อแอปเวอร์ชันของผู้ใช้ย้อนกลับไปเป็นเวอร์ชันก่อนหน้า (เช่น ผู้ใช้เปลี่ยนจากแอปเวอร์ชัน 1.5 เป็น 1.0) ดูข้อมูลเพิ่มเติมได้ในส่วนถัดไป

ตรวจสอบเวอร์ชันของข้อมูลที่กู้คืน

เมื่อตัวจัดการการสำรองข้อมูลบันทึกข้อมูลของคุณลงในพื้นที่เก็บข้อมูลระบบคลาวด์ ระบบจะรวมเวอร์ชันของแอปโดยอัตโนมัติตามที่กำหนดโดยแอตทริบิวต์ android:versionCode ของไฟล์ Manifest ก่อนที่เครื่องมือจัดการการสำรองข้อมูลจะเรียกใช้ตัวแทนการสำรองข้อมูลเพื่อกู้คืนข้อมูล เครื่องมือจะดู android:versionCode ของแอปที่ติดตั้งไว้และเปรียบเทียบกับค่าที่บันทึกไว้ในชุดข้อมูลการกู้คืน หากเวอร์ชันที่บันทึกไว้ในชุดข้อมูลการกู้คืนใหม่กว่าเวอร์ชันแอปในอุปกรณ์ แสดงว่าผู้ใช้ดาวน์เกรดแอป ในกรณีนี้ Backup Manager จะยกเลิกการดำเนินการกู้คืนสำหรับแอปของคุณและไม่เรียกใช้เมธอด onRestore() เนื่องจากระบบถือว่าชุดการกู้คืนไม่มีความหมายสำหรับเวอร์ชันที่เก่ากว่า

คุณลบล้างลักษณะการทำงานนี้ได้โดยใช้แอตทริบิวต์ android:restoreAnyVersion ดังนี้

  • false (ค่าเริ่มต้น): เครื่องมือจัดการการสำรองข้อมูลจะยกเลิกการดำเนินการกู้คืนหากเวอร์ชันที่บันทึกไว้ในชุดข้อมูลการกู้คืนใหม่กว่าเวอร์ชันของแอปในอุปกรณ์
  • true: เครื่องมือจัดการการสำรองข้อมูลจะละเว้น android:versionCode และเรียกใช้เมธอด 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;
}

จากนั้นเปรียบเทียบ version ที่ได้จาก PackageInfo กับ appVersionCode ที่ส่งไปยัง onRestore()

ขอข้อมูลสำรอง

คุณสามารถขอให้มีการดำเนินการสำรองข้อมูลได้ทุกเมื่อโดยโทรหา dataChanged() เมธอดนี้จะแจ้งให้ตัวจัดการการสำรองข้อมูลทราบว่าคุณต้องการสำรองข้อมูลโดยใช้ตัวแทนการสำรองข้อมูล จากนั้นเครื่องมือจัดการการสำรองข้อมูลจะเรียกใช้เมธอด onBackup() ของตัวแทนการสำรองข้อมูลในอนาคต โดยปกติแล้ว คุณควรขอสำรองข้อมูลทุกครั้งที่มีการเปลี่ยนแปลงข้อมูล (เช่น เมื่อผู้ใช้เปลี่ยนค่ากำหนดของแอปที่คุณต้องการสำรองข้อมูล) หากคุณเรียกใช้ dataChanged() หลายครั้งก่อนที่ Backup Manager จะขอข้อมูลสำรองจาก Agent ของคุณ Agent จะยังคงได้รับการเรียกใช้ onBackup() เพียงครั้งเดียว

ขอให้กู้คืน

ในระหว่างอายุการใช้งานปกติของแอป คุณไม่จำเป็นต้องขอการดำเนินการกู้คืน ระบบจะตรวจหาข้อมูลสำรองและทำการกู้คืนโดยอัตโนมัติเมื่อติดตั้งแอป

ย้ายข้อมูลไปยังการสำรองข้อมูลอัตโนมัติ

คุณสามารถเปลี่ยนแอปไปใช้การสำรองข้อมูลแบบเต็มได้โดยตั้งค่า android:fullBackupOnly เป็น true ในองค์ประกอบ <application> ในไฟล์ Manifest เมื่อทำงานในอุปกรณ์ที่ใช้ Android 5.1 (ระดับ API 22) หรือต่ำกว่า แอปจะละเว้นค่านี้ในไฟล์ Manifest และทำการสำรองข้อมูลคีย์-ค่าต่อไป เมื่อทำงานในอุปกรณ์ที่ใช้ Android 6.0 (ระดับ API 23) ขึ้นไป แอปจะทำการสำรองข้อมูลอัตโนมัติแทนการสำรองข้อมูลคีย์-ค่า

ความเป็นส่วนตัวของผู้ใช้

Google ทราบดีถึงความไว้วางใจที่ผู้ใช้มีให้และหน้าที่ความรับผิดชอบในการปกป้องความเป็นส่วนตัวของผู้ใช้ Google จะส่งข้อมูลสำรองไปยังและจากเซิร์ฟเวอร์ของ Google อย่างปลอดภัยเพื่อให้บริการฟีเจอร์สำรองและกู้คืนข้อมูล Google จะถือว่าข้อมูลนี้เป็นข้อมูลส่วนบุคคลตามนโยบายความเป็นส่วนตัวของ Google

นอกจากนี้ ผู้ใช้ยังปิดใช้ฟังก์ชันการสำรองข้อมูลผ่านการตั้งค่าการสำรองข้อมูลของระบบ Android ได้ด้วย เมื่อผู้ใช้ปิดใช้การสำรองข้อมูล Android Backup Service จะลบข้อมูลสำรองที่บันทึกไว้ทั้งหมด ผู้ใช้สามารถเปิดใช้การสำรองข้อมูลในอุปกรณ์อีกครั้งได้ แต่ Android Backup Service จะไม่กู้คืนข้อมูลที่ลบไปก่อนหน้านี้