Android 檔案儲存

zeroXuan發表於2019-05-13

選擇內部或外部儲存

所有Android裝置都有兩個檔案儲存區:“內部”和“外部”儲存。這些名稱來自Android的早期,大多數裝置提供內建的非易失性儲存器(內部儲存),以及可移動儲存介質,如microSD卡(外部儲存)。現在,許多裝置將永久儲存空間劃分為單獨的“內部”和“外部”分割槽。因此,即使沒有可移動儲存介質,這兩個儲存空間也始終存在,無論外部儲存是否可移動,API行為都是相同的。

由於外部儲存可能是可移除的,因此這兩個選項之間存在一些差異,如下所示。

內部儲存器:

  • 它始終可用。
  • 內部儲存器儲存的檔案只能由您的應用訪問。
  • 當使用者解除安裝您的應用程式時,系統會從內部儲存中刪除所有應用程式的檔案。
  • 當您希望確保使用者和其他應用程式都無法訪問您的檔案時,最好使用內部儲存。

外部儲存:

  • 外部儲存並不總是可用,因為使用者可以將外部儲存裝載為USB儲存器,並在某些情況下將其從裝置中移除。
  • 外部儲存是全域性可讀的,因此儲存在此處的檔案可能會在您的控制元件之外讀取。
  • 當使用者解除安裝您的應用程式時,只有將應用程式的檔案儲存在通過使用getExternalFilesDir()獲取的目錄時,系統才會從此處刪除應用程式的檔案。
  • 對於不需要訪問限制的檔案以及要與其他應用程式共享或允許使用者使用計算機訪問的檔案,外部儲存是最佳位置。

將檔案儲存在內部儲存上

您的應用程式的內部儲存目錄由您的應用程式包名稱指定在Android檔案系統的特殊位置,可以使用以下API訪問。

注意:與外部儲存目錄不同,您的應用程式不需要任何系統許可權來讀取和寫入這些方法返回的內部目錄。

寫一個檔案

將檔案儲存到內部儲存時,可以通過呼叫以下兩種方法獲取相應的目錄,進一步操作File

  • getFilesDir():返回File表示應用程式的內部目錄。
  • getCacheDir():返回File表示應用程式臨時快取檔案的內部目錄。

要在其中一個目錄中建立新檔案,可以使用File()建構函式,傳遞給File上述方法提供的指定內部儲存目錄的方法。

例如:

File file = new File(context.getFilesDir(), filename);
複製程式碼

或者,您可以呼叫openFileOutput()以獲取FileOutputStream 寫入內部目錄中的檔案的內容。

例如,以下是如何將一些文字寫入檔案:

String filename = "myfile";
String fileContents = "Hello world!";
FileOutputStream outputStream;

try {
    outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
    outputStream.write(fileContents.getBytes());
    outputStream.close();
} catch (Exception e) {
    e.printStackTrace();
}
複製程式碼

MODE_PRIVATE 將會建立檔案(或替換具有相同名稱的檔案),並將其設為應用的私有檔案。 其他可用模式包括:MODE_APPENDMODE_WORLD_READABLEMODE_WORLD_WRITEABLE

請注意,該openFileOutput()方法需要檔案模式引數。傳遞 MODE_PRIVATE 將會建立檔案(或替換具有相同名稱的檔案),並將其設為應用的私有檔案。 其他可用模式包括:MODE_APPENDMODE_WORLD_READABLEMODE_WORLD_WRITEABLE

自 API 級別 17 以來,常量 MODE_WORLD_READABLEMODE_WORLD_WRITEABLE 已被棄用。從 Android N(7.0,API24) 開始,使用這些常量將會導致引發 SecurityException。這意味著,面向 Android N 和更高版本的應用無法按名稱共享私有檔案,嘗試共享“file://”URI 將會導致引發 FileUriExposedException。 如果您的應用需要與其他應用共享私有檔案,則可以將 FileProviderFLAG_GRANT_READ_URI_PERMISSION配合使用

在Android 6.0(API級別23)及更低階別上,如果您將檔案模式設定為全域性可讀,則其他應用程式可以讀取您的內部檔案。但是,其他應用必須知道您的應用包名稱和檔名。除非您明確將檔案設定為可讀或可寫,否則其他應用程式無法瀏覽您的內部目錄並且沒有讀取或寫入許可權·因此,只要您在內部儲存上使用MODE_PRIVATE標記您的檔案,其他應用就永遠無法訪問它們

寫一個快取檔案

如果你需要快取一些檔案,你應該使用 createTempFile()

例如,以下方法從URL中提取檔名,並在應用程式的內部快取目錄中建立具有該名稱的檔案:

private 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;
}
複製程式碼

使用createTempFile()建立的檔案放置在應用程式專用的快取目錄中。您應該定期刪除不再需要的檔案。

注意: 如果系統儲存空間不足,可能會在沒有警告的情況下刪除快取檔案,因此請確保在讀取之前檢查快取檔案是否存在。

開啟現有檔案

要讀取現有檔案,請呼叫openFileInput(name),傳遞檔名。

您可以通過呼叫獲取所有應用程式檔名的陣列 fileList()

注意:如果您需要在應用程式中打包一個可在安裝時訪問的檔案,請將該檔案儲存在專案的res/raw/目錄中。您可以使用openRawResource()傳遞資源ID 開啟這些檔案。此方法返回可用於讀取檔案的方法。您無法寫入原始檔案。

開啟一個目錄

您可以使用以下方法在內部檔案系統上開啟目錄:

  • getFilesDir():返回File表示檔案系統上與您的應用唯一關聯的目錄。
  • getDir(name, mode):在應用程式的唯一檔案系統目錄中建立新目錄(或開啟現有目錄)。此新目錄顯示在提供的目錄中getFilesDir()
  • getCacheDir():返回File表示檔案系統上與您的應用唯一關聯的快取目錄。此目錄適用於臨時檔案,應定期清理。如果磁碟空間不足,系統可能會刪除那裡的檔案,因此請確保在讀取之前檢查快取檔案是否存在。

要在其中一個目錄中建立新檔案,可以使用 File()建構函式,傳遞File上述方法之一提供的指定內部儲存目錄的物件。

例如:

File directory = context.getFilesDir();
File file = new File(directory, filename);
複製程式碼

將檔案儲存在外部儲存上

使用外部儲存非常適合您要與其他應用共享允許使用者使用計算機訪問的檔案

請求儲存許可權驗證儲存可用後,您可以儲存兩種不同型別的檔案:

  • 公共檔案:應該可供其他應用程式和使用者免費使用的檔案。當使用者解除安裝您的應用時,這些檔案應該仍然可供使用者使用。例如,應用程式或其他下載檔案捕獲的照片應儲存為公共檔案。
  • 私人檔案:合法屬於您的應用的檔案,將在使用者解除安裝您的應用時刪除。雖然這些檔案在技術上可由使用者和其他應用程式訪問,因為它們位於外部儲存上,但它們不會為應用程式外的使用者提供價值。

注意如果使用者卸下SD卡或將裝置連線到計算機,外部儲存可能會變得不可用。並且使用者和具有READ_EXTERNAL_STORAGE 許可權的其他應用程式仍然可以看到這些檔案。因此,如果您的應用程式的功能取決於這些檔案,或者您需要完全限制訪問,則應將檔案寫入內部儲存。

請求外部儲存許可權

要寫入公共外部儲存,您必須在清單檔案中請求WRITE_EXTERNAL_STORAGE許可權:

 <uses-permission android:name = “android.permission.WRITE_EXTERNAL_STORAGE” />  
複製程式碼

如果您的應用使用該WRITE_EXTERNAL_STORAGE許可權,則它也隱式擁有讀取外部儲存的許可權。

如果您的應用只需要讀取外部儲存(但不能寫入),那麼您需要宣告 READ_EXTERNAL_STORAGE許可權:

<uses-permission android:name = “android.permission.READ_EXTERNAL_STORAGE” />
複製程式碼

從Android 4.4(API級別19)開始,在應用程式的私有外部儲存目錄中讀取或寫入檔案 - 使用getExternalFilesDir() 訪問, 不需要READ_EXTERNAL_STORAGEWRITE_EXTERNAL_STORAGE 許可權。因此,如果您的應用支援Android 4.3(API級別18)及更低版本,並且您只想訪問專用外部儲存目錄,則應通過新增maxSdkVersion 屬性宣告僅在較低版本的Android上請求許可權 :

<uses-permission android:name = “android.permission.WRITE_EXTERNAL_STORAGE”
android:maxSdkVersion = “18” /> 
複製程式碼

驗證外部儲存是否可用

由於外部儲存可能不可用 - 例如當使用者將儲存裝置安裝到PC或已移除提供外部儲存的SD卡時 - 您應始終在訪問之前驗證該卷是否可用。您可以通過呼叫來查詢外部儲存狀態getExternalStorageState()。如果返回狀態為MEDIA_MOUNTED,則可以讀取和寫入檔案。如果是MEDIA_MOUNTED_READ_ONLY,則只能讀取檔案。

例如,以下方法可用於確定儲存可用性:

