Android 백업 / 복원 : 내부 데이터베이스를 백업하는 방법은 무엇입니까?


86

BackupAgentHelper제공된 FileBackupHelper기본 데이터베이스를 백업하고 복원하기 위해 제공된 사용 을 구현했습니다 . 일반적으로 함께 사용 ContentProviders하고에 상주 하는 데이터베이스 입니다 /data/data/yourpackage/databases/.

이것은 일반적인 경우라고 생각할 것입니다. 그러나 문서는 http://developer.android.com/guide/topics/data/backup.html 에서 무엇을 해야할지 명확하지 않습니다 . 이러한 일반적인 데이터베이스에 대한 특별한 것은 없습니다 BackupHelper. 따라서를 사용하고FileBackupHelper " /databases/"의 .db 파일을 가리키고, 내에서 모든 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 수준까지 직접 수행해야하는 경우 다음 항목이 걱정됩니다.

  • 데이터베이스 및 트랜잭션을 엽니 다. 내 앱의 워크 플로 외부의 단일 클래스에서 닫는 방법을 모릅니다.

  • 백업이 진행 중이고 데이터베이스가 잠겨 있음을 사용자에게 알리는 방법. 시간이 오래 걸릴 수 있으므로 진행률 표시 줄을 표시해야 할 수 있습니다.

  • 복원시 동일한 작업을 수행하는 방법. 내가 이해했듯이 복원은 사용자가 이미 앱을 사용하고 데이터베이스에 데이터를 입력하기 시작했을 때 발생할 수 있습니다. 따라서 백업 된 데이터를 제자리에 복원한다고 가정 할 수 없습니다 (빈 데이터 나 오래된 데이터 삭제). 당신은 어떻게 든 그것에 가입해야 할 것입니다. ID 때문에 사소하지 않은 데이터베이스는 불가능합니다.

  • 복원이 완료된 후 사용자가 도달 할 수없는 지점에서 멈추지 않고 앱을 새로 고치는 방법.

  • 백업 또는 복원시 데이터베이스가 이미 업그레이드되었는지 확인할 수 있습니까? 그렇지 않으면 예상 스키마가 일치하지 않을 수 있습니다.


같은 문제가 있습니다 ...

간단한 백업 홀 db의 방법이 없습니까?
Jin35

FileBackupHelper를 사용하여 폴더를 백업해야합니다. 데이터베이스 저장 방법을 사용하면 하위 폴더와 하위 파일이 많은 폴더를 저장할 수 있습니까?
coolcool1994

이게 제대로 작동하게 되었나요?
DeNitE Appz 2015

예, 아래를 참조하십시오. 나는 내 질문에도 대답했다.
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 동의합니다.하지만 코드를 맹목적으로 붙여 넣는 것보다 작성자에게 적절한 속성을 제공하는 것이 더 낫다고 생각합니다.
logray

