CopyPasteUtil文件复制黏贴工具类

概述:总结了几篇大牛的文章,左拿右拿,东改西改,取其精华,逐步完善,撸了一个直接能用的文件搬迁的工具类,旨在于易用,易懂,简单,纤瘦。

FileChannel管道流复制文件是基于nio的传输方式。速度上有30%的提升,传统FileOutputStream方式,在复制大文件时。进度打印出现迟滞。综合这两点选择使用FileChannel方案。

该工具类主要用于复制文件、文件夹(批量文件)
以下内容主要包括:

  • 主要核心代码
  • 主要使用方式

主要核心代码:

import android.content.Context;
import android.content.DialogInterface;
import android.util.Log;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class CopyPasteUtil {
    /**
     * copy过程的监听接口
     */
    public interface CopyPasteListener {
        void onSuccess();

        void onProgress(long dirFileCount, long hasReadCount, long dirSize, long hasReadSize);

        void onFail(String errorMsg);

        void onCancle();
    }

    /**
     * 初始化的监听接口
     */
    public interface InitListener {
        void onNext(long dirFileCount, long dirSize, CopyPasteImp imp);
    }

    public static CopyPasteImp build() {
        return new CopyPasteImp();
    }

    public static class CopyPasteImp {
        private long dirSize = 0;// 文件夹总体积
        private long hasReadSize = 0;// 已复制的部分,体积
        private long dirFileCount = 0;// 文件总个数
        private long hasReadCount = 0;// 已复制的文件个数
        private CommonProgressDialog progressDialog;// 进度提示框
        private boolean isNeesDefaulProgressDialog = true;
        private Thread copyFileThread;
        private FileInputStream fileInputStream = null;
        private FileOutputStream fileOutputStream = null;
        private FileChannel fileChannelOutput = null;
        private FileChannel fileChannelInput = null;
        private BufferedInputStream inbuff = null; //todo 屏蔽之后,代码照跑无误,严重怀疑buff是否还有作用,未针对调试。
        private BufferedOutputStream outbuff = null;

        /**
         * 复制单个文件
         */
        public boolean copyFile(final String oldPathName, final String newPathName, Context context) {
            //大于50M时,才显示进度框
            final File oldFile = new File(oldPathName);
            if (oldFile.length() > 50 * 1024 * 1024) {
                if (isNeesDefaulProgressDialog && null == progressDialog) {
                    progressDialog = new CommonProgressDialog(context);
                    progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                        @Override
                        public void onCancel(DialogInterface dialog) {
                            // TODO Auto-generated method stub
                            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();
                }
            }
            Runnable 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;
                        if (null != progressDialog) {
                            progressDialog.setMax(fileVolume * 1024 * 1024);
                        }
                        while (fileChannelInput.read(buffer) != -1) {
                            buffer.flip();
                            transferSize += fileChannelOutput.write(buffer);
                            progress = (int) transferSize / (1024 * 1024);
                            if (progress > tempP) {
                                tempP = progress;
                                if (null != progressDialog) {
                                    progressDialog.setProgress(progress * 1024 * 1024);
                                }
                            }
                            buffer.clear();
                        }
                        fileOutputStream.flush();
                        fileOutputStream.close();
                        fileInputStream.close();
                        fileChannelOutput.close();
                        fileChannelInput.close();
                        if (null != progressDialog && progressDialog.isShowing()) {
                            progressDialog.dismiss();
                        }
                    } catch (Exception e) {
                        Log.e("CopyPasteUtil", "CopyPasteUtil copyFile error:" + e.getMessage());
                    }
                }
            };
            copyFileThread = new Thread(run);
            copyFileThread.start();
            return true;
        }

        /**
         * 复制文件夹
         */
        public void copyDirectiory(Context context, String sourceDir, String targetDir, CopyPasteListener call) {
            if (context != null) {
                if (isNeesDefaulProgressDialog && null == progressDialog) {
                    progressDialog = new CommonProgressDialog(context);
                }
                if (null != progressDialog) {
                    progressDialog.setMessage("文件迁移正在进行中...");
                    progressDialog.setOnCancelListener(new DialogInterface.OnCancelListener() {
                        @Override
                        public void onCancel(DialogInterface dialog) {
                            // TODO Auto-generated method stub
                            copyFileThread.interrupt();
                            copyFileThread = null;
                            try {
                                if (null != fileInputStream) fileInputStream.close();
                                if (null != fileOutputStream) fileOutputStream.close();
                                if (null != inbuff) inbuff.close();
                                if (null != outbuff) outbuff.close();
                                if (null != fileChannelOutput) fileChannelOutput.close();
                                if (null != fileChannelInput) fileChannelInput.close();
                                if (null != call) {
                                    call.onCancle();
                                }
                            } catch (IOException e) {
                                Log.e("CopyPasteUtil", "CopyPasteUtil copyDirectiory error:" + e.getMessage());
                            }
                        }
                    });
                    progressDialog.show();
                }
            }
            Runnable run = new Runnable() {
                @Override
                public void run() {
                    copyDirMethod(0, sourceDir, targetDir, call);
                }
            };
            copyFileThread = new Thread(run);
            copyFileThread.start();
        }

        /**
         * 复制文件夹,真正的执行动作
         */
        private void copyDirMethod(int dirLevel, String sourceDir, String targetDir, CopyPasteListener call) {
            (new File(targetDir)).mkdirs();
            dirLevel++; //进入下一层 层级+1
            File[] file = (new File(sourceDir)).listFiles();// 获取源文件夹当下的文件或目录
            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());// 目标文件
                    copyFileMethod(sourceFile, targetFile, call);
                } else if (file[i].isDirectory()) {
                    String dir1 = sourceDir + "/" + file[i].getName();
                    String dir2 = targetDir + "/" + file[i].getName();
                    copyDirMethod(dirLevel, dir1, dir2, call);
                }
            }
            dirLevel--;//该层已经循环遍历完毕,返回上一层 层级-1
            //层级小于等于0,说明已经计算完毕,递归回到最顶层
            if (dirLevel <= 0 && null != call) {
                if (null != progressDialog) {
                    progressDialog.setMessage("文件迁移完成");
                    call.onSuccess();
                }
            }
        }

        /**
         * 复制单个文件,用于上面的复制文件夹方法
         *
         * @param sourcefile 源文件路径
         * @param targetFile 目标路径
         */
        private synchronized void copyFileMethod(final File sourcefile, final File targetFile, CopyPasteListener call) {
            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;
                if (null != progressDialog) {
                    progressDialog.setMax(fileVolume * 1024 * 1024);
                }
                while (fileChannelInput.read(buffer) != -1) {
                    buffer.flip();
                    transferSize += fileChannelOutput.write(buffer);
                    progress = (int) (transferSize + hasReadSize) / (1024 * 1024);
                    if (progress > tempP) {
                        tempP = progress;
                        if (null != progressDialog) {
                            progressDialog.setProgress(progress * 1024 * 1024);
                        }
                        if (null != call) { //此处重点在于传递大小
                            call.onProgress(dirFileCount, hasReadCount, dirSize, transferSize + hasReadSize);
                        }
                    }
                    buffer.clear();
                }
                hasReadCount++;
                hasReadSize += sourcefile.length();
                if (null != call) { //此处重点在于传递文件个数
                    call.onProgress(dirFileCount, hasReadCount, dirSize, hasReadSize);
                }
                outbuff.flush();
                fileOutputStream.flush();
                inbuff.close();
                outbuff.close();
                fileOutputStream.close();
                fileInputStream.close();
                fileChannelOutput.close();
                fileChannelInput.close();
//                if (hasReadSize >= dirSize && null != call) {
//                    call.onSuccess();
//                }
            } catch (FileNotFoundException e) {
                if (null != call) {
                    call.onFail(e.getMessage());
                }
            } catch (IOException e) {
                if (null != call) {
                    call.onFail(e.getMessage());
                }
            }
        }


        /**
         * 删除整个文件夹
         * FileCopeTool.deleteFolder(URL.HtmlPath + "/" + identify);
         *
         * @param path 路径,无需文件名
         */
        public void deleteFolder(String path) {
            File f = new File(path);
            if (f.exists()) {
                // 在判断它是不是一个目录
                if (f.isDirectory()) {
                    // 列出该文件夹下的所有内容
                    String[] fileList = f.list();
                    if (fileList == null) {
                        return;
                    }
                    for (int i = 0; i < fileList.length; i++) {
                        // 对每个文件名进行判断
                        // 如果是文件夹 那么就循环deleteFolder
                        // 如果不是,直接删除文件
                        String name = path + File.separator + fileList[i];
                        File ff = new File(name);
                        if (ff.isDirectory()) {
                            deleteFolder(name);
                        } else {
                            ff.delete();
                        }
                    }
                    // 最后删除文件夹
                    f.delete();

                } else {
                    // 该文件夹不是一个目录
                }
            } else {
                //不存在该文件夹
            }
        }

        /**
         * 获取文件夹大小
         *
         * @param dirLevel 计算文件夹的层级,用于判断递归遍历是否完成,初始调用应该设置为0
         * @param file
         * @param call     完成回调
         */
        private void getDirSize(int dirLevel, File file, InitListener call) {
            if (file.isFile()) {
                // 如果是文件,获取文件大小累加
                dirSize += file.length();
                dirFileCount++;
            } else if (file.isDirectory()) {
                dirLevel++; //进入下一层 层级+1
                File[] f1 = file.listFiles();
                for (int i = 0; i < f1.length; i++) {
                    // 调用递归遍历f1数组中的每一个对象
                    getDirSize(dirLevel, f1[i], call);
                }
                dirLevel--;//该层已经循环遍历完毕,返回上一层 层级-1
            }
            //层级小于等于0,说明已经计算完毕,递归回到最顶层
            if (dirLevel <= 0 && null != call) {
                call.onNext(dirFileCount, dirSize, this);
            }
        }

        /**
         * 初始化全局变量
         */
        private void initDirSize() {
            dirSize = 0;
            hasReadSize = 0;
            dirFileCount = 0;
            hasReadCount = 0;
        }

        /**
         * 复制文件夹前,初始化四个个变量
         */
        public void initValueAndGetDirSize(Context context, File file, InitListener call) {
            if (isNeesDefaulProgressDialog) {
                progressDialog = new CommonProgressDialog(context);
                progressDialog.setMessage("准备...");
                progressDialog.show();
            }
            new Thread(new Runnable() {
                @Override
                public void run() {
                    initDirSize();
                    getDirSize(0, file, call);
                }
            }).start();
        }

        /**
         * 默认为true
         */
        public CopyPasteImp setIsNeesDefaulProgressDialog(boolean isNeed) {
            isNeesDefaulProgressDialog = isNeed;
            return this;
        }
    }
}

