TShopping

 找回密碼
 註冊
搜索
查看: 2703|回復: 0

[教學] Android 平台的檔案讀寫方式

[複製鏈接]
發表於 2013-12-5 23:20:26 | 顯示全部樓層 |閱讀模式
 
Push to Facebook Push to Plurk Push to Twitter 
處理檔案是程式開發過程中常會碰到的問題,在 Android 平台上讀寫檔案的也是利用 Java 的 File、InputStream 以及 OutputStream 物件來達成。不過 Android 系統對 App 的使用空間與檔案操作有一套自己的管理方式,透過系統提供的 Context 與 Environment 物件可以讓開發人員快速的進行檔案的各種操作。


A. 使用的物件以及方法
  • Context
    • abstract boolean deleteFile(String name)
    • abstract String[] fileList()
    • abstract File getCacheDir()
    • abstract File getDir(String name, int mode)
    • abstract File getExternalCacheDir()
    • abstract File getExternalFilesDir(String type)
    • abstract File getFileStreamPath(String name)
    • abstract File getFilesDir()
    • abstract FileInputStream openFileInput(String name)
    • abstract FileOutputStream openFileOutput(String name, int mode)
  • Environment
    • static File getDataDirectory()
    • static File getDownloadCacheDirectory()
    • static File getExternalStorageDirectory()
    • static File getExternalStoragePublicDirectory(String type)
    • static String getExternalStorageState()
    • static File getRootDirectory()
    • static boolean isExternalStorageEmulated()
    • static boolean isExternalStorageRemovable()



B. 原理說明
  • 在 Android 設備上的儲存體 (storage) 可分為內部 (internal) 以及外部(external) 兩種,內部儲存體指的是內建的 Flash,外部儲存體指的是外接的 SD 卡。有些設備即使沒有外接的儲存設備,Android 系統也會將儲存體分為內部以及外部兩個區域。因此,內部儲存體一定存在,外部儲存體則不一定,如果沒有外接儲存設備就不會有外部儲存體。
  • 在預設的情況下 App 會將新建立檔案存在內部儲存體,存在內部儲存體的檔案預設只能被該 App 存取。當 App 被移除時,儲存在該空間的檔案也會一併被刪除。因此,內部儲存體適合用來擺放專屬於該 App 的檔案,當 App 被移除時這些檔案也沒有存在的必要。
  • 除了內部儲存體外,App 也可以將檔案存放在外部儲存體,放在外部儲存體的檔案可以被其他的 App 讀取。當 App 被移除時,存放在外部儲存體的檔案並不會被移除,唯一的例外是存放在 getExternalFilesDir() 目錄底下的檔案會被移除 (該目錄底下的檔案算是 App 的私有檔案,雖然是放在外部儲存體,不過 App 被移除時系統也會將檔案刪除)。
  • 在安裝 App 時預設會裝在內部儲存體,也可以在 AndroidManifest.xml 中設定 android:installLocation 屬性,將 App 安裝在外部儲存體 (除非 App 的大小超過內部儲存體的空間大小,否則很少這樣做)。
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"    android:installLocation=["auto" | "internalOnly" | "preferExternal"]    ...</manifest>
  • 在預設的情況下 App 具有讀/寫內部儲存體的權限,因此,可以讀取 (read) 或寫入 (write) 內部儲存體裡面的檔案,並不需要在 AndroidManifest.xml 中宣告額外的權限。
  • 在預設的情況下,App 具有讀取 (沒有寫入) 外部儲存體的權限,不過這個權限在未來的 Android 版本可能會做調整,因此,若 App 有讀取外部儲存體的需求,最好還是在 AndroidManifest.xml 檔案中宣告 READ_EXTERNAL_STORAGE 的權限會比較保險,如:
    <manifest ...>    <uses-permission      android:name="android.permission.READ_EXTERNAL_STORAGE" />    ...</manifest>
  • 如果要將檔案存放在外部儲存體,必須取得寫入外部儲存體的權限才行,因此要在 AndroidManifest.xml 中宣告 WRITE_EXTERNAL_STORAGE 權限:
    <manifest ...>    <uses-permission      android:name="android.permission.WRITE_EXTERNAL_STORAGE" />    ...</manifest>如果 App 具有寫入外部儲存體的權限,隱含的意義就是該 App 也同時取得了讀取外部儲存體的權限 (能夠寫入就表示一定能夠讀取)。
  • 在 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 的檔案的絕對路徑
  • 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: 外部儲存體內建在系統中,無法被移除
  • 當 Android 系統發現空間不足時,會將存放在暫存目錄 getCacheDir() 裡面的檔案刪除。因此,App 在執行時不能假設存放在該目錄裡面的檔案一定存在,也不能假設該目錄底下的檔案一定會被系統刪除,最好是在檔案不用時 App 自己將它刪除,以免占用內部儲存體的空間。
  • 由於外部儲存體不一定存在,所以在使用前必須先檢查它的狀態,以避免在讀寫時發生錯誤。透過 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: 外部儲存體存在但未被掛載
  • 外部儲存體的另一個涵義指的是所有 App 的共用空間,對 App 來說存放在外部儲存體的檔案可以分為公開檔案 (public files) 與私有檔案 (private files) 兩種。擺放在 Environment.getExternalStoragePublicDirectory() 目錄底下的為公開檔案,擺放在 Context.getExternalFilesDir() 目錄底下的為私有檔案。
  • 公開檔案就像是照片或是音樂,由目前 App 產生可以提供其他 App 使用的檔案。私有檔案就像是 App 執行時產生的暫存檔,對其他 App 來說並沒有使用上的價值。擺放在外部儲存體的檔案都可以被其他 App 存取,不過當 App 被移除時,只有私有檔案會被移除,公開檔案並不會被移除。
  • 由於公開檔案可以提供其它 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 要擺放的檔案型態不屬於上述那幾類,也可以直接將檔案擺放在根目錄。
  • 將資料寫入儲存體時如果造成空間不足就發產生 IOException,使用 File 物件的 getTotalSpace() 與 getFreeSpace() 可以取得儲存體的總容量與剩餘空間資訊 (單位是 bytes)。如果可以事先知道要寫入的檔案大小,就可以在寫入前先判斷剩餘空間是否足夠,以避免寫入過程發生錯誤。


