Android存储访问框架的使用小结

目录
  • 打开系统文件选择器与文件过滤
  • 打开指定文件夹
  • 文件夹权限申请
  • 创建文件夹
  • 存储访问框架API
  • 获取文件夹文件
  • 和MediaStore API的不同

存储访问框架,简称:SAF, 就是系统文件选择器+文件操作API。先选择文件,在用文件操作API处理文件。系统文件选择器,就和Windows的文件选择框一样。

其实绝大多数app,都不会使用这个东西,因为太不方便了。图片,视频,普通文件,需要用户去翻文件夹找,这样的用户体验实在太差了。所以大家都是用第三方的或者自己写一个文件选择器。

之所以讲SAF,一,是因为Android11以后,使用MediaStore无法访问到非多媒体文件了,需要依赖SAF了。二,外卡和SD卡的操作依赖于存储访问框架授权。

打开系统文件选择器与文件过滤

 Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("application/*");

        startActivityForResult(intent, REQUEST_CODE)

setType的值是mime type, 可以是"image/*", "*/*",  其中*是通配符。"image/*"代码所有类型的图片。"*/*"代表所有类型的文件。

当只需要打开几种文件类型时,可以用Intent.EXTRA_MIME_TYPES。同时setType设成“*/*”。

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("*/*");
        intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {
                "application/pdf", // .pdf
                "application/vnd.oasis.opendocument.text", // .odt
                "text/plain" // .txt
        });

        startActivityForResult(intent, REQUEST_CODE)

Intent.ACTION_PICK和ACTION_GET_CONTENT,也可以打开文件选择框。ACTION_GET_CONTENT更加宽泛,除了文件其他类型的内容还可以取。

 Intent intent = new Intent(Intent.ACTION_PICK,
                    android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
    intent.setType("image/*");
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        intent.setType("image/*")

下面列举了所有的mime type:

private static final String[][] MIME_TYPES = new String[][]{
            {"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"},
            {"ape", "audio/ape"},
            {"flac", "audio/flac"},
            {"m4u", "video/vnd.mpegurl"},
            {"m4v", "video/x-m4v"},
            {"mov", "video/quicktime"},
            {"mp2", "audio/x-mpeg"},
            {"mp3", "audio/x-mpeg"},
            {"mp4", "video/mp4"},
            {"mkv", "video/x-matroska"},
            {"flv", "video/x-flv"},
            {"divx", "video/x-divx"},
            {"mpa", "video/mpeg"},
            {"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"},
            {"rar", "application/x-rar"},
            {"", "*/*"}
    };

打开指定文件夹

利用DocumentsContract.EXTRA_INITIAL_URI,在打开文件选择器的时候,跳转到指定文件夹。只有android 8以上才行。

Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary:Download");
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*");
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);
startActivityForResult(intent, 1);

文件夹权限申请

当需要读取非公共文件夹里面的文件时,可以申请授权,授权后保存Uri,之后可以拼接这个Uri操作文件夹里的所有文件。

尤其是SD卡,从Android 5 开始文件的修改删除必须先授权,且必须通过SVF框架接口才能操作。

可以使用EXTRA_INITIAL_URI,打开指定文件夹,让用户授权

  Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
       Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary:Download");

       intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);

        startActivityForResult(intent) 

需要注意的是,Android 11以后,无法授权访问存储根目录,以及Download/,Android/, 这两个文件夹也无法授权。

创建文件夹

创建文件夹有两个情况,一个是在已授权的文件夹下,可以使用SVF框架API。

DocumentsContract.createDocument()

还有一种是在无授权的文件夹下创建,那么可以直接指定类型和名字,通过跳系统选择框创建。

 Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
        intent.addCategory(Intent.CATEGORY_OPENABLE);
        intent.setType("application/txt");
        intent.putExtra(Intent.EXTRA_TITLE, "testfile.txt");

        startActivityForResult(intent)

存储访问框架API

存储访问框架API,都在DocumentsContract里面,典型的有:

