问题
- 拷贝数据库文件到指定目录下,拷贝完成后,并做了MD5校验,此时断电,重启车机,发现文件拷贝失败。
以下是拷贝文件的代码:
/**
* 将asset/DB_NAME_CIYT 拷贝到指定目录
*
* @param databasePath
* @throws IOException
*/
private void copyDBFile(File databasePath) throws IOException {
databasePath.getParentFile().mkdirs();
databasePath.createNewFile();
InputStream inputStream = null;
FileOutputStream fileOutputStream = null;
try {
inputStream = mContext.getAssets().open(DB_NAME_CIYT);
fileOutputStream = new FileOutputStream(databasePath);
byte[] buf = new byte[1024 * 10];
int lenth = 0;
Lg.e(TAG, "copyDBFile: startCopyDB");
while ((lenth = inputStream.read(buf)) != -1) {
fileOutputStream.write(buf, 0, lenth);
//不能确保数据保存到物理存储设备上,如突然断电可能导致文件未保存;
fileOutputStream.flush();
}
Lg.e(TAG, "copyDBFile: endCopyDB");
checkoutDB();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
inputStream.close();}
if (fileOutputStream != null) fileOutputStream.close();
}
}
/**
* 拷贝完成,校验文件
*/
public void checkoutDB(){
try {
String assetMd5 = MD5Utils.getAssetsFileMD5();
File copyfile = mContext.getDatabasePath(DB_NAME_CIYT);
String copyFileMd5 = MD5Utils.getFileMD5(copyfile);
Lg.e(TAG, "checkoutDB: assetMd5 = " + assetMd5 + " copyFileMd5 = " + copyFileMd5);
boolean flag = !TextUtils.isEmpty(assetMd5) && assetMd5.equals(copyFileMd5);
if(flag) {
SpUtils.saveToLocal(Constant.SP_DB_MD5,assetMd5);
} else {
SpUtils.saveToLocal(Constant.SP_DB_MD5,"");
copyfile.delete();
}
}catch (Exception e){
e.printStackTrace();
SpUtils.saveToLocal(Constant.SP_DB_MD5,"");
}
}
分析问题
Linux/Unix系统中,在文件或数据处理过程中一般先放到内存缓冲区中,等到适当的时候再写入磁盘,以提高系统的运行效率。以下是流程示意图:
分析以上流程,问题因该出在数据拷贝写入磁盘的过程,查看相关代码:
flush()方法不能确保数据保存到物理存储设备上,如突然断电可能导致文件未保存;
FIle 操作文件会取内核缓冲区的文件,校验文件的MD5值,此时文件并没有被写入磁盘中,此时断电,会导致文件写入磁盘失败。
解决问题
将数据同步到达物理存储设备上。
方法一: 注意sync是阻塞的方法,应该放到子线程中执行,避免ANR
//将数据同步到达物理存储设备
FileDescriptor fd = fileOutputStream.getFD();
fd.sync();
inputStream.close();
fileOutputStream.close();
方法二: 使用RandomAccessFile 的“rws”模式
/**
* 将asset/DB_NAME_CIYT 拷贝到指定目录
*
* @param databasePath
* @throws IOException
*/
private synchronized void copyDBFile(File databasePath) throws IOException {
String path = databasePath.getParentFile().getAbsolutePath() + "/";
Lg.e("dir = " + path);
File dir = new File(path);
if(!dir.exists()) dir.mkdirs();
databasePath.createNewFile();
InputStream inputStream = null;
RandomAccessFile randomAccessFile = null;
try {
inputStream = mContext.getAssets().open(DB_NAME_CIYT);
//rws模式,会同步到磁盘
randomAccessFile = new RandomAccessFile(databasePath,"rws");
byte[] buf = new byte[1024 * 10];
int lenth = 0;
Lg.e(TAG, "copyDBFile: startCopyDB");
while ((lenth = inputStream.read(buf)) != -1) {
randomAccessFile.write(buf, 0, lenth);
}
Lg.e(TAG, "copyDBFile: endCopyDB");
checkoutDB();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) inputStream.close();
if (randomAccessFile != null) randomAccessFile.close();
}
}