天天看點

Android:資料存儲之SharedPreference & 檔案Android:資料存儲之SharedPreference & 檔案

Android:資料存儲之SharedPreference & 檔案

存儲方式

Android主要的資料存儲方式包括下列5種, 下文将介紹前2種

1. SharePreference

2. 檔案

3. SQlite

4. ContentProvider

5. 網絡

儲存到Preference

當有一個相對較小的key-value集合需要儲存時,可以使用SharedPreferences APIs。

Note: SharedPreferences APIs 僅僅提供了讀寫key-value對的功能,請不要與Preference APIs相混淆。後者可以幫助我們建立一個設定使用者配置的頁面(盡管它實際上是使用SharedPreferences 來實作儲存使用者配置的)。

1. 建立preference檔案

一般有兩種方法:

context.getSharedPreferences(name, mode): context的方法, 需要指定 preference檔案的名字(preference檔案其實就是一個存放key-value的xml檔案). 同App的任何activity都可以通過 name來使用這個 preference檔案.

Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
        getString(R.string.preference_file_key), Context.MODE_PRIVATE);
           

context.getPreferences(mode): context的方法, 無需指定preference檔案名, 系統會自動建立以activity命名的preference檔案. 隻能擷取供單個activity使用.

如果建立了一個MODE_WORLD_READABLE或者MODE_WORLD_WRITEABLE 模式的shared preference檔案,則其他任何app均可通過檔案名通路該檔案。

2.寫入檔案

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();
           

3.讀取檔案

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
int defaultValue = getResources().getInteger(R.string.saved_high_score_default);
long highScore = sharedPref.getInt(getString(R.string.saved_high_score), default);
           

檔案

檔案主要分為兩部分: internal 和 external

- Internal storage:

1. 總是可用的

2. 這裡的檔案預設隻能被我們的app所通路。

3. 當使用者解除安裝app的時候,系統會把internal内該app相關的檔案都清除幹淨。

4. Internal是我們在想確定不被使用者與其他app所通路的最佳存儲區域。

- External storage:

1. 并不總是可用的,因為使用者有時會通過USB存儲模式挂載外部存儲器,當取下挂載的這部分後,就無法對其進行通路了。

2. 是大家都可以通路的,是以儲存在這裡的檔案可能被其他程式通路。

3. 當使用者解除安裝我們的app時,系統僅僅會删除external根目錄(getExternalFilesDir())下的相關檔案。

4. External是在不需要嚴格的通路權限并且希望這些檔案能夠被其他app所共享或者是允許使用者通過電腦通路時的最佳存儲區域。

Tip: 盡管app是預設被安裝到internal storage的,我們還是可以通過在程式的manifest檔案中聲明android:installLocation 屬性來指定程式安裝到external storage。當某個程式的安裝檔案很大且使用者的external storage空間大于internal storage時,使用者會傾向于将該程式安裝到external storage。更多安裝資訊見App Install Location。

聲名外部存儲讀寫權限

<manifest ...>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    ...
</manifest>
           

儲存檔案到Internal Storage

當儲存檔案到internal storage時,可以通過執行下面兩個方法之一來擷取合适的目錄作為 FILE 的對象:

- getFilesDir() : 傳回一個File,代表了我們app的internal目錄。

- getCacheDir() : 傳回一個File,代表了我們app的internal緩存目錄。請確定這個目錄下的檔案能夠在一旦不再需要的時候馬上被删除,并對其大小進行合理限制,例如1MB 。系統的内部存儲空間不夠時,會自行選擇删除緩存檔案。

可以使用File() 構造器在那些目錄下建立一個新的檔案,如下:

File file = new File(context.getFilesDir(), filename);

同樣,也可以執行openFileOutput() 擷取一個 FileOutputStream 用于寫檔案到internal目錄。如下:

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中抽取了一個檔案名,然後再在程式的internal緩存目錄下建立了一個以這個檔案名命名的檔案。

public 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;
}
           

儲存檔案到External Storage