C. 使用方式

1. 將資料寫入內部儲存體的檔案中

(1) 將檔案存放在 getFilesDir() 目錄

//**** 方法一 ****//
//取得內部儲存體擺放檔案的目錄
//預設擺放路徑為 /data/data/[package.name]/files/
File dir = context.getFilesDir();

//在該目錄底下開啟或建立檔名為 "test.txt" 的檔案
File outFile = new File(dir, "test.txt");

//將資料寫入檔案中,若 package name 為 com.myapp
//就會產生 /data/data/com.myapp/files/test.txt 檔案
writeToFile(outFile, "Hello! 大家好");

...
  1. //writeToFile 方法如下
  2. private void writeToFile(File fout, String data) {
  3.     FileOutputStream osw = null;
  4.     try {
  5.         osw = new FileOutputStream(fout);
  6.         osw.write(data.getBytes());
  7.         osw.flush();
  8.     } catch (Exception e) {
  9.         ;
  10.     } finally {
  11.         try {
  12.             osw.close();
  13.         } catch (Exception e) {
  14.             ;
  15.         }
  16.     }
  17. }


  18. //**** 方法二 ****//
  19. FileOutputStream out = null;
  20. try {
  21.     //在 getFilesDir() 目錄底下建立 test.txt 檔案用來進行寫入
  22.     out = openFileOutput("test.txt", Context.MODE_PRIVATE);

  23.     //將資料寫入檔案中
  24.     out.write("Hello! 大家好\n".getBytes());
  25.     out.flush();
  26. } catch (Exception e) {
  27.     ;
  28. } finally {
  29.     try {
  30.         out.close();
  31.     } catch (Exception e) {
  32.         ;
  33.     }
  34. }
複製代碼
(2) 將檔案存放在 getCacheDir() 目錄

//取得內部儲存體擺放暫存檔案的目錄
//預設擺放路徑為 /data/data/[package.name]/cache/
File dir = context.getCacheDir();

//在該目錄底下開啟或建立檔名為 "test.txt" 的檔案
File outFile1 = new File(dir, "test.txt");

//也可以使用 File.createTempFile() 來建立暫存檔案
File outFile2 = File.createTempFile("test", ".txt", dir);

//將資料寫入檔案中,若 package name 為 com.myapp
//就會產生 /data/data/com.myapp/cache/test.txt 檔案
writeToFile(outFile1, "Hello! 大家好");

//會產生 /data/data/com.myapp/cache/test-[亂數].txt 檔案
writeToFile(outFile2, "Hello! 大家好");
2. 讀取內部儲存體中的檔案內容

//** 方法一 **//
//取得內部儲存體擺放檔案的目錄
//預設擺放目錄為 /data/data/[package.name]/
File dir = context.getFilesDir();

//開啟或建立該目錄底下檔名為 "test.txt" 的檔案
File inFile = new File(dir, "test.txt");

//讀取 /data/data/com.myapp/test.txt 檔案內容
String data = readFromFile(inFile);

