Android 使用renameTo和move高效移动文件
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/dataat 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/sohuusfat 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 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 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 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 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 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 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 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;}
Moving a File or Directory
Java: Move Directory containing files and directories to new path
move定义
文件重命名知多少
Moving a file on android
Android 复制文件最快方法
Windows上可靠的File.renameTo()替代方法?
请慎用java的File#renameTo(File)方法(转)
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
