天天看点

Android常用的工具类汇总(方便日后使用)

平时进行android开发时候,可能需要使用各种各样的工具类,每次总要去上网搜索,费时费力,因此特此将其整理发出来,小伙伴可自行收藏.

1.LogUtils工具类

/**
 * 控制Log开关的工具类
 */
public class LogUtils {
    private LogUtils() {}

    // 如果想屏蔽所有log,可以设置为0
    public static final int LOG_LEVEL = 6;

    public static final int VERBOSE = 5;
    public static final int DEBUG = 4;
    public static final int INFO = 3;
    public static final int WARN = 2;
    public static final int ERROR = 1;

    public static void v(String tag, String msg) {
        if (LOG_LEVEL > VERBOSE) {
            Log.v(tag, msg);
        }
    }

    public static void d(String tag, String msg) {
        if (LOG_LEVEL > DEBUG) {
            Log.d(tag, msg);
        }
    }

    public static void i(String tag, String msg) {
        if (LOG_LEVEL > INFO) {
            Log.i(tag, msg);
        }
    }

    public static void w(String tag, String msg) {
        if (LOG_LEVEL > WARN) {
            Log.i(tag, msg);
        }
    }

    public static void e(String tag, String msg) {
        if (LOG_LEVEL > ERROR) {
            Log.e(tag, msg);
        }
    }

    public static void v(String msg) {
        if (LOG_LEVEL > VERBOSE) {
            Log.v(getCallerName(), msg);
        }
    }

    public static void d(String msg) {
        if (LOG_LEVEL > DEBUG) {
            Log.d(getCallerName(), msg);
        }
    }

    public static void i(String msg) {
        if (LOG_LEVEL > INFO) {
            Log.i(getCallerName(), msg);
        }
    }

    public static void w(String msg) {
        if (LOG_LEVEL > WARN) {
            Log.w(getCallerName(), msg);
        }
    }

    public static void e(String msg) {
        if (LOG_LEVEL > ERROR) {
            Log.e(getCallerName(), msg);
        }
    }

    /**
     * 获取调用者的类名
     */
    public static String getCallerName() {
        StackTraceElement caller = Thread.currentThread().getStackTrace()[4];
        String className = caller.getClassName();// 带有包名信息
        className = className.substring(className.lastIndexOf(".") + 1);
        return className;
    }

    /**
     *  描述:日志内容多的时候(超过4k)需要打印全时.
     */
    public static void showLog(String str) {
        str = str.trim();
        int index = 0;
        int maxLength = 4000;
        String finalString;
        while (index < str.length()) {
            if (str.length() <= index + maxLength) {
                finalString = str.substring(index);
            } else {
                finalString = str.substring(index, maxLength);
            }
            index += maxLength;
            i(getCallerName(), finalString.trim());
        }
    }
}
           

进行android 开发的小伙伴都用过Android Studio,比如有时候我打印log输入一个2048长度的short[]数组时候,你会发现,数组的后半部分没有输出,这是为什么呢?因为一个short类型占2个字节,所以2048个short型的数据就占用4k(超出了Log所规定的单个打印上限),自动被忽略了,当然这种情况毕竟比较少见,大家了解一下.

2.通用的自定义toast工具类

说它通用是因为满足以下需求:

(1).能自定义toast的弹出样式(包括设置toast弹出时的标题和背景等)

(2).既能在主线程中又能在子线程中使用

直接点击链接查看:Android通用自定义toast工具类(可在主线程和子线程中使用)

3.单位转换的辅助类DensityUtil

/**
 * 常用单位转换的辅助类
 */
public class DensityUtil {

    private DensityUtil() {
        throw new UnsupportedOperationException("cannot be instantiated");
    }

    /**
     * dp转px
     */
    public static int dp2px(Context context, float dpVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                dpVal, context.getResources().getDisplayMetrics());
    }

    /**
     * sp转px
     */
    public static int sp2px(Context context, float spVal) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
                spVal, context.getResources().getDisplayMetrics());
    }

    /**
     * px转dp
     */
    public static float px2dp(Context context, float pxVal) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (pxVal / scale);
    }

    /**
     * px转sp
     */
    public static float px2sp(Context context, float pxVal) {
        return (pxVal / context.getResources().getDisplayMetrics().scaledDensity);
    }
}
           

2.史上最全FileUtil工具类

/**
 * 1、文件的新建、删除;
 * 2、文件的复制;
 * 3、获取文件扩展名;
 * 4、文件的重命名;
 * 5、获取某个文件的详细信息;
 * 6、计算某个文件的大小;
 * 7、文件大小的格式化;
 * 8、获取某个目录下的文件列表;
 * 9、目录的新建、删除; 11、目录的复制;
 * 10、计算某个目录包含的文件数量;
 * 11、计算某个目录包含的文件大小;
 */
public class FileUtil {
    private static final String TAG = "FileUtil";
    private static final String[][] MIME_MapTable =
            {
                    // {后缀名, MIME类型}
                    {".3gp", "video/3gpp"}, {".apk", "application/vnd.android.package-archive"},
                    {".asf", "video/x-ms-asf"}, {".avi", "video/x-msvideo"},
                    {".bin", "application/octet-stream"}, {".bmp", "image/bmp"}, {".c", "text/plain"},
                    {".class", "application/octet-stream"}, {".conf", "text/plain"}, {".cpp", "text/plain"},
                    {".doc", "application/msword"},
                    {".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"},
                    {".xls", "application/vnd.ms-excel"},
                    {".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"},
                    {".exe", "application/octet-stream"}, {".gif", "image/gif"},
                    {".gtar", "application/x-gtar"}, {".gz", "application/x-gzip"}, {".h", "text/plain"},
                    {".htm", "text/html"}, {".html", "text/html"}, {".jar", "application/java-archive"},
                    {".java", "text/plain"}, {".jpeg", "image/jpeg"}, {".jpg", "image/jpeg"},
                    {".js", "application/x-javascript"}, {".log", "text/plain"}, {".m3u", "audio/x-mpegurl"},
                    {".m4a", "audio/mp4a-latm"}, {".m4b", "audio/mp4a-latm"}, {".m4p", "audio/mp4a-latm"},
                    {".m4u", "video/vnd.mpegurl"}, {".m4v", "video/x-m4v"}, {".mov", "video/quicktime"},
                    {".mp2", "audio/x-mpeg"}, {".mp3", "audio/x-mpeg"}, {".mp4", "video/mp4"},
                    {".mpc", "application/vnd.mpohun.certificate"}, {".mpe", "video/mpeg"},
                    {".mpeg", "video/mpeg"}, {".mpg", "video/mpeg"}, {".mpg4", "video/mp4"},
                    {".mpga", "audio/mpeg"}, {".msg", "application/vnd.ms-outlook"}, {".ogg", "audio/ogg"},
                    {".pdf", "application/pdf"}, {".png", "image/png"},
                    {".pps", "application/vnd.ms-powerpoint"}, {".ppt", "application/vnd.ms-powerpoint"},
                    {".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"},
                    {".prop", "text/plain"}, {".rc", "text/plain"}, {".rmvb", "audio/x-pn-realaudio"},
                    {".rtf", "application/rtf"}, {".sh", "text/plain"}, {".tar", "application/x-tar"},
                    {".tgz", "application/x-compressed"}, {".txt", "text/plain"}, {".wav", "audio/x-wav"},
                    {".wma", "audio/x-ms-wma"}, {".wmv", "audio/x-ms-wmv"},
                    {".wps", "application/vnd.ms-works"}, {".xml", "text/plain"},
                    {".z", "application/x-compress"}, {".zip", "application/x-zip-compressed"}, {"", "*/*"}
            };