...
  1. //readFromFile 方法如下
  2. private String readFromFile(File fin) {
  3.     StringBuilder data = new StringBuilder();
  4.     BufferedReader reader = null;
  5.     try {
  6.         reader = new BufferedReader(new InputStreamReader(
  7.                  new FileInputStream(fin), "utf-8"));
  8.         String line;
  9.         while ((line = reader.readLine()) != null) {
  10.             data.append(line);
  11.         }
  12.     } catch (Exception e) {
  13.         ;
  14.     } finally {
  15.         try {
  16.             reader.close();
  17.         } catch (Exception e) {
  18.             ;
  19.         }
  20.     }
  21.     return data.toString();
  22. }



  23. //** 方法二 **//
  24. FileInputStream in = null;
  25. StringBuffer data = new StringBuffer();
  26. try {
  27.     //開啟 getFilesDir() 目錄底下名稱為 test.txt 檔案
  28.     in = openFileInput("test.txt");

  29.     //讀取該檔案的內容
  30.     BufferedReader reader = new BufferedReader(
  31.                    new InputStreamReader(in, "utf-8"));
  32.     String line;
  33.     while ((line = reader.readLine()) != null) {
  34.        data.append(line);
  35.     }
  36. } catch (Exception e) {
  37.     ;
  38. } finally {
  39.     try {
  40.         in.close();
  41.     } catch (Exception e) {
  42.         ;
  43.     }
  44. }
複製代碼
3. 將資料寫入外部儲存體的檔案中

(1) 檢查外部儲存體的狀態是否可以讀寫
  1. //檢查外部儲存體是否可以進行寫入
  2. public boolean isExtStorageWritable() {
  3.     String state = Environment.getExternalStorageState();
  4.     if (Environment.MEDIA_MOUNTED.equals(state)) {
  5.         return true;
  6.     }
  7.     return false;
  8. }

  9. //檢查外部儲存體是否可以進行讀取
  10. public boolean isExtStorageReadable() {
  11.     String state = Environment.getExternalStorageState();
  12.     if (Environment.MEDIA_MOUNTED.equals(state) ||
  13.         Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
  14.         return true;
  15.     }
  16.     return false;
  17. }
複製代碼
(2) 將檔案存放在外部儲存體 (私有檔案)
  1. //將檔案存放在 getExternalFilesDir() 目錄
  2. if (isExtStorageWritable()){
  3.     File dir = context.getExternalFilesDir(null);
  4.     File outFile = new File(dir, "test.txt");
  5.     writeToFile(outFile, "Hello! 大家好");
  6. }

  7. //將檔案存放在 getExternalCacheDir() 目錄
  8. if (isExtStorageWritable()){
  9.     File dir = context.getExternalCacheDir();
  10.     File outFile = new File(dir, "test.txt");
  11.     writeToFile(outFile, "Hello! 大家好");
  12. }
複製代碼
(3) 將檔案存放在外部儲存體 (公開檔案)
  1. //取得存放公開圖片檔的目錄,並在該目錄下建立 subDir 子目錄
  2. public File getExtPubPicDir(String subDir) {
  3.     File file = new File(Environment.getExternalStoragePublicDirectory(
  4.             Environment.DIRECTORY_PICTURES), subDir);
  5.     //若目錄不存在則建立目錄
  6.     if (!file.mkdirs()) {
  7.         Log.e(LOG_TAG, "無法建立目錄");
  8.     }
  9.     return file;
  10. }
複製代碼
...
  1. //取得外部儲存體存放圖片公開檔案目錄底下的 flowers 子目錄
  2. File path = getExtPubPicDir("flowers");

  3. //在該目錄下建立檔名為 flower.jpg 的檔案
  4. File file = new File(path, "flower.jpg");

  5. //將圖片內容由 App 拷貝到該目錄下
  6. InputStream is = getResources().openRawResource(R.drawable.flower);
  7. OutputStream os = new FileOutputStream(file);

  8. byte[] buffer = new byte[1024];
  9. while (true) {
  10.     int bytesRead = in.read(buffer);
  11.     if (bytesRead == -1) break;
  12.     os.write(buffer, 0, bytesRead);
  13. }

  14. is.close();
  15. os.close();
複製代碼
4. 刪除檔案

當 App 被移除時,Android 系統會刪除所有由該 App 產生存放在內部儲存體的檔案,以及存放在外部儲存體的私有檔案 (Context.getExternalFilesDir() 目錄底下的檔案),不過最好還是在檔案不用時就將它刪除,以免佔用不必要的空間。
  1. //刪除暫存目錄中 test.txt 檔案
  2. File f = new File(context.getCacheDir(),"test.txt");
  3. f.delete();

  4. //刪除 getFilesDir() 目錄底下 test.txt 檔案
  5. context.deleteFile("test.txt");
複製代碼

 

臉書網友討論
您需要登錄後才可以回帖 登錄 | 註冊 |

本版積分規則



Archiver|手機版|小黑屋|免責聲明|TShopping

GMT+8, 2016-12-11 12:26 , Processed in 0.060554 second(s), 22 queries .

本論壇言論純屬發表者個人意見,與 TShopping綜合論壇 立場無關 如有意見侵犯了您的權益 請寫信聯絡我們。

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回復 返回頂部 返回列表