Sao lưu / khôi phục Android: Làm thế nào để sao lưu cơ sở dữ liệu nội bộ?


86

Tôi đã triển khai một BackupAgentHelperbằng cách sử dụng được cung cấp FileBackupHelperđể sao lưu và khôi phục cơ sở dữ liệu gốc mà tôi có. Đây là cơ sở dữ liệu bạn thường sử dụng cùng ContentProvidersvà nằm trong đó /data/data/yourpackage/databases/.

Một người sẽ nghĩ rằng đây là một trường hợp phổ biến. Tuy nhiên, tài liệu không rõ phải làm gì: http://developer.android.com/guide/topics/data/backup.html . Không có BackupHelpercụ thể cho những cơ sở dữ liệu điển hình này. Do đó, tôi đã sử dụngFileBackupHelper , trỏ nó đến tệp .db của tôi trong " /databases/", giới thiệu các khóa xung quanh bất kỳ thao tác db nào (chẳng hạn như db.insert) trong của tôi ContentProviders, và thậm chí đã thử tạo thư mục " /databases/" trước đó onRestore()vì nó không tồn tại sau khi cài đặt.

Tôi đã thực hiện một giải pháp tương tự cho SharedPreferencesthành công trong một ứng dụng khác trước đây. Tuy nhiên, khi tôi kiểm tra triển khai mới của mình trong trình giả lập-2.2, tôi thấy một bản sao lưu đang được thực hiện LocalTransporttừ các bản ghi, cũng như quá trình khôi phục đang được thực hiện (và onRestore()được gọi). Tuy nhiên, bản thân tệp db không bao giờ được tạo.

Lưu ý rằng đây là tất cả sau khi cài đặt và trước khi khởi chạy ứng dụng lần đầu tiên, sau khi quá trình khôi phục đã được thực hiện. Ngoài ra, chiến lược thử nghiệm của tôi dựa trên http://developer.android.com/guide/topics/data/backup.html#Testing .

Cũng xin lưu ý rằng tôi không nói về một số cơ sở dữ liệu sqlite mà tôi tự quản lý, cũng không nói về việc sao lưu vào SDcard, máy chủ riêng hoặc nơi khác.

Tôi đã thấy đề cập trong tài liệu về cơ sở dữ liệu khuyên sử dụng tùy chỉnh BackupAgentnhưng nó có vẻ không liên quan:

Tuy nhiên, bạn có thể muốn mở rộng BackupAgent trực tiếp nếu bạn cần: * Sao lưu dữ liệu trong cơ sở dữ liệu. Nếu bạn có cơ sở dữ liệu SQLite mà bạn muốn khôi phục khi người dùng cài đặt lại ứng dụng của mình, bạn cần tạo một BackupAgent tùy chỉnh để đọc dữ liệu thích hợp trong quá trình sao lưu, sau đó tạo bảng và chèn dữ liệu trong quá trình khôi phục.

Làm ơn nói rõ một chút.

Nếu tôi thực sự cần phải tự mình làm điều đó đến cấp SQL, thì tôi lo lắng về các chủ đề sau:

  • Mở cơ sở dữ liệu và giao dịch. Tôi không biết làm cách nào để đóng chúng khỏi một lớp singleton như vậy bên ngoài quy trình làm việc của ứng dụng của tôi.

  • Làm thế nào để thông báo cho người dùng rằng một bản sao lưu đang được tiến hành và cơ sở dữ liệu bị khóa. Có thể mất nhiều thời gian, vì vậy tôi có thể cần hiển thị thanh tiến trình.

  • Cách thực hiện tương tự trên khôi phục. Theo tôi hiểu, việc khôi phục có thể xảy ra ngay khi người dùng đã bắt đầu sử dụng ứng dụng (và nhập dữ liệu vào cơ sở dữ liệu). Vì vậy, bạn không thể cho rằng chỉ khôi phục dữ liệu dự phòng tại chỗ (xóa dữ liệu trống hoặc dữ liệu cũ). Bạn sẽ phải tham gia bằng cách nào đó, điều này đối với bất kỳ cơ sở dữ liệu không tầm thường nào là không thể do id của.

  • Cách làm mới ứng dụng sau khi quá trình khôi phục hoàn tất mà không khiến người dùng gặp khó khăn tại một số điểm - bây giờ - không thể truy cập.

  • Tôi có thể chắc chắn rằng cơ sở dữ liệu đã được nâng cấp khi sao lưu hoặc khôi phục không? Nếu không, lược đồ mong đợi có thể không khớp.


