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);
打開一個文件選擇器(系統提供)。如下圖:
這裏寫圖片描述

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章