进度条工具类:CommonProgressDialog.java


import android.app.AlertDialog;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.csair.mmpmobile.R;

import java.text.NumberFormat;

public class CommonProgressDialog extends AlertDialog {
    private ProgressBar mProgress;
    private TextView mProgressNumber;
    private TextView mProgressPercent;
    private TextView mProgressMessage;
    private Handler mViewUpdateHandler;
    private Runnable mViewUpdateRunnable;
    private int mMax;
    private CharSequence mMessage;
    private boolean mHasStarted;
    private int mProgressVal;
    private String TAG = "CommonProgressDialog";
    private String mProgressNumberFormat;
    private NumberFormat mProgressPercentFormat;

    public CommonProgressDialog(Context context) {
        super(context);
// TODO Auto-generated constructor stub
        initFormats();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        setContentView(R.layout.common_progress_dialog);
        mProgress = (ProgressBar) findViewById(R.id.progress);
        mProgressNumber = (TextView) findViewById(R.id.progress_number);
        mProgressPercent = (TextView) findViewById(R.id.progress_percent);
        mProgressMessage = (TextView) findViewById(R.id.progress_message);
        mViewUpdateRunnable = new Runnable() {
            @Override
            public void run() {
                int progress = mProgress.getProgress();
                int max = mProgress.getMax();
                double dProgress = (double) progress / (double) (1024 * 1024);
                double dMax = (double) max / (double) (1024 * 1024);
                if (mProgressNumberFormat != null) {
                    String format = mProgressNumberFormat;
                    mProgressNumber.setText(String.format(format, dProgress, dMax));
                } else {
                    mProgressNumber.setText("");
                }
                if (mProgressPercentFormat != null) {
                    double percent = (double) progress / (double) max;
                    SpannableString tmp = new SpannableString(mProgressPercentFormat.format(percent));
                    tmp.setSpan(new StyleSpan(android.graphics.Typeface.BOLD),
                            0, tmp.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
                    mProgressPercent.setText(tmp);
                } else {
                    mProgressPercent.setText("");
                }
            }
        };
        mViewUpdateHandler = new ViewUpdateHandler();
        onProgressChanged();
        if (mMessage != null) {
            setMessage(mMessage);
        }
        if (mMax > 0) {
            setMax(mMax);
        }
        if (mProgressVal > 0) {
            setProgress(mProgressVal);
        }
    }

    private void initFormats() {
        mProgressNumberFormat = "%1.2fM/%2.2fM";
        mProgressPercentFormat = NumberFormat.getPercentInstance();
        mProgressPercentFormat.setMaximumFractionDigits(0);
    }

    private void onProgressChanged() {
        mViewUpdateHandler.post(mViewUpdateRunnable);
    }

    public void setProgressStyle(int style) {
//mProgressStyle = style;
    }

    public int getMax() {
        if (mProgress != null) {
            return mProgress.getMax();
        }
        return mMax;
    }

    public void setMax(int max) {
        if (mProgress != null) {
            mProgress.setMax(max);
            onProgressChanged();
        } else {
            mMax = max;
        }
    }

    public void setIndeterminate(boolean indeterminate) {
        if (mProgress != null) {
            mProgress.setIndeterminate(indeterminate);
        }
// else {
// mIndeterminate = indeterminate;
// }
    }

    public void setProgress(int value) {
        if (mHasStarted) {
            mProgress.setProgress(value);
            onProgressChanged();
        } else {
            mProgressVal = value;
        }
    }

    @Override
    public void setMessage(CharSequence message) {
// TODO Auto-generated method stub
//super.setMessage(message);
        if (mProgressMessage != null) {
            mViewUpdateHandler.post(new Runnable() {
                @Override
                public void run() {
                    mProgressMessage.setText(message);
                }
            });
        } else {
            mMessage = message;
        }

    }

    @Override
    protected void onStart() {
// TODO Auto-generated method stub
        super.onStart();
        mHasStarted = true;
    }

    @Override
    protected void onStop() {
// TODO Auto-generated method stub
        super.onStop();
        mHasStarted = false;
    }

    static class ViewUpdateHandler extends Handler {
    }
}

进度条布局文件:common_progress_dialog.xml

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@color/gray"
        android:orientation="vertical">