    /**
     * 根据文件后缀名获得对应的MIME类型
     *
     * @param filePath 文件路径
     */
    public static String getMIMEType(String filePath) {
        File file = new File(filePath);
        if (!file.exists()) {
            Log.e(TAG, "getMIMEType: 文件不存在");
            return "";
        }

        if (!file.isFile()) {
            Log.e(TAG, "getMIMEType: 当前文件类型是目录");
            return "";
        }
        String type = "*/*";
        String fileName = file.getName();
        int dotIndex = fileName.lastIndexOf("."); // 获取后缀名前的分隔符"."在fileName中的位置
        if (dotIndex < 0) {
            return type;
        }

        String end = fileName.substring(dotIndex, fileName.length()).toLowerCase(Locale.getDefault()); // 获取文件的后缀名
        if (end.length() == 0) {
            return type;
        }

        // 在MIME和文件类型的匹配表中找到对应的MIME类型
        for (String[] aMIME_MapTable : MIME_MapTable) {
            if (end.equals(aMIME_MapTable[0])) {
                type = aMIME_MapTable[1];
            }
        }
        return type;
    }

    /**
     * 获取设备的sd根路径
     */
    public static String getSDPath() {
        File sdDir = null;
        String sdPath;
        boolean sdCardExist = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED);//判断sd卡是否存在
        if (sdCardExist) {
            sdDir = Environment.getExternalStorageDirectory();
        }
        assert sdDir != null;
        sdPath = sdDir.toString();
        Log.w(TAG, "getSDPath:" + sdPath);
        return sdPath;
    }

    /**
     * 根据文件名获得文件的扩展名
     *
     * @param fileName 文件名
     * @return 文件扩展名(不带点)
     */
    public static String getFileSuffix(String fileName) {
        Log.w(TAG, "getFileSuffix: fileName::" + fileName);
        int index = fileName.lastIndexOf(".");
        return fileName.substring(index + 1, fileName.length());
    }

    //**************************************************创建文件操作*****************************************************************

    /**
     * 创建文件
     *
     * @param dirPath  文件所在目录的目录名,如/java/test/1.txt,要在当前目录下创建一个文件名为1.txt的文件
     *                 则path为/java/test,fileName为1.txt(也可以封装成直接传递文件的绝对路径)
     * @param fileName 文件名
     * @return 文件新建成功则返回true
     */
    public static boolean createFile(String dirPath, String fileName) {
        String filePath = dirPath + File.separator + fileName;
        Log.w(TAG, "createFile: filePath::" + filePath + "  File.separator ::" + File.separator);
        File file = new File(filePath);
        File fileParent = file.getParentFile();
        if (!fileParent.exists()) {
            fileParent.mkdirs();
            Log.w(TAG, "createFile: 文件所在目录不存在,创建目录成功");
        }

        if (file.exists()) {
            Log.e(TAG, "新建文件失败:file.exist()=" + file.exists());
            return false;
        } else {
            try {
                return file.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return false;
    }

    /**
     * 新建目录
     *
     * @param dir 目录的绝对路径
     * @return 创建成功则返回true
     */
    public static boolean createFolder(String dir) {
        File file = new File(dir);
        return file.mkdir();
    }

    //*******************************************删除文件操作************************************************************************

    /**
     * 删除单个文件
     *
     * @param filePath 要删除的文件路径
     * @return 文件删除成功则返回true
     */
    public static boolean deleteFile(String filePath) {

        File file = new File(filePath);
        if (file.exists()) {
            boolean isDeleted = file.delete();
            Log.w(TAG, file.getName() + "删除结果:" + isDeleted);
            return isDeleted;
        } else {
            Log.w(TAG, "文件删除失败:文件不存在!");
            return false;
        }
    }

    /**
     * 删除单个文件
     *
     * @param file 要删除的文件对象
     * @return 文件删除成功则返回true
     */
    private static boolean deleteFile(File file) {
        if (file.exists()) {
            boolean isDeleted = file.delete();
            Log.w(TAG, file.getName() + "删除结果:" + isDeleted);
            return isDeleted;
        } else {
            Log.w(TAG, "文件删除失败:文件不存在!");
            return false;
        }
    }

    /**
     * 删除文件夹及其包含的所有文件(会自身循环调用)
     *
     * @param file 要删除的文件对象
     * @return 文件删除成功则返回true
     */
    public static boolean deleteFolder(File file) {
        boolean flag;
        File files[] = file.listFiles();
        if (files != null) // 目录下存在文件列表
        {
            for (File f : files) {
                if (f.isFile()) {
                    // 删除子文件
                    flag = deleteFile(f);
                    if (!flag) {
                        return false;
                    }
                } else {
                    // 删除子目录
                    flag = deleteFolder(f);
                    if (!flag) {
                        return false;
                    }
                }
            }
        }
        //能成功走到这,说明当前目录下的所有子文件和子目录都已经删除完毕
        flag = file.delete();//将此空目录也进行删除
        return flag;
    }

    //*************************************复制/重命名操作********************************************

    /**
     * 复制文件
     *
     * @param srcPath 源文件绝对路径
     * @param destDir 目标文件所在目录
     * @return boolean 复制成功则返回true
     */
    public static boolean copyFile(String srcPath, String destDir) {
        boolean flag = false;
        File srcFile = new File(srcPath); // 源文件
        if (!srcFile.exists()) {
            // 源文件不存在
            Log.w(TAG, "copyFile: 源文件不存在");
            return false;
        }
        // 获取待复制文件的文件名
        String fileName = srcPath.substring(srcPath.lastIndexOf(File.separator));
        String destPath = destDir + fileName;
        if (destPath.equals(srcPath)) {
            // 源文件路径和目标文件路径重复
            Log.w(TAG, "copyFile: 源文件路径和目标文件路径重复");
            return false;
        }

        File destFile = new File(destPath); // 目标文件
        if (destFile.exists() && destFile.isFile()) {
            // 该路径下已经有一个同名文件
            Log.w(TAG, "copyFile: 目标目录下已有同名文件!");
            return false;
        }

        File destFileDir = new File(destDir);
        destFileDir.mkdirs();
        try {
            FileInputStream fis = new FileInputStream(srcPath);
            FileOutputStream fos = new FileOutputStream(destFile);
            byte[] buf = new byte[1024];
            int c;
            while ((c = fis.read(buf)) != -1) {
                fos.write(buf, 0, c);
            }
            fis.close();
            fos.close();
            flag = true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        if (flag) {
            Log.w(TAG, "copyFile: 复制文件成功!");
        }
        return flag;
    }

    /**
     * 复制目录
     *
     * @param srcDir  源文件夹目录
     * @param destDir 目标文件夹所在目录
     * @return 复制成功则返回true
     */
    public static boolean copyFolder(String srcDir, String destDir) {
        Log.w(TAG, "copyFolder: 复制文件夹开始!");
        boolean flag = false;

        File srcFile = new File(srcDir);
        if (!srcFile.exists()) {
            // 源文件夹不存在
            Log.w(TAG, "copyFolder: 源文件夹不存在");
            return false;
        }
        String dirName = getDirName(srcDir); // 获得待复制的文件夹的名字,比如待复制的文件夹为"E://dir"则获取的名字为"dir"

        String destPath = destDir ; // 如果想连同源目录名拷贝则String destPath = destDir + File.separator + dirName
        // Util.toast("目标文件夹的完整路径为:" + destPath);
        if (destPath.equals(srcDir)) {
            Log.w(TAG, "copyFolder: 目标文件夹与源文件夹重复");
            return false;
        }
        File destDirFile = new File(destPath);
        if (!destDirFile.exists()) {
            destDirFile.mkdirs(); // 如果指定目录不存在则生成目录
        }

        File[] files = srcFile.listFiles(); // 获取源文件夹下的子文件和子文件夹
        Log.i(TAG, "copyFolder: -----files,length::"+files.length+"      files::"+files);
        if (files.length == 0) {
            // 如果源文件夹为空目录则直接设置flag为true,这一步非常隐蔽,debug了很久
            flag = true;
        } else {
            for (File temp : files) {
                if (temp.isFile()) {
                    // 文件
                    flag = copyFile(temp.getAbsolutePath(), destPath);
                } else if (temp.isDirectory()) {
                    // 文件夹
                    flag = copyFolder(temp.getAbsolutePath(), destPath);
                }
                if (!flag) {
                    break;
                }
            }
        }
        if (flag) {
            Log.w(TAG, "copyFolder: 复制文件夹成功!");
        }
        return flag;
    }

    /**
     * 重命名文件
     *
     * @param oldPath 旧文件的绝对路径
     * @param newPath 新文件的绝对路径
     * @return 文件重命名成功则返回true
     */
    public static boolean renameTo(String oldPath, String newPath) {
        if (oldPath.equals(newPath)) {
            Log.w(TAG, "文件重命名失败:新旧文件名绝对路径相同!");
            return false;
        }
        File oldFile = new File(oldPath);
        File newFile = new File(newPath);

        boolean isSuccess = oldFile.renameTo(newFile);
        Log.w(TAG, "文件重命名是否成功:" + isSuccess);
        return isSuccess;
    }

    /**
     * 重命名文件
     *
     * @param oldFile 旧文件对象,File类型
     * @param newName 新文件的文件名,String类型
     * @return 重命名成功则返回true
     */
    public static boolean renameTo(File oldFile, String newName) {
        File newFile = new File(oldFile.getParentFile() + File.separator + newName);
        boolean flag = oldFile.renameTo(newFile);
        Log.w(TAG, "文件重命名是否成功:" + flag);
        return flag;
    }

    //*********************************计算文件大小/格式化**************************************************************


    /**
     * 计算某个文件的大小
     *
     * @param path 文件的绝对路径
     * @return
     */
    public static long getFileSize(String path) {
        File file = new File(path);
        if (file.exists()&&file.isFile()) {
            return file.length();
        }else{
            Log.e(TAG, "getFileSize: error!" );
            return -1;
        }
    }

    /**
     * 计算某个文件的大小
     *
     * @param file 文件对象
     * @return 文件大小,如果file不是文件,则返回-1
     */
    private static long getFileSize(File file) {
        if (file.isFile()) {
            return file.length();
        } else {
            return -1;
        }
    }


    /**
     * 计算某个目录的大小
     *
     * @param directory 目录生成的file对象
     * @return
     */
    public static long getFolderSize(File directory) {
        File[] files = directory.listFiles();
        if (files != null) {
            long size = 0;
            for (File f : files) {
                if (f.isFile()) {
                    // 获得子文件的大小
                    size = size + getFileSize(f);
                } else {
                    // 获得子目录的大小
                    size = size + getFolderSize(f);
                }
            }
            return size;
        }
        return -1;
    }

    /**
     * 文件大小的格式化
     *
     * @param size 文件大小,单位为byte
     * @return 文件大小格式化后的文本
     */
    public static String formatSize(long size) {
        DecimalFormat df = new DecimalFormat("####.00");
        if (size < 1024) // 小于1KB
        {
            return size + "Byte";
        } else if (size < 1024 * 1024) // 小于1MB
        {
            float kSize = size / 1024f;
            return df.format(kSize) + "KB";
        } else if (size < 1024 * 1024 * 1024) // 小于1GB
        {
            float mSize = size / 1024f / 1024f;
            return df.format(mSize) + "MB";
        } else if (size < 1024L * 1024L * 1024L * 1024L) // 小于1TB
        {
            float gSize = size / 1024f / 1024f / 1024f;
            return df.format(gSize) + "GB";
        } else {
            return "size: error";
        }
    }

    /**
     * 格式化文件最后修改时间字符串(针对以时间戳结尾的文件)
     *
     * @param time
     * @return
     */
    public static String formatTime(long time) {
        Date date = new Date(time);
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd,HH:mm:ss", Locale.getDefault());
        String formatedTime = sdf.format(date);
        return formatedTime;
    }

    //*********************************************获取文件列表*******************************************
    /**
     * 获取某个目录下的文件列表(如果存在子目录,则子目录中的文件是否计入)
     *
     * @param dir 文件目录
     * @return 文件列表File[] files
     */
    public static File[] getFileList(String dir) {
        File file = new File(dir);
        if (file.isDirectory()) {
            File[] files = file.listFiles();
            if (files != null) {
                return files;
            } else {
                return null;
            }
        } else {
            return null;
        }
    }


    //********************************************文件操作中封装的一些小方法*********************************************
    /**
     * 获取待复制文件夹的文件夹名
     *
     * @param dir 待复制的目录名称
     * @return String
     */
    private static String getDirName(String dir) {
        if (dir.endsWith(File.separator)) {
            // 如果文件夹路径以"//"结尾,则先去除末尾的"//"
            dir = dir.substring(0, dir.lastIndexOf(File.separator));
        }
        return dir.substring(dir.lastIndexOf(File.separator) + 1);
    }

    /**
     * 计算某个目录包含的文件数量
     *
     * @param directory
     * @return
     */
    public static int getFileCount(File directory) {
        File[] files = directory.listFiles();
        int count = files.length;
        return count;
    }

}
           

关于FileUtil工具类我多说点吧。

上述贴出的FileUtil工具类,所有的方法都经过了测试,有些地方还做了优化,鉴于代码不多,我下面将测试代码和资源文件贴出来:

@RuntimePermissions
public class SecondActivity extends AppCompatActivity implements View.OnClickListener {
    private static final String TAG = "SecondActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.second_activity);

        findViewById(R.id.get_sd_path).setOnClickListener(this);
        findViewById(R.id.get_file_suffix).setOnClickListener(this);
        findViewById(R.id.get_file_mime).setOnClickListener(this);
        findViewById(R.id.create_file).setOnClickListener(this);
        findViewById(R.id.create_dir).setOnClickListener(this);
        findViewById(R.id.delete_specific_file).setOnClickListener(this);
        findViewById(R.id.delete_all_for_dir).setOnClickListener(this);
        findViewById(R.id.copy_file).setOnClickListener(this);
        findViewById(R.id.copy_dir).setOnClickListener(this);
        findViewById(R.id.rename_file).setOnClickListener(this);
        findViewById(R.id.format_file_size).setOnClickListener(this);
        findViewById(R.id.format_dir_size).setOnClickListener(this);
        findViewById(R.id.get_file_list).setOnClickListener(this);

    }

    @Override
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.get_sd_path:
                //eg."getSdPath:/storage/emulated/0"
                //获取sd路径
                SecondActivityPermissionsDispatcher.getSdPathWithCheck(SecondActivity.this);
                break;
            case R.id.get_file_suffix:
                //读取文件后缀
                String fileName = "log.txt";
                String fileSuffix = FileUtil.getFileSuffix(fileName);
                Log.i(TAG, "onClick: fileSuffix::" + fileSuffix);
                break;
            case R.id.get_file_mime:
                //获取文件MIME类型
                String filePath1 = FileUtil.getSDPath() + "/00/" + "1.doc";
                String mimeType = FileUtil.getMIMEType(filePath1);
                Log.i(TAG, "onClick: mimeType::" + mimeType);
                break;
            case R.id.create_file:
                //创建文件
                String dirPath2 = FileUtil.getSDPath() + "/00";
                String fileName2 = "2.doc";
                boolean isCreated = FileUtil.createFile(dirPath2, fileName2);
                Log.i(TAG, "onClick: file isCreated::" + isCreated);
                break;
            case R.id.create_dir:
                //创建目录
                String dirPath3 = FileUtil.getSDPath()+"/00";
                boolean isCreated3 = FileUtil.createFolder(dirPath3);
                Log.i(TAG, "onClick: dir isCreated::" + isCreated3);
                break;
            case R.id.delete_specific_file:
                //删除指定的某个文件
                String filePath4 = FileUtil.getSDPath() + "/00/4.doc";
                boolean isDeleted = FileUtil.deleteFile(filePath4);
                Log.i(TAG, "onClick: file isDeleted::" + isDeleted);
                break;
            case R.id.delete_all_for_dir:
                //删除某一目录下的所有文件
                String dirPath5 = FileUtil.getSDPath() + "/00";
                boolean isDeleted5 = FileUtil.deleteFolder(new File(dirPath5));
                Log.i(TAG, "onClick: all sub file isDeleted(include current dir)::" + isDeleted5);
                break;
            case R.id.copy_file:
                //复制文件
                String filePath6 = FileUtil.getSDPath() + "/00/6.wav";
                String dirPath6 = FileUtil.getSDPath() + "/11";
                boolean isCopyed = FileUtil.copyFile(filePath6, dirPath6);
                Log.i(TAG, "onClick: file isCopyed::" + isCopyed);
                break;
            case R.id.copy_dir://v
                //复制目录
                String srcDir7 = FileUtil.getSDPath() + "/00";
                String destDir7 = FileUtil.getSDPath() + "/11";
                boolean isCopyed7 = FileUtil.copyFolder(srcDir7, destDir7);
                Log.i(TAG, "onClick: file isCopyed::" + isCopyed7);
                break;
            case R.id.rename_file:
                //文件重命名
                String srcPath8 = FileUtil.getSDPath() + "/00/8.wav";
                String destPath8 = FileUtil.getSDPath() + "/00/8_1.wav";
                boolean b = FileUtil.renameTo(srcPath8, destPath8);

                //filename="7_1.wav";
                //boolean b = FileUtil.renameTo(new File(srcPath8), destPath8);
                Log.i(TAG, "onClick: file rename::" + b);
                break;
            case R.id.format_file_size://593.71kb---->579.79
                //格式化文件大小
                String filePath9 = FileUtil.getSDPath() + "/00/9.wav";
                long fileSize = FileUtil.getFileSize(filePath9);
                Log.i(TAG, "onClick: get fileSize::" + fileSize);
                if (fileSize != -1) {
                    String formatSize = FileUtil.formatSize(fileSize);
                    Log.i(TAG, "onClick:formatSize file::" + formatSize);
                }
                break;
            case R.id.format_dir_size://4.95--->4.72M
                //格式化目录大小
                String dir10 = FileUtil.getSDPath() + "/11";
                long folderSize = FileUtil.getFolderSize(new File(dir10));
                if (folderSize != -1) {
                    String formatSize10 = FileUtil.formatSize(folderSize);
                    Log.i(TAG, "onClick: formatSize dir::" + formatSize10);
                }
                break;
            case R.id.get_file_list:
                //获取当前目录的文件列表
                String dir11 = FileUtil.getSDPath() + "/11";
                File[] fileList = FileUtil.getFileList(dir11);
                Log.i(TAG, "onClick: get file list size::" + fileList.length + "  fileList::" + fileList);
                break;
        }
    }


    //*****************************封装的文件操作方法***********************************************
    @NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void getSdPath() {
        String sdPath = FileUtil.getSDPath();
        Log.d(TAG, "getSdPath:"+sdPath);
    }

    //****************************************************************************

    //首次权限弹出是系统权限。当点击拒绝之后才会走此
    // 向用户说明为什么需要这些权限(可选)
    @OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void showRationaleForCamera(final PermissionRequest request) {
        new AlertDialog.Builder(this)
                .setMessage("文件操作需要读写权限,请求授予")
                .setPositiveButton("下一步", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int button) {
                        request.proceed();//继续请求权限
                    }
                })
                .setNegativeButton("取消", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int button) {
                        request.cancel();
                    }
                })
                .show();
    }

    // 用户拒绝授权回调(可选)//当点击系统权限的拒绝时候,不会触发
    @OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void showDeniedForCamera() {
        Toast.makeText(this, "读写权限被拒绝。", Toast.LENGTH_SHORT).show();
    }

    // 用户勾选了“不再提醒”时调用(可选)
    @OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
    void showNeverAskForCamera() {
        openAppDetails();
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        // 代理权限处理到自动生成的方法
        //必须手动添加,不然下一步不走。。
        SecondActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
    }

    /**
     * 打开 APP 的详情设置
     */
    private void openAppDetails() {
        Toast.makeText(this, "勾选了不在拒绝。", Toast.LENGTH_SHORT).show();
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage("操作文件读写权限,请到'设置'中授予权限.");
        builder.setPositiveButton("去手动授权", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                Intent intent = new Intent();
                intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                intent.addCategory(Intent.CATEGORY_DEFAULT);
                intent.setData(Uri.parse("package:" + getPackageName()));
                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
                intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
                startActivity(intent);
            }
        });
        builder.setNegativeButton("取消", null);
        builder.show();
    }

}
           

