package com.proxgrind.chameleon.utils.stream; import android.app.Activity; import android.app.Application; import android.content.ActivityNotFoundException; import android.content.ContentResolver; import android.content.ContentUris; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Build; import android.os.Environment; import android.os.ParcelFileDescriptor; import android.provider.DocumentsContract; import android.provider.MediaStore; import androidx.annotation.NonNull; import androidx.core.content.FileProvider; import androidx.loader.content.CursorLoader; import android.provider.OpenableColumns; import android.widget.Toast; import com.proxgrind.chameleon.R; import com.proxgrind.chameleon.utils.system.AppContextUtils; import com.proxgrind.chameleon.utils.tools.ArrayUtils; import com.proxgrind.chameleon.utils.system.LogUtils; import com.proxgrind.chameleon.utils.tools.StringUtils; import java.io.BufferedReader; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.net.URI; import java.net.URL; import java.nio.channels.FileChannel; import java.text.DecimalFormat; import java.util.ArrayList; /* * 一系列文件操作的封装! * */ public class FileUtils { // 上下文,用于给File类获取资源! private static Application context = AppContextUtils.app; private static DecimalFormat format = new DecimalFormat("#.00"); public interface Size { long B = 1024; long KB = B * 1024; long MB = KB * 1024; long GB = MB * 1024; long TB = GB * 1024; } public interface Name { String B = "B"; String KB = "KB"; String MB = "MB"; String GB = "GB"; String TB = "TB"; } //获得文件的输入流! public static FileInputStream getFis(File file) throws FileNotFoundException { return new FileInputStream(file); } //获得文件的输入流! public static InputStream getFis(Uri stream) throws FileNotFoundException { return context.getContentResolver().openInputStream(stream); } //获得文件的输出流! public static FileOutputStream getFos(File file, boolean append) throws FileNotFoundException { return new FileOutputStream(file, append); } public static byte[] readBytes(InputStream is, long count, long max, long min) throws IOException { if (max != -1 && is.available() > max) return null; if (min != -1 && is.available() < min) return null; // The length correct. if (count == -1) count = is.available(); byte[] buf = new byte[(int) count]; if (is.read(buf, 0, (int) count) != count) { IOUtils.close(is); throw new IOException("read bytes file len is error!"); } IOUtils.close(is); return buf; } public static byte[] readBytes(InputStream is) throws IOException { // The length correct. int count = is.available(); byte[] buf = new byte[(int) count]; if (is.read(buf, 0, (int) count) != count) { IOUtils.close(is); throw new IOException("read bytes file len is error!"); } IOUtils.close(is); return buf; } public static byte[] readBytes(File file) throws IOException { return readBytes(file, (int) file.length()); } public static byte[] readBytes(File file, long count) throws IOException { FileInputStream fis = getFis(file); return readBytes(fis, count, -1, -1); } public static byte[] readBytes(File file, long count, long max, long min) throws IOException { FileInputStream fis = getFis(file); return readBytes(fis, count, max, min); } public static byte[] readBytes(Uri uri) throws IOException { return readBytes(uri, -1); } public static byte[] readBytes(Uri uri, long count) throws IOException { InputStream fis = getFis(uri); return readBytes(fis, count, -1, -1); } public static byte[] readBytes(Uri uri, long count, long max, long min) throws IOException { InputStream fis = getFis(uri); return readBytes(fis, count, max, min); } //读取文件以每行 public static String[] readLines(File file) throws IOException { FileInputStream fileIs; ArrayList resultList = new ArrayList<>(); fileIs = new FileInputStream(file); String tmpBUf; //创建字符缓冲读取对象 BufferedReader bReader = new BufferedReader(new InputStreamReader(fileIs)); while ((tmpBUf = bReader.readLine()) != null) { resultList.add(tmpBUf); } return ArrayUtils.list2Arr(resultList); } //按匹配的行读取文件 public static String[] readLines(File file, String regex) throws IOException { String[] resultArray = readLines(file); ArrayList resultList = new ArrayList<>(); if (resultArray == null) return new String[0]; for (String str : resultArray) { if (str.matches(regex)) { resultList.add(str); } } return ArrayUtils.list2Arr(resultList); } //写入指定的字节到文件中,以覆盖或者追加的方式! public static void writeBytes(byte[] data, File file, boolean append) throws IOException { if (!file.exists()) createFile(file); if (!file.isFile()) return; LogUtils.d("写入的目标: " + file.getAbsolutePath()); FileOutputStream fos = getFos(file, append); fos.write(data); } //写入指定的字节到文件中,以覆盖或者追加的方式! public static void writeBytes(byte[] data, FileOutputStream fos) throws IOException { fos.write(data); } // 写入指定的字节到文件中,适配SAF public static void writeBytes(byte[] data, Uri target) throws IOException { OutputStream os = context.getContentResolver().openOutputStream(target); if (os != null) { os.write(data); os.close(); } } // 写入指定的字节到文件中,适配SAF public static void writeBytes(File source, Uri target) throws IOException { writeBytes(readBytes(Uri.fromFile(source)), target); } //使用字符串内容覆盖文件内容 public static void writeString(File file, String str, boolean append) { try { writeBytes(str.getBytes(), file, append); } catch (IOException e) { e.printStackTrace(); } } // 根据文件后缀名获得对应的MIME类型。 private static String getMimeType(String filePath) { MediaMetadataRetriever mmr = new MediaMetadataRetriever(); String mime = "*/*"; if (filePath != null) { try { mmr.setDataSource(filePath); mime = mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE); } catch (RuntimeException e) { return mime; } } return mime; } //删除文件夹目录下所有的文件 public static boolean delete(File dir) { if (dir.isDirectory()) { if (dir.delete()) { return false; } String[] files = dir.list(); if (files == null) { return dir.delete(); } //非空目录,迭代删除所有的文件 for (String file : files) delete(new File(dir.getAbsolutePath() + File.separator + file)); //然后进行目录删除! return delete(dir); } else { return dir.delete(); } } public static boolean delete(String path) { return delete(new File(path)); } // 調用系統方法分享文件 public static void shareFile(Context context, Uri uri) { Intent share = new Intent(Intent.ACTION_SEND); //判断是否是AndroidN以及更高的版本 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {//如果SDK版本>=24,即:Build.VERSION.SDK_INT >= 24 uri = FileProvider.getUriForFile( context, FileUtils.getFileProviderName(), new File(FileUtils.getFilePathByUri(uri)) ); } share.putExtra(Intent.EXTRA_STREAM, uri); share.setType("*/*");//此处可发送多种文件 share.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); share.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); //context.startActivity(Intent.createChooser(share, "分享文件")); 会崩溃! try { context.startActivity(share); } catch ( Exception e) { e.printStackTrace(); Toast.makeText(context, R.string.tips_no_app_process, Toast.LENGTH_SHORT).show(); } } public final static String getFileProviderName() { return context.getPackageName() + ".fileprovider"; } public static String getFilePathByUri(Uri uri) { // 以 file:// 开头的 if (ContentResolver.SCHEME_FILE.equals(uri.getScheme())) { return uri.getPath(); } // 以/storage开头的也直接返回 if (isOtherDocument(uri)) { return uri.getPath(); } // 版本兼容的获取! String path = getFilePathByUri_BELOWAPI11(uri); if (path != null) { LogUtils.d("getFilePathByUri_BELOWAPI11获取到的路径为:" + path); return path; } path = getFilePathByUri_API11to18(uri); if (path != null) { LogUtils.d("getFilePathByUri_API11to18获取到的路径为:" + path); return path; } path = getFilePathByUri_API19(uri); LogUtils.d("getFilePathByUri_API19获取到的路径为:" + path); return path; } private static String getFilePathByUri_BELOWAPI11(Uri uri) { // 以 content:// 开头的,比如 content://media/extenral/images/media/17766 if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) { String path = null; String[] projection = new String[]{MediaStore.Images.Media.DATA}; Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null); if (cursor != null) { if (cursor.moveToFirst()) { try { int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); if (columnIndex > -1) { path = cursor.getString(columnIndex); } } catch (Exception e) { e.printStackTrace(); } } cursor.close(); } return path; } return null; } private static String getFilePathByUri_API11to18(Uri contentUri) { String[] projection = {MediaStore.Images.Media.DATA}; String result = null; CursorLoader cursorLoader = new CursorLoader(context, contentUri, projection, null, null, null); Cursor cursor = cursorLoader.loadInBackground(); if (cursor != null) { if (cursor.moveToFirst()) { try { int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA); result = cursor.getString(column_index); } catch (Exception e) { e.printStackTrace(); } } cursor.close(); } return result; } private static String getFilePathByUri_API19(Uri uri) { // 4.4及之后的 是以 content:// 开头的,比如 content://com.android.providers.media.documents/document/image%3A235700 // content://com.tencent.mtt.fileprovider/QQBrowser/tencent/MobileQQ/photo/_-176177031_Screenshot_20200225_213619_com.tencent.mobileqq_1582637779000_wifi_0.jpg if (ContentResolver.SCHEME_CONTENT.equals(uri.getScheme()) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { if (DocumentsContract.isDocumentUri(context, uri)) { if (isExternalStorageDocument(uri)) { // ExternalStorageProvider String docId = DocumentsContract.getDocumentId(uri); String[] split = docId.split(":"); String type = split[0]; if ("primary".equalsIgnoreCase(type)) { if (split.length > 1) { return Environment.getExternalStorageDirectory() + "/" + split[1]; } else { return Environment.getExternalStorageDirectory() + "/"; } // This is for checking SD Card } } else if (isDownloadsDocument(uri)) { //下载内容提供者时应当判断下载管理器是否被禁用 int stateCode = context.getPackageManager().getApplicationEnabledSetting("com.android.providers.downloads"); if (stateCode != 0 && stateCode != 1) { return null; } String id = DocumentsContract.getDocumentId(uri); // 如果出现这个RAW地址,我们则可以直接返回! if (id.startsWith("raw:")) { return id.replaceFirst("raw:", ""); } if (id.contains(":")) { String[] tmp = id.split(":"); if (tmp.length > 1) { id = tmp[1]; } } Uri contentUri = Uri.parse("content://downloads/public_downloads"); LogUtils.d("测试打印Uri: " + uri); try { contentUri = ContentUris.withAppendedId(contentUri, Long.parseLong(id)); } catch (Exception e) { e.printStackTrace(); } String path = getDataColumn(contentUri, null, null); if (path != null) return path; // 兼容某些特殊情况下的文件管理器! String fileName = getFileNameByUri(context, uri, true); if (fileName != null) { path = Environment.getExternalStorageDirectory().toString() + "/Download/" + fileName; return path; } } else if (isMediaDocument(uri)) { // MediaProvider String docId = DocumentsContract.getDocumentId(uri); String[] split = docId.split(":"); String type = split[0]; Uri contentUri = null; if ("image".equals(type)) { contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; } else if ("video".equals(type)) { contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; } else if ("audio".equals(type)) { contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; } String selection = "_id=?"; String[] selectionArgs = new String[]{split[1]}; return getDataColumn(contentUri, selection, selectionArgs); } } } return null; } public static String getFileNameByUri(Context context, Uri uri, boolean needPath) { String relativePath = getFileRelativePathByUri_API18(context, uri); if (relativePath == null) relativePath = ""; try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { int index = cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME); String result = cursor.getString(index); return needPath ? relativePath + result : result; } } catch (Exception e) { e.printStackTrace(); } return null; } private static String getFileRelativePathByUri_API18(Context context, Uri uri) { final String[] projection; if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { projection = new String[]{ MediaStore.MediaColumns.RELATIVE_PATH }; try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) { if (cursor != null && cursor.moveToFirst()) { int index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.RELATIVE_PATH); return cursor.getString(index); } } catch (Exception e) { e.printStackTrace(); } } return null; } private static String getDataColumn(Uri uri, String selection, String[] selectionArgs) { final String column = MediaStore.Images.Media.DATA; final String[] projection = {column}; try (Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null)) { if (cursor != null && cursor.moveToFirst()) { final int column_index = cursor.getColumnIndexOrThrow(column); return cursor.getString(column_index); } } catch (Exception e) { e.printStackTrace(); } return null; } private static boolean isExternalStorageDocument(Uri uri) { return "com.android.externalstorage.documents".equals(uri.getAuthority()); } private static boolean isOtherDocument(Uri uri) { // 以/storage开头的也直接返回 if (uri != null && uri.getPath() != null) { String path = uri.getPath(); if (path.startsWith("/storage")) { return true; } if (path.startsWith("/external_files")) { return true; } } return false; } private static boolean isDownloadsDocument(Uri uri) { return "com.android.providers.downloads.documents".equals(uri.getAuthority()); } private static boolean isMediaDocument(Uri uri) { return "com.android.providers.media.documents".equals(uri.getAuthority()); } public static void createFile(File file) { try { if (!file.exists()) file.createNewFile(); } catch (IOException e) { e.printStackTrace(); } } public static boolean createPaths(File file) { if (!file.exists()) return file.mkdirs(); return false; } public static File[] names2Files(String[] fileStrs) { ArrayList tmp = new ArrayList<>(); for (String s : fileStrs) { tmp.add(new File(s)); } return tmp.toArray(new File[0]); } public static String[] files2Names(File[] files) { ArrayList ret = new ArrayList<>(); if (files != null) { for (File f : files) { ret.add(f.getName()); } } return ret.toArray(new String[0]); } public static File getAppFilesDir() { if (context != null) return context.getFilesDir(); return null; } public static File getAppFilesDir(String sub) { File path = context.getFileStreamPath(sub); createPaths(path); return path; } public static File newFile(File dir, String name) { createPaths(dir); File ret = new File(dir.getAbsoluteFile() + File.separator + name); createFile(ret); return ret; } //判断文件名是否有效! public static boolean isValidFileName(String name) { return !StringUtils.isEmpty(name) && !name.contains("/"); } //获得so库的地址! public static String getNativePath() { String ss = context.getApplicationInfo().nativeLibraryDir; if (ss == null) ss = context.getFilesDir().getPath() + "/lib"; return ss; } public static boolean moveFile(File file, File path) { String pathStr = path.getAbsolutePath(); pathStr = pathStr.endsWith(File.separator) ? pathStr : pathStr + File.separator; String targetFile = pathStr + file.getName(); File tmp = new File(targetFile); createFile(tmp); if (tmp.exists() && tmp.isFile()) { // 开始写入! return file.renameTo(new File(targetFile)); } return true; } public static boolean copy(File source, File target) throws IOException { if (target == null || source == null) return false; FileChannel inputChannel = null; FileChannel outputChannel = null; try { File parentFile = target.getParentFile(); if (parentFile != null) { createPaths(parentFile); } createFile(target); inputChannel = new FileInputStream(source).getChannel(); outputChannel = new FileOutputStream(target).getChannel(); outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); } finally { IOUtils.close(inputChannel); IOUtils.close(outputChannel); } return true; } public static boolean copy(FileDescriptor descriptor, FileDescriptor target) throws IOException { if (target == null || descriptor == null) return false; FileChannel inputChannel = null; FileChannel outputChannel = null; try { inputChannel = new FileInputStream(descriptor).getChannel(); outputChannel = new FileOutputStream(target).getChannel(); outputChannel.transferFrom(inputChannel, 0, inputChannel.size()); } finally { IOUtils.close(inputChannel); IOUtils.close(outputChannel); } return true; } public static boolean copy(byte[] rawData, File target) { try { writeBytes(rawData, target, false); return true; } catch (IOException e) { e.printStackTrace(); return false; } } public static String getFileCountIfFolder(File[] files) { if (files == null) files = new File[0]; return files.length + " " + context.getString(R.string.item); } public static String getFileLengthIfFile(@NonNull File file) { long length = file.length(); String fileLength; if (length < Size.B) { fileLength = length + Name.B; } else if (length < Size.KB) { fileLength = format.format(length * 1.0 / Size.B) + Name.KB; } else if (length < Size.MB) { fileLength = format.format(length * 1.0 / Size.KB) + Name.MB; } else if (length < Size.GB) { fileLength = format.format(length * 1.0 / Size.MB) + Name.GB; } else { fileLength = format.format(length * 1.0 / Size.GB) + Name.TB; } return fileLength; } }