Android使用的文件系統與其他平台的基於磁盤的文件系統類似。本節課介紹如何使用File APIs來執行讀寫Android文件系統的操作。
File對象適用於按順序讀寫大數據,而不是跳躍式的讀寫。例如,它可以很好的讀寫鏡像文件或基於網絡的數據交換。
本節課介紹在你的應用程序中如何執行基本的文件相關的任務。本節課假定你熟悉Linux文件系統基礎和java.io中標准的輸入/輸出APIs。
選擇內部或外部存儲器
所有的Android設備都有兩個文件存儲區域:“內部”和“外部”存儲器。這兩個名稱來自早期的Android,當時大多數設備都提供內置的固定的內存(內置存儲器),外加一個可移動的存儲介質,如micro SD卡(外部存儲器)。有些設備把固定不變的存儲空間分成“內部”和“外部”兩部分,這樣即使沒有可移動的存儲介質,也總會有兩個存儲空間,並且不管外部存儲器是可移動的,還是固定的,API的行為是相同的。
下面列出了每種存儲空間的概要特性:
內部存儲器
外部存儲器
始終有效
它不是始終有效的,因為用戶可以安裝一個USB存儲器來作為外部存儲,並且在某些情況下會從設備上刪除該外部存儲器。
默認情況下,保存在這裡的文件只有保存它的應用程序才能訪問。
它是全局可讀的,因此保存在這裡的文件可以在你的控制之外被讀取。
用戶卸載你的應用程序時,系統會從內部存儲器中刪除該應用程序相關的所有文件。
用戶卸載你的應用程序時,如果你把文件保存在從getExternalFilesDir()方法中獲取的目錄中,那麼系統只會刪除這個目錄中的文件。
當你確定用戶和其他應用程序都不能訪問你的文件時,使用內部存儲是最好的選擇。
對於那些沒有訪問限制、想要跟其他應用程序共享或允許用戶使用計算機來訪問的文件,外部存儲器是最好的選擇。
提示:盡管默認的情況下,應用程序是被安裝在內部存儲器上的,但是你可以在清單文件中指定android:installLocation屬性,以便讓你的應用程序被安裝在外部存儲器上。當APK的尺寸比較大,並還有比內部存儲空間大的外部存儲空間時候,用戶會很樂意使用這個選項。更多信息,請看應用程序的安裝位置。
獲得使用外部存儲器的權限
要向外部存儲器寫入數據,你必須在清單文件中申請WRITE_EXTERNAL_STORAGE權限:
<manifest ...>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
...
</manifest>
警告:當前,所有應用程序在沒有指定權限的情況下,都具有讀取外部存儲器的能力。但是在未來發布的版本中,這種情況會被改變。如果你的應用程序需要讀取外部存儲器(但不向其中寫入),那麼你將會需要聲明READ_EXTERNAL_STORAGE權限。因此,要確保你的應用程序能夠繼續你所期望的工作,就要在改變帶來影響之前聲明這個權限:
<manifest ...>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
...
</manifest>
但是,如果你的應用使用了WRITE_EXTERNAL_STORAGE權限,那麼就隱含著聲明了讀取外部存儲器的權限。
在內部存儲器上保存數據,你不需任何權限。你的應用程序始終有權讀寫它在內部存儲器目錄中保存的文件。
在內部存儲器上保存文件
在把文件保存到內部存儲器上時,你可以通過調用以下兩個方法之一所返回的File對象來獲取相應的目錄:
getFileDir()
返回一個用於你的應用程序的、代表內部目錄的File對象。
getCacheDir()
返回一個用於你的應用程序緩存文件的、代表內部目錄的File對象。在不需要這個文件時一定要確保將其刪除,並且在任何時候,都要對你所使用的內存大小進行合理的限制,如1MB。如果系統在存儲器的運行開始變慢,系統就會刪除這些緩存文件而不會發出警告。
你可以使用File()構造器,在上述方法所返回的目錄中創建一個新的文件,例如:
File file =newFile(context.getFilesDir(), filename);
另外,你可以調用openFileOutput()方法來獲得一個向內部存儲目錄中寫入文件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();
}
或者,如果你需要緩存某些文件,你應該使用createTempFile()方法來替代。例如,下列方法從一個URL中提取了文件名稱,並用這個名稱在你的應用程序的內部緩存目錄中創建了一個文件:
publicFile 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;
}
注意:你的應用程序的內部存儲目錄是在Android文件系統的特定位置中由你的應用程序的包名稱來指定的。從技術上來說,如果你把該文件模式設置為可讀的,那麼另外一個應用程序是可以讀取你的內部文件的。但是,其他的應用程序還需要知道你的應用程序的包名和文件名。其他的應用程序不能浏覽你的內部目錄,並且除非你把該文件設置為可讀或可寫,否則其他的應用程序不能夠對其進行讀寫訪問。因此在內部存儲器上,只要你的文件使用了MODE_PRIVATE模式,那麼其他的應用程序就不會訪問到它們。
在外部存儲器上保存文件
因為外部存儲器可能是無效的---如當用戶把提供的外部存儲器(SD卡)安裝到PC或移除時---因此在訪問外部存儲器之前,你應該先確認它是否有效。你可以通過調用getExternalStorageState()方法來查詢外部存儲器的狀態。如果返回的狀態值等於MEDIA_MOUNTED,那麼你可以讀寫你的文件。例如,下列方法用於判斷存儲器是否有效:
/* 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;
}
盡管外部存儲器是可以比用戶和其他應用程序編輯的,但是你可以有以下兩種策略來保存你的文件:
public files
該類文件應該對其他應用程序和用戶完全有效。當用戶卸載你的應用程序時,這些文件應該保留對用戶的有效性。
例如,你的應用程序拍攝的照片或其他的下載文件。
private files
該類文件應該完全歸屬於你的應用程序,並且在用戶卸載你的應用程序時,這些文件應該被刪除。在技術上,盡管這些文件是可以被用戶和其他應用程序訪問的,因為它們是在外部存儲器上,但是,它們不會把文件的值提供給你的應用程序以外的用戶。當用戶卸載你的應用程序時,系統會刪除你的應用程序外部私有目錄中所有的文件。
例如,由你的應用程序下載的額外資源或臨時媒體文件。
如果你在外部存儲器上保存公共文件,就要使用getExternalStoragePublicDirectory()方法來獲取代表外部存儲器上對應目錄的File對象。這個方法需要指定你想要保存的文件的類型,以便他們能夠被邏輯的跟其他公共文件組織到一起,例如DIRECTORY_MUSIC或DIRECTORY_PICTURES:
publicFile 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;
}
如果想要把文件保存成只對你的應用程序私有,你可以通過調用getExternalFilesDir()方法,並給這個方法傳遞你希望的目錄類型,來獲取對應的目錄。這種方法創建的每個目錄都會被添加到一個父目錄中,這個父目錄封裝了你的應用的所有的外部存儲文件,在用戶卸載你的應用程序時,系統會刪除這些文件。
例如,使用下面的方法創建一個獨立的相冊目錄:
publicFile 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;
}
如果你的文件不打算放到預定義的子目錄中,你可以調用getExternalFilesDir()方法,並給該方法傳遞null參數。這樣就會返回你的應用程序在外部存儲器上的私有目錄的根目錄。
要記住,getExternalFilesDir()方法創建了一個內部目錄,用戶卸載你的應用程序時,這個目錄會被刪除。如果你希望應用程序保存的文件在用戶卸載你應用程序之後繼續有效---如卸載了照相機應用程序,並且還希望保留該應用程序拍攝的一些照片,你就要使用getExternalStoragePublicDirectory()方法來代替。
不管你使用getExternalStoragePublicDirectory()來創建共享文件,還是使用getExternalFilesDir()方法來創建應用程序的私有文件,重要的是使用由API提供的諸如DIRECTORY_PICTURES這樣的常量。這些目錄名確保了這些文件能夠被系統正確的處理。例如,保存在DIRECTORY_RINGTONES目錄中文件,會被系統的媒體掃描器作為鈴聲來處理,而不是音樂。
查詢可用的存儲空間
如果你想要事前了解你可以保存多少數據,你可以調用getFreeSpace()方法或getTotalSpace()方法來查看是否有足夠的存儲空間,而不會導致IOException異常發生。這兩個方法分別提供了當前存儲器中有效的可用空間和總空間。這些信息有益於避免在某個閥值之上來填充存儲卷。
但是,系統不會保證你可以向存儲器中寫入由getFreeSpace()方法所返回的字節數一樣多的數據。如果返回的數字比你要保存數據多幾MB,或者被占用的文件系統存儲空間小於90%,那麼數據可以被安全的寫入,否則你不應該向存儲器中寫入數據。
注意:在保存文件之前,你不必檢查有效的存儲空間,相反,你可以嘗試立即寫入文件,然後捕獲是否發生IOException異常。如果你不能確切的只讀你所需要的存儲空間,就需要使用這個方法。例如,如果你要把PNG格式文件轉換成JPEG格式的文件,在保存之前你不會知道該文件的尺寸。
刪除文件
在不需要的時候,你應該始終刪除這些文件。刪除文件的最直接的方法是調用打開的File對象自身的delete()方法。
myFile.delete();
如果文件被保存在內部存儲器上,你還可以調用Context對象的deleteFile()方法來定位和刪除文件:
myContext.deleteFile(fileName);
注意:當用戶卸載你的應用程序時,Android系統會刪除以下內容:
1. 內部存儲器上所有的你的應用程序的文件;
2. 外部存儲器上所有的使用getExternalFilesDir()方法保存的文件。
但是,你應該定期的手動刪除所有的用getCacheDir()方法創建的緩存文件,以及那些不再需要的其他文件。