renameTo是File的方法,move是Files的方法,Files是java8引入的,Android8.0(26)才支持了java8,也就是说Files.move方法在Android8.0及以上才能使用。move比renameTo更健壮些,简单来说就是renameTo只有在一个存储分区上才能成功,通俗来讲就是在同一个存储卡上,详细解释看源码注释。
不过两者有相同之处,以目录操作为例,如果目标目录已经存在,move和renameTo都会失效,renameTo返回false,move报FileSystemException异常
10-24 11:13:05.555 12575 12790 W System.err: java.nio.file.FileSystemException: /storage/emulated/0/shvdownload/video/SohuVideoGallery -> /storage/emulated/0/Movies/SHVideo: Directory not empty
10-24 11:13:05.555 12575 12790 W System.err: at sun.nio.fs.UnixCopyFile.move(UnixCopyFile.java:396)
10-24 11:13:05.555 12575 12790 W System.err: at sun.nio.fs.UnixFileSystemProvider.move(UnixFileSystemProvider.java:262)
10-24 11:13:05.555 12575 12790 W System.err: at java.nio.file.Files.move(Files.java:1395)
10-24 11:13:05.555 12575 12790 W System.err: at com.sohu.sohuvideo.sdk.android.storage.SHDataMigrateUtil.moveData(SHDataMigrateUtil.java:148)
10-24 11:13:05.555 12575 12790 W System.err: at com.sohu.sohuvideo.sdk.android.storage.SHDataMigrateUtil.migrateShareVideos(SHDataMigrateUtil.java:97)
10-24 11:13:05.555 12575 12790 W System.err: at com.sohu.sohuvideo.sdk.android.storage.SHDataMigrateUtil.access$300(SHDataMigrateUtil.java:24)
10-24 11:13:05.555 12575 12790 W System.err: at com.sohu.sohuvideo.sdk.android.storage.SHDataMigrateUtil$1.run(SHDataMigrateUtil.java:43)
10-24 11:13:05.555 12575 12790 W System.err: at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
10-24 11:13:05.555 12575 12790 W System.err: at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
所以,目标目录存在的话,只能通过内容拷贝的方式了。
private void moveData(File source, File target) {
long start = System.currentTimeMillis();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Path sourceP = source.toPath();
Path targetP = target.toPath();
if (target.exists()) {
copyDir(source, target);
LogUtils.i(TAG, "moveData copyDir");
} else {
try {
Files.move(sourceP, targetP, StandardCopyOption.ATOMIC_MOVE);
LogUtils.i(TAG, "moveData Files.move");
} catch (IOException e) {
e.printStackTrace();
}
}
} else {
if (target.exists()) {
copyDir(source, target);
LogUtils.i(TAG, "moveData copyDir");
} else {
boolean result = source.renameTo(target);
LogUtils.i(TAG, "moveData renameTo result " + result);
}
}
long end = System.currentTimeMillis();
long val = end - start;
LogUtils.i(TAG, "migrate data take time " + val +" from " + source.getAbsolutePath() + " to " + target.getAbsolutePath());
}
private void copyDir(File source, File target) {
if (source == null || target == null)
return;
if (!target.exists())
target.mkdirs();
String sourceS = source.getPath();
String targetS = target.getPath();
String[] paths = source.list();
for (String tmp : paths) {
File tmpFile = new File(sourceS + File.separator + tmp);
File newFile = new File(targetS + File.separator + tmp);
if (tmpFile.isDirectory()) {
copyDir(tmpFile, newFile);
} else {
FileUtils.copy(tmpFile, newFile);
}
}
}
Files的move方法,可以对文件夹操作,不过需要分情况:move可以去移动一个空目录,如果是在同一个分区操作,被移动的目录有内容,也是可以移动的,其实就是对目录名进行了重命名;如果不在同一个分区,就会fail。如果目标目录存在的话,move也会fail。
在实际开发中得测试,下面在Android不同系统上move表现就不一样。
特别注意!!!
Files.move操作在Android不同系统版本上有区别,这个很坑呀!!!
在Android11上,
/storage/emulated/0/shvdownload/video/SohuVideoGallery to /storage/emulated/0/Movies/SHVideo
SohuVideoGallery目录中有文件,SHVideo目录不存在,move可以成功
/storage/emulated/0/sohu/SohuVideo/data to /storage/emulated/0/Android/data/com.sohu.sohuvideo/files/data
data目录中有文件,files/data目录不存在,move失败:
StandardCopyOption.ATOMIC_MOVE的move报AtomicMoveNotSupportedException异常
StandardCopyOption.REPLACE_EXISTING的move报DirectoryNotEmptyException异常
java.nio.file.DirectoryNotEmptyException: /storage/emulated/0/sohu/SohuVideo/data
at sun.nio.fs.UnixCopyFile.move(UnixCopyFile.java:498)
at sun.nio.fs.UnixFileSystemProvider.move(UnixFileSystemProvider.java:262)
at java.nio.file.Files.move(Files.java:1395)
at com.sohu.sohuvideo.sdk.android.storage.SHDataMigrateUtil.moveData(SHDataMigrateUtil.java:148)
at com.sohu.sohuvideo.sdk.android.storage.SHDataMigrateUtil.migrateUIDData(SHDataMigrateUtil.java:80)
at com.sohu.sohuvideo.sdk.android.storage.SHDataMigrateUtil.access$400(SHDataMigrateUtil.java:24)
at com.sohu.sohuvideo.sdk.android.storage.SHDataMigrateUtil$1.run(SHDataMigrateUtil.java:44)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:923)
在Android10及一下系统,上面操作都是正常的!!!可能是Android 11对Android/data目录有了限制吧!
Files的move方法也是可以移动文件的,需要source和target都必须是文件路径,而且target路径中的文件夹都得存在。否则会报错
java.nio.file.NoSuchFileException: /storage/emulated/0/AAAA/sohuusf -> /storage/emulated/0/AAAC/sohuusf
at sun.nio.fs.UnixCopyFile.move(UnixCopyFile.java:457)
at sun.nio.fs.UnixFileSystemProvider.move(UnixFileSystemProvider.java:262)
at java.nio.file.Files.move(Files.java:1395)
at com.zy.myapplication7.FileUtils.moveData(FileUtils.java:115)
at com.zy.myapplication7.FirstFragment$1.onClick(FirstFragment.java:41)
at android.view.View.performClick(View.java:7258)
at com.google.android.material.button.MaterialButton.performClick(MaterialButton.java:992)
at android.view.View.performClickInternal(View.java:7220)
at android.view.View.access$3800(View.java:821)
at android.view.View$PerformClick.run(View.java:27712)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:237)
at android.app.ActivityThread.main(ActivityThread.java:7840)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:985)
移动(或复制)文件(或文件夹)的方法
public static List<String> copyData(File source, File target, boolean existJump) {
return copyData(source, target, existJump, false);
}
/**
* 复制文件或文件夹
*
* @param source 可以是文件也可以是文件夹
* @param target 必须是文件夹
* @param existJump 目标文件已经存在时,true表示跳过复制,false表示文件重命名(文件名加数字递增的方式)复制
* @param delete 复制成功后是否删除原文件
*
* @return 返回所有复制成功后的目标文件路径
*/
private static List<String> copyData(File source, File target, boolean existJump, boolean delete) {
if (source == null || target == null) {
return null;
}
if (!target.exists()) {
target.mkdirs();
} else {
if (!target.isDirectory()) {
throw new IllegalArgumentException("target must is directory!!!");
}
}
String sourceS = source.getPath();
String targetS = target.getPath();
String[] paths;
if (source.isDirectory()) {
paths = source.list();
} else {
sourceS = source.getParent();
paths = new String[]{source.getName()};
}
List<String> successList = new ArrayList<>();
for (String tmp : paths) {
File tmpFile = new File(sourceS + File.separator + tmp);
File newFile = new File(targetS + File.separator + tmp);
if (tmpFile.isDirectory()) {
List<String> middleList =copyData(tmpFile, newFile, existJump, delete);
if (!middleList.isEmpty())
successList.addAll(middleList);
} else {
if (newFile.exists()) {
//不跳过
if (!existJump) {
// 递增文件名
for (int i = 1;;i++) {
String[] arr = tmp.split("\\.");
String tmp2 = arr[0] + "("+ i + ")";
if (arr.length > 1) {
tmp2 += "." + arr[1];
}
newFile = new File(target, tmp2);
if(!newFile.exists())
break;
}
String successPath = copyFileCompat(tmpFile, newFile);
if (successPath != null && successPath.length() != 0) {
successList.add(successPath);
if (delete) {
tmpFile.delete();
}
}
}
} else {
String successPath = copyFileCompat(tmpFile, newFile);
if (successPath != null && successPath.length() != 0) {
successList.add(successPath);
if (delete) {
tmpFile.delete();
}
}
}
}
}
if (delete && source.isDirectory() && (source.list() == null || source.list().length == 0)) {
source.delete();
}
return successList;
}
private static String copyFileCompat(File oldFile, File newFile) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
Files.copy(oldFile.toPath(), newFile.toPath());
return newFile.getPath();
} catch (IOException e) {
e.printStackTrace();
}
} else {
boolean isCopySuccess = FileUtils.copy(oldFile, newFile);
if (isCopySuccess) {
return newFile.getPath();
}
}
return null;
}
/**
* 复制文件
*
* @param sourceFile
* @param destFile
* @return
*/
public static boolean copy(File sourceFile, File destFile) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(sourceFile);
fos = new FileOutputStream(destFile);
byte[] bytes = new byte[BUFFER];
int ret = -1;
while ((ret = fis.read(bytes)) != -1) {
fos.write(bytes, 0, ret);
}
return true;
} catch (IOException e) {
LogUtils.e(TAG, e);
} finally {
try {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
LogUtils.e(TAG, e);
}
}
return false;
}
/**
* 移动文件或文件夹
*
* @param source 可以是文件也可以是文件夹
* @param target 必须是文件夹
* @param existJump 目标文件已经存在时,true表示跳过,false表示文件重命名(文件名加数字递增的方式)移动
*
* @return 返回所有移动成功后的目标文件路径
*/
public static List<String> moveData(File source, File target, boolean existJump) {
if (source == null || target == null) {
return null;
}
if (!target.exists()) {
target.mkdirs();
} else {
if (!target.isDirectory()) {
throw new IllegalArgumentException("target must is directory!!!");
}
}
String sourceS = source.getPath();
String targetS = target.getPath();
String[] paths;
if (source.isDirectory()) {
paths = source.list();
} else {
sourceS = source.getParent();
paths = new String[]{source.getName()};
}
List<String> successList = new ArrayList<>();
for (String tmp : paths) {
File tmpFile = new File(sourceS + File.separator + tmp);
File newFile = new File(targetS + File.separator + tmp);
if (tmpFile.isDirectory()) {
List<String> middleList =moveData(tmpFile, newFile, existJump);
if (!middleList.isEmpty())
successList.addAll(middleList);
} else {
if (newFile.exists()) {
//不跳过
if (!existJump) {
// 递增文件名
for (int i = 1;;i++) {
String[] arr = tmp.split("\\.");
String tmp2 = arr[0] + "("+ i + ")";
if (arr.length > 1) {
tmp2 += "." + arr[1];
}
newFile = new File(target, tmp2);
if(!newFile.exists())
break;
}
String successPath = moveFileCompat(tmpFile, newFile);
if (successPath != null && successPath.length() != 0) {
successList.add(successPath);
}
}
} else {
String successPath = moveFileCompat(tmpFile, newFile);
if (successPath != null && successPath.length() != 0) {
successList.add(successPath);
}
}
}
}
if (source.isDirectory() && (source.list() == null || source.list().length == 0)) {
source.delete();
}
return successList;
}
private static String moveFileCompat(File oldFile, File newFile) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
try {
Files.move(oldFile.toPath(), newFile.toPath());
return newFile.getPath();
} catch (IOException e) {
e.printStackTrace();
}
} else {
boolean isCopySuccess = oldFile.renameTo(newFile);
if (isCopySuccess) {
return newFile.getPath();
}
}
return null;
}
Java: Move Directory containing files and directories to new path
Windows上可靠的File.renameTo()替代方法?