Android文檔Training之數據保存

大多數Android應用都需要保存數據,甚至說在onPause()狀態爲了防止用戶進度沒有丟失而保存用戶app狀態的消息,大多數非平凡的app也需要保存用戶設置,一些app必須要管理文件或者數據庫中大量的信息,這裏我們將介紹在android中幾種基本的數據存儲,包括:

在shared preferences文件中保存簡單的鍵值對
在Android文件系統中保存任意的文件
通過SQLite管理使用數據庫

鍵值對保存

如果你有相對比較少的鍵值對數據需要保存,你應該使用SharedPreferences APIs. 一個SharedPreferences 對象是一個包含鍵值對並提供簡單的方法來讀寫他們的文件系統,每一個SharedPreferences 文件都是framework來管理的,可以是私有的,可以被分享.
下面我們來介紹怎樣使用SharedPreferece APIs來保存或者獲取簡單的值.
注意: SharedPreferences APIs只能用來讀寫鍵值對,不要和Preference APIs搞混淆,Preference類是用來幫助你構造你的app設置裏的用戶界面(但是它們使用SharedPreferences 作爲保存app設置的工具),想知道更多關Preference APIs的使用,請看設置引導.

獲取一個SharedPreferences的句柄

你可以創建一個新的shared preference文件或者通過調用下面兩個方法之一來獲取已經存在的shared preference文件:

getSharedPreferences() — 如果你需要根據文件名獲取不同的share preference文件, 你可以使用這個方法並制定第一個參數(文件名).你可以在你的app中任何Context裏調用這個方法.
getPreferences() —如果你需要使用這個activity對應唯一的一個shared preference文件你可以在activity中調用該方法.這個方法將取回屬於這個activity的默認的一個shared preference文件,你不必提供文件名.

例如: 以下的代碼是在一個Fragment中執行的,它通過使用字符串R.string.preference_file_key來訪問shared preferences文件並用私有的方式打開,這樣這個文件只能被你自己的app訪問.

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

當給你的shared preference 文件命名的時候,你應該使用一個對你的app文藝標示的名稱,比如 “com.example.myapp.PREFERENCE_FILE_KEY”.

SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);

寫入數據到SharedPreferences

要寫入數據到一個shared preferences文件, 要先通過你的SharedPreferences調用edit()方法來獲取到一個SharedPreferences.Editor對象.

把鍵值對通過寫入數據的方法(比如putInt(),putString())來寫入Editor,然後再調用commit()方法來提交改變,例如:

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

從SharedPreferences文件中讀取數據

要從shared preferences 文件中獲取值, 調用類似getInt()或者getString()的方法,並準備好你需要的值對應的key,有時候會需要你提供一個默認的值以防在sharedPreferences中找不到對應的值,例如:

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

文件保存

android使用文件系統和其他平臺以磁盤爲基礎的文件系統是類似的,下面我們來介紹用File APIs來讀寫android文件系統中的文件.

一個文件對象適合用來都或者寫大量的從開頭到結尾中間不跳躍的數據. 例如:它比較適合用來處理圖片文件或者仍以其他網絡上的信息交流.

下面我們來介紹在你的app中執行基本的文件相關的任務,在這裏我們假設你對Linux 文件系統非常熟悉,並且瞭解java.io中的標準的文件輸入輸出APIs.

選擇內部或者外部存儲

素有的android設備都有兩個文件存儲區域:“內部”和“外部”,這個名稱來自早期的android,那時候大部分設備提供了內置的不易丟失的內存(內部存儲),再加上裏一個可以移除的中等尺寸的存儲比如micro SD卡(外部存儲).一些設備把永久的存儲空間分爲兩部分 “內部”和“外部”,所以就算沒有可以移除的外部存儲,也會總有兩個存儲空間,不管你有沒有移除外部存儲,APIs都是一樣的.下面的列表描述了每一個存儲空間的相關信息.

內部存儲:
    總是可以使用的.
    在這裏保存的文件默認只有你自己的app才能訪問到.
    當用戶卸載你的app時,系統將移除內部存儲裏的所有的文件.
內部存儲在你不想把文件直接暴露給用戶或者別的app的時候是最好的存儲位置.


