平时进行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());
}
}
}
针对上述代码块中的showLog()方法的代码描述,大家可能不太清楚.按我的理解,特此举例说明下, 相信进行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>
运行效果如下:
其中SecondActivity中设计android 6.0运行时权限的申请,使用教程可参考:
PermissionsDispatcher,Android 6.0 运行时权限
重点说明,避免踩坑:
(1).@NeedsPermission修饰的方法只能被void修饰
(2).android6.0动态权限申请不意味着只在代码中申请,清单文件也必须写。
(3).@NeedsPermission可以使用多次,即需要权限申请的都可以使用,不受次数限制
(4).FileUtils中的计算文件大小在小米5手机测试时候存在一定的偏差,比如某个文大小工具类中计算出来的是579.79KB,而通过小米手机的文件管理器查出来的是593.71KB. 这主要是进制的原因,MIUI目前使用的是1000进制,其他的app可能采用的是1024进制,因为存储芯片的生产厂商都是按照1000进制计算的这个问题的原因参见