第十章 應用程序數(shù)據(jù)

2018-02-24 15:47 更新

第十章 應用程序數(shù)據(jù)

我們已經(jīng)熟悉了Android應用程序的結(jié)構(gòu)與基本組成元素,其中包括資源、清單與用戶界面。在著手進行Android平臺的功能性應用開發(fā)之后,大家肯定需要保存這樣或者那樣的數(shù)據(jù)信息。Android平臺提供多種選項,用于打理應用程序中的數(shù)據(jù)存儲任務,而這正是今天這篇文章要討論的核心內(nèi)容。

從廣義上講,Android應用中的數(shù)據(jù)存儲選項共有五種主要類型:將數(shù)據(jù)保存在應用的共享偏好當中、保存在內(nèi)部存儲(專屬于應用本身)當中、保存在外部存儲(向設備公開)當中、保存在數(shù)據(jù)庫當中以及保存在可通過設備互聯(lián)網(wǎng)連接訪問的Web資源當中。受篇幅所限,我們無法詳細對這些選項作出論述,但會對每種方案的基礎(chǔ)特性加以概括、從而幫助大家在需要使用持久化數(shù)據(jù)時理清存儲問題的解決思路。

1. 共享偏好

第一步

共享偏好允許大家以鍵-值對的形式保存基本數(shù)據(jù)類型。應用程序的共享偏好文件通常被視為最簡單的數(shù)據(jù)存儲選項,但從本質(zhì)上說它對于存儲對象提出了一定程度的限制。大家可以通過它存儲基本類型數(shù)字(如整數(shù)、長數(shù)以及浮點數(shù)字)、布爾值以及文本字符串。我們需要為自己保存的每個數(shù)值分配一個名稱,從而在應用程序運行時據(jù)此對其進行檢索。由于大家很可能在自己創(chuàng)建的第一款應用中就用到共享偏好,因此我們?nèi)税阉鳛橹v解的重點、以更為詳盡的方式(相較于其它選項)進行表述,從而幫助各位鞏固必要知識。

大家可以在自己的主Activity類中嘗試這些代碼,并在稍后運行本系列教程的應用示例時對其加以測試。在理想情況下,共享偏好應該可以符合應用程序中的用戶配置選項,如同選擇外觀設置一樣。大家應該還記得,我們曾經(jīng)創(chuàng)建過一個簡單的按鈕,用戶點擊它之后屏幕上會顯示出“Ouch”文本內(nèi)容?,F(xiàn)在讓我們假設自己希望用戶在點擊一次之后,該按鈕上會持續(xù)顯示“Ouch”字樣,且該狀態(tài)在應用程序運行過程中始終保持不變。這意味著按鈕上的初始文本僅在用戶首次點擊操作之前存在。

讓我們?yōu)閼贸绦蛱砑庸蚕砥脙?nèi)容。在該類的起始位置、onCreate方法之前,我們?yōu)楣蚕砥眠x擇一個名稱:

public static final String MY_APP_PREFS = "MyAppPrefs"

利用“public static”修飾符,我們可以訪問處于應用內(nèi)任何類中的這項變量,因此我們只需要將偏好名稱字符串保存在這里即可。我們使用大寫是因為該變量屬于常數(shù),“final”修飾符也是因此而存在。每一次檢索或者在應用程序偏好當中設置數(shù)據(jù)條目時,大家都必須使用同樣的名稱。

第二步

現(xiàn)在我們來編寫共享偏好內(nèi)容。在我們的onClick方法中、按鈕“Ouch”文本設置部分的下方,嘗試通過名稱取回這條共享偏好:

?SharedPreferences thePrefs = getSharedPreferences(MY_APP_PREFS, 0);

大家需要為“android.conent.SharedPreferences”類添加一條導入。將鼠標懸停在“SharedPreferences”文本上方,并利用Eclipse提示完成導入。第一項參數(shù)是我們所定義的偏好名稱,第二項則是我們作為默認選項的基本模式。

