การสำรอง / กู้คืน Android: จะสำรองฐานข้อมูลภายในได้อย่างไร?


86

ฉันได้ใช้งานBackupAgentHelperโดยใช้ข้อมูลที่ให้มาFileBackupHelperเพื่อสำรองและกู้คืนฐานข้อมูลดั้งเดิมที่ฉันมี นี่คือฐานข้อมูลที่คุณมักจะใช้ร่วมกับContentProvidersและที่อยู่ใน/data/data/yourpackage/databases/และที่อาศัยอยู่ใน

ใครจะคิดว่านี่เป็นกรณีทั่วไป แต่เอกสารยังไม่ชัดเจนว่าจะทำอย่างไร: http://developer.android.com/guide/topics/data/backup.html ไม่มีBackupHelperเฉพาะสำหรับฐานข้อมูลทั่วไปเหล่านี้ ดังนั้นฉันจึงใช้FileBackupHelperมันชี้ไปที่ไฟล์. db ของฉันใน " /databases/" แนะนำการล็อกรอบ ๆ การดำเนินการ db ใด ๆ (เช่นdb.insert) ในของฉันContentProvidersและลองสร้าง/databases/ไดเร็กทอรี "" มาก่อนonRestore()เนื่องจากไม่มีอยู่หลังจากติดตั้ง

ฉันได้ใช้โซลูชันที่คล้ายกันสำหรับSharedPreferencesความสำเร็จในแอปอื่นในอดีต อย่างไรก็ตามเมื่อฉันทดสอบการใช้งานใหม่ของฉันในโปรแกรมจำลอง -2.2 ฉันเห็นว่ามีการสำรองข้อมูลLocalTransportจากบันทึกรวมถึงการกู้คืนที่ดำเนินการอยู่ (และonRestore()เรียก) กระนั้นก็ไม่เคยสร้างไฟล์ db ขึ้นมาเอง

โปรดทราบว่าทั้งหมดนี้เกิดขึ้นหลังจากการติดตั้งและก่อนการเปิดตัวแอปครั้งแรกหลังจากทำการกู้คืนแล้ว นอกเหนือจากที่กลยุทธ์การทดสอบของฉันอยู่บนพื้นฐานของhttp://developer.android.com/guide/topics/data/backup.html#Testing

โปรดทราบว่าฉันไม่ได้พูดถึงฐานข้อมูล sqlite ที่ฉันจัดการเองหรือสำรองข้อมูลไปยัง SDcard เซิร์ฟเวอร์ของตัวเองหรือที่อื่น

ฉันเห็นการกล่าวถึงในเอกสารเกี่ยวกับฐานข้อมูลที่แนะนำให้ใช้แบบกำหนดเองBackupAgentแต่ดูเหมือนจะไม่เกี่ยวข้อง:

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

กรุณาแจ้งความชัดเจน

ถ้าฉันจำเป็นต้องทำเองจนถึงระดับ SQL ฉันกังวลเกี่ยวกับหัวข้อต่อไปนี้:

  • เปิดฐานข้อมูลและธุรกรรม ฉันไม่รู้วิธีปิดจากคลาสซิงเกิลตันนอกเวิร์กโฟลว์ของแอป

  • วิธีแจ้งผู้ใช้ว่าการสำรองข้อมูลอยู่ระหว่างดำเนินการและฐานข้อมูลถูกล็อก อาจใช้เวลานานดังนั้นฉันอาจต้องแสดงแถบความคืบหน้า

  • วิธีการทำเช่นเดียวกันในการคืนค่า ตามที่ฉันเข้าใจการคืนค่าอาจเกิดขึ้นเมื่อผู้ใช้เริ่มใช้แอปแล้ว (และป้อนข้อมูลลงในฐานข้อมูล) ดังนั้นคุณจึงไม่สามารถสันนิษฐานได้ว่าจะกู้คืนข้อมูลที่สำรองไว้ในตำแหน่ง (การลบข้อมูลว่างหรือข้อมูลเก่า) คุณจะต้องเข้าร่วมอย่างใดอย่างหนึ่งซึ่งสำหรับฐานข้อมูลที่ไม่สำคัญใด ๆ นั้นเป็นไปไม่ได้เนื่องจากรหัส

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

  • ฉันแน่ใจได้หรือไม่ว่าฐานข้อมูลได้รับการอัพเกรดในการสำรองข้อมูลหรือกู้คืนแล้ว? มิฉะนั้นสคีมาที่คาดไว้อาจไม่ตรงกัน


ฉันมีปัญหาเดียวกัน ...

ไม่มีวิธีง่ายๆในการสำรองข้อมูล db?
Jin35

