資料持久化就是指將那些記憶體中的瞬時資料儲存到儲存裝置中,保證即使在手機或電腦 關機的情況下,這些資料仍然不會丟失。儲存在記憶體中的資料是處於瞬時狀態的,而儲存在 儲存裝置中的資料是處於持久狀態的,持久化技術則是提供了一種機制可以讓資料在瞬時狀 態和持久狀態之間進行轉換。
持久化技術被廣泛應用於各種程式設計的領域當中,而本書中要探討的自然是 Android 中的資料持久化技術。Android 系統中主要提供了三種方式用於簡單地實現資料持久化功能, 即檔案儲存、SharedPreference 儲存以及資料庫儲存。當然,除了這三種方式之外,你還可 以將資料儲存在手機的 SD 卡中,不過使用檔案、SharedPreference 或資料庫來儲存資料會相 對更簡單一些,而且比起將資料儲存在 SD 卡中會更加的安全。
檔案儲存是 Android 中最基本的一種資料儲存方式,它不對儲存的內容進行任何的格式 化處理,所有資料都是原封不動地儲存到檔案當中的,因而它比較適合用於儲存一些簡單的 文字資料或二進位制資料。如果你想使用檔案儲存的方式來儲存一些較為複雜的文字資料,就 需要定義一套自己的格式規範,這樣方便於之後將資料從檔案中重新解析出來。
那麼首先我們就來看一看,Android 中是如何通過檔案來儲存資料的。
6.2.1 將資料儲存到檔案中
Context 類中提供了一個 openFileOutput ()方法,可以用於將資料儲存到指定的檔案中。 這個方法接收兩個引數,第一個引數是檔名,在檔案建立的時候使用的就是這個名稱,注 意這裡指定的檔名不可以包含路徑,因為所有的檔案都是預設儲存到/data/data/<package name>/files/ 目 錄 下 的 。 第 二 個 參 數 是 文 件 的 操 作 模 式 , 主 要 有 兩 種 模 式 可 選 , MODE_PRIVATE 和 MODE_APPEND。其中 MODE_PRIVATE 是預設的操作模式,表示當指 定同樣檔名的時候,所寫入的內容將會覆蓋原檔案中的內容,而 MODE_APPEND 則表示 如果該檔案已存在就往檔案裡面追加內容,不存在就建立新檔案。其實檔案的操作模式本來 還有另外兩種,MODE_WORLD_READABLE 和 MODE_WORLD_WRITEABLE,這兩種模 式表示允許其他的應用程式對我們程式中的檔案進行讀寫操作,不過由於這兩種模式過於危 險,很容易引起應用的安全性漏洞,現已在 Android 4.2 版本中被廢棄。
openFileOutput ()方法返回的是一個 FileOutputStream 物件,得到了這個物件之後就可以 使用 Java 流的方式將資料寫入到檔案中了。以下是一段簡單的程式碼示例,展示瞭如何將一 段文字內容儲存到檔案中:
public void save() {
String data = "Data to save"; FileOutputStream out = null; BufferedWriter writer = null; try {
out = openFileOutput("data", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(data);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
如 果 你 已 經 比 較 熟 悉 Java 流 了 , 理 解 上 面 的 代 碼 一 定 輕 而 易 舉 吧 。 這 裡 通 過 openFileOutput() 方 法 能 夠 得 到 一 個 FileOutputStream 對 象 , 然 後 再 借 助 它 構 建 出 一 個 OutputStreamWriter 物件,接著再使用 OutputStreamWriter 構建出一個 BufferedWriter 物件, 這樣你就可以通過 BufferedWriter 來將文字內容寫入到檔案中了。
下面我們就編寫一個完整的例子,藉此學習一下如何在 Android 專案中使用檔案儲存的 技術。首先建立一個 FilePersistenceTest 專案,並修改 activity_main.xml 中的程式碼,如下所示:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" >
<EditText android:id="@+id/edit" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="Type something here"
/>
</LinearLayout>
這裡只是在佈局中加入了一個 EditText,用於輸入文字內容。其實現在你就可以執行一 下程式了,介面上肯定會有一個文字輸入框。然後在文字輸入框中隨意輸入點什麼內容,再 按下 Back 鍵,這時輸入的內容肯定就已經丟失了,因為它只是瞬時資料,在活動被銷燬後 就會被回收。而這裡我們要做的,就是在資料被回收之前,將它儲存到檔案當中。修改 MainActivity 中的程式碼,如下所示:
public class MainActivity extends Activity {
private EditText edit;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
edit = (EditText) findViewById(R.id.edit);
}
@Override
protected void onDestroy() {
super.onDestroy();
String inputText = edit.getText().toString();
save(inputText);
}
public void save(String inputText) { FileOutputStream out = null; BufferedWriter writer = null;
try {
out = openFileOutput("data", Context.MODE_PRIVATE);
writer = new BufferedWriter(new OutputStreamWriter(out));
writer.write(inputText);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
可以看到,首先我們在 onCreate()方法中獲取了 EditText 的例項,然後重寫了 onDestroy() 方法,這樣就可以保證在活動銷燬之前一定會呼叫這個方法。在 onDestroy()方法中我們獲取 了 EditText 中輸入的內容,並呼叫 save()方法把輸入的內容儲存到檔案中,檔案命名為 data。 save()方法中的程式碼和之前的示例基本相同,這裡就不再做解釋了。現在重新執行一下程式, 並在 Editext 中輸入一些內容,如圖 6.1 所示。
圖 6.1
然後按下 Back 鍵關閉程式,這時我們輸入的內容就已經儲存到檔案中了。那麼如何才 能證實資料確實已經儲存成功了呢?我們可以藉助 DDMS 的 File Explorer 來檢視一下。切換 到 DDMS 視 圖 , 並 點 擊 File Explorer 切 換 卡 , 在 這 裡 進 入 到 /data/data/com.example. filepersistencetest/files/目錄下,可以看到生成了一個 data 檔案,如圖 6.2 所示。
圖 6.2
然後點選圖 6.3 中最左邊的按鈕可以將這個檔案匯出到電腦上。
圖 6.3
使用記事本開啟這個檔案,裡面的內容如圖 6.4 所示。
圖 6.4
這樣就證實了,在 EditText 中輸入的內容確實已經成功儲存到檔案中了。 不過只是成功將資料儲存下來還不夠,我們還需要想辦法在下次啟動程式的時候讓這些
資料能夠還原到 EditText 中,因此接下來我們就要學習一下,如何從檔案中讀取資料。
6.2.2 從檔案中讀取資料
類似於將資料儲存到檔案中,Context 類中還提供了一個 openFileInput()方法,用於從文 件中讀取資料。這個方法要比 openFileOutput()簡單一些,它只接收一個引數,即要讀取的文 件名,然後系統會自動到/data/data/<package name>/files/目錄下去載入這個檔案,並返回一個 FileInputStream 物件,得到了這個物件之後再通過 Java 流的方式就可以將資料讀取出來了。
以下是一段簡單的程式碼示例,展示瞭如何從檔案中讀取文字資料:
public String load() { FileInputStream in = null; BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in)); String line = "";
while ((line = reader.readLine()) != null) {
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return content.toString();
}
在這段程式碼中,首先通過 openFileInput()方法獲取到了一個 FileInputStream 物件,然後 藉助它又構建出了一個 InputStreamReader 物件,接著再使用 InputStreamReader 構建出一個 BufferedReader 物件,這樣我們就可以通過 BufferedReader 進行一行行地讀取,把檔案中所 有的文字內容全部讀取出來並存放在一個 StringBuilder 物件中,最後將讀取到的內容返回就 可以了。
瞭解了從檔案中讀取資料的方法,那麼我們就來繼續完善上一小節中的例子,使得重新 啟動程式時 EditText 中能夠保留我們上次輸入的內容。修改 MainActivity 中的程式碼,如下所示:
public class MainActivity extends Activity {
private EditText edit;
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
edit = (EditText) findViewById(R.id.edit);
String inputText = load();
if (!TextUtils.isEmpty(inputText)) { edit.setText(inputText); edit.setSelection(inputText.length()); Toast.makeText(this, "Restoring succeeded",
Toast.LENGTH_SHORT).show();
}
}
……
public String load() { FileInputStream in = null; BufferedReader reader = null;
StringBuilder content = new StringBuilder();
try {
in = openFileInput("data");
reader = new BufferedReader(new InputStreamReader(in)); String line = "";
while ((line = reader.readLine()) != null) {
content.append(line);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return content.toString();
}
}
可以看到,這裡的思路非常簡單,在 onCreate()方法中呼叫 load()方法來讀取檔案中儲存 的文字內容,如果讀到的內容不為空,就呼叫 EditText 的 setText()方法將內容填充到 EditText 裡,並呼叫 setSelection 方法將輸入游標移動到文字的末尾位置以便於繼續輸入,然後彈出 一句還原成功的提示。load()方法中的細節我們前面已經講過了,這裡就不再贅述。
注意上述程式碼在對字串進行非空判斷的時候使用了 TextUtils.isEmpty()方法,這是一 個非常好用的方法,它可以一次性進行兩種空值的判斷。當傳入的字串等於 null 或者等於 空字串的時候,這個方法都會返回 true,從而使得我們不需要單獨去判斷這兩種空值,再 使用邏輯運算子連線起來了。
現在重新執行一下程式,剛才儲存的 Content 字串肯定會被填充到 EditText 中,然後編寫一點其他的內容,比如在 EditText 中輸入 Hello,接著按下 Back 鍵退出程式,再重新啟
動程式,這時剛才輸入的內容並不會丟失,而是還原到了 EditText 中,如圖 6.5 所示。
圖 6.5
這樣我們就已經把檔案儲存方面的知識學習完了,其實所用到的核心技術就是 Context 類中提供的 openFileInput()和 openFileOutput()方法,之後就是利用 Java 的各種流來進行讀寫 操作就可以了。
不過正如我前面所說,檔案儲存的方式並不適合用於儲存一些較為複雜的文字資料,因 此,下面我們就來學習一下 Android 中另一種資料持久化的方式,它比檔案儲存更加簡單易 用,而且可以很方便地對某一指定的資料進行讀寫操作。