現(xiàn)在我們需要為共享偏好指定一套編輯器,從而實現(xiàn)對其中數(shù)值的設定:

SharedPreferences.Editor prefsEd = thePrefs.edit();

現(xiàn)在我們可以向共享偏好當中寫入值了:

prefsEd.putBoolean("btnPressed", true);

這里我們使用了布爾類型,因為當前狀態(tài)只分為兩種——用戶已經(jīng)或者尚未按下按鈕。編輯器提供多種不同類型,我們可以從中選擇以保存這套共享偏好,其中每種方法都擁有自己的名稱與值參數(shù)。最后,我們需要提交編輯結(jié)果:

prefsEd.commit();

第三步

現(xiàn)在讓我們利用已經(jīng)保存的值來檢測用戶運行應用程序后,按鈕應該顯示什么樣的內(nèi)容。在onCreate中的現(xiàn)有代碼之后添加共享偏好:

SharedPreferences thePrefs = getSharedPreferences(MY_APP_PREFS, 0);

這一次我們不必使用編輯器,因為我們只需要獲取一個值:

boolean pressed = thePrefs.getBoolean("btnPressed", false);

現(xiàn)在我們利用已經(jīng)設置過的名稱檢索該值,并讀取變量中的結(jié)果。如果該值尚未被設置,返回的則為第二項參數(shù),也就是默認值——代表否定含義。現(xiàn)在讓我們使用該值:

if(pressed) theButton.setText("Ouch");

如果用戶在應用程序運行之后按下該按鈕,則按鈕直接顯示“Ouch”字樣。在本系列的后續(xù)文章當中,大家會看到我們在應用運行中進行這一操作的情況。這個簡單的例子很好地詮釋了共享偏好的使用過程。大家會發(fā)現(xiàn),共享偏好在幫助應用程序通過外觀及使用感受迎合用戶喜好方面具有重要的作用。

2. 私有內(nèi)部文件

第一步

大家可以將文件保存在用戶設備的內(nèi)部以及外部存儲當中。如果將文件保存在內(nèi)部存儲中,Android系統(tǒng)會將其視為專屬于當前應用的私有數(shù)據(jù)。這類文件基本上屬于應用程序的組成部分,我們無法在應用程序之外直接對其進行訪問。再有,如果應用程序被移除、這些文件也會同時被清空。

大家可以利用以下輸出例程在內(nèi)存存儲中創(chuàng)建一個文件:

FileOutputStream fileOut = openFileOutput("my_file", Context.MODE_PRIVATE);

大家需要為“java.io.FileOutputStream”類進行導入添加。我們提供了文件名稱與模式,選擇私有模式意味著該文件將只能被該應用程序所使用。如果大家現(xiàn)在就把這部分代碼加入到Activity當中,例如onClick方法中,Eclipse將彈出錯誤提示。這是因為當我們進行輸入/輸出操作時,應用程序可能遭遇一些需要應對的錯誤。如果大家的輸入/輸出操作無法解決這類錯誤,Eclipse就會提示異常狀況、應用程序也會中止運行。為了保證應用程序在這種情況下仍能正常運行,我們需要將自己的輸入/輸出代碼封裝在try代碼塊當中:

try{
    FileOutputStream fileOut = openFileOutput("my_file", Context.MODE_PRIVATE);
}
catch(IOException ioe){ 
    Log.e("APP_TAG", "IO Exception", ioe);
}

如果輸入/輸出操作導致異常,那么catch塊中的上述代碼就會付諸執(zhí)行,從而將錯誤信息寫入到日志當中。大家今后會經(jīng)常用到應用程序中的Log類(導入‘a(chǎn)ndroid.util.Log’),它會記錄代碼執(zhí)行時所發(fā)生的具體情況。我們可以為字符串標簽定義一個類變量,也就是上述代碼中的第一條參數(shù)。這樣一旦出現(xiàn)錯誤,大家就可以在Android LogCat中查看異常信息了。

第二步