因為external storage可能是不可用的,比如遇到SD卡被拔出等情況時。是以在通路之前應對其可用性進行檢查。我們可以通過執行 getExternalStorageState()來查詢external storage的狀态。若傳回狀态為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;
}
           

盡管external storage對于使用者與其他app是可修改的,我們可能會儲存下面兩種類型的檔案。

- Public files :這些檔案對與使用者與其他app來說是public的,當使用者解除安裝我們的app時,這些檔案應該保留。例如,那些被我們的app拍攝的圖檔或者下載下傳的檔案。

- Private files: 這些檔案完全被我們的app所私有,它們應該在app被解除安裝時删除。盡管由于存儲在external storage,那些檔案從技術上而言可以被使用者與其他app所通路,但實際上那些檔案對于其他app沒有任何意義。是以,當使用者解除安裝我們的app時,系統會删除其下的private目錄。例如,那些被我們的app下載下傳的緩存檔案。

想要将檔案以public形式儲存在external storage中,請使用getExternalStoragePublicDirectory()方法來擷取一個 File 對象,該對象表示存儲在external storage的目錄。這個方法會需要帶有一個特定的參數來指定這些public的檔案類型,以便于與其他public檔案進行分類。參數類型包括DIRECTORY_MUSIC 或者 DIRECTORY_PICTURES. 如下:

public File 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;
}
           

想要将檔案以private形式儲存在external storage中,可以通過執行getExternalFilesDir() 來擷取相應的目錄,并且傳遞一個訓示檔案類型的參數。每一個以這種方式建立的目錄都會被添加到external storage封裝我們app目錄下的參數檔案夾下(如下則是albumName)。這下面的檔案會在使用者解除安裝我們的app時被系統删除。如下示例:

public File 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. 它會傳回app在external storage下的private的根目錄。

請記住,getExternalFilesDir() 方法會建立的目錄會在app被解除安裝時被系統删除。如果我們的檔案想在app被删除時仍然保留,請使用getExternalStoragePublicDirectory().

無論是使用 getExternalStoragePublicDirectory() 來存儲可以共享的檔案,還是使用 getExternalFilesDir() 來儲存那些對于我們的app來說是私有的檔案,有一點很重要,那就是要使用那些類似DIRECTORY_PICTURES 的API的常量。那些目錄類型參數可以確定那些檔案被系統正确的對待。例如,那些以DIRECTORY_RINGTONES 類型儲存的檔案就會被系統的media scanner認為是ringtone而不是音樂。

查詢剩餘空間

如果事先知道想要儲存的檔案大小,可以通過執行getFreeSpace() or getTotalSpace() 來判斷是否有足夠的空間來儲存檔案,進而避免發生IOException。那些方法提供了目前可用的空間還有存儲系統的總容量。

然而,系統并不能保證可以寫入通過getFreeSpace()查詢到的容量檔案, 如果查詢的剩餘容量比我們的檔案大小多幾MB,或者說檔案系統使用率還不足90%,這樣則可以繼續進行寫的操作,否則最好不要寫進去。

Note:并沒有強制要求在寫檔案之前去檢查剩餘容量。我們可以嘗試先做寫的動作,然後通過捕獲 IOException 。這種做法僅适合于事先并不知道想要寫的檔案的确切大小。例如,如果在把PNG圖檔轉換成JPEG之前,我們并不知道最終生成的圖檔大小是多少。

删除檔案

在不需要使用某些檔案的時候應删除它。删除檔案最直接的方法是直接執行檔案的delete()方法。

myFile.delete();

如果檔案是儲存在internal storage,我們可以通過Context來通路并通過執行deleteFile()進行删除

myContext.deleteFile(fileName);

Note: 當使用者解除安裝我們的app時,android系統會删除以下檔案:

所有儲存到internal storage的檔案。

所有使用getExternalFilesDir()方式儲存在external storage的檔案。

然而,通常來說,我們應該手動删除所有通過 getCacheDir() 方式建立的緩存檔案,以及那些不會再用到的檔案。