前言:在高通8916平台,通过自带的文件管理器拷贝文件到U盘,显示完成后拔除U盘,在电脑上发现文件有问题,或是为0b或是打不开或是大小异常。如果正常通过设置中的移除U盘就没有这个问题,故问题定位在热插拔异常。
首先我来解释下为何会出现这种问题,基于linux, write操作和read操作有一点是不同的:
read操作需要从磁盘上读取到用户需的有效数据才算结束。
而write则只需要把数据写入内存cache就返回了,所以当copy完成后,数据还位于缓存中,还没有写入磁盘。
这样做的目的是为了提高write效率,先写入缓存,后台write back再会将数据写入磁盘(与之相对的是称之为Direct IO的跳过缓存的写操作,但是会write速率比较慢)
这种机制是为了performance是不会改变的,所以这就是为何不提倡热插拔磁盘。
问题找到了,那该如何解决呢?
实时刷新、写入io流。
高通自带文件管理器相关代码:
FileHelper.java
/**
* Method that copies a file
*
* @param src The source file
* @param dst The destination file
* @param bufferSize The buffer size for the operation
* @return boolean If the operation complete successfully
*/
public static boolean bufferedCopy(final File src, final File dst,
int bufferSize, Program program)
throws ExecutionException, CancelledOperationException {
BufferedInputStream bis = null;
BufferedOutputStream bos = null;
FileInputStream fis = null;
FileOutputStream fos = null;
FileDescriptor fd = null;
try {
fis = new FileInputStream(src);
fos = new FileOutputStream(dst);
fd = fos.getFD();
bis = new BufferedInputStream(fis, bufferSize);
bos = new BufferedOutputStream(fos, bufferSize);
int read = 0;
byte[] data = new byte[bufferSize];
while ((read = bis.read(data, 0, bufferSize)) != -1) {
// Short circuit if we've been cancelled. Show's over :(
if (program.isCancelled()) {
throw new CancelledOperationException();
}
bos.write(data, 0, read);
}
bos.flush(); // flush data from the stream into the buffer
fd.sync(); // confirms data to be written to the disk
return true;
} catch (Throwable e) {
Log.e(TAG,
String.format(TAG, "Failed to copy from %s to %d", src, dst), e); //$NON-NLS-1$
// Check if this error is an out of space exception and throw that specifically.
// ENOSPC -> Error No Space
if (e.getCause() instanceof ErrnoException
&& ((ErrnoException)e.getCause()).errno == OsConstants.ENOSPC) {
throw new ExecutionException(R.string.msgs_no_disk_space);
} if (e instanceof CancelledOperationException) {
// If the user cancelled this operation, let it through.
throw (CancelledOperationException)e;
}
return false;
} finally {
try {
if (bis != null) {
bis.close();
}
} catch (Throwable e) {/**NON BLOCK**/}
try {
if (bos != null) {
bos.close();
}
} catch (Throwable e) {/**NON BLOCK**/}
try {
if (fis != null) {
fis.close();
}
} catch (Throwable e) {/**NON BLOCK**/}
try {
if (fos != null) {
fos.close();
}
} catch (Throwable e) {/**NON BLOCK**/}
}
}
fd.sync();同步写入即可。
在所有要write的数据写完后,执行sync命令,相当于将缓存内容强制刷入磁盘。
副作用:
首先sync是系统提供的,没有优化办法。
这样做从原来系统在后台sync(写操作会迅速返回), 变成了前台去强制sync(sync完才返回)。
好处是,sync完返回,可以保证数据安全性,不让用户在sync完之前拔掉U盘。
坏处是,用户可能需要多等一段时间。
这是一个策略问题,需要权衡。