public static @Nullable Uri renameDocument(@NonNull ContentResolver content,
            @NonNull Uri documentUri, @NonNull String displayName) throws FileNotFoundException {

    }

    /**
     * Delete the given document.
     *
     * @param documentUri document with {@link Document#FLAG_SUPPORTS_DELETE}
     * @return if the document was deleted successfully.
     */
    public static boolean deleteDocument(@NonNull ContentResolver content, @NonNull Uri documentUri)
            throws FileNotFoundException {

    }

    /**
     * Copies the given document.
     *
     * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_COPY}
     * @param targetParentDocumentUri document which will become a parent of the source
     *         document's copy.
     * @return the copied document, or {@code null} if failed.
     */
    public static @Nullable Uri copyDocument(@NonNull ContentResolver content,
            @NonNull Uri sourceDocumentUri, @NonNull Uri targetParentDocumentUri)
            throws FileNotFoundException {

    }

    /**
     * Moves the given document under a new parent.
     *
     * @param sourceDocumentUri document with {@link Document#FLAG_SUPPORTS_MOVE}
     * @param sourceParentDocumentUri parent document of the document to move.
     * @param targetParentDocumentUri document which will become a new parent of the source
     *         document.
     * @return the moved document, or {@code null} if failed.
     */
    public static @Nullable Uri moveDocument(@NonNull ContentResolver content,
            @NonNull Uri sourceDocumentUri, @NonNull Uri sourceParentDocumentUri,
            @NonNull Uri targetParentDocumentUri) throws FileNotFoundException {

    }

    /**
     * Removes the given document from a parent directory.
     *
     * <p>In contrast to {@link #deleteDocument} it requires specifying the parent.
     * This method is especially useful if the document can be in multiple parents.
     *
     * @param documentUri document with {@link Document#FLAG_SUPPORTS_REMOVE}
     * @param parentDocumentUri parent document of the document to remove.
     * @return true if the document was removed successfully.
     */
    public static boolean removeDocument(@NonNull ContentResolver content, @NonNull Uri documentUri,
            @NonNull Uri parentDocumentUri) throws FileNotFoundException {

    }

获取文件夹文件

使用DocumentFile类获取文件夹里文件列表。

private ActivityResultLauncher<Object> openFile() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary:AuthSDK");
        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);
        return startActivityForResult(intent, new ActivityResultCallback<Intent>() {
            @Override
            public void onActivityResult(Intent result) {

                for (DocumentFile documentFile : DocumentFile.fromTreeUri(BaseApplication.getInstance().getApplicationContext(), Uri.parse(result.getData().toString())).listFiles()) {

                     Log.i("", documentFile.getUri());
                }
            }
        });
    }

下面的代码演示了,使用SVF读取文件内容,写内容,通过MediaStore查询文件属性。