現(xiàn)在回到try塊,在創(chuàng)建了文件輸出例程之后,大家可以嘗試將以下代碼寫入文件:

String fileContent = "my data file content"
fileOut.write(fileContent.getBytes());

在將所有必要內(nèi)容寫入數(shù)據(jù)文件之后,利用以下代碼作為結(jié)尾:

fileOut.close();

第三步

當大家需要檢索內(nèi)部文件中的內(nèi)容時,可以通過以下流程實現(xiàn):

try{
    FileInputStream fileIn = openFileInput("my_file");
    //read the file
}
catch(IOException ioe){ 
    Log.e("APP_TAG", "IO Exception", ioe);
}

在try塊當中,利用利用緩沖讀取器讀取文件內(nèi)容:

InputStreamReader streamIn = new InputStreamReader(fileIn);
BufferedReader fileRead = new BufferedReader(streamIn);
StringBuilder fileBuild = new StringBuilder("");
String fileLine=fileRead.readLine();
while(fileLine!=null){
    fileBuild.append(fileLine+"\n");
    fileLine=fileRead.readLine();
}
String fileText = fileBuild.toString();
streamIn.close();

大家不要被其中所涉及的大量不同對象所嚇倒,這其實屬于標準的Java輸入/輸出操作。其中的while循環(huán)會在文件中的每一行執(zhí)行一次。在執(zhí)行完成后,“fileText”變量將把文件內(nèi)容保存為字符串、以備我們直接使用。

3. 公共外部文件

第一步

只要用戶設備支持,我們的應用程序也可以將文件保存在外部存儲當中。外部存儲種類繁多,包括SD卡、其它便攜式介質(zhì)或者用戶無法移除但被系統(tǒng)認定為外部類型的內(nèi)存存儲機制。當我們將文件保存在外部存儲中時,其內(nèi)容將完全公開、大家也無法以任何方式阻止用戶或者其它應用對其進行訪問。

在我們嘗試將數(shù)據(jù)保存在外部存儲中之前,必須首先檢查對應存儲機制是否可用——盡量避免意外狀況絕對是種好習慣:

String extStorageState = Environment.getExternalStorageState();

系統(tǒng)會將信息以字符串的形式返回,大家可以對其進行分析、并與Environment類中的外部存儲狀態(tài)字段加以比對:

if(Environment.MEDIA_MOUNTED.equals(extStorageState)){
    //ok to go ahead and read/ write to external storage
}
else if(Environment.MEDIA_MOUNTED_READ_ONLY.equals(extStorageState)){
    //can only read
}
else{
    //cannot read or write
}

即使設備上確實存在外部存儲,我們也不能先入為主地假定應用可以向其寫入數(shù)據(jù)。

第二步

在證實了我們確實能夠向外部存儲寫入數(shù)據(jù)之后,大家接下來需要檢索目錄以指定文件保存的位置。以下應用程序設置內(nèi)容指向八級及更高API:

File myFile = new File(getExternalFilesDir(null), "MyFile.txt");

這樣大家就可以對該文件進行寫入與讀取了。不過也別忘了在項目的清單文件中添加以下僅限:

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

隨著我們開發(fā)的應用程序變得愈發(fā)復雜,大家可能希望將自己保存得到的文件與其它應用共享。在這種情況下,大家可以使用公共目錄下的各類通用條目,例如圖片以及音樂文件。

4. 數(shù)據(jù)庫

隨著我們的應用程序所涉及的復雜結(jié)構(gòu)數(shù)據(jù)越來越多,共享偏好或者內(nèi)部/外部文件可能已經(jīng)無法滿足實際需求,這時候大家就應該考慮使用數(shù)據(jù)庫方案了。Android支持開發(fā)人員在應用程序內(nèi)部創(chuàng)建并訪問SQLite數(shù)據(jù)庫。在我們創(chuàng)建一套數(shù)據(jù)庫時,其將作為私有組件服務單純服務于相關(guān)應用程序。