ฉันต้องการสำรองข้อมูลโฟลเดอร์โดยใช้ FileBackupHelper การใช้วิธีการบันทึกฐานข้อมูลจะช่วยให้ฉันบันทึกโฟลเดอร์นั้นซึ่งมีโฟลเดอร์ย่อยและไฟล์ย่อยจำนวนมากอยู่ข้างใต้ได้หรือไม่
coolcool1994

คุณเคยทำงานอย่างถูกต้องหรือไม่?
DeNitE Appz

ใช่ดูด้านล่าง ฉันตอบคำถามของตัวเองเช่นกัน
pjv

คำตอบ:


21

แนวทางที่สะอาดกว่าคือการสร้างแบบกำหนดเองBackupHelper:

public class DbBackupHelper extends FileBackupHelper {

    public DbBackupHelper(Context ctx, String dbName) {
        super(ctx, ctx.getDatabasePath(dbName).getAbsolutePath());
    }
}

จากนั้นเพิ่มไปที่BackupAgentHelper:

public void onCreate() {
    addHelper(DATABASE, new DbBackupHelper(this, DB.FILE));
}

2
@logray: รหัสนั้นเหมือนกับในคำถามทุกประการ แก้ไขโดยไม่จำเป็น
Linuxios

@Linuxios ฉันเห็นด้วยอย่างไรก็ตามฉันคิดว่าเป็นการดีกว่าที่จะระบุแหล่งที่มาที่เหมาะสมให้กับผู้เขียนแทนที่จะเพียงแค่วางโค้ดแบบสุ่มสี่สุ่มห้า ... เมื่อพิจารณาจาก OP / โพสต์ดั้งเดิมมีลิงก์จากแอปของเขา
logray

