<转>Android 平台的檔案讀寫方式
原文http://android-deve.blogspot.com/2012/11/file-access.html
1.在 Android 設備上的儲存體 (storage) 可分為內部 (internal) 以及外部(external) 兩種,內部儲存體指的是內建的 Flash,外部儲存體指的是外接的 SD 卡。有些設備即使沒有外接的儲存設備,Android 系統也會將儲存體分為內部以及外部兩個區域。因此,內部儲存體一定存在,外部儲存體則不一定,如果沒有外接儲存設備就不會有外部儲存體。
2.在預設的情況下 App 會將新建立檔案存在內部儲存體,存在內部儲存體的檔案預設只能被該 App 存取。當 App 被移除時,儲存在該空間的檔案也會一併被刪除。因此,內部儲存體適合用來擺放專屬於該 App 的檔案,當 App 被移除時這些檔案也沒有存在的必要。
3.除了內部儲存體外,App 也可以將檔案存放在外部儲存體,放在外部儲存體的檔案可以被其他的 App 讀取。當 App 被移除時,存放在外部儲存體的檔案並不會被移除,唯一的例外是存放在 getExternalFilesDir() 目錄底下的檔案會被移除 (該目錄底下的檔案算是 App 的私有檔案,雖然是放在外部儲存體,不過 App 被移除時系統也會將檔案刪除)。
4.在安裝 App 時預設會裝在內部儲存體,也可以在 AndroidManifest.xml 中設定 android:installLocation 屬性,將 App 安裝在外部儲存體 (除非 App 的大小超過內部儲存體的空間大小,否則很少這樣做)。
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation=["auto" | "internalOnly" | "preferExternal"] ... </manifest>
5.在預設的情況下 App 具有讀/寫內部儲存體的權限,因此,可以讀取 (read) 或寫入 (write) 內部儲存體裡面的檔案,並不需要在 AndroidManifest.xml 中宣告額外的權限。
6.在預設的情況下,App 具有讀取 (沒有寫入) 外部儲存體的權限,不過這個權限在未來的 Android 版本可能會做調整,因此,若 App 有讀取外部儲存體的需求,最好還是在 AndroidManifest.xml 檔案中宣告 READ_EXTERNAL_STORAGE 的權限會比較保險,如:
<manifest ...> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> ... </manifest>
7.如果要將檔案存放在外部儲存體,必須取得寫入外部儲存體的權限才行,因此要在 AndroidManifest.xml 中宣告 WRITE_EXTERNAL_STORAGE 權限:
<manifest ...> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> ... </manifest>
如果 App 具有寫入外部儲存體的權限,隱含的意義就是該 App 也同時取得了讀取外部儲存體的權限 (能夠寫入就表示一定能夠讀取)。
8.在 Android 平台上讀寫檔案的方式是透過 java.io.File 物件來達成,至於檔案的擺放位置或建立檔案的方式,可透過 Context 物件裡面的以下方法來達成:
abstract File getFilesDir()
取得 App 內部儲存體存放檔案的目錄 (絕對路徑)
預設路徑為 /data/data/[package.name]/files/
abstract File getCacheDir()
取得 App 內部儲存體存放暫存檔案的目錄 (絕對路徑)
預設路徑為 /data/data/[package.name]/cache/
abstract File getExternalFilesDir(String type)
取得 App 外部儲存體存放檔案的目錄 (絕對路徑)
abstract File getExternalCacheDir()
取得 App 外部儲存體存放暫存檔案的目錄 (絕對路徑)
abstract File getDir(String name, int mode)
取得 App 可以擺放檔案的目錄,若該目錄不存在則建立一個新的
ex: getDir("music", 0) -> /data/data/[package.name]/app_music
abstract boolean deleteFile(String name)
刪除 getFilesDir() 目錄底下名稱為 name 的檔案
abstract String[] fileList()
回傳 getFilesDir() 目錄底下的檔案及目錄名稱
abstract FileInputStream openFileInput(String name)
開啟 getFilesDir() 目錄下檔名為 name 的檔案來進行讀取
abstract FileOutputStream openFileOutput(String name, int mode)
在 getFilesDir() 目錄底下開啟或建立檔名為 name 的檔案來進行寫入
abstract File getFileStreamPath(String name)
取得 openFileOutput() 所建立之名稱為 name 的檔案的絕對路徑
9.Environment 物件提供 Android 系統環境的相關資訊,包含外部儲存體的狀態,以及相關檔案的擺放位置,如:
static File getDataDirectory()
取得系統的資料擺放目錄,預設位置為 /data
static File getDownloadCacheDirectory()
取得系統檔案下載或暫存檔案的擺放目錄,預設位置為 /cache
static File getExternalStorageDirectory()
取得外部儲存體的根目錄,預設位置為 /mnt/sdcard
static File getExternalStoragePublicDirectory(String type)
取得外部儲存體存放公開檔案的目錄
static String getExternalStorageState()
取得外部儲存體的狀態資訊
static File getRootDirectory()
取得檔案系統的根目錄,預設位置為 /system
static boolean isExternalStorageEmulated()
判斷外部儲存體是否使用內部儲存體模擬產生
true: 外部儲存體不存在,而是使用內部儲存體模擬產生
false: 外部儲存體存在,並非使用內部儲存體模擬
static boolean isExternalStorageRemovable()
判斷外部儲存體是否可以移除,回傳值的意義如下:
true: 外部儲存體屬於外接式的,且可以移除
false: 外部儲存體內建在系統中,無法被移除
10.當 Android 系統發現空間不足時,會將存放在暫存目錄 getCacheDir() 裡面的檔案刪除。因此,App 在執行時不能假設存放在該目錄裡面的檔案一定存在,也不能假設該目錄底下的檔案一定會被系統刪除,最好是在檔案不用時 App 自己將它刪除,以免占用內部儲存體的空間。
11.由於外部儲存體不一定存在,所以在使用前必須先檢查它的狀態,以避免在讀寫時發生錯誤。透過 Environment 物件的 getExternalStorageState() 方法可以查詢目前外部儲存體的狀態,其中狀態可以是以下這幾種:
MEDIA_BAD_REMOVAL: 外部儲存體在正常卸載之前就被拔除
MEDIA_CHECKING: 外部儲存體存在且正在進行磁碟檢查
MEDIA_MOUNTED: 外部儲存體存在且可以進行讀取與寫入
MEDIA_MOUNTED_READ_ONLY: 外部儲存體存在但只能進行讀取
MEDIA_NOFS: 外部儲存體存在,但內容是空的或是 Android 不支援該檔案系統
MEDIA_REMOVED: 外部儲存體不存在
MEDIA_SHARED: 外部儲存體存在但未被掛載,且為 USB 的裝置
MEDIA_UNMOUNTABLE: 外部儲存體存在但不能被掛載
MEDIA_UNMOUNTED: 外部儲存體存在但未被掛載
12.外部儲存體的另一個涵義指的是所有 App 的共用空間,對 App 來說存放在外部儲存體的檔案可以分為公開檔案 (public files) 與私有檔案 (private files) 兩種。擺放在 Environment.getExternalStoragePublicDirectory() 目錄底下的為公開檔案,擺放在 Context.getExternalFilesDir() 目錄底下的為私有檔案。
13.公開檔案就像是照片或是音樂,由目前 App 產生可以提供其他 App 使用的檔案。私有檔案就像是 App 執行時產生的暫存檔,對其他 App 來說並沒有使用上的價值。擺放在外部儲存體的檔案都可以被其他 App 存取,不過當 App 被移除時,只有私有檔案會被移除,公開檔案並不會被移除。
14.由於公開檔案可以提供其它 App 使用,所以在放置這些檔案時 Android 系統提供了一些基本的分類,讓 App 可以依檔案屬性將檔案放置在不同目錄裡面,方便其它 App 可以使用。因此,getExternalStoragePublicDirectory(String type) 可以接受一個 type 參數,該參數表示目錄中儲存的檔案型態,例如:getExternalStoragePublicDirectory(DIRECTORY_PICTURES) 會回傳用來擺放圖片檔的目錄,如果 App 產生的圖片要提供給其它 App 使用,就可以擺放在這個目錄。目前 Android 定義的目錄型態包含以下這幾種:
DIRECTORY_ALARMS: 鬧鐘的音效檔
DIRECTORY_DCIM: 相機的圖片與影片檔
DIRECTORY_DOWNLOADS: 使用者下載的檔案
DIRECTORY_MOVIES: 電影檔
DIRECTORY_MUSIC: 音樂檔
DIRECTORY_NOTIFICATIONS: 通知音效檔
DIRECTORY_PICTURES: 一般的圖片檔
DIRECTORY_PODCASTS: 訂閱的廣播檔
DIRECTORY_RINGTONES: 鈴聲檔
type 參數如果為 null 時可取得擺放公開檔案的根目錄,如果 App 要擺放的檔案型態不屬於上述那幾類,也可以直接將檔案擺放在根目錄。
15.將資料寫入儲存體時如果造成空間不足就發產生 IOException,使用 File 物件的 getTotalSpace() 與 getFreeSpace() 可以取得儲存體的總容量與剩餘空間資訊 (單位是 bytes)。如果可以事先知道要寫入的檔案大小,就可以在寫入前先判斷剩餘空間是否足夠,以避免寫入過程發生錯誤。