/* 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;
}
複製程式碼

儲存到外部公共目錄

如果要將公共檔案儲存在外部儲存上,請使用該 getExternalStoragePublicDirectory()方法獲取File表示外部儲存上的相應目錄。該方法接受一個引數,指定要儲存的檔案型別,以便可以使用其他公共檔案(如DIRECTORY_MUSIC或) 對其進行邏輯組織DIRECTORY_PICTURES。例如:

public File getPublicAlbumStorageDir(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;
}
複製程式碼

如果要從Media Scanner中隱藏檔案,請在外部檔案目錄中包含一個名為.nomedia空檔案(請注意檔名 中的點字首)。這可以防止媒體掃描程式讀取您的媒體檔案,並通過MediaStore內容提供商將其提供給其他應用程式。

儲存到外部私有目錄

如果要將檔案儲存在應用程式專用且外部提供程式無法訪問的外部儲存上MediaStore,您可以通過呼叫getExternalFilesDir()並向其傳遞一個名稱來獲取一個目錄,該目錄僅由您的應用程式使用, 該名稱指示您希望的目錄型別。以這種方式建立的每個目錄都會新增到父目錄中,該目錄封裝了應用程式的所有外部儲存檔案,系統會在使用者解除安裝應用程式時將其刪除。

public File getPrivateAlbumStorageDir(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() 建立一個在使用者解除安裝應用程式時刪除的目錄。如果您儲存的檔案在使用者解除安裝應用程式後仍然可用 - 例如當您的應用程式捕獲照片並且使用者應保留這些照片時 - 您應該將檔案儲存到公共目錄。

在多個儲存位置之間選擇

有時,分配內部儲存器分割槽以用作外部儲存器的裝置也提供SD卡插槽。這意味著該裝置有兩個不同的外部儲存目錄,因此您需要選擇在將“私有”檔案寫入外部儲存時使用哪個目錄。

從Android 4.4(API級別19)開始,您可以通過呼叫訪問這兩個位置getExternalFilesDirs(),該位置 返回一個包含每個儲存位置條目的File陣列。陣列中的第一個條目被視為主要外部儲存,您應該使用該位置,除非它已滿或不可用。

如果您的應用支援Android 4.3及更低版本,則應使用支援庫的靜態方法ContextCompat.getExternalFilesDirs()。這總是返回一個File陣列,但如果裝置執行的是Android 4.3及更低版本,那麼它只包含一個主外部儲存條目(如果有第二個儲存位置,則無法在Android 4.3及更低版本上訪問它)。

刪除檔案

您應該始終刪除您的應用不再需要的檔案。刪除檔案最直接的方法是呼叫File物件delete()方法。

myFile.delete();
複製程式碼

如果檔案儲存在內部儲存器上,您還可以通過呼叫ContextdeleteFile()來查詢和刪除檔案:

myContext.deleteFile(fileName );
複製程式碼

注意:當使用者解除安裝您的應用時,Android系統會刪除以下內容:

  • 您在內部儲存上儲存的所有檔案。
  • 使用getExternalFilesDir()儲存在外部儲存的所有檔案
  • 但是,您應該手動刪除getCacheDir()定期建立的所有快取檔案, 並定期刪除不再需要的其他檔案。

Android 目錄總結

($rootDir)
+- /data                -> Environment.getDataDirectory()
|   |
|   |   ($appDataDir)
|   +- data/com.srain.cube.sample
|       |
|       |   ($filesDir)
|       +- files            -> Context.getFilesDir() / Context.getFileStreamPath("")
|       |       |
|       |       +- file1    -> Context.getFileStreamPath("file1")
|       |   ($cacheDir)
|       +- cache            -> Context.getCacheDir()
|       |
|       +- app_$name        ->(Context.getDir(String name, int mode)
|
|   ($rootDir)
+- /storage/sdcard0     -> Environment.getExternalStorageDirectory()
    |                       / Environment.getExternalStoragePublicDirectory("")
    |
    +- dir1             -> Environment.getExternalStoragePublicDirectory("dir1")
    |
    |   ($appDataDir)
    +- Andorid/data/com.srain.cube.sample
        |
        |   ($filesDir)
        +- files        -> Context.getExternalFilesDir("")
        |   |
        |   +- file1    -> Context.getExternalFilesDir("file1")
        |   +- Music    -> Context.getExternalFilesDir(Environment.Music);
        |   +- Picture  -> ... Environment.Picture
        |   +- ...
        |
        |   ($cacheDir)
        +- cache        -> Context.getExternalCacheDir()
        |
        +- ???
複製程式碼

相關文章