布局文件second_activity.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scrollbars="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">


        <Button
            android:id="@+id/get_sd_path"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="获取sd卡根路径" />


        <Button
            android:id="@+id/get_file_suffix"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="获取文件后缀名" />

        <Button
            android:id="@+id/get_file_mime"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="获取文件MIME类型" />


        <Button
            android:id="@+id/create_file"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="创建文件" />

        <Button
            android:id="@+id/create_dir"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="创建目录" />


        <Button
            android:id="@+id/delete_specific_file"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="删除指定文件(单个)" />


        <Button
            android:id="@+id/delete_all_for_dir"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="删除某一目录下的所有文件(包括此目录)" />


        <Button
            android:id="@+id/copy_file"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="复制文件" />

        <Button
            android:id="@+id/copy_dir"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="复制目录" />

        <Button
            android:id="@+id/rename_file"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="文件重命名" />


        <Button
            android:id="@+id/format_file_size"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="计算某个文件大小,并格式化" />

        <Button
            android:id="@+id/format_dir_size"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="计算某个目录大小,并格式化" />


        <Button
            android:id="@+id/get_file_list"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="获取某个路径下的文件列表" />
    </LinearLayout>
</ScrollView>
           

运行效果如下:

Android常用的工具类汇总(方便日后使用)