        <TextView
            android:id="@+id/progress_message"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="40dp"
            android:text="Message"
            android:textColor="#ffffff"
            android:textSize="16dp" />

        <ProgressBar
            android:id="@+id/progress"
            style="?android:attr/progressBarStyleHorizontal"
            android:layout_width="match_parent"
            android:layout_height="15dp"
            android:layout_marginLeft="20dp"
            android:layout_marginTop="40dp"
            android:layout_marginRight="20dp"
            android:progressDrawable="@drawable/common_progressdialog_progressbar_background" />

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginTop="10dp"
            android:layout_marginBottom="10dp"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/progress_percent"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="30dp"
                android:layout_weight="1"
                android:gravity="left"
                android:text="10%"
                android:textColor="#ffffff"
                android:textSize="12sp" />

            <TextView
                android:id="@+id/progress_number"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginRight="30dp"
                android:layout_weight="1"
                android:gravity="right"
                android:text="10M/100M"
                android:textColor="#ffffff"
                android:textSize="12sp" />
        </LinearLayout>
    </LinearLayout>
</FrameLayout>

进度条背景资源:

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@android:id/background">
        <shape android:shape="rectangle">
            <corners android:radius="4dp"/>
            <gradient android:startColor="#EFF3F7"
                android:endColor="#EFF3F7"/>
        </shape>
    </item>
    <item android:id="@android:id/progress">
        <scale android:scaleWidth="100%">
            <shape android:shape="rectangle">
                <corners android:radius="4dp"/>
                <gradient android:angle="45"
                    android:startColor="#42D673"
                    android:endColor="#42D673"/>
            </shape>
        </scale>
    </item>

</layer-list>

主要使用方式

1,启用默认进度条,初始化或得总体文件大小和总个数,进入下载流程

CopyPasteUtil.build()
                .setIsNeesDefaulProgressDialog(true)
                .initValueAndGetDirSize(mActivity, new File(MMPUrl.SD_PATH), new CopyPasteUtil.InitListener() {
                    @Override
                    public void onNext(long dirFileCount, long dirSize, CopyPasteUtil.CopyPasteImp imp) {
                        int fileVolume = (int) (dirSize / (1024 * 1024));
                        UiThread.run(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(mActivity, "onNext->dirFileCount:" + dirFileCount + "==onNext->dirSize:" + fileVolume + "M", Toast.LENGTH_LONG).show();
                            }
                        });

                        imp.copyDirectiory(mActivity, MMPUrl.SD_PATH, MMPUrl.SD_PATH_COPY, new CopyPasteUtil.CopyPasteListener() {
                            @Override
                            public void onSuccess() {
                                UiThread.run(new Runnable() {
                                    @Override
                                    public void run() {
                                        Toast.makeText(mActivity, "onSuccess:" + i++, Toast.LENGTH_LONG).show();
                                    }
                                });
                            }

                            @Override
                            public void onProgress(long dirFileCount, long hasReadCount, long dirSize, long hasReadSize) {
                                UiThread.run(new Runnable() {
                                    @Override
                                    public void run() {
                                        mVersion.setText(dirFileCount + "-" + hasReadCount + "==" + dirSize + "-" + hasReadSize);
                                    }
                                });
                            }

                            @Override
                            public void onFail(String errorMsg) {
                                UiThread.run(new Runnable() {
                                    @Override
                                    public void run() {
                                        Toast.makeText(mActivity, "onFail", Toast.LENGTH_LONG).show();
                                    }
                                });
                            }

                            @Override
                            public void onCancle() {
                                UiThread.run(new Runnable() {
                                    @Override
                                    public void run() {
                                        Toast.makeText(mActivity, "onCancle", Toast.LENGTH_LONG).show();
                                    }
                                });
                            }
                        });
                    }
                });