3
สำคัญ : (documentation) ( developer.android.com/guide/topics/data/backup.html#Files ) สำหรับการสำรองไฟล์ระบุว่าการดำเนินการไม่ปลอดภัยสำหรับเธรดดังนั้นคุณควรซิงโครไนซ์เมธอดฐานข้อมูลและตัวแทนสำรองของคุณ
nicopico

@yanchenko เอกสารสำหรับ FileBackupHelper: "หมายเหตุ: ควรใช้กับไฟล์คอนฟิกูเรชันขนาดเล็กเท่านั้นไม่ใช่ไฟล์ไบนารีขนาดใหญ่" ดังนั้นฐานข้อมูลมักจะมีคุณสมบัติเป็นไฟล์ไบนารีขนาดใหญ่ ...
IgorGanapolsky

ไม่ได้ผลจริง! (เว้นแต่ฉันจะพลาดบางอย่าง ... ) ปัญหาคือคุณส่งในพา ธ สัมบูรณ์ของ db ไปยัง FileBackupHelper แต่ FileBackupHelper จะกำหนดเส้นทางล่วงหน้าด้วยพา ธ ไดเร็กทอรีไฟล์เช่น data / data / <your package> / files ซึ่งส่งผลให้เกิด path ที่ไม่ถูกต้องเช่น data / data / <your package> / files / data / data / <your package> / databases / <DB Name> เมื่อทั้งหมดที่คุณต้องการคือ ชื่อสัมบูรณ์ DB และเนื่องจากซูเปอร์คลาสนำหน้าไดเร็กทอรีไฟล์คุณต้องทำให้พา ธ สัมพันธ์กับไดเร็กทอรีไฟล์
bitrock

34

หลังจากอ่านคำถามของฉันอีกครั้งฉันก็สามารถทำให้มันใช้งานได้หลังจากดูว่า ConnectBotทำงานอย่างไรอย่างไร ขอบคุณ Kenny และ Jeffrey!

มันง่ายเหมือนการเพิ่ม:

FileBackupHelper hosts = new FileBackupHelper(this,
    "../databases/" + HostDatabase.DB_NAME);
addHelper(HostDatabase.DB_NAME, hosts);

ไปยังBackupAgentHelperไฟล์.

จุดที่ฉันพลาดไปคือคุณต้องใช้เส้นทางสัมพัทธ์กับ " ../databases/"

ถึงกระนั้นนี่ก็ไม่ใช่วิธีที่สมบูรณ์แบบ เอกสารสำหรับการFileBackupHelperกล่าวถึงเช่น: " FileBackupHelperควรใช้เฉพาะกับไฟล์คอนฟิกูเรชันขนาดเล็กไม่ใช่ไฟล์ไบนารีขนาดใหญ่ " ซึ่งเป็นกรณีของฐานข้อมูล SQLite

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


1
ฉันควรจะเพิ่มว่าแม้ว่านี่จะดูไม่เป็นทางการ แต่ก็ทำงานได้ดีมากตลอดเวลา
pjv

6
เส้นทางการเข้ารหัสเป็นสิ่งชั่วร้าย
Pointer Null

@pjv คุณทำให้ทุกการโต้ตอบ db ซิงโครไนซ์หรือไม่? ที่ฉันทำในสิ่งเดียวกันและพยายามที่จะคิดว่าฉันจะต้องประสานdb.open()ผ่านdb.close()หรือเพียงแค่insertงบหรือสิ่งที่
NSouth

22

นี่เป็นวิธีที่สะอาดกว่าในการสำรองฐานข้อมูลเป็นไฟล์ ไม่มีพา ธ แบบฮาร์ดโค้ด

class MyBackupAgent extends BackupAgentHelper{
   private static final String DB_NAME = "my_db";

   @Override
   public void onCreate(){
      FileBackupHelper dbs = new FileBackupHelper(this, DB_NAME);
      addHelper("dbs", dbs);
   }

   @Override
   public File getFilesDir(){
      File path = getDatabasePath(DB_NAME);
      return path.getParentFile();
   }
}

หมายเหตุ: จะแทนที่getFilesDirเพื่อให้ FileBackupHelper ทำงานในฐานข้อมูล dir ไม่ใช่ไฟล์ dir

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


การรวมสิ่งนี้เข้ากับโซลูชันที่กำหนดโดย @pjv ทำงานได้อย่างสมบูรณ์ อาจจะไม่ตรงกับข้อกำหนดที่มีในใจ ... แต่ก็ใช้ได้ดีทีเดียว!
ทอม

1
ใช้ไม่ได้มากหากคุณมีฐานข้อมูลและไฟล์ปกติในการสำรองข้อมูล
Dan Hulme

1
@ แดน: ใช้ตัวแทนหลายคนในกรณีนั้น
pjv

@Pointer Null คุณช่วยอธิบายอีกหน่อยได้ไหม getFilesDir () ทำไมมันถึงต้องการจริงๆและทำไม? มันทำงานอย่างไร?
powder366

7

การใช้FileBackupHelperเพื่อสำรอง / กู้คืน sqlite db ทำให้เกิดคำถามร้ายแรง:
1. จะเกิดอะไรขึ้นหากแอปใช้เคอร์เซอร์ที่ดึงมาจากContentProvider.query()และตัวแทนสำรองพยายามที่จะแทนที่ไฟล์ทั้งหมด
2. ลิงก์เป็นตัวอย่างที่ดีของการทดสอบที่สมบูรณ์แบบ (เอนโทรฟีต่ำ) คุณถอนการติดตั้งแอพติดตั้งอีกครั้งและการสำรองข้อมูลจะถูกเรียกคืน อย่างไรก็ตามชีวิตอาจโหดร้าย ลองดูที่การเชื่อมโยง ลองนึกภาพสถานการณ์เมื่อผู้ใช้ซื้ออุปกรณ์ใหม่ เนื่องจากไม่มีชุดของตัวเองตัวแทนสำรองจึงใช้ชุดของอุปกรณ์อื่น ติดตั้งแอปแล้วและตัวช่วยสำรองของคุณจะดึงไฟล์เก่าที่มีสคีมาเวอร์ชัน db ต่ำกว่าปัจจุบัน SQLiteOpenHelperโทรonDowngradeด้วยการใช้งานเริ่มต้น:

public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    throw new SQLiteException("Can't downgrade database from version " +
            oldVersion + " to " + newVersion);
}

ไม่ว่าผู้ใช้จะทำอะไรก็ไม่สามารถใช้แอพของคุณบนอุปกรณ์เครื่องใหม่ได้

ฉันขอแนะนำให้ใช้ContentResolverเพื่อรับข้อมูล -> ทำให้เป็นอนุกรม (ไม่มี_ids) สำหรับการสำรองข้อมูลและการกำหนดค่าเริ่มต้น -> แทรกข้อมูลเพื่อกู้คืน

หมายเหตุ: รับ / ใส่ข้อมูลผ่าน ContentResolver จึงหลีกเลี่ยงปัญหาสกุลเงิน cuncurrency การทำให้เป็นอนุกรมเสร็จสิ้นใน backupAgent ของคุณ ถ้าคุณทำเคอร์เซอร์ของคุณเอง <-> การแม็พอ็อบเจ็กต์การทำให้เป็นอนุกรมของไอเท็มสามารถทำได้ง่ายเพียงแค่ใช้Serializableกับtransientฟิลด์ _id บนคลาสที่แสดงเอนทิตีของคุณ

ฉันยังต้องการใช้จำนวนมากแทรกเช่นContentProviderOperation ตัวอย่างและCursorLoader.setUpdateThrottleเพื่อให้ app ไม่ได้ติดอยู่กับการเริ่มต้นใหม่สำหรับรถตักดินในการเปลี่ยนแปลงข้อมูลระหว่างการสำรองข้อมูลขั้นตอนการเรียกคืน

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

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

หวังว่านี่จะช่วยได้


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