3
중요 : 파일 백업을위한 (documentation) ( developer.android.com/guide/topics/data/backup.html#Files )에는 작업이 스레드로부터 안전하지 않다고 명시 되어 있으므로 데이터베이스 메서드와 백업 에이전트를 동기화해야합니다
nicopico

FileBackupHelper에 대한 @yanchenko 설명서에 "참고 :이 파일은 큰 바이너리 파일이 아닌 작은 구성 파일에만 사용해야합니다." 그래서 데이터베이스는 종종 ... 큰 바이너리 파일로 자격이
IgorGanapolsky

이것은 실제로 작동하지 않습니다! (내가 뭔가를 놓치고 있지 않는 한 ...). 문제는 db의 절대 경로를 FileBackupHelper에 전달하지만 FileBackupHelper는 경로 앞에 파일 디렉토리 경로를 추가한다는 것입니다. data / data / <your package> / files로 인해 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
하드 코딩 경로는 사악합니다.
포인터 널

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

참고 : FileBackupHelper가 파일 dir이 아닌 데이터베이스 dir에서 작동하도록 getFilesDir 을 대체 합니다.

또 다른 힌트 : databaseList 를 사용 하여이 목록 (부모 경로 없음)에서 모든 DB 및 피드 이름을 FileBackupHelper로 가져올 수도 있습니다 . 그러면 모든 앱의 DB가 백업에 저장됩니다.


이것을 @pjv가 제공하는 솔루션과 결합하면 완벽하게 작동합니다! 사양이 염두에 둔 것과 정확히 일치하지 않을 수도 있지만 ... 꽤 잘 작동합니다!
Tom

1
백업 할 데이터베이스 일반 파일 이 있으면 그다지 유용하지 않습니다 .
Dan Hulme

1
@Dan :이 경우 여러 에이전트를 사용하십시오.
pjv

@Pointer Null getFilesDir ()을 조금 더 자세히 설명해 주시겠습니까? 왜 이것이 정말로 필요하고 왜 필요한가요? 어떻게 작동합니까?
powder366

7

FileBackupHelpersqlite db를 백업 / 복원하는 데 사용 하면 몇 가지 심각한 질문이 제기됩니다.
1. 앱이 검색된 커서를 사용 ContentProvider.query()하고 백업 에이전트가 전체 파일을 덮어 쓰려고하면 어떻게됩니까?
2. 링크 는 완벽한 (낮은 엔트로피) 테스트의 좋은 예입니다. 앱을 제거하고 다시 설치하면 백업이 복원됩니다. 그러나 인생은 잔인 할 수 있습니다. 한 번 봐 가지고 링크를 . 사용자가 새 장치를 구입하는 시나리오를 상상해 봅시다. 자체 세트가 없기 때문에 백업 에이전트는 다른 장치의 세트를 사용합니다. 앱이 설치되고 backupHelper는 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를 통해 수행되므로 통화 문제를 방지 할 수 있습니다. 직렬화는 backupAgent에서 수행됩니다. 직접 커서 <-> 객체 매핑을 수행하는 경우 항목을 직렬화하는 것은 엔티티를 나타내는 클래스의 _id 필드를 사용하여 구현 Serializable하는 것처럼 간단 할 수 있습니다 transient.

또한, 즉 삽입 대부분을 사용하십시오 ContentProviderOperation 예를 하고 CursorLoader.setUpdateThrottle그래서 응용 프로그램 복원 프로세스를 백업하는 동안 데이터 변경에 로더를 다시 시작과 붙어 아니라고.

다운 그레이드 상황이 발생하면 데이터 복원을 중단하거나 다운 그레이드 된 버전과 관련된 필드를 사용하여 ContentResolver를 복원 및 업데이트하도록 선택할 수 있습니다.

주제가 쉽지 않고 문서에 잘 설명되어 있지 않으며 일부 질문은 여전히 ​​대량 데이터 크기 등으로 남아 있다는 데 동의합니다.

도움이 되었기를 바랍니다.


몇 가지 유효한 질문을 반복합니다. 하지만 데이터베이스 직렬화가 문제를 어떻게 해결하는지 모르겠습니다. 동시성 문제에는 도움이되지 않습니다. 그리고 데이터베이스를 다운 그레이드하는 스크립트를 작성할 수 없다면 오래된 데이터를 역 직렬화하는 스크립트를 작성할 수 없습니다.
pjv

@pjv ContentResolver를 사용하면 동시성 문제가 해결됩니다.
IgorGanapolsky

@IgorGanapolsky 네, 사실이라고 생각합니다. 그러나 직렬화가 충분히 느리고 사용자가 이미 앱을 사용하고 데이터를 입력하고있는 경우 복원중인 데이터와 충돌 할 수 있습니다. 사용자가 앱에 액세스하기 전에 원자 적으로 할 수 있습니까?
pjv

@pjv ContentObserver를 등록하여 알림을 받기 위해 관찰중인 데이터를 동기화 할 수 있습니다. 따라서 갈등에 대해 걱정할 필요가 없습니다.
IgorGanapolsky

DB 버전에 대한 귀하의 요점은 좋은 것입니다. DB 버전을 저장하면 (예 : 공유 환경 설정에서 백업한다고 가정) 복원 할 때 기존 논리를 사용하여 DB를 해당 DB로 업그레이드 할 수 있어야합니다. onDowngrade () 메서드의 현재 버전. 아직 업그레이드 코드가 없다면 어쨌든 데이터를 유지하는 데 신경 쓰지 않아도됩니다!
Ben Neill

3

Android M부터는 앱에서 사용할 수있는 전체 데이터 백업 / 복원 API가 있습니다. 이 새로운 API에는 개발자가 직접 의미 론적 방식으로 백업 할 파일을 설명 할 수있는 XML 기반 사양이 앱 매니페스트에 포함되어 있습니다. ' "mydata.db"라는 데이터베이스 백업'. 이 새로운 API는 개발자가 훨씬 쉽게 사용할 수 있습니다. diff를 추적하거나 명시 적으로 백업 패스를 요청할 필요가 없으며 백업 할 파일에 대한 XML 설명은 종종 코드를 작성할 필요가 없음을 의미합니다. 조금도.

(당신은 할 수 도 전체 데이터 백업에 참여 /를 예를 들어, 어떻게 복원 할 때 콜백을 얻기 위해 복원 작업. 그것은 그런 식으로 유연.)

새 API 사용 방법에 대한 설명은 developer.android.com의 앱용 자동 백업 구성 섹션을 참조하세요 .


이는 Android 6.0 (API 23)에만 해당되며 사용자가 이전 버전을 사용하는 경우 여전히 키 값 백업을 구현해야합니다.
live-love

0

한 가지 옵션은 데이터베이스 위의 애플리케이션 로직에서 빌드하는 것입니다. 실제로 그런 수준의 비명을 지르는 것 같습니다. 이미하고 있는지 확실하지 않지만 대부분의 사람들 (Android 콘텐츠 관리자 커서 접근 방식에도 불구하고)은 사용자 지정 또는 일부 orm-lite 접근 방식 중 일부 ORM 매핑을 도입 할 것입니다. 그리고이 경우에 제가하고 싶은 것은 :

  1. 응용 프로그램이 이미 시작된 상태에서 새 데이터가 추가 / 제거 된 백그라운드에서 응용 프로그램 / 데이터가 추가 될 때 응용 프로그램이 제대로 작동하는지 확인합니다.
  2. Java-> protobuf 또는 단순히 Java 직렬화 매핑을 만들고 자신의 BackupHelper를 작성하여 스트림에서 데이터를 읽고 간단히 데이터베이스에 추가합니다 ....

따라서이 경우에는 db 수준에서 수행하는 대신 응용 프로그램 수준에서 수행합니다.


문제는 데이터베이스에서 BackupHelperAgent로 또는 그 반대로 데이터를 가져 오는 방법이 아닙니다. 내 질문 끝에있는 5 개의 글 머리 기호를 읽어주세요.
pjv

@JarekPotiuk 당신은 "대부분의 사람들 (안드로이드 콘텐츠 관리자 커서 접근 방식에도 불구하고)"이라고 말했습니다. 그러나 대부분의 사람들이 데이터베이스 액세스를 위해 ContentProvider 및 ContentResolver를 사용하지 않을 것이라고 생각하는 이유는 무엇입니까?
IgorGanapolsky
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.