天天看點

Android4.4之後SD卡存儲方案1,Context.getExternalFilesDir()2,Context.getExternalMediaDirs()3,Storage Access Framework (SAF)

由于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,Context.getExternalFilesDir()2,Context.getExternalMediaDirs()3,Storage Access Framework (SAF)

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() + ", mime=" + docCursor
                        .getString());
                mCurrentDirectoryUri = uri;
                mCurrentDirectoryTextView.setText(docCursor.getString());
                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() + ", mime=" + childCursor
                        .getString());
                DirectoryEntry entry = new DirectoryEntry();
                entry.fileName = childCursor.getString();
                entry.mimeType = childCursor.getString();
                entry.docId = childCursor.getString();
                entry.flag = childCursor.getInt();
                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