Android官方培訓課程學習(四): 資料儲存

塵埃zza發表於2016-09-06

主要包括:

  • 儲存到Preferences,使用Shared Preferences檔案以Key-Value的方式儲存簡要的資訊。
  • 儲存到檔案,儲存基本的檔案。
  • 儲存到資料庫,使用SQLite資料庫讀寫資料。(暫無記錄)

儲存到Preference

獲取SharedPreference

我們可以通過以下兩種方法之一建立或者訪問shared preference 檔案:

  • getSharedPreferences() — 如果需要多個通過名稱引數來區分的shared preference檔案, 名稱可以通過第一個引數來指定。可在app中通過任何一個Context 執行該方法。
Context context = getActivity();
SharedPreferences sharedPref = context.getSharedPreferences(
        getString(R.string.preference_file_key), Context.MODE_PRIVATE);
  • getPreferences() — 當activity僅需要一個shared preference檔案時。因為該方法會檢索activity下預設的shared preference檔案,並不需要提供檔名稱。
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);

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

寫Shared Preference

  • 為了寫shared preferences檔案,需要通過執行edit()建立一個 SharedPreferences.Editor
  • 通過類似putInt()與putString()等方法傳遞keys與values,接著通過commit() 提交改變
SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt(getString(R.string.saved_high_score), newHighScore);
editor.commit();

讀Shared Preference

為了從shared preference中讀取資料,可以通過類似於 getInt() 及 getString()等方法來讀取。在那些方法裡面傳遞我們想要獲取的value對應的key,並提供一個預設的value作為查詢的key不存在時函式的返回值。如下:

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);

儲存到檔案

儲存在內部還是外部

所有的Android裝置均有兩個檔案儲存區域:”internal” 與 “external” 。 這兩個名稱來自於早先的Android系統,當時大多裝置都內建了不可變的記憶體(internal storage)及一個類似於SD card(external storage)這樣的可解除安裝的儲存部件。之後有一些裝置將”internal” 與 “external” 都做成了不可解除安裝的內建儲存,雖然如此,但是這一整塊還是從邏輯上有被劃分為”internal”與”external”的。只是現在不再以是否可解除安裝進行區分了。 下面列出了兩者的區別:

  • Internal storage:
    • 總是可用的
    • 這裡的檔案預設只能被我們的app所訪問。
    • 當使用者解除安裝app的時候,系統會把internal內該app相關的檔案都清除乾淨。
    • Internal是我們在想確保不被使用者與其他app所訪問的最佳儲存區域。
  • External storage:
    • 並不總是可用的,因為使用者有時會通過USB儲存模式掛載外部儲存器,當取下掛載的這部分後,就無法對其進行訪問了。
    • 是大家都可以訪問的,因此儲存在這裡的檔案可能被其他程式訪問。
    • 當使用者解除安裝我們的app時,系統僅僅會刪除external根目錄(getExternalFilesDir())下的相關檔案。
    • External是在不需要嚴格的訪問許可權並且希望這些檔案能夠被其他app所共享或者是允許使用者通過電腦訪問時的最佳儲存區域。

Tip: 儘管app是預設被安裝到internal storage的,我們還是可以通過在程式的manifest檔案中宣告android:installLocation 屬性來指定程式安裝到external storage。

獲取External儲存的許可權

為了寫資料到external storage, 必須在你manifest檔案中請求WRITE_EXTERNAL_STORAGE許可權:

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

Caution:目前,所有的apps都可以在不指定某個專門的許可權下做讀external
storage的動作。但這在以後的安卓版本中會有所改變。如果我們的app只需要讀的許可權(不是寫), 那麼將需要宣告
READ_EXTERNAL_STORAGE 許可權。為了確保app能持續地正常工作,我們現在在編寫程式時就需要宣告讀許可權。

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

但是,如果我們的程式有宣告WRITE_EXTERNAL_STORAGE 許可權,那麼就預設有了讀的許可權。

對於internal storage,我們不需要宣告任何許可權,因為程式預設就有讀寫程式目錄下的檔案的許可權。

儲存到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。那些方法提供了當前可用的空間還有儲存系統的總容量。

Note:並沒有強制要求在寫檔案之前去檢查剩餘容量。我們可以嘗試先做寫的動作,然後通過捕獲 IOException 。這種做法僅適合於事先並不知道想要寫的檔案的確切大小。

刪除檔案

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

myFile.delete();

如果檔案是儲存在internal storage,我們可以通過Context來訪問並通過執行deleteFile()進行刪除

myContext.deleteFile(fileName);

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

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

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

相關文章