終于又回到組件篇炒考,Android中非常重要的四大組件--Activity、ContentProvider、BroadcastReceiver和Service藕赞,它們分工明確,共同構(gòu)成了可重用卖局、靈活斧蜕、低耦合的安卓系統(tǒng)。通過之前的學(xué)習(xí)砚偶,我們知道Activity主要負(fù)責(zé)UI加載和頁面跳轉(zhuǎn)批销,接下來幾篇就依次介紹后三種組件,本篇先學(xué)習(xí)ContentProvider蟹演,目錄見下:
- ContentProvider概要
- 從系統(tǒng)提供的Provider訪問數(shù)據(jù)
- 內(nèi)容URI的組成
- ContentResolve類
- 例子:讀取聯(lián)系人的電話
- 創(chuàng)建自己的Provider
- UriMater類
- 自定義一個(gè)Provider的步驟
- 例子:為student.db創(chuàng)建MyProvider
1.ContentProvider概要
ContentProvider也有存儲(chǔ)數(shù)據(jù)的功能风钻,但與上一篇中學(xué)習(xí)的那三種數(shù)據(jù)存儲(chǔ)方法不同的是,后者保存下的數(shù)據(jù)只能被該應(yīng)用程序使用酒请,而前者可以讓不同應(yīng)用程序之間進(jìn)行數(shù)據(jù)共享,它還可以選擇只對(duì)哪一部分?jǐn)?shù)據(jù)進(jìn)行共享鸣个,從而保證程序中的隱私數(shù)據(jù)不會(huì)有泄漏風(fēng)險(xiǎn)羞反。所以組件ContentProvider主要負(fù)責(zé)存儲(chǔ)和共享數(shù)據(jù)。
ContentProvider有兩種形式:可以使用現(xiàn)有的內(nèi)容提供者來讀取和操作相應(yīng)程序中的數(shù)據(jù)囤萤,也可以創(chuàng)建自己的內(nèi)容提供者給這個(gè)程序的數(shù)據(jù)提供外部訪問接口昼窗。下面分別學(xué)習(xí)一下。
2.從系統(tǒng)提供的Provider訪問數(shù)據(jù)
既然ContentProvider有對(duì)外共享數(shù)據(jù)的功能涛舍,換句話說澄惊,其他應(yīng)用程序可以通過ContentProvider對(duì)應(yīng)用中的數(shù)據(jù)進(jìn)行增刪改查,說到這里是否感到熟悉富雅?上篇學(xué)習(xí)SQLite數(shù)據(jù)存儲(chǔ)的時(shí)候就提到過可以實(shí)現(xiàn)增刪改查的各種輔助性方法掸驱,實(shí)際上ContentProvider是對(duì)SQLiteOpenHelper的進(jìn)一步封裝,因此它們使用的方法太像了没佑,只不過不再用單純的表名指明被操作的表毕贼,畢竟現(xiàn)在是其他程序訪問它,而是用有一定格式規(guī)范的內(nèi)容URI來代替蛤奢。下面先來學(xué)習(xí)URI的組成鬼癣。
(1)內(nèi)容URI的組成
以上篇最后做的關(guān)于數(shù)據(jù)庫的demo為例,它的包名是com.example.myapplication啤贩,如果其他程序想訪問該程序student.db中的student表慌随,那么需要的內(nèi)容URI如圖所示:
可以看出內(nèi)容 URI 可以非常清楚地表達(dá)出我們想要訪問哪個(gè)程序中哪張表里的數(shù)據(jù),但還沒完萍恕,還需要將它解析成 Uri 對(duì)象才可以作為參數(shù)傳入轮洋。通過調(diào)用 Uri.parse()方法,就可以將內(nèi)容 URI 字符串解析成 Uri 對(duì)象了痢掠,代碼如下:
(2)ContentResolve類
現(xiàn)在有了酷似“表名”的Uri驱犹,類似的嘲恍,在ContentResolver類中提供的一系列用于對(duì)數(shù)據(jù)進(jìn)行增刪改查操作的方法也酷似SQLiteDatabase的那些輔助性方法:insert()方法用于添加數(shù)據(jù),update()方法用于更新數(shù)據(jù)雄驹,delete()方法用于刪除數(shù)據(jù)佃牛,query()方法用于查詢數(shù)據(jù)。它們不僅方法名一樣医舆,連提供的參數(shù)都非常相似俘侠,見下圖,紅色部分是區(qū)別:
所以其他程序若想要訪問ContentProvider中共享的數(shù)據(jù)的方法是:
第一步:通過Context 中的getContentResolver()方法實(shí)例化一個(gè)ContentResolve對(duì)象蔬将。
第二步:調(diào)用該對(duì)象的增刪改查方法去操作ContentProvider中的數(shù)據(jù)爷速。
接下來通過讀取聯(lián)系人電話的小例子體驗(yàn)這個(gè)過程:
(3)例子:讀取聯(lián)系人的電話
先在虛擬機(jī)上手動(dòng)添加兩個(gè)聯(lián)系人和電話:
這樣準(zhǔn)備工作就做好了。然后建個(gè)布局霞怀,這里我們希望讀取出來的聯(lián)系人的信息能夠在ListView中顯示:
在活動(dòng)中的onCreate()方法中惫东,首先獲取ListView控件的實(shí)例,并給它設(shè)置適配器毙石,用ArrayAdapter就可以廉沮;然后調(diào)用init()方法去實(shí)現(xiàn)讀取聯(lián)系人電話的需求。
在init()方法中使用了ContentResolver的query()方法來查詢系統(tǒng)的聯(lián)系人數(shù)據(jù)徐矩。不過這里傳入的 Uri 參數(shù)是一個(gè)常量ContactsContract.Contacts.CONTENT_URI滞时,這是因?yàn)镃ontactsContract.Contacts類已經(jīng)幫我們做好了封裝,而這個(gè)常量就是使用 Uri.parse()方法解析出來的結(jié)果滤灯。接下來的過程就很熟悉了:用Cursor 對(duì)象進(jìn)行遍歷坪稽,先取出聯(lián)系人姓名,這一列對(duì)應(yīng)的常量是是ContactsContract.Contacts.DISPLAY_NAME鳞骤,之后取出聯(lián)系人的電話時(shí)窒百,又進(jìn)行一次遍歷,這是因?yàn)橐粋€(gè)聯(lián)系人可能有多個(gè)電話弟孟,所以需要用ID唯一標(biāo)識(shí)某個(gè)聯(lián)系人然后到另一個(gè)表找他的所有電話贝咙。等名字和電話都取出之后,將它們拼接起來再添加到 ListView里拂募。最后千萬不要忘記將Cursor對(duì)象關(guān)閉掉庭猩。
因?yàn)樽x取系統(tǒng)聯(lián)系人也是需要聲明權(quán)限的,一定在配置文件中聲明好:
另外陈症,Android6.0以上的系統(tǒng)要求部分權(quán)限還要手動(dòng)申請(qǐng)蔼水,因此在活動(dòng)中務(wù)必要添加一段代碼:
這段代碼很通用,不同權(quán)限只要更換名稱就可用了:
final private int REQUEST_CODE_ASK_PERMISSIONS = 123;
int hasWriteContactsPermission = ContextCompat.checkSelfPermission(this,Manifest.permission.READ_CONTACTS);
if(hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},REQUEST_CODE_ASK_PERMISSIONS);
}
return;
}
需要運(yùn)行時(shí)權(quán)限的就是下圖這幾個(gè):
運(yùn)行程序跑一下看看吧录肯!下圖展示的內(nèi)容就是上面一段代碼的作用趴腋,選擇ALLOW:
現(xiàn)在就能看到之前添加的兩個(gè)聯(lián)系人的姓名和電話了!
3.創(chuàng)建自己的Provider
(1)UriMater類
UriMater類有匹配內(nèi)容URI的功能,在這里常用它的兩個(gè)方法:一個(gè)是addURI()方法用來傳入U(xiǎn)RI优炬,它接收三個(gè)參數(shù)(權(quán)限颁井,路徑,一個(gè)自定義代碼)蠢护;另一個(gè)是match()方法用來匹配URI雅宾,接受一個(gè)Uri對(duì)象,返回值是某個(gè)能夠匹配這個(gè)Uri對(duì)象所對(duì)應(yīng)的自定義代碼葵硕,利用這個(gè)自定義代碼眉抬,就可以判斷出調(diào)用方期望訪問的是哪張表中的數(shù)據(jù)了。
(2)自定義一個(gè)Provider的步驟
步驟一:新建一個(gè)類去繼承ContentProvider懈凹。
步驟二:重寫ContentProvider的六個(gè)抽象方法蜀变,方法及含義如圖:
步驟三:在配置文件中進(jìn)行注冊(cè),并注明屬性:
android:authorities即Provider的權(quán)限介评,形式是包名.provider
android:name即Provider的全名库北,形式是包名.類名
android:exported="true"指明該P(yáng)rovider可被其它程序訪問。
以上這些知識(shí)還是有點(diǎn)抽象们陆,那還是再來個(gè)例子更深刻感受一下吧贤惯!
(3)例子:為student.db創(chuàng)建MyProvider
接下里還是給上篇的數(shù)據(jù)庫demo創(chuàng)建一個(gè)自定義提供器MyProvider,然后在別的應(yīng)用程序中通過MyProvider去操作student.db中的數(shù)據(jù)棒掠。
先修改MyHelper,將Toast提示語句都去掉屁商,因?yàn)榭绯绦蛟L問時(shí)我們不能直接使用 Toast烟很。
然后開始自定義提供器吧!一開始定義了四個(gè)常量蜡镶,分別表示訪問student表中的所有數(shù)據(jù)雾袱、訪問student表中的單條數(shù)據(jù)(student/#用于表示student表中任意一行記錄)、訪問course表中的所有數(shù)據(jù)和訪問course表中的單條數(shù)據(jù)官还。然后在靜態(tài)代碼塊里對(duì)UriMatcher進(jìn)行了初始化操作芹橡,將期望匹配的幾種URI格式添加了進(jìn)去。
接下來就是六個(gè)抽象方法的具體實(shí)現(xiàn)了望伦,先看onCreate()方法林说,這里創(chuàng)建了一個(gè)MyHelper的實(shí)例,然后返回true表示內(nèi)容提供器初始化成功屯伞,現(xiàn)在數(shù)據(jù)庫就已經(jīng)完成了創(chuàng)建或升級(jí)操作腿箩。
接下來是 getType()方法,需要返回一個(gè)MIME字符串劣摇。一個(gè)內(nèi)容URI所對(duì)應(yīng)的MIME字符串主要由三部分組分珠移,Android對(duì)這三個(gè)部分做了以下格式規(guī)定:必須以vnd開頭;如果內(nèi)容URI以路徑結(jié)尾,則后接android.cursor.dir/钧惧,如果內(nèi)容URI以id結(jié)尾暇韧,則后接android.cursor.item/;最后接上vnd.< authority>.< path>浓瞪。所以四個(gè)內(nèi)容URI對(duì)應(yīng)的MIME字符串分別是:
在query()方法里先獲取到SQLiteDatabase的實(shí)例懈玻,然后根據(jù)傳入的Uri參數(shù)判斷出用戶想要訪問哪張表,再調(diào)用SQLiteDatabase的query()進(jìn)行查詢并將Cursor對(duì)象返回就好了追逮。注意當(dāng)訪問的是單條數(shù)據(jù)時(shí)調(diào)用了Uri對(duì)象的getPathSegments()方法酪刀,它會(huì)將內(nèi)容URI權(quán)限之后的部分以“/”符號(hào)進(jìn)行分割,并把分割后的結(jié)果放入到一個(gè)字符串列表中钮孵,那這個(gè)列表的第0個(gè)位置存放的就是路徑骂倘,第1個(gè)位置存放的就是id了。得到了id之后巴席,再通過selection和selectionArgs參數(shù)進(jìn)行約束历涝,就實(shí)現(xiàn)了查詢單條數(shù)據(jù)的功能。
再看insert()方法漾唉,同樣的荧库,先獲取到SQLiteDatabase的實(shí)例,然后根據(jù)傳入的Uri參數(shù)判斷出用戶想要往哪張表里添加數(shù)據(jù)赵刑,再調(diào)用SQLiteDatabase的insert()方法進(jìn)行添加就可以了分衫。注意insert()方法要求返回一個(gè)能夠表示這條新增數(shù)據(jù)的 URI,所以還需要調(diào)用Uri.parse()方法將一個(gè)以新增數(shù)據(jù)的id結(jié)尾的內(nèi)容URI解析成Uri對(duì)象般此。
再來看delete()方法蚪战,和前面一樣的,不同的是這里需要在調(diào)用SQLiteDatabase的delete()方法刪除特定記錄的同時(shí)還要把被刪除的行數(shù)作為返回值返回铐懊。
終于到了最后一個(gè)方法update()邀桑,和delete()相似的,在調(diào)用SQLiteDatabase的 update()方法進(jìn)行更新的同時(shí)還要把受影響的行數(shù)作為返回值返回科乎。
最后將MyProvider在AndroidManifest.xml文件中注冊(cè)壁畸,一個(gè)自定義內(nèi)容提供器終于完成了!
現(xiàn)在需要做的是將該程序從模擬器中卸載防止之前產(chǎn)生的遺留數(shù)據(jù)對(duì)后面操作有干擾茅茂,然后再運(yùn)行一下重新安裝在模擬器上捏萍,啟動(dòng)后直接關(guān)閉掉。接下來創(chuàng)建一個(gè)新的module玉吁,注意包要不同照弥,代表其他程序。新建一個(gè)布局test.xml并放四個(gè)按鈕:
接下來在活動(dòng)分別處理四個(gè)按鈕的點(diǎn)擊事件进副。到目前為止這是第三次用增刪改出方法去操作數(shù)據(jù)了这揣,相信這些代碼已經(jīng)不難理解了悔常!調(diào)用Uri.parse()將一個(gè)內(nèi)容URI解析成Uri對(duì)象,這里希望操作student.db中的student表给赞。又獲取到ContentResolver對(duì)象就可以進(jìn)行CRUD操作了机打,這里插入兩條記錄,并且通過第一條記錄的insert()方法得到一個(gè)Uri對(duì)象片迅,這個(gè)對(duì)象中包含了新增記錄的id残邀,調(diào)用getPathSegments()方法將它取出,之后利用這個(gè)id合成新的內(nèi)容URI和Uri對(duì)象方便給該條記錄進(jìn)行更改和刪除的操作柑蛇。查詢操作完成的打印出表中所有的數(shù)據(jù)芥挣。
現(xiàn)在運(yùn)行這個(gè)程序,分別進(jìn)行以下幾個(gè)測(cè)試耻台,觀察打印出的數(shù)據(jù)的變化空免,和預(yù)想是一樣的!
關(guān)于ContentProvider的知識(shí)點(diǎn)就到這里~
>下一篇預(yù)告:組件篇之BroadcastReceiver