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

问题

  1. 拷贝数据库文件到指定目录下,拷贝完成后,并做了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系统中,在文件或数据处理过程中一般先放到内存缓冲区中,等到适当的时候再写入磁盘,以提高系统的运行效率。以下是流程示意图:

拷贝文件时,做完MD5校验,异常断电导致文件拷贝失败_第1张图片
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();
    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();
    }
}

你可能感兴趣的:(拷贝文件时,做完MD5校验,异常断电导致文件拷贝失败)