Tôi gặp vấn đề tương tự ...

Không có cách nào của lỗ dự phòng đơn giản db?
Jin35,

Tôi cần sao lưu một thư mục bằng FileBackupHelper. Sử dụng cách tiếp cận cơ sở dữ liệu lưu có cho phép tôi lưu thư mục có rất nhiều thư mục con và tệp con bên dưới nó không?
coolcool1994

Bạn đã bao giờ làm cho điều này hoạt động chính xác?
DeNitE Appz

Có, hãy xem bên dưới. Tôi cũng đã trả lời câu hỏi của chính mình.
pjv

Câu trả lời:


21

Một cách tiếp cận rõ ràng hơn sẽ là tạo một tùy chỉnh BackupHelper:

public class DbBackupHelper extends FileBackupHelper {

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

và sau đó thêm nó vào BackupAgentHelper:

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

2
@logray: Mã đó giống hệt như trong câu hỏi. Chỉnh sửa không cần thiết.
Linuxios

@Linuxios Tôi đồng ý, tuy nhiên tôi nghĩ tốt hơn nên cung cấp ghi nhận tác giả thích hợp, thay vì chỉ dán mã một cách mù quáng ... xem xét OP / bài đăng ban đầu bao gồm một liên kết từ ứng dụng của anh ấy.
logray

3
Quan trọng : (tài liệu) ( developer.android.com/guide/topics/data/backup.html#Files ) để sao lưu tệp cho biết rằng hoạt động không phảian toàn luồng , vì vậy bạn nên đồng bộ hóa các phương thức Cơ sở dữ liệu và tác nhân sao lưu của mình
nicopico

@yanchenko Tài liệu cho FileBackupHelper nêu rõ: "Lưu ý: Điều này chỉ nên được sử dụng với các tệp cấu hình nhỏ, không phải tệp nhị phân lớn." Vì vậy, một cơ sở dữ liệu sẽ thường hội đủ điều kiện như một tập tin nhị phân lớn ...
IgorGanapolsky

Điều này thực sự không hoạt động! (trừ khi tôi đang thiếu thứ gì đó ...). Vấn đề là bạn chuyển đường dẫn tuyệt đối của db đến FileBackupHelper nhưng FileBackupHelper lại thêm đường dẫn với đường dẫn thư mục tệp, ví dụ. data / data / <your package> / files dẫn đến một đường dẫn không chính xác, chẳng hạn như data / data / <your package> / files / data / data / <your package> / databases / <DB Name> khi tất cả những gì bạn muốn là Tên tuyệt đối DB và vì lớp siêu thêm trước thư mục tệp nên bạn cần tạo đường dẫn liên quan đến thư mục tệp.
bitrock

34

Sau khi xem lại câu hỏi của mình, tôi đã có thể làm cho nó hoạt động sau khi xem cách ConnectBot thực hiện nó . Cảm ơn Kenny và Jeffrey!

Nó thực sự dễ dàng như thêm:

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

cho của bạn BackupAgentHelper.

Điểm tôi đã thiếu là thực tế là bạn phải sử dụng một đường dẫn tương đối với " ../databases/".

Tuy nhiên, đây không phải là một giải pháp hoàn hảo. FileBackupHelperVí dụ: " FileBackupHelperchỉ nên sử dụng tài liệu với các tệp cấu hình nhỏ, không phải tệp nhị phân lớn. ", Sau đó là trường hợp với cơ sở dữ liệu SQLite.

Tôi muốn nhận thêm gợi ý, thông tin chi tiết về những gì mong đợi ở chúng tôi (giải pháp thích hợp là gì) và lời khuyên về cách điều này có thể phá vỡ.


1
Tôi có lẽ nên nói thêm, rằng mặc dù điều này hơi không chính thức, nhưng nó đã / đang hoạt động rất tốt trong suốt thời gian qua.
pjv

6
Con đường khó mã hóa là xấu xa.
Pointer Null

@pjv, Vậy bạn có thực hiện đồng bộ hóa mọi tương tác db không? Tôi đang làm điều tương tự và cố gắng tìm hiểu xem tôi có cần đồng bộ hóa db.open()thông qua db.close()hay chỉ các insertcâu lệnh hay cái gì.
NSouth

22

Đây là cách gọn gàng hơn để sao lưu cơ sở dữ liệu dưới dạng tệp. Không có đường dẫn được mã hóa cứng.

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

Lưu ý: nó ghi đè getFilesDir để FileBackupHelper hoạt động trong cơ sở dữ liệu dir, không phải tệp dir.

Một gợi ý khác: bạn cũng có thể sử dụng databaseList để lấy tất cả các tên nguồn cấp dữ liệu và DB của bạn từ danh sách này (không có đường dẫn mẹ) vào FileBackupHelper. Sau đó, tất cả DB của ứng dụng sẽ được lưu trong bản sao lưu.


Kết hợp điều này với giải pháp do @pjv đưa ra hoạt động hoàn hảo! Nó có thể không phải là chính xác những gì thông số kỹ thuật đã nghĩ ... nhưng nó hoạt động khá tốt!
Tom,

1
Điều đó không được sử dụng nhiều nếu bạn có cơ sở dữ liệu các tệp bình thường để sao lưu.
Dan Hulme

1
@Dan: Sử dụng nhiều tác nhân trong trường hợp đó.
pjv

@Pointer Null Bạn có thể giải thích thêm một chút về getFilesDir (), tại sao nó thực sự cần thiết và tại sao? Làm thế nào nó hoạt động?
powder366,

7

Việc sử dụng FileBackupHelperđể sao lưu / khôi phục sqlite db đặt ra một số câu hỏi nghiêm trọng:
1. Điều gì xảy ra nếu ứng dụng sử dụng con trỏ được truy xuất từ ContentProvider.query()và tác nhân sao lưu cố gắng ghi đè toàn bộ tệp?
2. Liên kết là một ví dụ điển hình về thử nghiệm hoàn hảo (ít entrophy;). Bạn gỡ cài đặt ứng dụng, cài đặt lại và bản sao lưu được khôi phục. Tuy nhiên, cuộc sống có thể tàn bạo. Hãy xem liên kết . Hãy tưởng tượng kịch bản khi người dùng mua một thiết bị mới. Vì nó không có bộ riêng nên tác nhân sao lưu sử dụng bộ của thiết bị khác. Ứng dụng đã được cài đặt và backupHelper của bạn truy xuất tệp cũ với giản đồ phiên bản db thấp hơn hiện tại. SQLiteOpenHelpercuộc gọi onDowngradevới triển khai mặc định:

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

Bất kể người dùng làm gì, họ không thể sử dụng ứng dụng của bạn trên thiết bị mới.

Tôi khuyên bạn nên sử dụng ContentResolverđể lấy dữ liệu -> tuần tự hóa (không có _ids) để sao lưu và giải mã hóa -> chèn dữ liệu để khôi phục.

Lưu ý: lấy / chèn dữ liệu được thực hiện thông qua ContentResolver, do đó tránh được các vấn đề về tiền tệ. Serializing được thực hiện trong backupAgent của bạn. Nếu bạn thực hiện ánh xạ đối tượng <-> con trỏ của riêng mình, việc tuần tự hóa một mục có thể đơn giản như việc triển khai Serializablevới transienttrường _id trên lớp đại diện cho thực thể của bạn.

Tôi cũng sẽ sử dụng ví ContentProviderOperation dụ tức là chèn số lượng lớn và CursorLoader.setUpdateThrottleđể ứng dụng không bị mắc kẹt với việc khởi động lại trình tải khi thay đổi dữ liệu trong quá trình khôi phục sao lưu.

Nếu bạn rơi vào trường hợp bị hạ cấp, bạn có thể chọn hủy khôi phục dữ liệu hoặc khôi phục và cập nhật ContentResolver với các trường liên quan đến phiên bản bị hạ cấp.

Tôi đồng ý rằng chủ đề này không dễ, không được giải thích kỹ trong tài liệu và một số câu hỏi vẫn còn như kích thước dữ liệu hàng loạt, v.v.

Hi vọng điêu nay co ich.


Bạn lặp lại một số câu hỏi hợp lệ. Nhưng tôi không thấy cách tuần tự hóa cơ sở dữ liệu giải quyết vấn đề. Nó không giúp giải quyết các vấn đề đồng thời. Và nếu bạn không thể viết một tập lệnh để hạ cấp cơ sở dữ liệu, thì bạn không thể viết một tập lệnh để giải mã dữ liệu cũ.
pjv

@pjv Nếu bạn sử dụng ContentResolver, nó sẽ giải quyết các vấn đề đồng thời cho bạn.
IgorGanapolsky

@IgorGanapolsky Vâng, tôi nghĩ điều đó đúng. Tuy nhiên, nếu tuần tự hóa đủ chậm và người dùng đã sử dụng ứng dụng và nhập dữ liệu, nó có thể xung đột với dữ liệu bạn đang khôi phục. Bạn có thể làm điều đó một cách nguyên tử và trước khi người dùng có quyền truy cập vào ứng dụng không?
pjv

@pjv Bạn có thể đăng ký ContentObserver để đồng bộ hóa dữ liệu bạn đang quan sát để được thông báo. Do đó bạn không phải lo lắng về những xung đột.
IgorGanapolsky

Quan điểm của bạn về các phiên bản DB là một điểm tốt - tôi đoán nếu bạn lưu phiên bản DB của mình (ví dụ: trong các tài liệu tham khảo được chia sẻ, giả sử bạn cũng sao lưu nó) thì khi khôi phục, bạn có thể sử dụng logic hiện có để nâng cấp DB lên phiên bản hiện tại trong phương thức onDowngrade (). Nếu bạn chưa có mã nâng cấp thì bạn vẫn không bận tâm đến việc lưu giữ dữ liệu!
Ben Neill

3

Kể từ Android M, hiện đã có một API sao lưu / khôi phục toàn bộ dữ liệu có sẵn cho các ứng dụng. API mới này bao gồm đặc tả dựa trên XML trong tệp kê khai ứng dụng cho phép nhà phát triển mô tả tệp nào cần sao lưu theo cách ngữ nghĩa trực tiếp: 'sao lưu cơ sở dữ liệu có tên "mydata.db"'. API mới này dễ sử dụng hơn cho các nhà phát triển - bạn không cần phải theo dõi các điểm khác biệt hoặc yêu cầu một thẻ sao lưu một cách rõ ràng và mô tả XML về các tệp cần sao lưu có nghĩa là bạn thường không cần viết bất kỳ mã nào ở tất cả.

(Bạn có thể tham gia ngay cả vào hoạt động sao lưu / khôi phục toàn bộ dữ liệu để nhận được cuộc gọi lại khi quá trình khôi phục xảy ra chẳng hạn. Theo cách đó, rất linh hoạt.)

Xem phần Định cấu hình Tự động sao lưu cho ứng dụng trên developer.android.com để biết mô tả về cách sử dụng API mới.


Điều này chỉ dành cho Android 6.0 (API 23), nếu người dùng có phiên bản cũ hơn, người dùng vẫn phải triển khai sao lưu Giá trị khóa.
live-love

0

Một tùy chọn sẽ là xây dựng nó theo logic ứng dụng phía trên cơ sở dữ liệu. Tôi nghĩ nó thực sự hét lên cho mức độ như vậy. Không chắc liệu bạn đã làm chưa nhưng hầu hết mọi người (mặc dù cách tiếp cận con trỏ của trình quản lý nội dung android) sẽ giới thiệu một số ánh xạ ORM - tùy chỉnh hoặc một số phương pháp orm-lite. Và những gì tôi muốn làm trong trường hợp này là:

  1. để đảm bảo ứng dụng của bạn hoạt động tốt khi ứng dụng / dữ liệu được thêm vào nền với dữ liệu mới được thêm vào / xóa trong khi ứng dụng đã khởi động
  2. để tạo một số Java-> protobuf hoặc thậm chí đơn giản là ánh xạ tuần tự hóa java và viết BackupHelper của riêng bạn để đọc dữ liệu từ luồng và chỉ cần thêm nó vào cơ sở dữ liệu ....

Vì vậy, trong trường hợp này thay vì làm ở cấp db, hãy làm ở cấp ứng dụng.


Vấn đề không phải là làm thế nào để lấy dữ liệu từ cơ sở dữ liệu vào BackupHelperAgent hoặc ngược lại. Vui lòng đọc 5 gạch đầu dòng ở cuối câu hỏi của tôi.
pjv

@JarekPotiuk Bạn đã nói: "hầu hết mọi người (mặc dù cách tiếp cận con trỏ của trình quản lý nội dung android)". Nhưng điều gì khiến bạn nghĩ rằng hầu hết mọi người sẽ tránh sử dụng ContentProvider và ContentResolver để truy cập cơ sở dữ liệu?
IgorGanapolsky
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.