外部存儲:
    並不總是可以使用的,因爲用戶可以連接USB存儲來增加容量,也可以通過把它從設備上移除.
    這裏保存的文件誰都有權利去讀,所以這裏保存的文件不再是完全在你的掌控之下.
    當用戶卸載你的app的時候,系統將會移除你保存在getExternalFilesDir()裏的你的app的文件.
外部存儲是在你不需要對文件進行限制訪問,並且你希望分享給其他的app或者允許用戶在電腦上訪問時最好的選擇.

提示: 雖然app是默認安裝在內部存儲的,但是你能在manifest指定android:installLocation屬性,這樣你的app將會被安裝在外部存儲上,用戶一般比較喜歡當apk安裝包比較大而且外部存儲比內部存儲大得多的時候安裝到外部存儲,想了解更多,可以查看App Install Location.

爲外部存儲獲取相應的權限:想要獲取外部存儲的權限,你需要在manifest 文件中請求WRITE_EXTERNAL_STORAGE 權限:

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

注意: 一般來說,所有的app在沒有特殊權限的情況下都有讀取外部存儲的能力. 然而, 在以後這都將會被改變. 如果你的app需要讀取外部存儲 (但不需要寫入), 那你需要在manifest中定義 READ_EXTERNAL_STORAGE 權限. 爲了確保你的app能正常工作,你應該在它權限申請有效之前先定義這個權限.

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

記住, 如果你的app使用了 WRITE_EXTERNAL_STORAGE 權限, 則它也隱式的擁有了讀取外部存儲的權限.

保存文件到內部存儲你不需要任何權限,你的應用總是有權限來讀寫它的內部的存儲文件夾裏的文件.

保存文件到內部存儲

當保存文件到內部存儲的時候,你可以通過調用下面的兩個方法之一來獲取到對應的文件夾:

getFilesDir()
    返回你的app對應的內部存儲文件夾.
getCacheDir()
    返回你的app的臨時緩存文件的內部存儲文件夾. 一旦你不再需要這些文件或者緩存文件超過了你所設置的最大容量(比如1MB)請刪除此文件夾裏面的每一個文件. 如果系統存儲比較低的時候,它將會毫無警告就刪掉這些緩存文件. 

在這些文件夾中創建一個新文件,你可以使用File()構造方法,把上面的指定內部存儲目錄的方法值傳遞給File,例如:

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

一般來說,你可以調用 openFileOutput()來獲得FileOutputStream 用來寫入文件到你的內部存儲. 例如,這裏是寫入一部分文本到一個文件中的代碼:

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獲取文件名然後在你的app內部緩存文件夾中那個文件名創建一個文件:

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

注意:你的app的內部存儲文件夾是在android文件系統中的具體的位置由你的app包名指定的. 理論上說, 如果你設置文件模式是readable,那麼其他的app都可以都可以讀取內部文件. 然而,前提是其他的app知道你包名和對應的文件名. 其他的app不能瀏覽你內部的文件夾也不能讀寫文件除非你指定了這些文件是可讀的或者可寫的. 所以只要你在內部存儲文件時使用 MODE_PRIVATE, 他們就永遠不能被其它的app所訪問.

保存文件到外部存儲

因爲外部存儲可能是不可用的-例如當用戶把存儲空間轉交給PC或者用戶移除了提供外部存儲的SD卡-你應該總是在訪問之前驗證一下外部存儲是否可用.你可以通過調用來查詢外部存儲狀態 getExternalStorageState(). 如果返回狀態等於 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;
}

雖然外部存儲可以被用戶或者其他的app修改,但是你也許有需要在這裏保存的兩類文件.

Public files(公有文件)
    該文件可以被其他app或者用戶自由訪問.當你卸載你的app的時候,這些文件會被留在磁盤,用戶仍然可以訪問.
    例如,你的app照相機照的照片或者其他的下載下來的文件.
Private files(私有文件)
    文件完全屬於你的app,在用戶卸載後將會被刪除. 雖然這些文件能被用戶或者其他app訪問(因爲它在外部存儲上), 這些文件在app之外不提供值給用戶,當用戶卸載app時,系統將會刪除你的app在外部存儲的你的私有的文件夾裏的所有文件.
    例如,你的app下載的額外的資源文件或者臨時媒體文件.

如果你想在外部存儲上保存公有文件,使用getExternalStoragePublicDirectory()方法來獲取一個在外部存儲上的合適的文件夾. 這個方法需要指定一個你需要保存的文件類型的參數比如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;
}

