Android Tutorial 第三堂(4)存取 Android 檔案系統
Android Tutorial 第三堂(3)Android 內建的 SQLite 資料庫 << 前情 Android應用程式在運作的時候,可能需要存取記憶卡或裝置內部的檔案,例如照片或影音檔,Android系統API也包含「java.io」套件,所以在一般Java應用程式用來執行檔案輸出與輸入的API,也可以在Android 應用程式中使用。要使用Android 的檔案系統之前,要先瞭解它的檔案系統分為這兩類:
這一章說明應用程式讀取與寫入檔案的作法,這是為了後面應用程式需要儲存與讀取照片與錄音檔案的準備工作,所以只會建立一些公用的類別,並沒有增加記事應用程式的功能。 12-1 管理AVD的檔案開始在應用程式中執行儲存設備的檔案管理之前,可以先認識如何使用ADT提 先啟動Android模擬裝置,如果在Eclipse開發工具中,畫面的右上角只有「Java」的按鈕可以選擇,可以選擇Eclipse功能表「Window → Open Perspective → Other」,在出現的視窗中選擇「DDMS」,Eclipse開發工具會把開發用程式的畫面切換為DDMS 管理工具的畫面,畫面右上角也會多出一個「DDMS」的按鈕,就可以在開發應用程式的時候,選擇Java 與DDMS 切換這兩種畫面。下圖是在Eclipse開發工具中切換到DDMS 的畫面: 切換到DDMS畫面後,先選擇畫面左側要執行檔案管理的模擬裝置,然後選擇 在執行檔案管理的畫面中,右上角有幾個用來管理檔案的功能按鈕,由左到右 使用這種方式一次只能處理一個檔案,如果需要傳送多個檔案,就要重複這些步驟完成檔案的傳送工作。如果因為測試應用程式的需求,需要一次傳送比較多檔案,使用Android開發環境提供的工具程式傳送檔案會比較方便一些。使用這種方式傳送多個檔案之前,先確認檔案在模擬裝置中存放的位置,如果需要存放在一個新的路徑,在DDMS的File Explorer中先建立好需要的路徑。例如想要在Pictures下的「icon640」存放一些應用程式測試用的照片檔案,選擇/storage/scdard/Pictures路徑後,建立一個名稱為icon640的路徑。 接下來啟動命令提示字元(Windows)或終端機(Mac OS),把路徑切換到Android工具程式的位置,如果使用ADT Bundle建立的環境,位置在「ADT Bundle解壓縮路徑/sdk/platform-tools」;如果是自己安裝的開發環境,位置在「SDK解壓縮路徑/sdk/platform-tools」。參考這個做法執行路徑的切換: Windows作業系統: cd C:\adt-bundle-...\sdk\platform-tools Mac OS作業系統: cd /adt-bundle-.../sdk/platform-tools 切換路徑以後,執行Android adb工具程式執行檔案的傳送,它的用法是「adb 存放多個照片檔案的路徑 儲存照片的路徑」。參考這個作法執行傳送多個檔案的工作: Windows作業系統: adb push C:\mypictures\ /storage/sdcard/Pictures/ Mac OS作業系統: ./adb push /mypictures/ /storage/sdcard/Pictures/ 12-2 裝置內部的儲存設備應用程式需要儲存一些需要的檔案到內部儲存設備,這些檔案的需求應該是經常在應用程式中存取,而且因為內部儲存設備的容量通常不會太大,所以要注意檔案的大小,如果檔案比較大的話,應該考慮使用外部儲存設備。存取Android的檔案系統,應該避免直接使用絕對路徑,Android 提供一些API 可以讓你取得儲存設備的基本路徑,如果需要儲存或讀取內部儲存設備中的檔案,應該呼叫這些宣告在Context 類別中的方法取得基本路徑:
在Context 類別中也宣告這些用來取得儲存和讀取檔案物件的方法:
使用宣告在Context 類別的方法取得寫入和讀取的物件,都是一種處理byte 資料的輸出入串流物件,適合用來處理照片或音樂這類檔案;如果要處理字元資料的話,就需要把它們包裝為「OutputStreamWriter」和「InputStreamReader」物件。它們是java.io 套件下的API,跟一般Java 應用程式的用法是一樣的。下面這個程式片段示範寫入一些文字到內部儲存設備: // 要儲存資料的檔案名稱 String fileName = "myfile.data"; FileOutputStream os = null; OutputStreamWriter osw = null; try { // 取得寫入檔案用的FileOutputStream物件 os = context.openFileOutput(fileName, Context.MODE_PRIVATE); // 包裝為寫入字元資料的OutputStreamWriter物件 osw = new OutputStreamWriter(os); // 寫入文字 osw.write("Hello! Android!"); } // 需要處理IOException例外 catch (IOException e) { Log.d(fileName, e.toString()); } // 在finally區塊執行關閉的工作 finally { if (osw != null) { try { osw.close(); } catch (IOException e) { Log.d(fileName, e.toString()); } } } 這個程式片段示範從內部儲存設備指定的檔案讀取內容的作法: //要讀取資料的檔案名稱 String fileName = "myfile.data"; FileInputStream is = null; InputStreamReader isr = null; BufferedReader br = null; try { // 取得讀取檔案用的FileInputStream物件 is = context.openFileInput(fileName); // 包裝為讀取字元資料的InputStreamReader物件 isr = new InputStreamReader(is); // 再包裝為可一次讀取一行字串資料的BufferedReader物件 br = new BufferedReader(isr); // 讀取一行字串 result = br.readLine(); } //需要處理IOException例外 catch (IOException e) { Log.d(fileName, e.toString()); } //在finally區塊執行關閉的工作 finally { if (is != null) { try { is.close(); } catch (IOException e) { Log.d(fileName, e.toString()); } } } 12-3 裝置的外部儲存設備Android系統的外部儲存設備在存取檔案的時候,會比使用內部儲存設備的效率差一些,不過它的容量通常會大一些,例如在裝置中使用的記憶卡,容量的大小通常可以支援到8或16GB以上。所以外部儲存設備適合存取比較大的檔案,例如高解析度的照片、音樂與影片檔案。 因為使用者可以卸載外部儲存設備,或是裝置本來就沒有外部儲存設備,所以在執行所有的操作前,都應該先執行外部儲存設備是否可以使用的判斷。這些判斷的工作通常會宣告為獨立的方法,在需要使用的時候會方便一些。需要判斷外部儲存設備的狀態,需要使用「andfoid.os.Environment」類別中宣告的方法與變數。這個程式片段示範執行判斷的作法: // 外部儲存設備是否可寫入 public static boolean isExternalStorageWritable() { // 取得目前外部儲存設備的狀態 String state = Environment.getExternalStorageState(); // 判斷是否可寫入 if (Environment.MEDIA_MOUNTED.equals(state)) { return true; } return false; } // 外部儲存設備是否可讀取 public static boolean isExternalStorageReadable() { // 取得目前外部儲存設備的狀態 String state = Environment.getExternalStorageState(); // 判斷是否可讀取 if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { return true; } return false; } 在應用程式執行外部儲存設備檔案的寫入或讀取之前,應該先呼叫上面提供的「isExternalStorageWritable」和「isExternalStorageReadable」方法執行判斷,確定可以存取後再執行後續的工作。 外部儲存設備可以區分為公用和應用程式專用的檔案,公用的檔案可以讓所有的應用程式存取,如果使用者移除應用程式,這些檔案還是會繼續保存;應用程式中用的檔案,在使用者移除應用程式後就會跟著刪除,別的應用程式不可以存取這些檔案。存取Android的檔案系統,應該避免直接使用絕對路徑,Environment類別提供下面這些方法取得外部儲存設備的基本路徑:
應用程式可能需要從網際網路下載一些檔案,例如照片與音樂,Android系統為這些通用類型的檔案,在外部儲存設備中建立一些預設的路徑保存,所以應用程式也可以依照檔案的類型放在這些路徑中。所以在上面說明的方法中,可以使用宣告在Environment類別中的變數設定傳回不同檔案類型的預設路徑。下面是宣告在Environment類別中代表不同檔案類型的變數名稱:
如果應用程式使用裝置的照像設備拍照或是從網路上下載一些照片,希望把這些照片儲存在公用的照片路徑下,可以使用下面的方法來取得路徑: // 建立並傳回在公用相簿下參數指定的路徑 public static File getPublicAlbumStorageDir(String albumName) { // 取得公用的照片路徑 File pictures = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES); // 準備在照片路徑下建立一個指定的路徑 File file = new File(pictures, albumName); // 如果建立路徑不成功 if (!file.mkdirs()) { Log.e("getAlbumStorageDir", "Directory not created"); } return file; } 使用宣告在Context類別中的「getExternalFilesDir(String)」方法,可以取得應用程式專用的外部儲存設備路徑,同樣使用宣告在Environment類別中的變數,在參數中指定需要的檔案類型路徑。如果參數指定為null值的話,就傳回應用程式專用的外部儲存設備主路徑。這個程式片段示範建立與取得應用程式專用的照片路徑: // 建立並傳回在應用程式專用相簿下參數指定的路徑 public static File getAlbumStorageDir(Context context, String albumName) { // 取得應用程式專用的照片路徑 File pictures = context.getExternalFilesDir( Environment.DIRECTORY_PICTURES); // 準備在照片路徑下建立一個指定的路徑 File file = new File(pictures, albumName); // 如果建立路徑不成功 if (!file.mkdirs()) { Log.e("getAlbumStorageDir", "Directory not created"); } return file; } 12-4 檔案公用類別如果應用程式需要執行檔案的存取,例如記事應用程式儲存的照片或錄音檔案,除了使用上面介紹的基本檔案操作,另外還需要一些輔助的功能,像是產生檔案的名稱,還有設定圖片檔案給ImageView元件顯示。這些都是一般應用程式常用的功能,可以把它們撰寫在一個公用類別裡面,再讓需要的元件使用,這樣可以簡化一般元件的程式碼。 在「net.macdidi.myandroidtutorial」套件下建立「FileUtil」類別,它是一般的Java類別,裡面都是一些宣告為public與static的方法: package net.macdidi.myandroidtutorial; import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Environment; import android.util.Log; import android.widget.ImageView; public class FileUtil { // 應用程式儲存檔案的目錄 public static final String APP_DIR = "androidtutorial"; // 外部儲存設備是否可寫入 public static boolean isExternalStorageWritable() { // 取得目前外部儲存設備的狀態 String state = Environment.getExternalStorageState(); // 判斷是否可寫入 if (Environment.MEDIA_MOUNTED.equals(state)) { return true; } return false; } // 外部儲存設備是否可讀取 public static boolean isExternalStorageReadable() { // 取得目前外部儲存設備的狀態 String state = Environment.getExternalStorageState(); // 判斷是否可讀取 if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { return true; } return false; } // 建立並傳回在公用相簿下參數指定的路徑 public static File getPublicAlbumStorageDir(String albumName) { // 取得公用的照片路徑 File pictures = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_PICTURES); // 準備在照片路徑下建立一個指定的路徑 File file = new File(pictures, albumName); // 如果建立路徑不成功 if (!file.mkdirs()) { Log.e("getAlbumStorageDir", "Directory not created"); } return file; } // 建立並傳回在應用程式專用相簿下參數指定的路徑 public static File getAlbumStorageDir(Context context, String albumName) { // 取得應用程式專用的照片路徑 File pictures = context.getExternalFilesDir( Environment.DIRECTORY_PICTURES); // 準備在照片路徑下建立一個指定的路徑 File file = new File(pictures, albumName); // 如果建立路徑不成功 if (!file.mkdirs()) { Log.e("getAlbumStorageDir", "Directory not created"); } return file; } // 建立並傳回外部儲存媒體參數指定的路徑 public static File getExternalStorageDir(String dir) { File result = new File( Environment.getExternalStorageDirectory(), dir); if (!isExternalStorageWritable()) { return null; } if (!result.exists() && !result.mkdirs()) { return null; } return result; } // 讀取指定的照片檔案名稱設定給ImageView元件 public static void fileToImageView(String fileName, ImageView imageView) { if (new File(fileName).exists()) { Bitmap bitmap = BitmapFactory.decodeFile(fileName); imageView.setImageBitmap(bitmap); } else { Log.e("fileToImageView", fileName + " not found."); } } // 產生唯一的檔案名稱 public static String getUniqueFileName() { // 使用年月日_時分秒格式為檔案名稱 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss"); return sdf.format(new Date()); } } 12-5 應用程式使用權限設定應用程式如果需要讀取或儲存外部儲存設備,必須在應用程式設定檔中加入授權設定,Android目前並沒有規定讀取外部儲存設備需要加入授權設定,不過為了以後的相容性,還是把它加入會好一些。為了讓後續的功能可以正確的使用檔案存取的功能,開啟應用程式設定檔案「AndroidManifest.xml」,在「mainfest」標簽裡面加入下列的授權設定: <!-- 如果應用程式需要寫入外部儲存設備,一定要加入這個授權 --> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <!-- 如果應用程式需要讀取外部儲存設備,最好加入這個授權 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> 你也可以使用ADT提供的圖形介面工具設定應用程式權限,開啟應用程式設定檔案「AndroidManifest.xml」以後,切換到「Permissions」標籤: 如果需要為應用程式加入新的授權設定,在這個畫面選擇「Add」按鈕: 選擇「Uses Permission」後選擇「OK」按鈕: 選擇需要的授權名稱以後,ADT就會自動幫你加入設定檔,切換到「AndroidManifest.xml」標籤確認後,就可以儲存檔案: 完成這一章的工作了,在這裡加入的程式碼與設定,會在後續的功能使用。 課程相關的檔案都可以GitHub瀏覽與下載。 |