2,关闭默认进度条,直接进入下载流程,但是没有总体文件大小和总个数

CopyPasteUtil.build()
                .setIsNeesDefaulProgressDialog(false)
                .copyDirectiory(mActivity, MMPUrl.SD_PATH, MMPUrl.SD_PATH_COPY, new CopyPasteUtil.CopyPasteListener() {
                    @Override
                    public void onSuccess() {
                        UiThread.run(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(mActivity, "onSuccess:" + i++, Toast.LENGTH_LONG).show();
                            }
                        });
                    }

                    @Override
                    public void onProgress(long dirFileCount, long hasReadCount, long dirSize, long hasReadSize) {
                        UiThread.run(new Runnable() {
                            @Override
                            public void run() {
                                mVersion.setText(dirFileCount + "-" + hasReadCount + "==" + dirSize + "-" + hasReadSize);
                            }
                        });
                    }

                    @Override
                    public void onFail(String errorMsg) {
                        UiThread.run(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(mActivity, "onFail", Toast.LENGTH_LONG).show();
                            }
                        });
                    }

                    @Override
                    public void onCancle() {
                        UiThread.run(new Runnable() {
                            @Override
                            public void run() {
                                Toast.makeText(mActivity, "onCancle", Toast.LENGTH_LONG).show();
                            }
                        });
                    }
                });

感谢:
android复制文件、文件夹,使用FileChannel带进度条
实例详解Android自定义ProgressDialog进度条对话框的实现
给progressbar设置drawable和自定义progressbar
FileCopeUtil 文件操作

推荐阅读更多精彩内容