前言
在了解Android文件相关知识前,先来了解下Android文件相关常用的API
Log.i("相关API调用", "=======Environment调用=======\n"
+ "getRootDirectory = " + Environment.getRootDirectory().getPath() + "\n"
+ "getDownloadCacheDirectory = " + Environment.getDownloadCacheDirectory().getPath() + "\n"
+ "getDataDirectory = " + Environment.getDataDirectory().getPath() + "\n"
+ "getExternalStorageDirectory = " + Environment.getExternalStorageDirectory().getPath() + "\n"
+ "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getPath() + "\n"
+ "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_ALARMS).getPath() + "\n"
+ "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getPath() + "\n"
+ "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getPath() + "\n"
+ "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getPath() + "\n"
+ "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath() + "\n"
+ "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_NOTIFICATIONS).getPath() + "\n"
+ "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getPath() + "\n"
+ "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PODCASTS).getPath() + "\n"
+ "getExternalStoragePublicDirectory = " + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES).getPath() + "\n"
+ "=======Environment调用结束=======\n \n"
+ "=======Context调用出的目录=======\n"
+ "getCacheDir = " + getCacheDir().getPath() + "\n"
+ "getDataDir = " + getDataDir().getPath() + "\n"//Android7.0
+ "getFilesDir = " + getFilesDir().getPath() + "\n"
+ "getExternalMediaDirs = " + Arrays.toString(getExternalMediaDirs()) + "\n"
+ "getExternalCacheDir = " + getExternalCacheDir() + "\n"
+ "getExternalCacheDirs = " + Arrays.toString(getExternalCacheDirs()) + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(null) + "\n"
+ "getExternalFilesDirs = " + Arrays.toString(getExternalFilesDirs(null)) + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getPath() + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(Environment.DIRECTORY_ALARMS).getPath() + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(Environment.DIRECTORY_DCIM).getPath() + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(Environment.DIRECTORY_MOVIES).getPath() + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(Environment.DIRECTORY_MUSIC).getPath() + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getPath() + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(Environment.DIRECTORY_NOTIFICATIONS).getPath() + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(Environment.DIRECTORY_PICTURES).getPath() + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(Environment.DIRECTORY_PODCASTS).getPath() + "\n"
+ "getExternalFilesDir = " + getExternalFilesDir(Environment.DIRECTORY_RINGTONES).getPath() + "\n"
+ "=======Context调用出的目录结束=======\n");
输出如下:
I/相关API调用: =======Environment调用=======
getRootDirectory = /system
getDownloadCacheDirectory = /data/cache
getDataDirectory = /data
getExternalStorageDirectory = /storage/emulated/0
getExternalStoragePublicDirectory = /storage/emulated/0/Documents
getExternalStoragePublicDirectory = /storage/emulated/0/Alarms
getExternalStoragePublicDirectory = /storage/emulated/0/DCIM
getExternalStoragePublicDirectory = /storage/emulated/0/Movies
getExternalStoragePublicDirectory = /storage/emulated/0/Music
getExternalStoragePublicDirectory = /storage/emulated/0/Download
getExternalStoragePublicDirectory = /storage/emulated/0/Notifications
getExternalStoragePublicDirectory = /storage/emulated/0/Pictures
getExternalStoragePublicDirectory = /storage/emulated/0/Podcasts
getExternalStoragePublicDirectory = /storage/emulated/0/Ringtones
=======Environment调用结束=======
=======Context调用出的目录=======
getCacheDir = /data/user/0/com.xxx.xxxxx/cache
getDataDir = /data/user/0/com.xxx.xxxxx
getFilesDir = /data/user/0/com.xxx.xxxxx/files
getExternalMediaDirs = [/storage/emulated/0/Android/media/com.xxx.xxxxx]
getExternalCacheDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/cache
getExternalCacheDirs = [/storage/emulated/0/Android/data/com.xxx.xxxxx/cache]
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files
getExternalFilesDirs = [/storage/emulated/0/Android/data/com.xxx.xxxxx/files]
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files/Documents
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files/Alarms
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files/DCIM
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files/Movies
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files/Music
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files/Download
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files/Notifications
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files/Pictures
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files/Podcasts
getExternalFilesDir = /storage/emulated/0/Android/data/com.xxx.xxxxx/files/Ringtones
=======Context调用出的目录结束=======
1、获取文件夹、文件和Uri
1.1、Android7.0前
在Android6.0之前和Android6.0之后,只是获取文件需要增加一个读取权限的区别而已
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
而获取文件目录都是如下
Environment.getExternalStorageDirectory().getPath()+"子目录";
//或者
Environment.getExternalStoragePublicDirectory(String type).getPath()+"子目录";
//若想要取以上文件夹中的其他文件夹或者文件,则在后面拼接即可,如下
String testFileTest = Environment.getExternalStoragePublicDirectory(String type).getPath()+File.separator+"test(.文件后缀)";
获取文件Uri
Uri uri = Uri.fromFile(file);
1.2、Android7.0后
Android7.0和Android7.0之后,获取文件目录的方式跟Android7.0之前没什么区别
只不过要在Manifest.xml文件中增加FileProvider配置,否则取不到File,并且不能再直接通过File方式获取文件输入输出流对文件进行读写和其他操作
<!--FileProvider-->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.test.provider.xxxxxx"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/public_file_path" />
</provider>
上面android:authorities="com.test.provider.xxxxxx"的内容为com.test.provider.xxxxxx,一般情况下,android:authorities的值为包名+其他,这个值关系到后面如何获取文件的Uri
//获取外部存储空间目录及其子文件夹目录下的文件夹和文件的Uri,即通过Environment.getXXXDir获取到的目录和文件获取Uri的方式
Uri uri = FileProvider.getUriForFile(this, getPackageName(), file);
//如果android:authorities的值不止有包名,则获取方式为
Uri uri = FileProvider.getUriForFile(this, getPackageName()+"其他", file);
//而Context调出的目录,例如getExternalCacheDir()目录下用这种方式取得Uri
Uri uri = Uri.fromFile(file);
//得到uri之后才可以根据Uri得到输入输出流对文件进行操作
InputStream inputStream = context.getContentResolver().openInputStream(uri);
OutputStream outputStream = context.getContentResolver().openOutputStream(uri);
到这里为止,根据以上的所得Android7.0前和Android7.0及之后,在获取外部存储空间Uri时的兼容代码可以如下
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri uri = FileProvider.getUriForFile(this, getPackageName()+"其他", file);
} else {
Uri uri = Uri.fromFile(file);
}
而Context调出的目录,例如getExternalCacheDir()目录下文件的Uri获取方式则不变
1.3、Android Q后
Android Q 在外部存储设备中为每个应用提供了一个“隔离存储沙盒”(例如 /sdcard)。任何其他应用都无法直接访问您应用的沙盒文件。自己本应用访问自己保存在沙盒中的文件属于私有,所以访问不需要任何权限。如果需要访问系统媒体文件,需要申请新的媒体权限:READ_MEDIA_IMAGES,READ_MEDIA_VIDEO,READ_MEDIA_AUDIO
对于Android Q来说,沙盒还算是刚开始,所以官方的限制也没那么严格,可以通过在<application>中配置android:requestLegacyExternalStorage="true"
,那么同样能访问系统的相关媒体文件
对于沙盒存储,文件存放都要存放到对应的位置,如一张图片要放到
getExternalFilesDir(Environment.DIRECTORY_PICTURES)
当中,若getExternalFilesDir(Environment.DIRECTORY_PICTURES)
为空,则放到
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
当中,这样能将对应类型的文件放到对应的文件目录下,不至于乱放
理解:沙盒存储,就是将对应的文件类型,放到对应的目录或者Android/data/对应的包名文件当中去
1.3.1、获取Uri
对于Android Q来说,设置了android:requestLegacyExternalStorage="true"
依然可以跟之前一样,存放文件并没有一定要求完全沙盒存储
Android Q以下或者Android Q设置了android:requestLegacyExternalStorage="true",依然可以在Environment.getExternalStorageDirectory().getPath();
目录下存放文件和操作文件
//Uri获取
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri uri = FileProvider.getUriForFile(this, getPackageName()+"其他", file);
} else {
Uri uri = Uri.fromFile(file);
}
若无设置android:requestLegacyExternalStorage
或者大于Android Q来说,则必须按照完全沙盒存储
//文件存储和操作目录必须放于该公共目录下
Environment.getExternalStoragePublicDirectory(String type).getPath();
//或者私有存储
getExternalFilesDir(String type).getPath()
//Uri获取
Uri uri = Uri.fromFile(file);
2、创建文件夹
2.1、创建文件夹和其子目录
if(!file.exists()){
file.mkdirs();
}
2.2、创建单一文件夹
if(!file.exists()){
file.mkdir();
}
3、获取文件输入输出流
3.1、根据Uri获取输入输出流
InputStream inputStream = context.getContentResolver().openInputStream(uri);
OutputStream outputStream = context.getContentResolver().openOutputStream(uri);
3.2、根据File获取输入输出流
根据File获取输入输出流是有前提的,那就是文件路径必须对。
这个前提看似很简单,但是根据以上说的版本差异,经常会出现兼容未做好而导致通过File获取输入输出流或者对文件进行操作时,提示:FileNotFoundException: open failed: EPERM (Operation not permitted)
所以必须根据版本差异,是使用非沙盒存储还是沙盒存储,这点是必须要注意的
File file = new File(path);
//path一定,一定,一定要根据是否沙盒确定好存放路径,并且申请相关权限
InputStream inputStream = new FileInputStream(file);
OutputStream outputStream = new FileOutputStream(file);
4、FileProvider配置
<?xml version="1.0" encoding="utf-8"?>
<paths>
<!--1、对应内部内存卡根目录:Context.getFileDir()/com.xxx.xxx-->
<files-path
name="int_root"
path="com.xxx.xxx/" />
<!--2、对应应用默认缓存根目录:Context.getCacheDir()/com.xxx.xxx-->
<cache-path
name="app_cache"
path="com.xxx.xxx/" />
<!--3、对应外部内存卡根目录:Environment.getExternalStorageDirectory()/com.xxx.xxx-->
<external-path
name="ext_root"
path="com.xxx.xxx/" />
<!--4、对应外部内存卡根目录下的APP公共目录:Context.getExternalFileDir(String)/com.xxx.xxx-->
<external-files-path
name="ext_pub"
path="com.xxx.xxx/" />
<!--5、对应外部内存卡根目录下的APP缓存目录:Context.getExternalCacheDir()/com.xxx.xxx-->
<external-cache-path
name="ext_cache"
path="com.xxx.xxx/" />
</paths>
5、copy文件
/**
* 复制文件
* @param context 上下文对象
* @param sourceUri 被复制文件的Uri
* @param savePath 粘贴路径
* @return 返回File
*/
public static File copyFile(Context context, Uri sourceUri, String savePath) {
InputStream inputStream = null;
OutputStream fos = null;
try {
inputStream = context.getContentResolver().openInputStream(sourceUri);
String path = sourceUri.getPath();
if (path == null) {
return null;
}
int i = path.lastIndexOf("/") + 1;
String substring = path.substring(i);
File directory = new File(savePath);
if (!directory.exists()) {
directory.mkdirs();
}
File file = new File(savePath, substring);
if (file.exists()) {
if (inputStream != null) {
inputStream.close();
}
return file;
}
fos = new FileOutputStream(file);
// long sum = 0;
byte[] buf = new byte[2048];
int len;
while (inputStream != null && (len = inputStream.read(buf)) != -1) {
fos.write(buf, 0, len);
// sum += len;
}
fos.flush();
return file;
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
使用
File file;
Uri uri;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
//想放到公共目录,理论上可被复制的文件也可被访问,故而大多情况下复制到沙盒公共目录下是多余的
file = FileUtils.copyFile(this, uri,
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS).getPath()
+ File.separator + Env.DIR_PUBLIC);
//或者放到私有目录沙盒
file = FileUtils.copyFile(this, uri,
getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS).getPath()
+ File.separator + Env.DIR_PUBLIC);
uri = Uri.fromFile(file);
} else {
file = FileUtils.copyFile(this, uri,Environment.getExternalStorageDirectory().getPath()+ File.separator + Env.DIR_PUBLIC);
uri = FileProvider.getUriForFile(this, getPackageName() + "其他", file);
}
6、根据路径路径以合适的程序打开文件
//建立一个文件类型与文件后缀名的匹配表
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"},
{"", "*/*"}
};
/**
* 根据路径打开文件
*
* @param context 上下文
* @param path 文件路径
*/
public static void openFileByPath(Context context, String path) {
try {
Intent intent = new Intent();
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
//设置intent的Action属性
intent.setAction(Intent.ACTION_VIEW);
File file = new File(path);
//获取文件file的MIME类型
String type = getMIMEType(file);
//设置intent的data和Type属性。android 7.0以上crash,改用provider
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", file);//android 7.0以上
intent.setDataAndType(fileUri, type);
grantUriPermission(context, fileUri, intent);
} else {
intent.setDataAndType(/*uri*/Uri.fromFile(file), type);
}
//跳转
context.startActivity(intent);
} catch (Exception e) {
e.printStackTrace();
}
}
private static String getMIMEType(File file) {
String type = "*/*";
String fName = file.getName();
//获取后缀名前的分隔符"."在fName中的位置。
int dotIndex = fName.lastIndexOf(".");
if (dotIndex < 0) {
return type;
}
/* 获取文件的后缀名 */
String end = fName.substring(dotIndex).toLowerCase();
if (end.equals("")) return type;
//在MIME和文件类型的匹配表中找到对应的MIME类型。
for (int i = 0; i < MIME_MapTable.length; i++) { //MIME_MapTable??在这里你一定有疑问,这个MIME_MapTable是什么?
if (end.equals(MIME_MapTable[i][0]))
type = MIME_MapTable[i][1];
}
return type;
}
private static void grantUriPermission(Context context, Uri fileUri, Intent intent) {
List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
String packageName = resolveInfo.activityInfo.packageName;
context.grantUriPermission(packageName, fileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
}
7、根据Uri得到文件路径
/**
* 根据公共存储目录文件Uri得到文件路径
* @param context 上下文对象
* @param uri 需要查找文件的Uri
* @return 返回路径
*/
public static String getPath(final Context context, final Uri uri) {
// DocumentProvider
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final String type = split[0];
if ("primary".equalsIgnoreCase(type)) {
return Environment.getExternalStorageDirectory() + "/" + split[1];
}
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
final String id = DocumentsContract.getDocumentId(uri);
final Uri contentUri = ContentUris.withAppendedId(
Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
return getDataColumn(context, contentUri, null, null);
}
// MediaProvider
else if (isMediaDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
final String[] split = docId.split(":");
final 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;
}
final String selection = "_id=?";
final String[] selectionArgs = new String[]{split[1]};
return getDataColumn(context, contentUri, selection, selectionArgs);
}
}
// MediaStore (and general)
else if ("content".equalsIgnoreCase(uri.getScheme())) {
return getDataColumn(context, uri, null, null);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
return uri.getPath();
}
return null;
}