其中SecondActivity中设计android 6.0运行时权限的申请,使用教程可参考:

PermissionsDispatcher,Android 6.0 运行时权限

重点说明,避免踩坑:

(1)[email protected]修饰的方法只能被void修饰

(2).android6.0动态权限申请不意味着只在代码中申请,清单文件也必须写。

(3)[email protected]可以使用多次,即需要权限申请的都可以使用,不受次数限制

(4).FileUtils中的计算文件大小在小米5手机测试时候存在一定的偏差,比如某个文大小工具类中计算出来的是579.79KB,而通过小米手机的文件管理器查出来的是593.71KB. 这主要是进制的原因,MIUI目前使用的是1000进制,其他的app可能采用的是1024进制,因为存储芯片的生产厂商都是按照1000进制计算的这个问题的原因参见

3.常用的正则表达式验证

/**
 * 常用的正则表达式
 */
public class RegularUtils {

    /**
     * 验证手机号(简单)
     */
    private static final String REGEX_MOBILE_SIMPLE = "^[1]\\d{10}$";
    /**
     * 验证手机号(精确)
     * 移动:134(0-8)、135、136、137、138、139、147、150、151、152、157、158、159、178、182、183、184、187、188
     * 联通:130、131、132、145、155、156、175、176、185、186
     * 电信:133、153、173、177、180、181、189
     * 全球星:1349
     * 虚拟运营商:170
     */
    private static final String REGEX_MOBILE_EXACT = "^((13[0-9])|(14[5,7])|(15[0-3,5-8])|(17[0,3,5-8])|(18[0-9])|(147))\\d{8}$";
    /**
     * 验证座机号,正确格式:xxx/xxxx-xxxxxxx/xxxxxxxx/
     */
    private static final String REGEX_TEL = "^0\\d{2,3}[- ]?\\d{7,8}";
    /**
     * 验证邮箱
     */
    private static final String REGEX_EMAIL = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
    /**
     * 验证url
     */
    private static final String REGEX_URL = "http(s)?://([\\w-]+\\.)+[\\w-]+(/[\\w-./?%&=]*)?";
    /**
     * 验证汉字
     */
    private static final String REGEX_CHZ = "^[\\u4e00-\\u9fa5]+$";
    /**
     * 验证用户名,取值范围为a-z,A-Z,0-9,"_",汉字,不能以"_"结尾,用户名必须是6-20位
     */
    private static final String REGEX_USERNAME = "^[\\w\\u4e00-\\u9fa5]{6,20}(?<!_)$";
    /**
     * 验证IP地址
     */
    private static final String REGEX_IP = "((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)";
    //If u want more please visit http://toutiao.com/i6231678548520731137/