private ActivityResultLauncher<Object> openFile() {
        Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
        Uri uri = Uri.parse("content://com.android.externalstorage.documents/document/primary:AuthSDK");
        intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri);

        return startActivityForResult(intent, new ActivityResultCallback<Intent>() {
            @Override
            public void onActivityResult(Intent result) {

                for (DocumentFile documentFile : DocumentFile.fromTreeUri(BaseApplication.getInstance().getApplicationContext(), Uri.parse(result.getData().toString())).listFiles()) {
                    try {
                        InputStream inputStream = BaseApplication.getInstance().getContentResolver().openInputStream(documentFile.getUri());
                        byte[] readData = new byte[1024];
                        inputStream.read(readData);

                        OutputStream outputStream = BaseApplication.getInstance().getContentResolver().openOutputStream(documentFile.getUri());
                        byte[] writeData = "alan gong".getBytes(StandardCharsets.UTF_8);
                        outputStream.write(writeData, 0, 9);
                        outputStream.close();
                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                            Uri mediaUri = MediaStore.getMediaUri(BaseApplication.getInstance().getApplicationContext(), documentFile.getUri());
                            long fileId = ContentUris.parseId(mediaUri);
                            Cursor query = BaseApplication.getInstance().getContentResolver().query(documentFile.getUri(), null, MediaStore.MediaColumns._ID + "=" + fileId, null, null);
                            int columnIndex = query.getColumnIndexOrThrow(MediaStore.MediaColumns.MIME_TYPE);
                            String mimeType = query.getString(columnIndex);
                            Log.i("", "");
                        }

                    } catch (FileNotFoundException e) {
                        e.printStackTrace();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
    }

使用MediaStore.getMediaUri(documentUri)可以转换,MediaStore Uri 和 Document Uri。通过MediaStore Uri中的数据库id,就可以查询文件的所有属性了。

MediaStore Uri:content://media/external_primary/file/101750

Document Uri: content://com.android.externalstorage.documents/tree/primary%3AAuthSDK

另外,

非公共目录下不能用File API操作的,即使通过SVF授权了, READ_EXTRNAL_PERMISSION的权限也给了。还是会抛出FileNotFoundException, 并且显示permission deny。

和MediaStore API的不同

存储访问框架API和MediaStore API的差异,在于存储访问框架API,是基于系统文件选择框的,用户选择了文件,那么相当于授权了, 可以访问所有类型的文件。而MediaStore的特点是可以查询出所有文件,但是开启分区存储后,只能查处多媒体文件,其他类型文件是不可以的。

到此这篇关于Android存储访问框架的使用的文章就介绍到这了,更多相关Android存储访问框架内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Android中的存储详解

    目录 1.存储在App内部 2.SD卡外部存储 3.SharedPreferences存储 4.使用SQLite数据库存储 4.1 自己完成一个BaseDao类 4.2 使用Google写的API处理 4.3 事务使用 总结 1.存储在App内部 最简单的一种.在尝试过程中发现,手机中很多文件夹都没有权限读写.我们可以将我们需要写的文件存放到App中的files文件夹中,当然我们有权限在整个App中读写文件 可以通过API获取一个file对象,这里的this就是MainActivity类 //

  • Android开发文件存储实例

    Android的文件存储,有I/O流的方式存储,与java一样,还有一种Android自己的SharePreferences存储方法. 下面看一个例子: 用I/O流的方式存储方法和SharePreferences存储方法,存放QQ账号和密码,再次进入页面时,把存储在文件中的账号密码显示在上面. activity_main.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns

  • Android实现文件存储案例

    本文实例为大家分享了Android实现文件存储的具体代码,供大家参考,具体内容如下 1.文件存储案例 public class TestActivity extends AppCompatActivity { private EditText mFileEdit; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.l

  • Android存储访问框架的使用小结

    目录 打开系统文件选择器与文件过滤 打开指定文件夹 文件夹权限申请 创建文件夹 存储访问框架API 获取文件夹文件 和MediaStore API的不同 存储访问框架,简称:SAF, 就是系统文件选择器+文件操作API.先选择文件,在用文件操作API处理文件.系统文件选择器,就和Windows的文件选择框一样. 其实绝大多数app,都不会使用这个东西,因为太不方便了.图片,视频,普通文件,需要用户去翻文件夹找,这样的用户体验实在太差了.所以大家都是用第三方的或者自己写一个文件选择器. 之所以讲S

  • Java集合框架超详细小结

    目录 一:Collection集合 1.1集合概述: 1.2集合架构 1.3Collection集合常用方法 二:迭代器Iterator 2.1Iterator接口 2.2Iterator的实现原理: 2.3增强for() 2.4迭代器注意事项 三:泛型 3.1泛型概述 3.2泛型的优缺点 3.3泛型的定义与使用 泛型方法 泛型接口 3.4泛型的通配符 通配符高级使用-----受限泛型 四:Java常见数据结构 4.1栈 4.2队列 4.3数组 4.4链表 4.5红黑树 五:List集合体系 5

  • Android的搜索框架实例详解

    基础知识 Android的搜索框架将代您管理的搜索对话框,您不需要自己去开发一个搜索框,不需要担心要把搜索框放什么位置,也不需要担心搜索框影响您当前的界面.所有的这些工作都由SearchManager类来为您处理(以下简称"搜索管理器"),它管理的Android搜索对话框的整个生命周期,并执行您的应用程序将发送的搜索请求,返回相应的搜索关键字. 当用户执行一个搜索,搜索管理器将使用一个专门的Intent把搜索查询的关键字传给您在配置文件中配置的处理搜索结果的Activity.从本质上讲

  • Android中Volley框架进行请求网络数据的使用

    问题的阐述:Android SDK中的HttpClient和HttpUrlConnection两种请求方式用来处理网络的复杂的操作,但当应用比较复杂的时候需要我们编写大量的代码处理很多东西:图像缓存,请求的调度等等: 解决:Volley就是为解决这些而生,它与2013年Google I/O大会上被提出:使得Android应用网络操作更方便更快捷:抽象了底层Http Client等实现的细节,让开发者更专注与产生RESTful Request.另外,Volley在不同的线程上异步执行所有请求而避免

  • Spring+Mybatis+Mysql搭建分布式数据库访问框架的方法

    一.前言 用Java开发企业应用软件, 经常会采用Spring+MyBatis+Mysql搭建数据库框架.如果数据量很大,一个MYSQL库存储数据访问效率很低,往往会采用分库存储管理的方式.本文讲述如何通过Spring+Mybatis构建多数据库访问的架构,并采用多线程提升数据库的访问效率. 需要说明一下,这种方式只适合数据库数量.名称固定,且不是特别多的情况.针对数据库数量不固定的情况,后面再写一篇处理方案. 二.整体方案 三.开发环境准备 3.1 下载Spring.Mybatis.Mysql

  • Android中网络框架简单封装的实例方法

    Android中网络框架的简单封装 前言 Android作为一款主要应用在移动终端的操作系统,访问网络是必不可少的功能.访问网络,最基本的接口有:HttpUrlConnection,HttpClient,而在后续的发展中,出现了Volley,OkHttp,Retrofit等网络封装库.由于各种原因,在实际的项目开发中,我们可能会需要在项目的版本迭代中,切换网络框架.如果对于网络框架没有好的封装,那么当需要切换网络框架时,可能就会有大量的迁移工作要做. 封装网络框架 在架构设计中,面向接口和抽象,

  • Android 网络请求框架解析之okhttp与okio

    安卓网络请求 先看一下今天的大纲 导入okhttp和okio依赖 禁用掉明文流量请求的检查 添加访问权限 布局及代码实现 运行结果 下面是具体步骤 一.导入okhttp和okio的依赖 1.打开File-Project Structure-Dependencies, 2.选择自己的程序文件,点击加号,选择Library Dependency 3.搜索okhttp,选择Com.squareup.okhttp3,点击ok按钮,此时可能需要较长时间 4.okio同上 5.应用,确认 6.此时我们可以看

  • 使用SharedPreferences在Android存储对象详细代码

    为什么不用SQLite? 原因多种:除了面向对象和关系数据库之间的阻抗不匹配时,SQLite可能是矫枉过正(带来了更多的开销)对于一些简单的用例,或者其用法和语句可能会完全不喜欢.其他Android ORM 框架(ORMLite, greenDAO, Sugar ORM, ActiveAndroid, 等)或NOSQL移动数据库,如Couchbase精简版(Beta版在此时间). Couchbase精简版基本上是一个JSON数据库,它们旨在降低复杂性,但是同时又违背了抽象漏洞定律(所有不证自明的

  • 详解Android控件状态依赖框架

    在生产型Android客户端软件(企业级应用)开发中,界面可能存在多个输入(EditText)和多个操作(MotionEvent和KeyEvent),且操作依赖于输入的状态.如下图所示的场景: 设定图中 确认操作依赖于商品编码和储位的状态 跳过操作不依赖于输入状态 登记差异操作依赖于储位和数量的状态 输入框有三种状态: 待输入: 待校验: 校验成功. 操作需要当其依赖的输入数据校验成功,才能执行. 如果在Activity中去判断输入框状态,那么实际需要调用(3个输入框)*(3种状态)*(3个按钮

  • Android互联网访问图片并在客户端显示的方法

    本文实例讲述了Android互联网访问图片并在客户端显示的方法.分享给大家供大家参考,具体如下: 1.布局界面 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:

随机推荐