@pjv หากคุณใช้ ContentResolver จะช่วยแก้ปัญหาการทำงานพร้อมกันสำหรับคุณ
IgorGanapolsky

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

@pjv คุณสามารถลงทะเบียน ContentObserver เพื่อซิงโครไนซ์ข้อมูลที่คุณกำลังสังเกตเพื่อรับการแจ้งเตือน ดังนั้นคุณไม่ต้องกังวลเกี่ยวกับความขัดแย้ง
IgorGanapolsky

ประเด็นของคุณเกี่ยวกับเวอร์ชัน DB เป็นสิ่งที่ดี - ฉันเดาว่าถ้าคุณบันทึกเวอร์ชัน DB ของคุณ (ในการกำหนดค่ามาตรฐานเช่นสมมติว่าคุณสำรองข้อมูลไว้ด้วย) เมื่อคุณกู้คืนคุณควรจะสามารถใช้ตรรกะที่มีอยู่เพื่ออัปเกรดฐานข้อมูลเป็น เวอร์ชันปัจจุบันในเมธอด onDowngrade () หากคุณยังไม่มีรหัสอัพเกรดคุณก็ไม่ต้องกังวลกับการเก็บรักษาข้อมูลอยู่ดี!
Ben Neill

3

สำหรับ Android M ตอนนี้มี API สำรอง / กู้คืนข้อมูลแบบเต็มสำหรับแอป API ใหม่นี้มีข้อกำหนดที่ใช้ XML ในรายการแอปที่ช่วยให้นักพัฒนาสามารถอธิบายไฟล์ที่จะสำรองข้อมูลด้วยวิธีความหมายโดยตรง: "สำรองฐานข้อมูลที่เรียกว่า" mydata.db "" API ใหม่นี้ง่ายกว่ามากสำหรับนักพัฒนาที่จะใช้ - คุณไม่จำเป็นต้องติดตามความแตกต่างหรือขอรหัสผ่านสำรองอย่างชัดเจนและคำอธิบาย XML ของไฟล์ที่จะสำรองข้อมูลหมายความว่าคุณมักไม่จำเป็นต้องเขียนโค้ดใด ๆ เลย.

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

ดูส่วนการกำหนดค่าการสำรองข้อมูลอัตโนมัติสำหรับแอปใน developer.android.com สำหรับคำอธิบายวิธีใช้ API ใหม่


สิ่งนี้มีไว้สำหรับ Android 6.0 (API 23) เท่านั้นหากผู้ใช้มีเวอร์ชันที่เก่ากว่าก็ยังต้องใช้การสำรองข้อมูล Key Value
live-love

0

ทางเลือกหนึ่งคือการสร้างในตรรกะของแอปพลิเคชันเหนือฐานข้อมูล ฉันคิดว่ามันน่ากลัวจริงๆสำหรับระดับดังกล่าว ไม่แน่ใจว่าคุณกำลังทำอยู่แล้ว แต่คนส่วนใหญ่ (แม้จะใช้วิธีเคอร์เซอร์ของตัวจัดการเนื้อหา Android) จะแนะนำการแมป ORM บางอย่างไม่ว่าจะเป็นวิธีกำหนดเองหรือวิธี orm-lite และสิ่งที่ฉันอยากจะทำในกรณีนี้คือ:

  1. เพื่อให้แน่ใจว่าแอปพลิเคชันของคุณทำงานได้ดีเมื่อมีการเพิ่มแอป / ข้อมูลในพื้นหลังพร้อมกับเพิ่ม / ลบข้อมูลใหม่ในขณะที่แอปพลิเคชันเริ่มทำงานแล้ว
  2. เพื่อสร้าง Java-> protobuf หรือแม้แต่เพียงแค่การแมปอนุกรม java และเขียน BackupHelper ของคุณเองเพื่ออ่านข้อมูลจากสตรีมและเพิ่มลงในฐานข้อมูล ...

ดังนั้นในกรณีนี้แทนที่จะทำในระดับฐานข้อมูลให้ทำในระดับแอปพลิเคชัน


ปัญหาไม่ใช่วิธีการรับข้อมูลจากฐานข้อมูลไปยัง BackupHelperAgent หรือในทางกลับกัน โปรดอ่านสัญลักษณ์แสดงหัวข้อย่อย 5 รายการในตอนท้ายของคำถามของฉัน
pjv

@JarekPotiuk คุณพูดว่า: "คนส่วนใหญ่ (แม้จะใช้เคอร์เซอร์โปรแกรมจัดการเนื้อหา Android)" แต่อะไรทำให้คุณคิดว่าคนส่วนใหญ่จะหลีกเลี่ยงการใช้ ContentProvider และ ContentResolver ในการเข้าถึงฐานข้อมูล
IgorGanapolsky
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.