FileChannel管道流复制文件是基于nio的传输方式。速度上有30%的提升。其次在我的项目中使用传统FileOutputStream方式,在复制大文件时。进度打印出现迟滞。综合这两点选择使用FileChannel方案。本文章是简单使用多线程方式实现,个人测试文件读写在多线程下比单线程快4倍,有参考的小伙伴使用的话 还是最好使用线程池进行处理性能上的优化。
public class CopyPasteUtil {
private static long dirSize = 0;// 文件夹总体积
private static long hasReadSize = 0;// 已复制的部分,体积
private static CopyProgressDialog progressDialog;// 进度提示框
private static Thread copyFileThread;
private static Runnable run = null;
private static FileInputStream fileInputStream = null;
private static FileOutputStream fileOutputStream = null;
private static FileChannel fileChannelOutput = null;
private static FileChannel fileChannelInput = null;
private static Thread copyDirThread;
private static BufferedInputStream inbuff = null;
private static BufferedOutputStream outbuff = null;
/**
* handler用于在主线程刷新ui
*/
private final static Handler handler = new Handler() {
public void handleMessage(android.os.Message msg) {
if (msg.what == 0) {
int progress = msg.getData().getInt("progress");
long fileVolume = msg.getData().getLong("fileVolume");
progressDialog.setProgress(progress);
progressDialog.setProgressText(progress + "%");
progressDialog.setFileVolumeText(fileVolume * progress / 100 + " MB/" + fileVolume + " MB");
} else if (msg.what == 1) {
if (progressDialog != null) {
int fileVolume = msg.getData().getInt("fileVolume");
progressDialog.setFileVolumeText(0 + " MB/" + fileVolume + " MB");
}
} else if (msg.what == 3) {
Log.e("jinchen", "源文件不存在");
}
};
};
/**
* 复制单个文件
*/
public static boolean copyFile(final String oldPathName, final String newPathName, Context context) {
//大于50M时,才显示进度框
final File oldFile = new File(oldPathName);
if (oldFile.length() > 50 * 1024 * 1024) {
if (progressDialog == null) {
progressDialog = new CopyProgressDialog(context);
progressDialog.setOnCancelListener(new CopyProgressDialog.OnCancelListener() {//点击返回取消时,关闭线程和流
@Override
public void onCancel() {
run = null;
copyFileThread.interrupt();
copyFileThread = null;
try {
fileInputStream.close();
fileOutputStream.close();
fileChannelOutput.close();
fileChannelInput.close();
} catch (IOException e) {
Log.e("CopyPasteUtil", "CopyPasteUtil copyFile error:" + e.getMessage());
}
}
});
}
progressDialog.show();
progressDialog.setNameText(oldPathName);
}
run = new Runnable() {
@Override
public void run() {
try {
File fromFile = new File(oldPathName);
File targetFile = new File(newPathName);
fileInputStream = new FileInputStream(fromFile);
fileOutputStream = new FileOutputStream(targetFile);
fileChannelOutput = fileOutputStream.getChannel();
fileChannelInput = fileInputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(4096);
long transferSize = 0;
long size = new File(oldPathName).length();
int fileVolume = (int) (size / 1024 /1024);
int tempP = 0;
int progress = 0;
while (fileChannelInput.read(buffer) != -1) {
buffer.flip();
transferSize += fileChannelOutput.write(buffer);
progress = (int) (transferSize * 100 / size);
if (progress > tempP) {
tempP = progress;
Message message = handler.obtainMessage(0);
Bundle b = new Bundle();
b.putInt("progress", progress);
b.putLong("fileVolume", fileVolume);
message.setData(b);
handler.sendMessage(message);
}
buffer.clear();
}
// outbuff.flush();
// inbuff.close();
// outbuff.close();
// fileOutputStream.close();
// fileInputStream.close();
// fileChannelOutput.close();
// fileChannelInput.close();
if (progressDialog.isShowing()) {
progressDialog.dismiss();
}
} catch (Exception e) {
Log.e("CopyPasteUtil", "CopyPasteUtil copyFile error:" + e.getMessage());
} finally {
try {
outbuff.flush();
inbuff.close();
outbuff.close();
fileOutputStream.close();
fileInputStream.close();
fileChannelOutput.close();
fileChannelInput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
};
copyFileThread = new Thread(run);
copyFileThread.start();
return true;
}
/**
* 复制文件夹
*/
public static void copyDirectiory(final String sourceDir, final String targetDir, final Context context) {
if (context != null) {
if (dirSize > 50 * 1024 * 1024) {
if (progressDialog == null) {
progressDialog = new CopyProgressDialog(context);
progressDialog.setOnCancelListener(new CopyProgressDialog.OnCancelListener() {//点击返回取消时,关闭线程和流
@Override
public void onCancel() {
run = null;
copyDirThread.interrupt();
copyDirThread = null;
try {
if(fileInputStream != null) {
fileInputStream.close();
}
if(fileOutputStream != null) {
fileOutputStream.close();
}
if(inbuff != null) {
inbuff.close();
}
if(outbuff != null) {
outbuff.close();
}
if(fileChannelOutput != null) {
fileChannelOutput.close();
}
if(fileChannelInput != null) {
fileChannelInput.close();
}
} catch (IOException e) {
Log.e("CopyPasteUtil", "CopyPasteUtil copyDirectiory error:" + e.getMessage());
}
}
});
}
progressDialog.show();
progressDialog.setNameText(sourceDir);
}
}
run = new Runnable() {
@Override
public void run() {
// (new File(targetDir)).mkdirs();
File dst = new File(targetDir);
if (!dst.exists()) {
dst.mkdir();
}
File src = new File(sourceDir);
if (!src.exists()) {
Message message = handler.obtainMessage(3);
handler.sendMessage(message);
}
File[] file = src.listFiles(); // 获取源文件夹当下的文件或目录
if (file != null) {
for (int i = 0; i < file.length; i++) {
if (file[i].isFile()) {
File sourceFile = file[i];
File targetFile = new File(
new File(targetDir).getAbsolutePath() + File.separator + file[i].getName()); // 目标文件
copyFile(sourceFile, targetFile);
}
if (file[i].isDirectory()) {
String dir1 = sourceDir + "/" + file[i].getName();
String dir2 = targetDir + "/" + file[i].getName();
copyDirectiory(dir1, dir2, null);
}
}
}
}
};
copyDirThread = new Thread(run);
copyDirThread.start();
}
/**
* 复制单个文件,用于上面的复制文件夹方法
*
* @param sourcefile
* 源文件路径
* @param targetFile
* 目标路径
*/
public static synchronized void copyFile(final File sourcefile, final File targetFile) {
try {
fileInputStream = new FileInputStream(sourcefile);
inbuff = new BufferedInputStream(fileInputStream);
fileOutputStream = new FileOutputStream(targetFile); // 新建文件输出流并对它进行缓冲
outbuff = new BufferedOutputStream(fileOutputStream);
int fileVolume = (int) (dirSize / (1024 * 1024));
fileChannelOutput = fileOutputStream.getChannel();
fileChannelInput = fileInputStream.getChannel();
ByteBuffer buffer = ByteBuffer.allocate(4096);
long transferSize = 0;
int tempP = 0;
int progress = 0;
while (fileChannelInput.read(buffer) != -1) {
buffer.flip();
transferSize += fileChannelOutput.write(buffer);
if (dirSize > 50 * 1024 * 1024) {
progress = (int) (((transferSize + hasReadSize) * 100) / dirSize);
if (progress > tempP) {
tempP = progress;
Message message = handler.obtainMessage(0);
Bundle b = new Bundle();
b.putInt("progress", progress);
b.putLong("fileVolume", fileVolume);
message.setData(b);
handler.sendMessage(message);
// if(progressDialog.isShow() && progress==100){
// progressDialog.dismiss();
// }
if (progress == 100) {
Log.e("jinchen", "copy over time " + System.currentTimeMillis());
}
}
}
buffer.clear();
}
hasReadSize += sourcefile.length();
// outbuff.flush();
// inbuff.close();
// outbuff.close();
// fileOutputStream.close();
// fileInputStream.close();
// fileChannelOutput.close();
// fileChannelInput.close();
} catch (FileNotFoundException e) {
Log.e("CopyPasteUtil", "CopyPasteUtil copyFile error:" + e.getMessage());
} catch (IOException e) {
Log.e("CopyPasteUtil", "CopyPasteUtil copyFile error:" + e.getMessage());
} finally {
try {
outbuff.flush();
inbuff.close();
outbuff.close();
fileOutputStream.close();
fileInputStream.close();
fileChannelOutput.close();
fileChannelInput.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 获取文件夹大小
* @param file
*/
public static void getDirSize(File file) {
if (!file.exists()) {
Log.e("jinchen", "getDirSize 源文件不存在");
return;
}
if (file.isFile()) {
// 如果是文件,获取文件大小累加
dirSize += file.length();
} else if (file.isDirectory()) {
File[] f1 = file.listFiles();
if (f1 != null) {
for (int i = 0; i < f1.length; i++) {
// 调用递归遍历f1数组中的每一个对象
getDirSize(f1[i]);
}
}
}
}
/**
* 初始化全局变量
*/
public static void initDirSize() {
dirSize = 0;
hasReadSize = 0;
}
/**
* 复制文件夹前,初始化两个变量
*/
public static void initValueAndGetDirSize(File file) {
initDirSize();
getDirSize(file);
}
}
有很多小伙伴很懒的写dialog, 在这 我把dialog代码也贴出来 方便大家使用。xml我就不粘贴了,自己写下吧 很简单。
public class CopyProgressDialog extends Dialog {
public OnCancelListener mOnCancelListener;
private TextView tv_old_name;
private TextView tv_progress;
private TextView tv_mb;
private Button cancel_btn;
public CopyProgressDialog(@NonNull Context context) {
super(context, R.style.TransparentDialog);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.dialog_progrees);
setCanceledOnTouchOutside(false);
tv_mb = findViewById(R.id.tv_mb);
tv_progress = findViewById(R.id.tv_progress);
tv_old_name = findViewById(R.id.tv_old_name);
cancel_btn = findViewById(R.id.cancel_btn);
cancel_btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnCancelListener != null) {
mOnCancelListener.onCancel();
}
}
});
}
public void setProgress(int progress) {
}
public void setProgressText(String progress) {
tv_progress.setText(progress);
}
public void setFileVolumeText(String volume) {
tv_mb.setText(volume);
}
public void setNameText(String name) {
tv_old_name.setText(name);
}
public void setOnCancelListener(OnCancelListener onCancelListener) {
this.mOnCancelListener = onCancelListener;
}
public interface OnCancelListener {
void onCancel();
}
}
在使用toChannel.transferFrom(fromChannel, 0, Long.MAX_VALUE);这个方法时,遇到一个问题就是,在jdk1.7环境(android7之前)会出现复制文件失败,报warning。
原来是Long.MAX_VALUE在jdk7中被执行为Integer.MAX_VALUE导致长度不够。真是一波三折啊。最终解决方式是通过一篇博客,https://fucknmb.com/2017/11/06/Android-FileChannel%E7%9A%84%E5%9D%91/。感谢!记录下,感觉生僻的技术还是得谷歌。积累吧!