使用 Google 驱动器备份和恢复 SQLite 数据库
Using Google Drive to backup and restore SQLite Database
我设法在 SD 卡上创建了我的数据库备份并从那里恢复,但我意识到我备份的目的是确保数据的安全,在这种情况下,如果物理设备本身是损坏、丢失或自燃,SD 卡上的备份也会如此。因此,在这种情况下,将备份与原始文件放在同一位置,坦率地说,这违背了备份的目的。
所以我想到使用 Google Drive 作为保存 db 文件的更安全的地方,而且它是免费的。我已经查看了 Google 的 quickstart 演示,它运行良好。但我仍然不知道如何为我的案子完成这项工作。
我找到了 fiddle 的一些代码,但它仍在使用一些已弃用的方法,到目前为止,我只设法 运行 在省略弃用区域时使用它,但它只会创建一个我的 Google 驱动器中的空白二进制文件,所以我认为弃用区域是它实际上传数据库备份内容的地方。如果有人能提供帮助,将不胜感激。
我会把它留在下面,以防有人可以用它更好地向我解释事情。我还在下面标记了已弃用的方法,它已接近尾声。
public class ExpectoPatronum extends Activity implements ConnectionCallbacks, OnConnectionFailedListener {
private static final String TAG = "MainActivity";
private GoogleApiClient api;
private boolean mResolvingError = false;
private DriveFile mfile;
private static final int DIALOG_ERROR_CODE =100;
private static final String DATABASE_NAME = "demodb";
private static final String GOOGLE_DRIVE_FILE_NAME = "sqlite_db_backup";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create the Drive API instance
api = new GoogleApiClient.Builder(this).addApi(Drive.API).addScope(Drive.SCOPE_FILE).
addConnectionCallbacks(this).addOnConnectionFailedListener(this).build();
}
@Override
public void onStart() {
super.onStart();
if(!mResolvingError) {
api.connect(); // Connect the client to Google Drive
}
}
@Override
public void onStop() {
super.onStop();
api.disconnect(); // Disconnect the client from Google Drive
}
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.v(TAG, "Connection failed");
if(mResolvingError) { // If already in resolution state, just return.
return;
} else if(result.hasResolution()) { // Error can be resolved by starting an intent with user interaction
mResolvingError = true;
try {
result.startResolutionForResult(this, DIALOG_ERROR_CODE);
} catch (SendIntentException e) {
e.printStackTrace();
}
} else { // Error cannot be resolved. Display Error Dialog stating the reason if possible.
ErrorDialogFragment fragment = new ErrorDialogFragment();
Bundle args = new Bundle();
args.putInt("error", result.getErrorCode());
fragment.setArguments(args);
fragment.show(getFragmentManager(), "errordialog");
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == DIALOG_ERROR_CODE) {
mResolvingError = false;
if(resultCode == RESULT_OK) { // Error was resolved, now connect to the client if not done so.
if(!api.isConnecting() && !api.isConnected()) {
api.connect();
}
}
}
}
@Override
public void onConnected(Bundle connectionHint) {
Log.v(TAG, "Connected successfully");
/* Connection to Google Drive established. Now request for Contents instance, which can be used to provide file contents.
The callback is registered for the same. */
Drive.DriveApi.newDriveContents(api).setResultCallback(contentsCallback);
}
final private ResultCallback<DriveApi.DriveContentsResult> contentsCallback = new ResultCallback<DriveApi.DriveContentsResult>() {
@Override
public void onResult(DriveApi.DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
Log.v(TAG, "Error while trying to create new file contents");
return;
}
String mimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType("db");
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(GOOGLE_DRIVE_FILE_NAME) // Google Drive File name
.setMimeType(mimeType)
.setStarred(true).build();
// create a file on root folder
Drive.DriveApi.getRootFolder(api)
.createFile(api, changeSet, result.getDriveContents())
.setResultCallback(fileCallback);
}
};
final private ResultCallback<DriveFileResult> fileCallback = new ResultCallback<DriveFileResult>() {
@Override
public void onResult(DriveFileResult result) {
if (!result.getStatus().isSuccess()) {
Log.v(TAG, "Error while trying to create the file");
return;
}
mfile = result.getDriveFile();
mfile.open(api, DriveFile.MODE_WRITE_ONLY, null).setResultCallback(contentsOpenedCallback);
}
};
final private ResultCallback<DriveApi.DriveContentsResult> contentsOpenedCallback = new ResultCallback<DriveApi.DriveContentsResult>() {
@Override
public void onResult(DriveApi.DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
Log.v(TAG, "Error opening file");
return;
}
try {
FileInputStream is = new FileInputStream(getDbPath());
BufferedInputStream in = new BufferedInputStream(is);
byte[] buffer = new byte[8 * 1024];
DriveContents content = result.getDriveContents();
BufferedOutputStream out = new BufferedOutputStream(content.getOutputStream());
int n = 0;
while( ( n = in.read(buffer) ) > 0 ) {
out.write(buffer, 0, n);
}
in.close();
commitAndCloseContents is DEPRECATED -->/**mfile.commitAndCloseContents(api, content).setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(Status result) {
// Handle the response status
}
});**/
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
private File getDbPath() {
return this.getDatabasePath(DATABASE_NAME);
}
@Override
public void onConnectionSuspended(int cause) {
// TODO Auto-generated method stub
Log.v(TAG, "Connection suspended");
}
public void onDialogDismissed() {
mResolvingError = false;
}
public static class ErrorDialogFragment extends DialogFragment {
public ErrorDialogFragment() {}
public Dialog onCreateDialog(Bundle savedInstanceState) {
int errorCode = this.getArguments().getInt("error");
return GooglePlayServicesUtil.getErrorDialog(errorCode, this.getActivity(), DIALOG_ERROR_CODE);
}
public void onDismiss(DialogInterface dialog) {
((ExpectoPatronum) getActivity()).onDialogDismissed();
}
}
}
用于访问 Google 驱动器的两个 API 都处理二进制内容。所以你唯一要做的就是上传你的二进制数据库文件,给它一个正确的 MIME 类型和一个名称(标题)。
API的选择取决于你,GDAA behaves like a 'local' entity with uploads / downloads handled by Google Play Services, REST Api更多的是low-level,给你更多的控制权,但你必须照顾网络问题(wifi on/off, 等), 即你通常必须建立一个同步服务来做到这一点。使用 GDAA,它由 GooPlaySvcs 为您完成。但是我跑题了。
我可以向您指出这个 GitHub demo,相当新的 (GooPlaySvcs 7.00.+),我用来测试不同的 REST / GDAA 问题。
MainActivity 有点复杂,因为它允许在不同的 Google 帐户之间切换,但如果你通过 these hurdles,你可以使用 REST 或 GDAA CRUD 包装器。
看看this line。 byte[] 缓冲区包含二进制 JPEG 数据,它带有 "image/jpeg" mime 类型(和 time-based 名称)。你唯一需要做的就是使用这样的结构将你的数据库文件加载到 byte[] 缓冲区中:
private static final int BUF_SZ = 4096;
static byte[] file2Bytes(File file) {
if (file != null) try {
return is2Bytes(new FileInputStream(file));
} catch (Exception ignore) {}
return null;
}
static byte[] is2Bytes(InputStream is) {
byte[] buf = null;
BufferedInputStream bufIS = null;
if (is != null) try {
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
bufIS = new BufferedInputStream(is);
buf = new byte[BUF_SZ];
int cnt;
while ((cnt = bufIS.read(buf)) >= 0) {
byteBuffer.write(buf, 0, cnt);
}
buf = byteBuffer.size() > 0 ? byteBuffer.toByteArray() : null;
} catch (Exception e) {le(e);}
finally {
try {
if (bufIS != null) bufIS.close();
} catch (Exception e) {le(e);}
}
return buf;
}
我现在不记得 SQLite DB 的 MIME 类型了,但我确信它可以完成,因为我确实做过一次(不幸的是,代码现在已经消失了)。我记得我实际上可以使用一些网络应用程序访问和修改 SQLite 数据库 'up in the cloud'。
祝你好运
更新:
在我写完上面的咆哮之后,我看了你正在谈论的演示。如果你让它工作,最简单的方法实际上是插入你的数据库文件 here,设置正确的 MIME,你就可以开始了。任你挑
并解决您的 'deprecated' 问题。 GDAA 仍在开发中,快速入门已有一年多了。这就是我们生活的世界 :-)
您需要将已弃用的代码替换为:
contents.commit(api, 空);
见https://developer.android.com/reference/com/google/android/gms/drive/DriveContents.html
我设法在 SD 卡上创建了我的数据库备份并从那里恢复,但我意识到我备份的目的是确保数据的安全,在这种情况下,如果物理设备本身是损坏、丢失或自燃,SD 卡上的备份也会如此。因此,在这种情况下,将备份与原始文件放在同一位置,坦率地说,这违背了备份的目的。
所以我想到使用 Google Drive 作为保存 db 文件的更安全的地方,而且它是免费的。我已经查看了 Google 的 quickstart 演示,它运行良好。但我仍然不知道如何为我的案子完成这项工作。
我找到了 fiddle 的一些代码,但它仍在使用一些已弃用的方法,到目前为止,我只设法 运行 在省略弃用区域时使用它,但它只会创建一个我的 Google 驱动器中的空白二进制文件,所以我认为弃用区域是它实际上传数据库备份内容的地方。如果有人能提供帮助,将不胜感激。
我会把它留在下面,以防有人可以用它更好地向我解释事情。我还在下面标记了已弃用的方法,它已接近尾声。
public class ExpectoPatronum extends Activity implements ConnectionCallbacks, OnConnectionFailedListener {
private static final String TAG = "MainActivity";
private GoogleApiClient api;
private boolean mResolvingError = false;
private DriveFile mfile;
private static final int DIALOG_ERROR_CODE =100;
private static final String DATABASE_NAME = "demodb";
private static final String GOOGLE_DRIVE_FILE_NAME = "sqlite_db_backup";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create the Drive API instance
api = new GoogleApiClient.Builder(this).addApi(Drive.API).addScope(Drive.SCOPE_FILE).
addConnectionCallbacks(this).addOnConnectionFailedListener(this).build();
}
@Override
public void onStart() {
super.onStart();
if(!mResolvingError) {
api.connect(); // Connect the client to Google Drive
}
}
@Override
public void onStop() {
super.onStop();
api.disconnect(); // Disconnect the client from Google Drive
}
@Override
public void onConnectionFailed(ConnectionResult result) {
Log.v(TAG, "Connection failed");
if(mResolvingError) { // If already in resolution state, just return.
return;
} else if(result.hasResolution()) { // Error can be resolved by starting an intent with user interaction
mResolvingError = true;
try {
result.startResolutionForResult(this, DIALOG_ERROR_CODE);
} catch (SendIntentException e) {
e.printStackTrace();
}
} else { // Error cannot be resolved. Display Error Dialog stating the reason if possible.
ErrorDialogFragment fragment = new ErrorDialogFragment();
Bundle args = new Bundle();
args.putInt("error", result.getErrorCode());
fragment.setArguments(args);
fragment.show(getFragmentManager(), "errordialog");
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if(requestCode == DIALOG_ERROR_CODE) {
mResolvingError = false;
if(resultCode == RESULT_OK) { // Error was resolved, now connect to the client if not done so.
if(!api.isConnecting() && !api.isConnected()) {
api.connect();
}
}
}
}
@Override
public void onConnected(Bundle connectionHint) {
Log.v(TAG, "Connected successfully");
/* Connection to Google Drive established. Now request for Contents instance, which can be used to provide file contents.
The callback is registered for the same. */
Drive.DriveApi.newDriveContents(api).setResultCallback(contentsCallback);
}
final private ResultCallback<DriveApi.DriveContentsResult> contentsCallback = new ResultCallback<DriveApi.DriveContentsResult>() {
@Override
public void onResult(DriveApi.DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
Log.v(TAG, "Error while trying to create new file contents");
return;
}
String mimeType = MimeTypeMap.getSingleton().getExtensionFromMimeType("db");
MetadataChangeSet changeSet = new MetadataChangeSet.Builder()
.setTitle(GOOGLE_DRIVE_FILE_NAME) // Google Drive File name
.setMimeType(mimeType)
.setStarred(true).build();
// create a file on root folder
Drive.DriveApi.getRootFolder(api)
.createFile(api, changeSet, result.getDriveContents())
.setResultCallback(fileCallback);
}
};
final private ResultCallback<DriveFileResult> fileCallback = new ResultCallback<DriveFileResult>() {
@Override
public void onResult(DriveFileResult result) {
if (!result.getStatus().isSuccess()) {
Log.v(TAG, "Error while trying to create the file");
return;
}
mfile = result.getDriveFile();
mfile.open(api, DriveFile.MODE_WRITE_ONLY, null).setResultCallback(contentsOpenedCallback);
}
};
final private ResultCallback<DriveApi.DriveContentsResult> contentsOpenedCallback = new ResultCallback<DriveApi.DriveContentsResult>() {
@Override
public void onResult(DriveApi.DriveContentsResult result) {
if (!result.getStatus().isSuccess()) {
Log.v(TAG, "Error opening file");
return;
}
try {
FileInputStream is = new FileInputStream(getDbPath());
BufferedInputStream in = new BufferedInputStream(is);
byte[] buffer = new byte[8 * 1024];
DriveContents content = result.getDriveContents();
BufferedOutputStream out = new BufferedOutputStream(content.getOutputStream());
int n = 0;
while( ( n = in.read(buffer) ) > 0 ) {
out.write(buffer, 0, n);
}
in.close();
commitAndCloseContents is DEPRECATED -->/**mfile.commitAndCloseContents(api, content).setResultCallback(new ResultCallback<Status>() {
@Override
public void onResult(Status result) {
// Handle the response status
}
});**/
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
private File getDbPath() {
return this.getDatabasePath(DATABASE_NAME);
}
@Override
public void onConnectionSuspended(int cause) {
// TODO Auto-generated method stub
Log.v(TAG, "Connection suspended");
}
public void onDialogDismissed() {
mResolvingError = false;
}
public static class ErrorDialogFragment extends DialogFragment {
public ErrorDialogFragment() {}
public Dialog onCreateDialog(Bundle savedInstanceState) {
int errorCode = this.getArguments().getInt("error");
return GooglePlayServicesUtil.getErrorDialog(errorCode, this.getActivity(), DIALOG_ERROR_CODE);
}
public void onDismiss(DialogInterface dialog) {
((ExpectoPatronum) getActivity()).onDialogDismissed();
}
}
}
用于访问 Google 驱动器的两个 API 都处理二进制内容。所以你唯一要做的就是上传你的二进制数据库文件,给它一个正确的 MIME 类型和一个名称(标题)。
API的选择取决于你,GDAA behaves like a 'local' entity with uploads / downloads handled by Google Play Services, REST Api更多的是low-level,给你更多的控制权,但你必须照顾网络问题(wifi on/off, 等), 即你通常必须建立一个同步服务来做到这一点。使用 GDAA,它由 GooPlaySvcs 为您完成。但是我跑题了。
我可以向您指出这个 GitHub demo,相当新的 (GooPlaySvcs 7.00.+),我用来测试不同的 REST / GDAA 问题。
MainActivity 有点复杂,因为它允许在不同的 Google 帐户之间切换,但如果你通过 these hurdles,你可以使用 REST 或 GDAA CRUD 包装器。
看看this line。 byte[] 缓冲区包含二进制 JPEG 数据,它带有 "image/jpeg" mime 类型(和 time-based 名称)。你唯一需要做的就是使用这样的结构将你的数据库文件加载到 byte[] 缓冲区中:
private static final int BUF_SZ = 4096;
static byte[] file2Bytes(File file) {
if (file != null) try {
return is2Bytes(new FileInputStream(file));
} catch (Exception ignore) {}
return null;
}
static byte[] is2Bytes(InputStream is) {
byte[] buf = null;
BufferedInputStream bufIS = null;
if (is != null) try {
ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();
bufIS = new BufferedInputStream(is);
buf = new byte[BUF_SZ];
int cnt;
while ((cnt = bufIS.read(buf)) >= 0) {
byteBuffer.write(buf, 0, cnt);
}
buf = byteBuffer.size() > 0 ? byteBuffer.toByteArray() : null;
} catch (Exception e) {le(e);}
finally {
try {
if (bufIS != null) bufIS.close();
} catch (Exception e) {le(e);}
}
return buf;
}
我现在不记得 SQLite DB 的 MIME 类型了,但我确信它可以完成,因为我确实做过一次(不幸的是,代码现在已经消失了)。我记得我实际上可以使用一些网络应用程序访问和修改 SQLite 数据库 'up in the cloud'。
祝你好运
更新:
在我写完上面的咆哮之后,我看了你正在谈论的演示。如果你让它工作,最简单的方法实际上是插入你的数据库文件 here,设置正确的 MIME,你就可以开始了。任你挑
并解决您的 'deprecated' 问题。 GDAA 仍在开发中,快速入门已有一年多了。这就是我们生活的世界 :-)
您需要将已弃用的代码替换为:
contents.commit(api, 空);
见https://developer.android.com/reference/com/google/android/gms/drive/DriveContents.html