在Android應用中利用SQLite數(shù)據(jù)庫的方法多種多樣,推薦大家使用擴展SQLiteOpenHelper的類來實現(xiàn)這方面需求。在該類當中,我們需要定義數(shù)據(jù)庫屬性、創(chuàng)建各種類變量(包括我們所定義的數(shù)據(jù)庫列表名稱及其SQL創(chuàng)建字符串),具體代碼如下所示:

private static final String NOTE_TABLE_CREATE = 
    "CREATE TABLE Note (noteID INTEGER PRIMARY KEY AUTOINCREMENT, " +
    "noteTxt TEXT);";

這里所舉的例子只涉及一套非常簡單的表格,其中包含兩列,一列內(nèi)容為ID、另一列內(nèi)容為文本;兩列都用于記錄用戶注釋信息。在SQLiteOpenHelper類當中,大家可以重寫onCreate方法來創(chuàng)建自己的數(shù)據(jù)庫。在應用程序的其它部分當中,例如Activity類中,大家可以通過SQLiteOpenHelper實現(xiàn)對數(shù)據(jù)庫的訪問,并利用WritableDatabase方法插入新記錄、利用getReadableDatabase方法來查詢現(xiàn)有記錄,而后將結(jié)果顯示在應用程序UI當中。

在對查詢結(jié)果進行迭代時,我們的應用程序?qū)⑹褂肅ursor類——該類會依次引用結(jié)果集中的每一行內(nèi)容。

5. 互聯(lián)網(wǎng)數(shù)據(jù)

很多應用都會使用互聯(lián)網(wǎng)數(shù)據(jù)資源,而且某些應用甚至基本是由一套界面與大量Web數(shù)據(jù)源所構(gòu)成。大家可以利用用戶設備上的互聯(lián)網(wǎng)連接來存儲并檢索來自Web的數(shù)據(jù),只要網(wǎng)絡連接有效、這一機制就能正常運作。為了實現(xiàn)這一目標,我們需要在自己的清單文件中添加“android.permission.INTERNET”權(quán)限。

如果我們希望自己的應用能夠從互聯(lián)網(wǎng)中獲取數(shù)據(jù),則必須保證這一流程脫離應用主UI線程。利用AsyncTask,大家可以通過后臺進程的方式從Web源獲取數(shù)據(jù)、在數(shù)據(jù)下載完成后將結(jié)果寫入UI、最后讓UI正常執(zhí)行自身功能。

大家還可以將一個內(nèi)部AsyncTask類添加到Activity類當中,并在需要獲取數(shù)據(jù)的時候在該Activity中創(chuàng)建一個AsyncTask實例。通過在AsyncTask中引入doInBackground與onPostExecute兩種方法,大家可以檢索Activity中所獲取到的數(shù)據(jù)并將其寫入用戶界面。

獲取Web數(shù)據(jù)在應用開發(fā)工作當中屬于中等難度的任務,大家最好在熟練掌握了Android開發(fā)知識之后再進行嘗試。不過大家可能很快就會發(fā)現(xiàn),這樣的數(shù)據(jù)獲取機制對不少應用都非常適合,因為這能有效利用用戶設備的連接資源。Java與Android都提供相關(guān)工具,用于處理返回的結(jié)構(gòu)化數(shù)據(jù)——例如JSON feed。

結(jié)論

在今天的文章中,我們基本了解了開發(fā)Android應用程序時需要接觸到的數(shù)據(jù)存儲方案。無論大家最終選擇哪種方案,都應該以實際需求作為參考標準,因為不同的方案只適合特定需求。在本系列教程的下一篇當中,我們將共同探討如何將物理設備與已安裝的Eclipse相連、同時學習如何創(chuàng)建虛擬設備。在此之后,我們還將探索如何讓應用程序運行在這兩種類型的設備之上。順便向大家報告,再有兩篇文章本系列教程就將徹底結(jié)束;在最后一篇文章中,我們將研究通用類以及Android Activity生命周期,從而幫助大家做好開發(fā)應用程序的一切準備。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號