由于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() + ", 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