轉(zhuǎn)載地址:https://www.zhihu.com/question/19703373/answer/131249943
原作者 :劉朋
作者:劉朋鏈接:https://www.zhihu.com/question/22996204/answer/131249991來(lái)源:知乎著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請(qǐng)聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請(qǐng)注明出處。Android 使用與其他平臺(tái)上基于磁盤的文件系統(tǒng)類似的文件系統(tǒng)。本文講述如何使用 Android 文件系統(tǒng)通過(guò) File API 讀取和寫入文件。
File 對(duì)象適合按照從開(kāi)始到結(jié)束的順序不跳過(guò)地讀取或?qū)懭氪罅繑?shù)據(jù)。 例如,它適合于圖片文件或通過(guò)網(wǎng)絡(luò)交換的任何內(nèi)容。
本文展示如何在您的應(yīng)用中執(zhí)行基本的文件相關(guān)任務(wù)。假定您熟悉 Linux 文件系統(tǒng)的基礎(chǔ)知識(shí)和 http://java.io** 中的標(biāo)準(zhǔn)文件輸入/輸出 API。
選擇內(nèi)部或外部存儲(chǔ)所有 Android 設(shè)備都有兩個(gè)文件存儲(chǔ)區(qū)域:“內(nèi)部”和“外部”存儲(chǔ)。這些名稱在 Android 早期產(chǎn)生,當(dāng)時(shí)大多數(shù)設(shè)備都提供內(nèi)置的非易失性內(nèi)存(內(nèi)部存儲(chǔ)),以及移動(dòng)存儲(chǔ)介質(zhì),比如微型 SD 卡(外部存儲(chǔ))。一些設(shè)備將永久性存儲(chǔ)空間劃分為“內(nèi)部”和“外部”分區(qū),即便沒(méi)有移動(dòng)存儲(chǔ)介質(zhì),也始終有兩個(gè)存儲(chǔ)空間,并且無(wú)論外部存儲(chǔ)設(shè)備是否可移動(dòng),API 的行為均一致。以下列表匯總了關(guān)于各個(gè)存儲(chǔ)空間的實(shí)際信息。
內(nèi)部存儲(chǔ):它始終可用。只有您的應(yīng)用可以訪問(wèn)此處保存的文件。當(dāng)用戶卸載您的應(yīng)用時(shí),系統(tǒng)會(huì)從內(nèi)部存儲(chǔ)中移除您的應(yīng)用的所有文件。當(dāng)您希望確保用戶或其他應(yīng)用均無(wú)法訪問(wèn)您的文件時(shí),內(nèi)部存儲(chǔ)是最佳選擇。
外部存儲(chǔ):它并非始終可用,因?yàn)橛脩艨刹捎?USB 存儲(chǔ)設(shè)備的形式裝載外部存儲(chǔ),并在某些情況下會(huì)從設(shè)備中將其移除。它是全局可讀的,因此此處保存的文件可能不受您控制地被讀取。當(dāng)用戶卸載您的應(yīng)用時(shí),只有在您通過(guò) getExternalFilesDir() 將您的應(yīng)用的文件保存在目錄中時(shí),系統(tǒng)才會(huì)從此處移除您的應(yīng)用的文件。對(duì)于無(wú)需訪問(wèn)限制以及您希望與其他應(yīng)用共享或允許用戶使用計(jì)算機(jī)訪問(wèn)的文件,外部存儲(chǔ)是最佳位置。
注:在 Android N 之前,內(nèi)部文件可以通過(guò)放寬文件系統(tǒng)權(quán)限讓其他應(yīng)用訪問(wèn)。而如今不再是這種情況。如果您希望讓其他應(yīng)用訪問(wèn)私有文件的內(nèi)容,則您的應(yīng)用可使用 FileProvider。
提示:盡管應(yīng)用默認(rèn)安裝在內(nèi)部存儲(chǔ)中,但您可在您的清單文件中指定 android:installLocation 屬性,這樣您的應(yīng)用便可安裝在在外部存儲(chǔ)中。當(dāng) APK 非常大且它們的外部存儲(chǔ)空間大于內(nèi)部存儲(chǔ)時(shí),用戶更青睞這個(gè)選擇。 如需了解詳細(xì)信息,請(qǐng)參閱應(yīng)用安裝位置。
獲取外部存儲(chǔ)的權(quán)限要向外部存儲(chǔ)寫入信息,您必須在您的清單文件中請(qǐng)求 WRITE_EXTERNAL_STORAGE 權(quán)限。
注意:目前,所有應(yīng)用都可以讀取外部存儲(chǔ),而無(wú)需特別的權(quán)限。 但這在將來(lái)版本中會(huì)進(jìn)行更改。如果您的應(yīng)用需要讀取外部存儲(chǔ)(但不向其寫入信息),那么您將需要聲明 READ_EXTERNAL_STORAGE 權(quán)限。要確保您的應(yīng)用繼續(xù)正常工作,您應(yīng)在更改生效前聲明此權(quán)限。
將文件保存在內(nèi)部存儲(chǔ)中在內(nèi)部存儲(chǔ)中保存文件時(shí),您可以通過(guò)調(diào)用以下兩種方法之一獲取作為 File 的相應(yīng)目錄:
1.getFilesDir()返回表示您的應(yīng)用的內(nèi)部目錄的 File 。2.getCacheDir()返回表示您的應(yīng)用臨時(shí)緩存文件的內(nèi)部目錄的 File。 務(wù)必刪除所有不再需要的文件并對(duì)在指定時(shí)間您使用的內(nèi)存量實(shí)現(xiàn)合理大小限制,比如,1MB。 如果在系統(tǒng)即將耗盡存儲(chǔ),它會(huì)在不進(jìn)行警告的情況下刪除您的緩存文件。
要在這些目錄之一中新建文件1.您可以使用 File() 構(gòu)造函數(shù),傳遞指定您的內(nèi)部存儲(chǔ)目錄的上述方法之一所提供的 File。例如:
File file = new File(context.getFilesDir(), filename);2.或者,您可以調(diào)用 openFileOutput() 獲取寫入到內(nèi)部目錄中的文件的 FileOutputStream。例如,下面顯示如何向文件寫入一些文本:
String filename = "myfile";
String string = "Hello world!";
FileOutputStream outputStream;
try {
outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
outputStream.write(string.getBytes()); outputStream.close();
} catch (Exception e) { e.printStackTrace();}
3.或者,如果您需要緩存某些文件,您應(yīng)改用 createTempFile()。例如,以下方法從 URL 提取文件名并正在您的應(yīng)用的內(nèi)部緩存目錄中以該名稱創(chuàng)建文件:
public File getTempFile(Context context, String url) { File file; try { String fileName = Uri.parse(url).getLastPathSegment(); file = File.createTempFile(fileName, null, context.getCacheDir()); } catch (IOException e) { // Error while creating file } return file;}
注:您的應(yīng)用的內(nèi)部存儲(chǔ)設(shè)備目錄由您的應(yīng)用在 Android 文件系統(tǒng)特定位置中的軟件包名稱指定。從技術(shù)上講,如果您將文件模式設(shè)置為可讀,那么,另一應(yīng)用也可以讀取您的內(nèi)部文件。 但是,此應(yīng)用也需要知道您的應(yīng)用的軟件包名稱和文件名。 其他應(yīng)用無(wú)法瀏覽您的內(nèi)部目錄并且沒(méi)有讀寫權(quán)限,除非您明確將文件設(shè)置為可讀或可寫。 只要您為內(nèi)部存儲(chǔ)上的文件使用 MODE_PRIVATE,其他應(yīng)用便從不會(huì)訪問(wèn)它們。
將文件保存在外部存儲(chǔ)中由于外部存儲(chǔ)可能不可用—比如,當(dāng)用戶已將存儲(chǔ)裝載到電腦或已移除提供外部存儲(chǔ)的 SD 卡時(shí)—因此,在訪問(wèn)它之前,您應(yīng)始終確認(rèn)其容量。 您可以通過(guò)調(diào)用 getExternalStorageState() 查詢外部存儲(chǔ)狀態(tài)。 如果返回的狀態(tài)為 MEDIA_MOUNTED,那么您可以對(duì)您的文件進(jìn)行讀寫。 例如,以下方法對(duì)于確定存儲(chǔ)可用性非常有用:
* Checks if external storage is available for read and write */
public boolean isExternalStorageWritable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state)) { return true; }
return false;
}
/* Checks if external storage is available to at least read */public boolean isExternalStorageReadable() {
String state = Environment.getExternalStorageState();
if (Environment.MEDIA_MOUNTED.equals(state) ||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state))
{ return true; }
return false;
}
盡管外部存儲(chǔ)可被用戶和其他應(yīng)用進(jìn)行修改,但您可在此處保存兩類文件:公共文件應(yīng)供其他應(yīng)用和用戶自由使用的文件。 當(dāng)用戶卸載您的應(yīng)用時(shí),用戶應(yīng)仍可以使用這些文件。例如,您的應(yīng)用拍攝的照片或其他已下載的文件。
私有文件屬于您的應(yīng)用且在用戶卸載您的應(yīng)用時(shí)應(yīng)予刪除的文件。 盡管這些文件在技術(shù)上可被用戶和其他應(yīng)用訪問(wèn)(因?yàn)樗鼈兇鎯?chǔ)在外部存儲(chǔ)中), 但它們實(shí)際上不向您的應(yīng)用之外的用戶提供任何輸出值。 當(dāng)用戶卸載您的應(yīng)用時(shí),系統(tǒng)會(huì)刪除應(yīng)用外部私有目錄中的所有文件。例如,您的應(yīng)用下載的其他資源或臨時(shí)介質(zhì)文件。
如果您要將公共文件保存在外部存儲(chǔ)設(shè)備上,請(qǐng)使用 getExternalStoragePublicDirectory() 方法獲取表示外部存儲(chǔ)設(shè)備上相應(yīng)目錄的 File。 該方法使用指定您想要保存以便它們可以與其他公共文件在邏輯上組織在一起的文件類型的參數(shù),比如 DIRECTORY_MUSIC 或 DIRECTORY_PICTURES。例如:
public File getAlbumStorageDir(String albumName) {// Get the directory for the user’s public pictures directory.
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), albumName);
if (!file.mkdirs())
{Log.e(LOG_TAG, “Directory not created”);}
return file;
}
如果您要保存您的應(yīng)用專用文件,您可以通過(guò)調(diào)用 getExternalFilesDir() 并向其傳遞指示您想要的目錄類型的名稱,從而獲取相應(yīng)的目錄。通過(guò)這種方法創(chuàng)建的各個(gè)目錄將添加至封裝您的應(yīng)用的所有外部存儲(chǔ)文件的父目錄,當(dāng)用戶卸載您的應(yīng)用時(shí),系統(tǒng)會(huì)刪除這些文件。
例如,您可以使用以下方法來(lái)創(chuàng)建個(gè)人相冊(cè)的目錄:
public File getAlbumStorageDir(Context context, String albumName) {// Get the directory for the app’s private pictures directory.File file = new File(context.getExternalFilesDir(Environment.DIRECTORY_PICTURES), albumName);if (!file.mkdirs()) {Log.e(LOG_TAG, “Directory not created”);}return file;}
如果沒(méi)有適合您文件的預(yù)定義子目錄名稱,您可以改為調(diào)用 getExternalFilesDir() 并傳遞 null。這將返回外部存儲(chǔ)上您的應(yīng)用的專用目錄的根目錄。切記,getExternalFilesDir() 在用戶卸載您的應(yīng)用時(shí)刪除的目錄內(nèi)創(chuàng)建目錄。如果您正保存的文件應(yīng)在用戶卸載您的應(yīng)用后仍然可用—比如,當(dāng)您的應(yīng)用是照相機(jī)并且用戶要保留照片時(shí)—您應(yīng)改用 getExternalStoragePublicDirectory()。
無(wú)論您對(duì)于共享的文件使用 {@linkandroid.os.Environment#getExternalStoragePublicDirectory getExternalStoragePublicDirectory()} 還是對(duì)您的應(yīng)用專用文件使用 getExternalFilesDir(),您使用諸如 DIRECTORY_PICTURES 的 API 常數(shù)提供的目錄名稱非常重要。這些目錄名稱可確保系統(tǒng)正確處理文件。 例如,保存在 DIRECTORY_RINGTONES 中的文件由系統(tǒng)媒體掃描程序歸類為鈴聲,而不是音樂(lè)。
查詢可用空間如果您事先知道您將保存的數(shù)據(jù)量,您可以查出是否有足夠的可用空間,而無(wú)需調(diào)用 getFreeSpace() 或 getTotalSpace() 引起 IOException。這些方法分別提供目前的可用空間和存儲(chǔ)卷中的總空間。 此信息也可用來(lái)避免填充存儲(chǔ)卷以致超出特定閾值。
但是,系統(tǒng)并不保證您可以寫入與 getFreeSpace() 指示的一樣多的字節(jié)。如果返回的數(shù)字比您要保存的數(shù)據(jù)大小大出幾 MB,或如果文件系統(tǒng)所占空間不到 90%,則可安全繼續(xù)操作。否則,您可能不應(yīng)寫入存儲(chǔ)。
注:保存您的文件之前,您無(wú)需檢查可用空間量。 您可以嘗試立刻寫入文件,然后在 IOException 出現(xiàn)時(shí)將其捕獲。 如果您不知道所需的確切空間量,您可能需要這樣做。 例如,如果在保存文件之前通過(guò)將 PNG 圖像轉(zhuǎn)換成 JPEG 更改了文件的編碼,您事先將不知道文件的大小。
刪除文件您應(yīng)始終刪除不再需要的文件。刪除文件最直接的方法是讓打開(kāi)的文件參考自行調(diào)用 delete()。
myFile.delete();如果文件保存在內(nèi)部存儲(chǔ)中,您還可以請(qǐng)求 Context 通過(guò)調(diào)用 deleteFile() 來(lái)定位和刪除文件:
myContext.deleteFile(fileName);
注:當(dāng)用戶卸載您的應(yīng)用時(shí),Android 系統(tǒng)會(huì)刪除以下各項(xiàng):您保存在內(nèi)部存儲(chǔ)中的所有文件您使用 getExternalFilesDir() 保存在外部存儲(chǔ)中的所有文件。但是,您應(yīng)手動(dòng)刪除使用 getCacheDir() 定期創(chuàng)建的所有緩存文件并且定期刪除不再需要的其他文件。】