Android4.4之后SD卡存储方案

由于Android4.4之后,Android限制了第三方应用在SD卡中的公用目录的写权限,所以我们无法再公用目录创建文件夹,写入文件,但是读操作不受限制,(系统应用如文件管理器,或者root用户不除外)

第三方应用想要写入SD卡,有以下几种方案:

1,Context.getExternalFilesDir()

获取应用的专有目录如:/storage/sdcard1/Android/data/com.xxx.email
第三方应用有该目录的读写权限,但是该目录下的文件不会被MediaScanner检索到,并且在应用删除后,该目录同样会被删除。

2,Context.getExternalMediaDirs()

Android5.0新增,该目录为:
/storage/sdcard1/Android/media/com.xxx.email
该目录和Context.getExternalFilesDir()获取的目录的唯一区别就是该目录下的文件能够被媒体扫描到,进而在如 相册中浏览到,并且可以通过MediaStore被其他应用获取。 应用删除后该目录会被删除。
(在金立手机上出现过没有写权限的时候,然后拔插sd卡后,又有了写权限,不知道是金立手机不稳定还是本身这个API不稳定)

3,Storage Access Framework (SAF)

中文guide:http://developer.android.com/intl/zh-cn/guide/topics/providers/document-provider.html
这个存储访问框架是Android4.4引入的。其功能类似于ContentProvider和ContentResolver
文件提供者-通过DocumentsProvider提供文件访问服务(例如云存储应用,Google云硬盘)
客户端应用-则通过调用 ACTION_OPEN_DOCUMENT和/或 ACTION_CREATE_DOCUMENT Intent 。接收文件提供者返回的文件。
选取器-系统提供的通用UI,用来访问浏览提供者提供的文件
比如通过:
Intent intent =newIntent(Intent.ACTION_OPEN_DOCUMENT);
获取一个图片时,系统会提供一个下图形式的选择器,让用户从中选择文件。类似于ACTION_PICK 或ACTION_GET_CONTENT。

除了上面所说的,Android5.0之后还提供了ACTION_OPEN_DOCUMENT_TREE这种方式来访问目录,并可以新建,删除文件。

我认为这会是Android存储方向上的趋势,操作方法如下:

1.通过Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);

startActivityForResult(intent, DOC_TREE_CODE);
打开一个文件选择器(系统提供)。如下图:
Android4.4之后SD卡存储方案_第1张图片

2.用户可以在sd目录下新建目录,然后选择该目录。

在选择后,onActivityResult返回的intent中返回该目录的uri:
content://com.android.externalstorage.documents/tree/00F3-1408%3Awps
然后可以通过
buildDocumentUriUsingTree:可以获取文件或者文件夹本身的信息。
buildChildDocumentsUriUsingTree:可以获取文件夹下的内容信息。
示例代码如下:

ContentResolver contentResolver = getActivity().getContentResolver();
        Uri docUri = DocumentsContract.buildDocumentUriUsingTree(uri,
                DocumentsContract.getTreeDocumentId(uri));
        Uri childrenUri = DocumentsContract.buildChildDocumentsUriUsingTree(uri,
                DocumentsContract.getTreeDocumentId(uri));
//通过docUri可以查询该文档或者目录的 名称,类型,flags(是否可写等信息)
        Cursor docCursor = contentResolver.query(docUri, new String[]{
                Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE}, null, null, null);
        try {
            while (docCursor.moveToNext()) {
                Log.d(TAG, "found doc =" + docCursor.getString(0) + ", mime=" + docCursor
                        .getString(1));
                mCurrentDirectoryUri = uri;
                mCurrentDirectoryTextView.setText(docCursor.getString(0));
                mCreateDirectoryButton.setEnabled(true);
            }
        } finally {
            closeQuietly(docCursor);
        }
//通过childrenUri可以查询目录下子文件或者子目录的信息
        Cursor childCursor = contentResolver.query(childrenUri, new String[]{
                Document.COLUMN_DISPLAY_NAME, Document.COLUMN_MIME_TYPE, Document.COLUMN_DOCUMENT_ID,Document.COLUMN_FLAGS}, null, null, null);
        try {
            List<DirectoryEntry> directoryEntries = new ArrayList<>();
            while (childCursor.moveToNext()) {
                Log.d(TAG, "found child=" + childCursor.getString(0) + ", mime=" + childCursor
                        .getString(1));
                DirectoryEntry entry = new DirectoryEntry();
                entry.fileName = childCursor.getString(0);
                entry.mimeType = childCursor.getString(1);
                entry.docId = childCursor.getString(2);
                entry.flag = childCursor.getInt(3);
                directoryEntries.add(entry);
            }
            mAdapter.setDirectoryEntries(directoryEntries);
            mAdapter.setTreeUri(uri);
            mAdapter.setActivity(getActivity());
            mAdapter.notifyDataSetChanged();
        } finally {
            closeQuietly(childCursor);
        }

3.新建文件

通过createDocument

Uri dirPic = DocumentsContract.createDocument(cr, dir, "image/png", "pic2.png");

可以在文件夹下创建目录或者文档。返回的依然是Uri
如果想对新建的文件进行写操作(比如邮件下载)只能通过如下方式:

os = contentResolver.openOutputStream(dirPic);

获取文件的输出流,然后写入内容

4.删除文件

文件的删除则是:

//注意这里的pic这个uri是DocumentUri
DocumentsContract.deleteDocument(contentResolver, pic))

综上

方案1和2可以实现将附件保存在SD卡上
优点:不用改变现有的存储结构,只需将sd卡下的附件存储位置限定在
/storage/sdcard1/Android/data/com.xxx.email或者/storage/sdcard1/Android/media/com.xxx.email下折腾。并且/storage/sdcard1/Android/data/com.xxx.email目录可以覆盖android4.4以上的系统
缺点:目录选择受限,并且随着应用卸载,该目录会被删除。

方案3.
优点:可以在5.0以上很好的解决sd卡下文件夹新建,随便哪里都可以建.并且这种操作方式也是一种趋势。
缺点:
1,Android4.4 和4.4W这两个版本是个空挡。
2,由于整套API是新的,不涉及File操作,所以所有应用内涉及到新建文件,删除文件的地方,都要写2套操作,一套是File的操作,一套是Uri的操作。并需要对系统版本进行判断。

参考:
Android源码:
android5.1\development\samples\ApiDemos\src\com\example\android\apis\content\DocumentsSample.java
示例代码:https://github.com/googlesamples/android-DirectorySelection
文档:http://developer.android.com/intl/zh-cn/guide/topics/providers/document-provider.html

你可能感兴趣的:(android,存储,SD卡)