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/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;
    }

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)方法(转)

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值