美文网首页
关于Android文件的那些事

关于Android文件的那些事

作者: TenXu | 来源:发表于2022-05-10 17:41 被阅读0次

前言

在了解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;
    }

相关文章

网友评论

      本文标题:关于Android文件的那些事

      本文链接:https://www.haomeiwen.com/subject/fyrlurtx.html