如果你想保存你的app的私有文件到外部存儲,你可以通過調用getExternalFilesDir()來獲取合適的文件夾,並傳給它一個代表文件夾類型的名稱,每個用這種方法創建的每個文件夾愛將會被添加到父目錄,這個父目錄囊括了所有你的app的外部存儲文件,當用戶卸載你的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在外部存儲的私有目錄的根目錄.

記住getExternalFilesDir()在文件夾中創建了一個在app被卸載的時候會被刪除的文件夾. 如果你想保存在app卸載後仍然可用的文件–例如當你的app是一個照相機,用戶卸載後仍然想把這些照片保存起來–這時候你應該使用getExternalStoragePublicDirectory().

不管你是使用getExternalStoragePublicDirectory()(這裏的文件可以分享)還是使用getExternalFilesDir() (這裏的文件是你的app私有的), 文件夾名使用API提供的常數(例如DIRECTORY_PICTURES)是非常重要的. 這些文件夾名稱確保了裏面的文件會被系統正確的處理. 例如, 保存在DIRECTORY_RINGTONES的中的文件將會被系統的媒體掃描器識別爲鈴聲還不是音樂.

查詢剩餘空間

如果你提前知道你將要保存多少數據,你可以通過調用getFreeSpace() 或者getTotalSpace()來判斷存儲空間是否充足以防止產生IO異常. 這些方法返回在存儲盤中當前可用的空間和總空間,這些信息在避免存儲數據超過磁盤總容量時是非常有用的.

然而, 系統不保證你能寫入getFreeSpace()返回值給定的那麼多的字節數. 如果返回值比你要保存的數據多幾MB,或者如果文件系統使用不超過90%,那麼保存數據到文件系統一般是比較安全的(不會出現因空間不足引起的IO異常),否則,你可能寫入數據到存儲空間會失敗.

注意: 你再保存文件的時候不需要檢測有多少空間是可用的. 你可以直接寫入,然後如果發生一個IO異常你可以抓住它. 如果你不知道你需要使用多少空間你也許需要這樣做. 例如, 如果在保存之前先通過把一個PNG圖片轉化爲JPEG圖片來改變文件編碼,你肯定提前不知道文件的大小.

刪除一個文件

你應該經常刪除那些不再需要的文件,刪除文件最直接的方法就是先獲得一個打開的文件的引用,然後調用它自己的delete()方法.

myFile.delete();    

如果文件是保存在內部存儲,你也可以通過在Context通過調用deleteFile()來鎖定和刪除文件:

myContext.deleteFile(fileName);

注意: 當用戶卸載你的app的時候androd系統刪除以下的文件::

所有你保存在內部存儲裏的文件
所喲你保存在外部存儲裏的用getExternalFilesDir()創建/保存的文件

然而, 你應該手動刪除所有的用getCacheDir()創建的緩存文件,這些緩存文件是通過正規的方方法創建的,也要通過正規的方式來刪除你不再需要的文件.

SQL數據庫中數據保存

保存數據到數據庫對重複或有結構的數據是非常理想的方法,比如聯繫人信息,在這裏假設你對SQL數據庫非常熟悉,這裏幫助你熟悉android上的SQLite數據庫. 你在android需要的使用的 APIs在android.database.sqlite裏.

定義架構和協議

SQL數據庫主要的原理之一是架構:一個個好似話的
One of the main principles of SQL databases is the schema: a formal declaration of how the database is organized. The schema is reflected in the SQL statements that you use to create your database. You may find it helpful to create a companion class, known as a contract class, which explicitly specifies the layout of your schema in a systematic and self-documenting way.

A contract class is a container for constants that define names for URIs, tables, and columns. The contract class allows you to use the same constants across all the other classes in the same package. This lets you change a column name in one place and have it propagate throughout your code.

A good way to organize a contract class is to put definitions that are global to your whole database in the root level of the class. Then create an inner class for each table that enumerates its columns.

Note: By implementing the BaseColumns interface, your inner class can inherit a primary key field called _ID that some Android classes such as cursor adaptors will expect it to have. It’s not required, but this can help your database work harmoniously with the Android framework.

For example, this snippet defines the table name and column names for a single table:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章