    /**
     * * @param string 待验证文本
     *
     * @return 是否符合手机号(简单)格式
     */
    public static boolean isMobileSimple(String string) {
        return isMatch(REGEX_MOBILE_SIMPLE, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合手机号(精确)格式
     */
    public static boolean isMobileExact(String string) {
        return isMatch(REGEX_MOBILE_EXACT, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合座机号码格式
     */
    public static boolean isTel(String string) {
        return isMatch(REGEX_TEL, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合邮箱格式
     */
    public static boolean isEmail(String string) {
        return isMatch(REGEX_EMAIL, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合网址格式
     */
    public static boolean isURL(String string) {
        return isMatch(REGEX_URL, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合汉字
     */
    public static boolean isChz(String string) {
        return isMatch(REGEX_CHZ, string);
    }

    /**
     * @param string 待验证文本
     * @return 是否符合用户名
     */
    public static boolean isUsername(String string) {
        return isMatch(REGEX_USERNAME, string);
    }

    /**
     * @param regex  正则表达式字符串
     * @param string 要匹配的字符串
     * @return 如果str 符合 regex的正则表达式格式,返回true, 否则返回 false;
     */
    public static boolean isMatch(String regex, String string) {
        return !TextUtils.isEmpty(string) && Pattern.matches(regex, string);
    }

}
           

4.时间戳工具类

/**
 * 时间戳
 */
public class DateUtils {
    private static SimpleDateFormat sf;
    private static SimpleDateFormat sdf;

    /**
     * 获取系统时间 格式为:"yyyy/MM/dd "
     **/
    public static String getCurrentDate() {
        Date d = new Date();
        sf = new SimpleDateFormat("yyyy年MM月dd日");
        return sf.format(d);
    }

    /**
     * 获取系统时间 格式为:"yyyy "
     **/
    public static String getCurrentYear() {
        Date d = new Date();
        sf = new SimpleDateFormat("yyyy");
        return sf.format(d);
    }

    /**
     * 获取系统时间 格式为:"MM"
     **/
    public static String getCurrentMonth() {
        Date d = new Date();
        sf = new SimpleDateFormat("MM");
        return sf.format(d);
    }

    /**
     * 获取系统时间 格式为:"dd"
     **/
    public static String getCurrentDay() {
        Date d = new Date();
        sf = new SimpleDateFormat("dd");
        return sf.format(d);
    }

    /**
     * 获取当前时间戳 * * @return
     */
    public static long getCurrentTime() {
        long d = new Date().getTime() / 1000;
        return d;
    }

    /**
     * 时间戳转换成字符窜
     */
    public static String getDateToString(long time) {
        Date d = new Date(time * 1000);
        sf = new SimpleDateFormat("yyyy年MM月dd日");
        return sf.format(d);
    }

    /**
     * 时间戳中获取年
     */
    public static String getYearFromTime(long time) {
        Date d = new Date(time * 1000);
        sf = new SimpleDateFormat("yyyy");
        return sf.format(d);
    }

    /**
     * 时间戳中获取月
     */
    public static String getMonthFromTime(long time) {
        Date d = new Date(time * 1000);
        sf = new SimpleDateFormat("MM");
        return sf.format(d);
    }

    /**
     * 时间戳中获取日
     */
    public static String getDayFromTime(long time) {
        Date d = new Date(time * 1000);
        sf = new SimpleDateFormat("dd");
        return sf.format(d);
    }

    /**
     * 将字符串转为时间戳
     */
    public static long getStringToDate(String time) {
        sdf = new SimpleDateFormat("yyyy年MM月dd日");
        Date date = new Date();
        try {
            date = sdf.parse(time);
        } catch (ParseException e) { // TODO Auto-generated catch block e.printStackTrace();

        }
        return date.getTime();
    }

    /**
     * 2018-12-13T13:33:43.441+08:00--->2018-12-13   13:33:43
     *
     * @param str
     * @return
     */
    public static String getGeneralTime(String str) {
        if (TextUtils.isEmpty(str)) {
            return "";
        }

        SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
        Date date = null;
        try {
            date = formatter.parse(str);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return sdf.format(date);
    }

    /**
     * get the one day of past
     *
     * @param past
     * @return
     */
    public static String getPastDate(int past) {
        Calendar calendar = Calendar.getInstance();
        calendar.set(Calendar.DAY_OF_YEAR, calendar.get(Calendar.DAY_OF_YEAR) - past);
        Date today = calendar.getTime();
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        String result = format.format(today);
        Log.e(null, result);
        return result;
    }


    /**
     * 比较两个日期的大小,日期格式为yyyy-MM-dd
     *
     * @param startDateStr the first date
     * @param endDateStr   the second date
     * @return true <br/>false
     */
    public static boolean isDate2Bigger(String startDateStr, String endDateStr) {
        boolean isBigger = false;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date dt1 = null;
        Date dt2 = null;
        try {
            dt1 = sdf.parse(startDateStr);
            dt2 = sdf.parse(endDateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        if (dt1.getTime() > dt2.getTime()) {
            isBigger = false;
        } else if (dt1.getTime() <= dt2.getTime()) {
            isBigger = true;
        }
        return isBigger;
    }

    /**
     * 比较两个日期的大小间隔是否超过30天,日期格式为yyyy-MM-dd
     *
     * @param startDateStr the start date
     * @param endDateStr   the end date
     * @return true <br/>false
     */
    public static boolean isDateOverOneMonth(String startDateStr, String endDateStr) {
        boolean isOverOneMonth = false;
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        Date dt1 = null;
        Date dt2 = null;
        try {
            dt1 = sdf.parse(startDateStr);
            dt2 = sdf.parse(endDateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }

        long dt1Time = dt1.getTime();
        long dt2Time = dt2.getTime();
        int period = (int) ((dt2Time - dt1Time) / (24 * 60 * 60 * 1000));

        if (period > 30) {
            isOverOneMonth = true;
        }

        return isOverOneMonth;
    }


    /**
     * 比较两个日期之间间隔的天数
     *
     * @param startDateStr
     * @param endDateStr
     * @return
     */
    public static int getIntervalDays(String startDateStr, String endDateStr, SimpleDateFormat sdf) {
        int intervalDays;
        Date dt1 = null;
        Date dt2 = null;
        try {
            dt1 = sdf.parse(startDateStr);
            dt2 = sdf.parse(endDateStr);
        } catch (ParseException e) {
            e.printStackTrace();
        }

        long dt1Time = dt1.getTime();
        long dt2Time = dt2.getTime();
        intervalDays = (int) ((dt2Time - dt1Time) / (24 * 60 * 60 * 1000));
        return intervalDays;
    }

    /**
     * 获取两个日期之间的所有日期
     *
     * @param startTime 开始日期
     * @param endTime   结束日期
     * @return
     */
    public static List<String> getIntervalDays(String startTime, String endTime) {

        // 返回的日期集合
        List<String> days = new ArrayList<>();

        DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        try {
            Date start = dateFormat.parse(startTime);
            Date end = dateFormat.parse(endTime);

            Calendar tempStart = Calendar.getInstance();
            tempStart.setTime(start);

            Calendar tempEnd = Calendar.getInstance();
            tempEnd.setTime(end);
            tempEnd.add(Calendar.DATE, +1);// 日期加1(包含结束)
            while (tempStart.before(tempEnd)) {
                days.add(dateFormat.format(tempStart.getTime()));
                tempStart.add(Calendar.DAY_OF_YEAR, 1);
            }

        } catch (ParseException e) {
            e.printStackTrace();
        }
        return days;
    }
/**
 * 比较两个日期之间的间隔
 *
 * @param startDateStr
 * @param endDateStr
 * @return
 */
public static String getIntervalTime(String startDateStr, String endDateStr, SimpleDateFormat sdf) {
    LogUtils.d(TAG, "getIntervalDays, startDateStr:" + startDateStr + " endDateStr:" + endDateStr);
    Date dt1 = null;
    Date dt2 = null;
    try {
        dt1 = sdf.parse(startDateStr);
        dt2 = sdf.parse(endDateStr);
    } catch (ParseException e) {
        e.printStackTrace();
    }

    double dt1Time = dt1.getTime();
    double dt2Time = dt2.getTime();
    double time = dt2Time - dt1Time;

    int intervalMins = (int) (time / (TimeConstants.MIN));
    int intervalHours = (int) (time / (TimeConstants.HOUR));
    int intervalDays = (int) (time / (TimeConstants.DAY));

    int intervalWeeks = (intervalDays / 7);
    int intervalMonths = (intervalDays / 30);
    int intervalYears = (intervalDays / 365);

    LogUtils.d(TAG, "" +
            "intervalMins::" + intervalMins + "\n" +
            " intervalHours::" + intervalHours + "\n" +
            " intervalDays::" + intervalDays + "\n" +
            " intervalWeeks::" + intervalWeeks + "\n" +
            " intervalMonths::" + intervalMonths + "\n" +
            " intervalYears::" + intervalYears + "\n");

    if (intervalYears < 1) {//in year
        if (intervalMonths < 1) {//in month
            if (intervalWeeks < 1) {//in week
                if (intervalDays < 1) {//in day
                    if (intervalHours < 1) {//in hour
                        if (intervalMins < 10) {//in minute
                            return "刚刚";
                        }
                        return intervalMins + "分钟前";
                    }
                    return intervalHours + "小时前";
                }
                return intervalDays + "天前";
            }
            return intervalWeeks + "周前";
        }
        return intervalMonths + "月前";
    }
    return intervalYears + "年前";
}

/***
 * 获取一天之内所有的时间点的集合
 * @return
 */
public static List<String> getOneDayTimeList() {
    ArrayList<String> timeList = new ArrayList<>();
    for (int i = 0; i < 24; i++) {
        timeList.add(i + ":00");
    }
    return timeList;
}

}
           

4. 6.0+权限申请工具类

/**
 * <pre>
 *     author: Blankj
 *     blog  : http://blankj.com
 *     time  : 2017/12/29
 *     desc  : utils about permission
 * </pre>
 */
public final class PermissionUtils {

    private static final List<String> PERMISSIONS = getPermissions();

    private static PermissionUtils sInstance;

    private OnRationaleListener mOnRationaleListener;
    private SimpleCallback      mSimpleCallback;
    private FullCallback        mFullCallback;
    private ThemeCallback       mThemeCallback;
    private Set<String>         mPermissions;
    private List<String>        mPermissionsRequest;
    private List<String>        mPermissionsGranted;
    private List<String>        mPermissionsDenied;
    private List<String>        mPermissionsDeniedForever;

    /**
     * Return the permissions used in application.
     *
     * @return the permissions used in application
     */
    public static List<String> getPermissions() {
        return getPermissions(Utils.getApp().getPackageName());
    }

    /**
     * Return the permissions used in application.
     *
     * @param packageName The name of the package.
     * @return the permissions used in application
     */
    public static List<String> getPermissions(final String packageName) {
        PackageManager pm = Utils.getApp().getPackageManager();
        try {
            return Arrays.asList(
                    pm.getPackageInfo(packageName, PackageManager.GET_PERMISSIONS)
                            .requestedPermissions
            );
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
            return Collections.emptyList();
        }
    }

    /**
     * Return whether <em>you</em> have granted the permissions.
     *
     * @param permissions The permissions.
     * @return {@code true}: yes<br>{@code false}: no
     */
    public static boolean isGranted(final String... permissions) {
        for (String permission : permissions) {
            if (!isGranted(permission)) {
                return false;
            }
        }
        return true;
    }

    private static boolean isGranted(final String permission) {
        return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
                || PackageManager.PERMISSION_GRANTED
                == ContextCompat.checkSelfPermission(Utils.getApp(), permission);
    }

    /**
     * Launch the application's details settings.
     */
    public static void launchAppDetailsSettings() {
        Intent intent = new Intent("android.settings.APPLICATION_DETAILS_SETTINGS");
        intent.setData(Uri.parse("package:" + Utils.getApp().getPackageName()));
        Utils.getApp().startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
    }

    /**
     * Set the permissions.
     *
     * @param permissions The permissions.
     * @return the single {@link PermissionUtils} instance
     */
    public static PermissionUtils permission(@PermissionConstants.Permission final String... permissions) {
        return new PermissionUtils(permissions);
    }

    private PermissionUtils(final String... permissions) {
        mPermissions = new LinkedHashSet<>();
        for (String permission : permissions) {
            for (String aPermission : PermissionConstants.getPermissions(permission)) {
                if (PERMISSIONS.contains(aPermission)) {
                    mPermissions.add(aPermission);
                }
            }
        }
        sInstance = this;
    }

    /**
     * Set rationale listener.
     *
     * @param listener The rationale listener.
     * @return the single {@link PermissionUtils} instance
     */
    public PermissionUtils rationale(final OnRationaleListener listener) {
        mOnRationaleListener = listener;
        return this;
    }

    /**
     * Set the simple call back.
     *
     * @param callback the simple call back
     * @return the single {@link PermissionUtils} instance
     */
    public PermissionUtils callback(final SimpleCallback callback) {
        mSimpleCallback = callback;
        return this;
    }

    /**
     * Set the full call back.
     *
     * @param callback the full call back
     * @return the single {@link PermissionUtils} instance
     */
    public PermissionUtils callback(final FullCallback callback) {
        mFullCallback = callback;
        return this;
    }

    /**
     * Set the theme callback.
     *
     * @param callback The theme callback.
     * @return the single {@link PermissionUtils} instance
     */
    public PermissionUtils theme(final ThemeCallback callback) {
        mThemeCallback = callback;
        return this;
    }

    /**
     * Start request.
     */
    public void request() {
        mPermissionsGranted = new ArrayList<>();
        mPermissionsRequest = new ArrayList<>();
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            mPermissionsGranted.addAll(mPermissions);
            requestCallback();
        } else {
            for (String permission : mPermissions) {
                if (isGranted(permission)) {
                    mPermissionsGranted.add(permission);
                } else {
                    mPermissionsRequest.add(permission);
                }
            }
            if (mPermissionsRequest.isEmpty()) {
                requestCallback();
            } else {
                startPermissionActivity();
            }
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private void startPermissionActivity() {
        mPermissionsDenied = new ArrayList<>();
        mPermissionsDeniedForever = new ArrayList<>();
        PermissionActivity.start(Utils.getApp());
    }

    @RequiresApi(api = Build.VERSION_CODES.M)
    private boolean rationale(final Activity activity) {
        boolean isRationale = false;
        if (mOnRationaleListener != null) {
            for (String permission : mPermissionsRequest) {
                if (activity.shouldShowRequestPermissionRationale(permission)) {
                    getPermissionsStatus(activity);
                    mOnRationaleListener.rationale(new OnRationaleListener.ShouldRequest() {
                        @Override
                        public void again(boolean again) {
                            if (again) {
                                startPermissionActivity();
                            } else {
                                requestCallback();
                            }
                        }
                    });
                    isRationale = true;
                    break;
                }
            }
            mOnRationaleListener = null;
        }
        return isRationale;
    }

    private void getPermissionsStatus(final Activity activity) {
        for (String permission : mPermissionsRequest) {
            if (isGranted(permission)) {
                mPermissionsGranted.add(permission);
            } else {
                mPermissionsDenied.add(permission);
                if (!activity.shouldShowRequestPermissionRationale(permission)) {
                    mPermissionsDeniedForever.add(permission);
                }
            }
        }
    }

    private void requestCallback() {
        if (mSimpleCallback != null) {
            if (mPermissionsRequest.size() == 0
                    || mPermissions.size() == mPermissionsGranted.size()) {
                mSimpleCallback.onGranted();
            } else {
                if (!mPermissionsDenied.isEmpty()) {
                    mSimpleCallback.onDenied();
                }
            }
            mSimpleCallback = null;
        }
        if (mFullCallback != null) {
            if (mPermissionsRequest.size() == 0
                    || mPermissions.size() == mPermissionsGranted.size()) {
                mFullCallback.onGranted(mPermissionsGranted);
            } else {
                if (!mPermissionsDenied.isEmpty()) {
                    mFullCallback.onDenied(mPermissionsDeniedForever, mPermissionsDenied);
                }
            }
            mFullCallback = null;
        }
        mOnRationaleListener = null;
        mThemeCallback = null;
    }

    private void onRequestPermissionsResult(final Activity activity) {
        getPermissionsStatus(activity);
        requestCallback();
    }


    @RequiresApi(api = Build.VERSION_CODES.M)
    public static class PermissionActivity extends Activity {

        public static void start(final Context context) {
            Intent starter = new Intent(context, PermissionActivity.class);
            starter.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            context.startActivity(starter);
        }

        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            getWindow().addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
                    | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH);
            if (sInstance == null) {
                super.onCreate(savedInstanceState);
                Log.e("PermissionUtils", "request permissions failed");
                finish();
                return;
            }
            if (sInstance.mThemeCallback != null) {
                sInstance.mThemeCallback.onActivityCreate(this);
            }
            super.onCreate(savedInstanceState);

            if (sInstance.rationale(this)) {
                finish();
                return;
            }
            if (sInstance.mPermissionsRequest != null) {
                int size = sInstance.mPermissionsRequest.size();
                if (size <= 0) {
                    finish();
                    return;
                }
                requestPermissions(sInstance.mPermissionsRequest.toArray(new String[size]), 1);
            }
        }

        @Override
        public void onRequestPermissionsResult(int requestCode,
                                               @NonNull String[] permissions,
                                               @NonNull int[] grantResults) {
            sInstance.onRequestPermissionsResult(this);
            finish();
        }

        @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
            finish();
            return true;
        }
    }

    /**
     * 检查是否拥有权限
     */
    public static boolean hasPermission(Context context, String perm) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (context.getApplicationContext().checkSelfPermission(perm) != PackageManager.PERMISSION_DENIED) {
                return true;
            }
            return false;
        }
        return true;
    }

    /**
     * 批量申请权限(如果当前没有权限的话)。授权结果在onRequestPermissionsResult中处理
     */
    public static void requestPermissionsIfNeed(Activity activity, String[] perms, int requestCode){
        if(perms.length == 0) {
            return;
        }

        HashSet<String> needPerms = new HashSet<>();
        for (String perm : perms){
            if(!isGranted(perm)){
                needPerms.add(perm);
            }
        }

        if(needPerms.size() == 0){
            return;
        } else {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){
                activity.requestPermissions(needPerms.toArray(new String[needPerms.size()]), requestCode);
            }
        }
    }


    ///
    // interface
    ///

    public interface OnRationaleListener {

        void rationale(ShouldRequest shouldRequest);

        interface ShouldRequest {
            void again(boolean again);
        }
    }

    public interface SimpleCallback {
        void onGranted();

        void onDenied();
    }

    public interface FullCallback {
        void onGranted(List<String> permissionsGranted);

        void onDenied(List<String> permissionsDeniedForever, List<String> permissionsDenied);
    }

    public interface ThemeCallback {
        void onActivityCreate(Activity activity);
    }
}