Android開發(fā)之?dāng)?shù)據(jù)保存技術(shù)(一)
/*
* Android開發(fā)之?dāng)?shù)據(jù)保存技術(shù)(一)
* 北京Android俱樂部群:167839253
* Created on: 2011-8-17
* Author: blueeagle
* Email: liujiaxiang@gmail.com
*/
本文主要講解Android開發(fā)的數(shù)據(jù)保存技術(shù)。Android的數(shù)據(jù)保存技術(shù)主要有preference,本地文件,SQLite輕量級(jí)數(shù)據(jù)庫,和Content Provider。本文只要講SQLite和Conent Provider。preference和本地文件,將放在后面討論。
SQLite
Android通過SQLite庫提供了完善的關(guān)系數(shù)據(jù)庫功能,而沒有強(qiáng)加任何額外的限制。通過使用SQLite,可以為每一個(gè)應(yīng)用程序創(chuàng)建獨(dú)立的關(guān)系數(shù)據(jù)庫。
所有的Android數(shù)據(jù)庫都存儲(chǔ)在/data/data/<package_name>/databases文件夾下。默認(rèn)條件下,所有的數(shù)據(jù)庫都是私有的,并且只能被創(chuàng)建它們的應(yīng)用程序訪問。要跨應(yīng)用程序共享數(shù)據(jù)庫,可以使用內(nèi)容提供器。
SQLite是一個(gè)關(guān)系數(shù)據(jù)管理系統(tǒng)。它被普遍認(rèn)為是:開源,兼容標(biāo)準(zhǔn),輕量級(jí),Single-tier。
可以用一個(gè)例子來演示SQLite。該例子將記錄存儲(chǔ)在一個(gè)數(shù)據(jù)庫中,然后顯示出來。
新建一個(gè)HelloSQLite的程序。
需要在某個(gè)地方放置該數(shù)據(jù)庫中描述的一些常量,所以要?jiǎng)?chuàng)建一個(gè)Constants接口。
代碼如下:
/*
* Android開發(fā)之?dāng)?shù)據(jù)保存技術(shù)(一)
* Constants.java
* Created on: 2011-8-15
* Author: blueeagle
* Email: liujiaxiang@gmail.com
*/
每個(gè)事件都將作為HeloSQLite表中的一行進(jìn)行存儲(chǔ)。每行都包含一個(gè)_id、time和title列。_id是主鍵,在擴(kuò)展的BaseColums接口中聲明。time和title分別作為時(shí)間和事件標(biāo)記。
使用SQliteOpenHelper。接下來,創(chuàng)建一個(gè)名為SQLiteData的幫助器來表示數(shù)據(jù)庫本身。這個(gè)類擴(kuò)展自Android的SQLiteOpenHelper類,它負(fù)責(zé)管理數(shù)據(jù)庫的創(chuàng)建和版本。需要做的就是提供一個(gè)構(gòu)造方法并且覆寫兩個(gè)方法。
代碼如下:
首次訪問數(shù)據(jù)庫時(shí),SQLiteOpenHelper將注意到該數(shù)據(jù)庫不存在,并調(diào)用onCreate()方法來創(chuàng)建它。
定義Activity主程序。
在HelloSQLite程序中做的第一次嘗試是使用本地的SQLite數(shù)據(jù)庫來存儲(chǔ)事件,并將這些事件顯示為TextView中的一個(gè)字符串。
布局xml文件如下:
這段代碼使用一個(gè)ScrollView,以防太多的時(shí)間沾滿了屏幕。
對(duì)于HelloSQLite.java的實(shí)現(xiàn)如下所示:相關(guān)代碼有注釋說明。
完成上述文件-》運(yùn)行,即可看到結(jié)果顯示出來。這樣就完成了第一個(gè)Android數(shù)據(jù)庫程序。
當(dāng)然,這里面需要注意的問題:
1. 新建數(shù)據(jù)庫的問題
新建數(shù)據(jù)庫的時(shí)候,會(huì)遇到我們用:
db.execSQL("CREATE TABLE "+ TABLE_NAME +"("+ _ID +"INTEGER PRIMARY KEY AUTOINCREMENT,"+ TIME +"INTEGER,"+ TITLE +"TEXT NOT NULL);");
這樣一條語句。有時(shí)候SQL語句書寫錯(cuò)誤,比如少一個(gè)空格就會(huì)引起程序異常退出。這個(gè)時(shí)候建議在db.execSQL()函數(shù)中填寫一個(gè)字符串變量,然后把自己寫的SQL語句賦給字符串變量,在做賦值操作的時(shí)候,先打印出來看看,是不是少空格,多+號(hào)什么的小錯(cuò)誤。
2. 版本號(hào)的問題
關(guān)于構(gòu)造函數(shù)中的版本號(hào),是有明確的說明的。
public MySQLite(Context context, String name, CursorFactory factory,
int version) {
super (context, DATABASE_NAME , factory, DATABASE_VERSION );
這個(gè)版本號(hào)version當(dāng)你有新版本的時(shí)候,則做更新操作,新版本的版本號(hào)將要比老版本高,如果此時(shí)修改版本號(hào),不是向高修改,而是向低修改的話,程序就會(huì)發(fā)生異常了。
3. 數(shù)據(jù)庫文件的問題
對(duì)于onCreate()方法,在程序運(yùn)行的時(shí)候,只運(yùn)行一次,運(yùn)行的這一次就是去創(chuàng)建數(shù)據(jù)庫。將數(shù)據(jù)庫的文件存儲(chǔ)在SD卡中。路徑是data/data/包名/databases里。可以在Eclipse里通過DDMS里的File Explorer來查看。當(dāng)數(shù)據(jù)庫文件存在了以后,則不會(huì)再次運(yùn)行。
完成了上述例子,就算完成了一個(gè)數(shù)據(jù)庫的應(yīng)用。但是如果列表中有數(shù)千個(gè)或者上百萬個(gè)事件。程序?qū)⑦\(yùn)行的非常慢。甚至于耗盡內(nèi)存。解決辦法就是數(shù)據(jù)綁定。
有了數(shù)據(jù)綁定,只需要幾行代碼,就可以將數(shù)據(jù)連接到視圖,從而實(shí)現(xiàn)需求。為了演示,我們修改上面的實(shí)例。
將主程序HelloSQLite.java繼承ListActivity而不是Activity。
將代碼修改為如下所示:
將main.xml修改成如下形式:
增加item.xml為如下:
運(yùn)行結(jié)果如圖所示。
當(dāng)然,這只是一個(gè)DEMO,用來展示如何使用SQLite,這個(gè)DEMO存在一些問題。其他應(yīng)用程序都不能向事件數(shù)據(jù)庫添加內(nèi)容,甚至于無法看這些事件。那么針對(duì)這一點(diǎn),引出了Android里的內(nèi)容提供器,即ContentProvider。
內(nèi)容提供器
在Android安全模型中,一個(gè)應(yīng)用程序編寫的文件無法被其他任何應(yīng)用程序所讀寫。每個(gè)應(yīng)用程序都有自己的Linux用戶ID和數(shù)據(jù)目錄,以及其受保護(hù)的內(nèi)存空間。Android程序可以通過下面兩種方式進(jìn)行彼此間的通信。
第一種是IPC(Inter-Process Communication,進(jìn)程間通信):一個(gè)進(jìn)程使用AIDL(Android Interface Definition Language,接口定義語言)和IBinder接口聲明一個(gè)任意的API。調(diào)用該API時(shí),將在進(jìn)程間安全且有效地隊(duì)參數(shù)進(jìn)行編組。這項(xiàng)先進(jìn)技術(shù)用于對(duì)后臺(tái)Service線程進(jìn)行遠(yuǎn)程過程調(diào)用。
第二種就是ContentProvider:進(jìn)程在系統(tǒng)中將他們本身注冊(cè)為某些數(shù)據(jù)類型的提供者。請(qǐng)求該信息時(shí),Android就會(huì)通過一個(gè)固定的API調(diào)用這些進(jìn)程,以它們認(rèn)為合適的方式查詢或者修改內(nèi)容。
ContentProvider管理的任何信息部分都通過一個(gè)URI來尋址,這個(gè)URI類似于以下形式:content://authority/path/id
其中content://是標(biāo)準(zhǔn)要求的前綴。authority是提供者的名稱,建議使用完全限定包名稱,避免出現(xiàn)名稱沖突。Path是提供者內(nèi)部的一個(gè)虛擬目錄,用于標(biāo)識(shí)被請(qǐng)求的數(shù)據(jù)類型。Id是被請(qǐng)求的特定記錄的主鍵,要請(qǐng)求獲得具有特定類型的所有記錄,可以省略此參數(shù)以及后面的斜杠。
Android已經(jīng)內(nèi)置提供了幾個(gè)提供者,包括:
content://browser;
content://contacts;
content://media;
content://settings。
下面我們就來演示如何使用內(nèi)容提供器。
將HelloSQLite程序改為使用內(nèi)容提供器的。對(duì)于HelloSQLite的提供者,下面都是有效的URI:
content://com.blueeagle.HelloSQLite/3 ——表示取id為3的數(shù)據(jù)
content://com.blueeagle.HelloSQLite/ ——表示取所有數(shù)據(jù)
首先需要向Contants.java添加兩個(gè)常量:
public static final String AUTHORITY = "com.blueeagle.HelloSQLite";
public static final Uri CONTENT_URI = Uri. parse ("content://"+ AUTHORITY +"/"+ TABLE_NAME );
接下來可以對(duì)HelloSQLite類做一些修改:
代碼如下:
下面就是實(shí)現(xiàn)ContentProvider
ContentProvider是一個(gè)類似于Activity的高級(jí)對(duì)象,需要向系統(tǒng)進(jìn)行聲明。因此,實(shí)現(xiàn)ContentProvider的第一步是將其添加到AndroidManifest.xml文件中的<activity>標(biāo)簽之前。
其中android:name是類名,android:authorities是在內(nèi)容URI中使用的字符串。接下來創(chuàng)建我們自己的ContentProvider類,為繼承類。ContentProvider創(chuàng)建后就會(huì)被調(diào)用:public boolean onCreate()ContentProvider類中有6個(gè)繼承而來的方法,需要實(shí)現(xiàn):
具體來說需要實(shí)現(xiàn)ContentProvider 類中的6個(gè)抽象方法。
Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):將查詢的數(shù)據(jù)以Cursor 對(duì)象的形式返回。
Uri insert(Uri uri, ContentValues values):向 Content Provider中插入新數(shù)據(jù)記錄,ContentValues 為數(shù)據(jù)記錄的列名和列值映射。
int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):更新Content Provider中已存在的數(shù)據(jù)記錄。
int delete(Uri uri, String selection, String[] selectionArgs):從Content Provider中刪除數(shù)據(jù)記錄。
String getType(Uri uri):返回Content Provider中的數(shù)據(jù)( MIME )類型。
boolean onCreate():當(dāng) Content Provider 啟動(dòng)時(shí)被調(diào)用。
定義一個(gè) URI 類型的靜態(tài)常量,命名為CONTENT_URI。 必須為該常量對(duì)象定義一個(gè)唯一的URI字符串,一般的做法是將 ContentProvider子類的全稱類名作為URI字符串。
定義每個(gè)字段的列名,如果采用的數(shù)據(jù)庫存儲(chǔ)系統(tǒng)為SQLite 數(shù)據(jù)庫,數(shù)據(jù)表列名可以采用數(shù)據(jù)庫中表的列名。不管數(shù)據(jù)表中有沒有其他的唯一標(biāo)識(shí)一個(gè)記錄的字段,都應(yīng)該定義一個(gè)"_id"字段 來唯一標(biāo)識(shí)一個(gè)記錄。模式使用 "INTEGER PRIMARY KEY AUTOINCREMENT" 自動(dòng)更新 一般將這些列名字符串定義為靜態(tài)常量, 如"_id"字段名定義為一個(gè)名為"_ID" 值為 "_id" 的靜態(tài)字符串對(duì)象。
創(chuàng)建好的一個(gè)Content Provider必須在AndroidManifest.xml中聲明。
<provider android:name= ".ItemsProvider"
android:authorities= "com.blueeagle" />
其中name屬性為ContentProvider 子類的全稱類名,authorities 屬性唯一標(biāo)識(shí)了一個(gè)ContentProvider。還可以通過 setReadPermission() 和 setWritePermission() 來設(shè)置其操作權(quán)限。當(dāng)然也可以再上面的 xml中加入 android:readPermission 或者 android: writePermission屬性來控制其權(quán)限。
注意:因?yàn)镃ontentProvider可能被不同的進(jìn)程和線程調(diào)用,所以這些方法必須是線程安全的。
然后需要使用UriMatcher,用于匹配Uri。
用法如下:
首先把需要匹配Uri路徑全部給注冊(cè)上:
對(duì)于Uri:
什么是URI?將其分為A,B,C,D 4個(gè)部分:
A:標(biāo)準(zhǔn)前綴,用來說明一個(gè)Content Provider控制這些數(shù)據(jù),無法改變的;"content://"
B:URI的標(biāo)識(shí),它定義了是哪個(gè)Content Provider提供這些數(shù)據(jù)。對(duì)于第三方應(yīng)用程序,為了保證URI標(biāo)識(shí)的唯一性,它必須是一個(gè)完整的、小寫的 類名。這個(gè)標(biāo)識(shí)在 元素的 authorities屬性中說明:一般是定義該ContentProvider的包.類的名稱 ;"content://hx.android.text.myprovider"
C:路徑,不知道是不是路徑,通俗的講就是你要操作的數(shù)據(jù)庫中表的名字,或者你也可以自己定義,記得在使用的時(shí)候保持一致就ok了;"content://hx.android.text.myprovider/tablename"
D:如果URI中包含表示需要獲取的記錄的ID;則就返回該id對(duì)應(yīng)的數(shù)據(jù),如果沒有ID,就表示返回全部; "content://hx.android.text.myprovider/tablename/#" #表示數(shù)據(jù)id
注冊(cè)完需要匹配的Uri后,就可以使用sMatcher.match(uri)方法對(duì)輸入的Uri進(jìn)行匹配,如果匹配就返回匹配碼,匹配碼是調(diào)用addURI()方法傳入的第三個(gè)參數(shù),例如匹配content://com.blueeagle路徑,返回的匹配碼為1。
//常量UriMatcher.NO_MATCH表示不匹配任何路徑的返回碼
UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
//添加需要匹配的uri,如果匹配就會(huì)返回匹配碼
//如果match()方法匹配content://com.blueeagle路徑,返回匹配碼為1
sUriMatcher.addURI(“content://com.blueeagle”, “HelloSQLite”, 1);
//如果match()方法匹配content://com.blueeagle/ ***路徑,返回匹配碼為2
//#號(hào)為通配符
sUriMatcher.addURI(“content://com.blueeagle”, “HelloSQLite/#”, 2);
switch(sUriMatcher.match(Uri.parse("content://com.blueeagle /***"))) {
case 1 break;
case 2 break;
default:
//不匹配 break;
}
自定義的contentprovider如下:
ItemsProvider.java
總結(jié)一下,創(chuàng)建一個(gè)新的內(nèi)容提供器。
1.通過擴(kuò)展抽象類ContentProvider可以創(chuàng)建新的內(nèi)容提供器。重寫onCreate方法來打開或者初始化將要使用這個(gè)新的提供器來提供底層數(shù)據(jù)源。
2.還應(yīng)該提供那些用來返回指向這個(gè)提供器的完整的URI的公共靜態(tài)CONTENT_URI變量。提供器之間的內(nèi)容URI應(yīng)該是唯一的,所以最好的做法是使URI路徑以包名為基礎(chǔ)。
定義一個(gè)內(nèi)容提供器URI一般的形式為:
content://com.pakagename/datapath
例如:content://com.blueeagle/items
或者:content://com.blueeagle./items/3
內(nèi)容URI可以表示為這兩種形式中的任意一種形式。前面的一種URI表示對(duì)那種類型中所有的值的請(qǐng)求,后面附加一個(gè)/3的表示對(duì)一條記錄的請(qǐng)求(這里請(qǐng)求的是記錄3)。這兩種形式來訪問提供器都是可行的。
完成這項(xiàng)工作最簡(jiǎn)單的方式是使用一個(gè)UriMatcher。當(dāng)通過內(nèi)容解析器來訪問內(nèi)容提供器的時(shí)候,可以配置UriMathcer來解析URI以確定它們的形式。就像前文說的那樣。
更多文章、技術(shù)交流、商務(wù)合作、聯(lián)系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號(hào)聯(lián)系: 360901061
您的支持是博主寫作最大的動(dòng)力,如果您喜歡我的文章,感覺我的文章對(duì)您有幫助,請(qǐng)用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點(diǎn)擊下面給點(diǎn)支持吧,站長(zhǎng)非常感激您!手機(jī)微信長(zhǎng)按不能支付解決辦法:請(qǐng)將微信支付二維碼保存到相冊(cè),切換到微信,然后點(diǎn)擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對(duì)您有幫助就好】元
