拷贝文件时,做完MD5校验,异常断电导致文件拷贝失败

问题

  1. 拷贝数据库文件到指定目录下,拷贝完成后,并做了MD5校验,此时断电,重启车机,发现文件拷贝失败。
【拷贝文件时,做完MD5校验,异常断电导致文件拷贝失败】以下是拷贝文件的代码:
/** * 将asset/DB_NAME_CIYT 拷贝到指定目录 * * @param databasePath * @throws IOException */ private void copyDBFile(File databasePath) throws IOException { databasePath.getParentFile().mkdirs(); databasePath.createNewFile(); InputStreaminputStream= null; FileOutputStream fileOutputStream = null; try { inputStream = mContext.getAssets().open(DB_NAME_CIYT); fileOutputStream = new FileOutputStream(databasePath); byte[] buf= new byte[1024 * 10]; intlenth = 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系统中,在文件或数据处理过程中一般先放到内存缓冲区中,等到适当的时候再写入磁盘,以提高系统的运行效率。以下是流程示意图:
拷贝文件时,做完MD5校验,异常断电导致文件拷贝失败
文章图片
8.png 分析以上流程,问题因该出在数据拷贝写入磁盘的过程,查看相关代码:
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(); InputStreaminputStream= null; RandomAccessFile randomAccessFile = null; try {inputStream = mContext.getAssets().open(DB_NAME_CIYT); //rws模式,会同步到磁盘 randomAccessFile = new RandomAccessFile(databasePath,"rws"); byte[] buf= new byte[1024 * 10]; intlenth = 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(); } }

    推荐阅读