Android四大組件之Content Provider簡(jiǎn)析

內(nèi)容內(nèi)容提供器(Content Provider)和內(nèi)容接收器(ContentResolver)竖慧,有程序?qū)⑵鋽?shù)據(jù)提供了對(duì)外的訪問接口,繼而就有對(duì)應(yīng)的其他程序調(diào)用這些接口。Android系統(tǒng)提供了內(nèi)容內(nèi)容提供器(Content Provider)和內(nèi)容接收器(ContentResolver)就是為了這兩個(gè)方面提供的對(duì)象变勇。

一乔夯,內(nèi)容提供器(Content Provider)簡(jiǎn)介

1鸥鹉,Android 數(shù)據(jù)持久化的技術(shù)撑帖,包括文件存儲(chǔ)筐眷、SharedPreferences 存儲(chǔ)、以及數(shù)據(jù)庫(kù)存儲(chǔ)垒手。不知道你有沒有發(fā)現(xiàn)蒜焊,使用這些持久化技術(shù)所保存的數(shù)據(jù)都只能在當(dāng)前應(yīng)用程序中訪,問。雖然文件和SharedPreferences 存儲(chǔ)中提供了MODE_WORLD_READABLE和MODE_WORLD_WRITEABLE 這兩種操作模式科贬,用于供給其他的應(yīng)用程序訪問當(dāng)前應(yīng)用的數(shù)據(jù)泳梆,但這兩種模式在Android 4.2 版本中都已被廢棄了。因?yàn)锳ndroid官方已經(jīng)不再推薦使用這種方式來實(shí)現(xiàn)跨程序數(shù)據(jù)共享的功能,而是應(yīng)該使用更加安全可靠的內(nèi)容提供器技術(shù)优妙。

2乘综,可能你會(huì)有些疑惑,為什么要將我們程序中的數(shù)據(jù)共享給其他程序呢套硼?當(dāng)然卡辰,這個(gè)要視情況而定的,比如說賬號(hào)和密碼這樣的隱私數(shù)據(jù)顯然是不能共享給其他程序的邪意,不過一些,可以讓其他程序進(jìn)行二次開發(fā)的基礎(chǔ)性數(shù)據(jù)看政,我們還是可以選擇將其共享的。例如系統(tǒng)的電話簿程序抄罕,它的數(shù)據(jù)庫(kù)中保存了很多的聯(lián)系人信息,如果這些數(shù)據(jù)都不允許第三方的程序進(jìn)行訪問的話于颖,恐怕很多應(yīng)用的功能都要大打折扣了呆贿。

3,內(nèi)容提供器(Content Provider)主要用于在不同的應(yīng)用程序之間實(shí)現(xiàn)數(shù)據(jù)共享的功能森渐,它提供了一套完整的機(jī)制做入,允許一個(gè)程序訪問另一個(gè)程序中的數(shù)據(jù),同時(shí)還能保證被訪,數(shù)據(jù)的安全性同衣。目前竟块,使用內(nèi)容提供器是Android 實(shí)現(xiàn)跨程序共享數(shù)據(jù)的標(biāo)準(zhǔn)方式。

4耐齐,不同于文件存儲(chǔ)和SharedPreferences 存儲(chǔ)中的兩種全局可讀寫操作模式浪秘,內(nèi)容提供器可以選擇只對(duì)哪一部分?jǐn)?shù)據(jù)進(jìn)行共享,從而保證我們程序中的隱私數(shù)據(jù)不會(huì)有泄漏的風(fēng)險(xiǎn),埠况。內(nèi)容提供器的用法一般有兩種耸携,一種是使用現(xiàn)有的內(nèi)容提供器來讀取和操作相應(yīng)程序中的數(shù)據(jù),另一種是創(chuàng)建自己的內(nèi)容提供器給我們程序的數(shù)據(jù)提供外部訪問接口辕翰。

二夺衍,內(nèi)容接收器(ContentResolver)簡(jiǎn)介

1,當(dāng)一個(gè)應(yīng)用程序通過內(nèi)容提供器對(duì)其數(shù)據(jù)提供了外部訪問接口喜命,任何其他的應(yīng)用程序就都可以對(duì)這部分?jǐn)?shù)據(jù)進(jìn)行訪問沟沙。Android 系統(tǒng)中自帶的電話簿、短信壁榕、媒體庫(kù)等程序都提,供了類似的訪問接口矛紫。

2,對(duì)于每一個(gè)應(yīng)用程序來說护桦,如果想要訪問內(nèi)容提供器中共享的數(shù)據(jù)含衔,就一定要借助ContentResolve 類,可以通過Context 中的getContentResolver()方法獲取到該類的實(shí)例。ContentResolver 中提供了一系列的方法用于對(duì)數(shù)據(jù)進(jìn)行CRUD 操作贪染,其中insert()方法用于添加數(shù)據(jù)缓呛,update()方法用于更新數(shù)據(jù),delete()方法用于刪除數(shù)據(jù)杭隙,query()方法用于查詢數(shù)據(jù)哟绊。有沒有似曾相識(shí)的感覺?沒錯(cuò)痰憎,SQLiteDatabase 中也是使用的這幾個(gè)方法來進(jìn)行CRUD操作的票髓,只不過它們?cè)诜椒▍?shù)上稍微有一些區(qū)別。不同于SQLiteDatabase铣耘,ContentResolver 中的增刪改查方法都是不接收表名參數(shù)的洽沟,而是使用一個(gè)Uri 參數(shù)代替,這個(gè)參數(shù)被稱為內(nèi)容URI蜗细。內(nèi)容URI 給內(nèi)容提供器中的數(shù)據(jù)建立了唯一標(biāo)識(shí)符裆操,它主要由兩部分組成,權(quán)限(authority)和路徑(path)炉媒。權(quán)限是用于對(duì)不同的應(yīng)用程序做區(qū)分的踪区,一般為了避免沖突,都會(huì)采用程序包名的方式來進(jìn)行命名吊骤。比如某個(gè)程序的包名是com.example.app缎岗,那么該程序?qū)?yīng)的權(quán)限就可以命名為com.example.app.provider。路徑則是用于對(duì)同一應(yīng)用程序中不同的表做區(qū)分的白粉,通常都會(huì)添加到權(quán)限的后面传泊。比如某個(gè)程序的數(shù)據(jù)庫(kù)里存在兩張表,table1 和table2鸭巴,這時(shí)就可以將路徑分別命名為/table1和/table2或渤,然后把權(quán)限和路徑進(jìn)行組合,內(nèi)容URI 就變成com.example.app.provider/table1和com.example.app.provider/table2奕扣。不過薪鹦,目前還很難辨認(rèn)出這兩個(gè)字符串就是兩個(gè)內(nèi)容URI,我們還需要在字符串的頭部加上協(xié)議聲明惯豆。因此池磁,內(nèi)容URI 最標(biāo)準(zhǔn)的格式寫法如下:

content://com.example.app.provider/table1

content://com.example.app.provider/table2

3,有沒有發(fā)現(xiàn)楷兽,內(nèi)容URI 可以非常清楚地表達(dá)出我們想要訪問哪個(gè)程序中哪張表里的數(shù)據(jù)地熄。也正是因此,ContentResolver 中的增刪改查方法才都接收Uri 對(duì)象作為參數(shù)芯杀,因?yàn)槭褂帽砻脑捪到y(tǒng)將無法得知我們期望訪問的是哪個(gè)應(yīng)用程序里的表端考。,在得到了內(nèi)容URI 字符串之后雅潭,我們還需要將它解析成Uri 對(duì)象才可以作為參數(shù)傳入。

解析的方法也相當(dāng)簡(jiǎn)單却特,代碼如下所示:

Uri uri = Uri.parse("content://com.example.app.provider/table1")

只需要調(diào)用Uri.parse()方法扶供,就可以將內(nèi)容URI 字符串解析成Uri 對(duì)象了。

三裂明,內(nèi)容接收器(ContentResolver)的增刪改查

Uri uri = Uri.parse("content://com.example.app.provider/table1")

只需要調(diào)用Uri.parse()方法椿浓,就可以將內(nèi)容URI 字符串解析成Uri 對(duì)象了。

現(xiàn)在我們就可以使用這個(gè)Uri 對(duì)象來查詢table1 表中的數(shù)據(jù)了闽晦,代碼如下所示:

1扳碍,查詢

Cursor cursor = getContentResolver().query(

uri,

projection,

selection,

selectionArgs,

sortOrder);

這些參數(shù)和SQLiteDatabase 中query()方法里的參數(shù)很像,但總體來說要簡(jiǎn)單一些仙蛉,畢竟這是在訪問其他程序中的數(shù)據(jù)笋敞,沒必要構(gòu)建過于復(fù)雜的查詢語(yǔ)句。

query()方法 參數(shù)對(duì)應(yīng)SQL 部分描述

uri from table_name 指定查詢某個(gè)應(yīng)用程序下的某一張表

projection select column1, column2 指定查詢的列名

selection where column = value 指定where 的約束條件

selectionArgs - 為where 中的占位符提供具體的值

orderBy order by column1, column2 指定查詢結(jié)果的排序方式

查詢完成后返回的仍然是一個(gè)Cursor 對(duì)象荠瘪,這時(shí)我們就可以將數(shù)據(jù)從Cursor 對(duì)象中逐個(gè)讀取出來了液样。讀取的思路仍然是通過移動(dòng)游標(biāo)的位置來遍歷Cursor 的所有行,然后再取出每一行中相應(yīng)列的數(shù)據(jù)巧还,代碼如下所示:

if (cursor != null) {

while (cursor.moveToNext()) {

String column1 = cursor.getString(cursor.getColumnIndex("column1"));

int column2 = cursor.getInt(cursor.getColumnIndex("column2"));

}

cursor.close();

}

2,增加

我們先來看看如何向table1 表中添加一條數(shù)據(jù),代碼如下所示:

ContentValues values = new ContentValues();

values.put("column1", "text");

values.put("column2", 1);

getContentResolver().insert(uri, values);

可以看到坊秸,仍然是將待添加的數(shù)據(jù)組裝到ContentValues 中麸祷,然后調(diào)用ContentResolver的insert()方法,將Uri 和ContentValues 作為參數(shù)傳入即可褒搔。

3阶牍,更新

現(xiàn)在如果我們想要更新這條新添加的數(shù)據(jù),把column1的值清空星瘾,可以借助ContentResolver 的update()方法實(shí)現(xiàn)走孽,代碼如下所示:

ContentValues values = new ContentValues();

values.put("column1", "");

getContentResolver().update(uri, values, "column1 = ? and column2 = ?", newString[] {"text", "1"});

注意上述代碼使用了selection 和selectionArgs 參數(shù)來對(duì)想要更新的數(shù)據(jù)進(jìn)行約束,以防止所有的行都會(huì)受影響琳状。

4,刪除

可以調(diào)用ContentResolver 的delete()方法將這條數(shù)據(jù)刪除掉磕瓷,代碼如下所示:

getContentResolver().delete(uri, "column2 = ?", new String[] { "1" });

四,內(nèi)容接收器(ContentResolver)的具體應(yīng)用(讀取系統(tǒng)聯(lián)系人)

private void init() {

readContacts();

adapter = new ArrayAdapter<String>(MainActivity1.this, android.R.layout.simple_list_item_1, contactsList);

contactsView = (ListView) findViewById(R.id.contacts_view);

contactsView.setAdapter(adapter);

}

private void readContacts() {

Cursor cursor = null;

try{

cursor = getContentResolver().query(

ContactsContract.CommonDataKinds.Phone.CONTENT_URI,

null, null, null, null);

while(cursor.moveToNext()){

// 獲取聯(lián)系人姓名

String displayName = cursor.getString(cursor.getColumnIndex(

ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));

// 獲取聯(lián)系人手機(jī)號(hào)

String number = cursor.getString(cursor.getColumnIndex(

ContactsContract.CommonDataKinds.Phone.NUMBER));

contactsList.add(displayName + "\n" + number);

}

} catch(Exception e) {

e.printStackTrace();

} finally {

if(cursor!=null){

cursor.close();

}

}

}

讀取系統(tǒng)聯(lián)系人也是需要聲明權(quán)限的念逞,因此修改AndroidManifest.xml 中的代碼困食,如下所示:

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

五,創(chuàng)建自己的內(nèi)容提供器(將自己的程序的部分?jǐn)?shù)據(jù)共享翎承,是其他程序也可以使用)

如果想要實(shí)現(xiàn)跨程序共享數(shù)據(jù)的功能硕盹,官方推薦的方式就是使用內(nèi)容提供器,可以通過新建一個(gè)類去繼承ContentProvider 的方式來創(chuàng)建一個(gè)自己的內(nèi)容提供器叨咖。ContentProvider 類中有六個(gè)抽象方法瘩例,我們?cè)谑褂米宇惱^承它的時(shí)候啊胶,需要將這六個(gè)方法全部重寫。

1,示例

public class MyProvider extends ContentProvider {

@Override

public boolean onCreate() {

return false;

}

@Override

public Cursor query(Uri uri, String[] projection, String selection,

String[] selectionArgs, String sortOrder) {

return null;

}

@Override

public Uri insert(Uri uri, ContentValues values) {

return null;

}

@Override

public int update(Uri uri, ContentValues values, String selection,

String[] selectionArgs) {

return 0;

}

@Override

public int delete(Uri uri, String selection, String[] selectionArgs) {

return 0;

}

@Override

public String getType(Uri uri) {

return null;

}

}

2垛贤,方法介紹

在這六個(gè)方法中焰坪,相信大多數(shù)你都已經(jīng)非常熟悉了,我再來簡(jiǎn)單介紹一下吧南吮。

1). onCreate()

初始化內(nèi)容提供器的時(shí)候調(diào)用琳彩。通常會(huì)在這里完成對(duì)數(shù)據(jù)庫(kù)的創(chuàng)建和升級(jí)等操作,返回true 表示內(nèi)容提供器初始化成功部凑,返回false 則表示失敗露乏。注意,只有當(dāng)存在ContentResolver 嘗試訪問我們程序中的數(shù)據(jù)時(shí)涂邀,內(nèi)容提供器才會(huì)被初始化瘟仿。

2). query()

從內(nèi)容提供器中查詢數(shù)據(jù)。使用uri 參數(shù)來確定查詢哪張表比勉,projection 參數(shù)用于確定查詢哪些列劳较,selection 和selectionArgs 參數(shù)用于約束查詢哪些行,sortOrder 參數(shù)用于對(duì)結(jié)果進(jìn)行排序浩聋,查詢的結(jié)果存放在Cursor 對(duì)象中返回观蜗。

3). insert()

向內(nèi)容提供器中添加一條數(shù)據(jù)。使用uri 參數(shù)來確定要添加到的表衣洁,待添加的數(shù)據(jù)保存在values 參數(shù)中墓捻。添加完成后,返回一個(gè)用于表示這條新記錄的URI坊夫。

4). update()

更新內(nèi)容提供器中已有的數(shù)據(jù)砖第。使用uri 參數(shù)來確定更新哪一張表中的數(shù)據(jù),新數(shù)據(jù)保存在values 參數(shù)中环凿,selection 和selectionArgs 參數(shù)用于約束更新哪些行梧兼,受影響的行數(shù)將作為返回值返回。

5). delete()

從內(nèi)容提供器中刪除數(shù)據(jù)智听。使用uri 參數(shù)來確定刪除哪一張表中的數(shù)據(jù)羽杰,selection和selectionArgs 參數(shù)用于約束刪除哪些行,被刪除的行數(shù)將作為返回值返回到推。

6). getType()

根據(jù)傳入的內(nèi)容URI 來返回相應(yīng)的MIME 類型忽洛。

3,路徑(Uri)詳解

可以看到环肘,幾乎每一個(gè)方法都會(huì)帶有Uri 這個(gè)參數(shù)欲虚,這個(gè)參數(shù)也正是調(diào)用ContentResolver的增刪改查方法時(shí)傳遞過來的。而現(xiàn)在悔雹,我們需要對(duì)傳入的Uri 參數(shù)進(jìn)行解析复哆,從中分析出調(diào)用方期望訪問的表和數(shù)據(jù)欣喧。

回顧一下,一個(gè)標(biāo)準(zhǔn)的內(nèi)容URI 寫法是這樣的:

content://com.example.app.provider/table1

這就表示調(diào)用方期望訪問的是com.example.app 這個(gè)應(yīng)用的table1 表中的數(shù)據(jù)梯找。除此之外唆阿,我們還可以在這個(gè)內(nèi)容URI 的后面加上一個(gè)id,如下所示:

content://com.example.app.provider/table1/1

這就表示調(diào)用方期望訪問的是com.example.app 這個(gè)應(yīng)用的table1 表中id 為1 的數(shù)據(jù)锈锤。

內(nèi)容URI 的格式主要就只有以上兩種驯鳖,以路徑結(jié)尾就表示期望訪問該表中所有的數(shù)據(jù),以id 結(jié)尾就表示期望訪問該表中擁有相應(yīng)id 的數(shù)據(jù)久免。我們可以使用通配符的方式來分別匹配這兩種格式的內(nèi)容URI浅辙,規(guī)則如下。

1). *:表示匹配任意長(zhǎng)度的任意字符

2). #:表示匹配任意長(zhǎng)度的數(shù)字

所以阎姥,一個(gè)能夠匹配任意表的內(nèi)容URI 格式就可以寫成:

content://com.example.app.provider/*

而一個(gè)能夠匹配table1 表中任意一行數(shù)據(jù)的內(nèi)容URI 格式就可以寫成:

content://com.example.app.provider/table1/#

接著记舆,我們?cè)俳柚鶸riMatcher 這個(gè)類就可以輕松地實(shí)現(xiàn)匹配內(nèi)容URI 的功能。UriMatcher中提供了一個(gè)addURI()方法呼巴,這個(gè)方法接收三個(gè)參數(shù)泽腮,可以分別把權(quán)限、路徑和一個(gè)自定義代碼傳進(jìn)去衣赶。這樣诊赊,當(dāng)調(diào)用UriMatcher 的match()方法時(shí),就可以將一個(gè)Uri 對(duì)象傳入府瞄,返回值是某個(gè)能夠匹配這個(gè)Uri 對(duì)象所對(duì)應(yīng)的自定義代碼碧磅,利用這個(gè)代碼,我們就可以判斷出調(diào)用方期望訪問的是哪張表中的數(shù)據(jù)了摘能。

4,Uri 對(duì)象所對(duì)應(yīng)的MIME 類型格式

getType()方法敲街。它是所有的內(nèi)容提供器都必須提供的一個(gè)方法团搞,用于獲取Uri 對(duì)象所對(duì)應(yīng)的MIME 類型。一個(gè)內(nèi)容URI 所對(duì)應(yīng)的MIME字符串主要由三部分組分多艇,Android 對(duì)這三個(gè)部分做了如下格式規(guī)定逻恐。

1). 必須以vnd 開頭。

2). 如果內(nèi)容URI 以路徑結(jié)尾峻黍,則后接android.cursor.dir/复隆,如果內(nèi)容URI 以id 結(jié)尾,則后接android.cursor.item/姆涩。

3). 最后接上vnd.<authority>.<path>挽拂。

所以,對(duì)于content://com.example.app.provider/table1 這個(gè)內(nèi)容URI骨饿,它所對(duì)應(yīng)的MIME類型就可以寫成:

vnd.android.cursor.dir/vnd.com.example.app.provider.table1

對(duì)于content://com.example.app.provider/table1/1 這個(gè)內(nèi)容URI亏栈,它所對(duì)應(yīng)的MIME 類型就可以寫成:

vnd.android.cursor.item/vnd.com.example.app.provider.table1

六台腥,具體案例

內(nèi)容提供者是為不同的程序之間提供數(shù)據(jù)共享的,所以要驗(yàn)證自定義的內(nèi)容提供者绒北,則需建立兩個(gè)項(xiàng)目黎侈,其中一個(gè)自定義內(nèi)容提供者,另一個(gè)調(diào)用該內(nèi)容提供者完成數(shù)據(jù)共享闷游。

1峻汉,項(xiàng)目一,自定義內(nèi)容提供者

public class DatabaseProvider extends ContentProvider {

public static final int BOOK_DIR = 0;

public static final int BOOK_ITEM = 1;

public static final int CATEGORY_DIR = 2;

public static final int CATEGORY_ITEM = 3;

public static final String AUTHORITY = "com.example.databasetest.provider";

private static UriMatcher uriMatcher;

private MyDatabaseHelper dbHelper;

static {

uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);

uriMatcher.addURI(AUTHORITY, "book", BOOK_DIR);

uriMatcher.addURI(AUTHORITY, "book/#", BOOK_ITEM);

uriMatcher.addURI(AUTHORITY, "category", CATEGORY_DIR);

uriMatcher.addURI(AUTHORITY, "category/#", CATEGORY_ITEM);

}

@Override

public boolean onCreate() {

dbHelper = new MyDatabaseHelper(getContext(), "BookStore.db", null, 2);

return true;

}

@Override

public Cursor query(Uri uri, String[] projection, String selection,

String[] selectionArgs, String sortOrder) {

// 查詢數(shù)據(jù)

SQLiteDatabase db = dbHelper.getReadableDatabase();

Cursor cursor = null;

switch (uriMatcher.match(uri)) {

case BOOK_DIR:

cursor = db.query("Book", projection, selection, selectionArgs,

null, null, sortOrder);

break;

case BOOK_ITEM:

String bookId = uri.getPathSegments().get(1);

cursor = db.query("Book", projection, "id = ?", new String[]

{ bookId }, null, null, sortOrder);

break;

case CATEGORY_DIR:

cursor = db.query("Category", projection, selection,

selectionArgs, null, null, sortOrder);

break;

case CATEGORY_ITEM:

String categoryId = uri.getPathSegments().get(1);

cursor = db.query("Category", projection, "id = ?", new String[]

{ categoryId }, null, null, sortOrder);

break;

default:

break;

}

return cursor;

}

@Override

public Uri insert(Uri uri, ContentValues values) {

// 添加數(shù)據(jù)

SQLiteDatabase db = dbHelper.getWritableDatabase();

Uri uriReturn = null;

switch (uriMatcher.match(uri)) {

case BOOK_DIR:

case BOOK_ITEM:

long newBookId = db.insert("Book", null, values);

uriReturn = Uri.parse("content://" + AUTHORITY + "/book/" +

newBookId);

break;

case CATEGORY_DIR:

case CATEGORY_ITEM:

long newCategoryId = db.insert("Category", null, values);

uriReturn = Uri.parse("content://" + AUTHORITY + "/category/" +

newCategoryId);

break;

default:

break;

}

return uriReturn;

}

@Override

public int update(Uri uri, ContentValues values, String selection,

String[] selectionArgs) {

// 更新數(shù)據(jù)

SQLiteDatabase db = dbHelper.getWritableDatabase();

int updatedRows = 0;

switch (uriMatcher.match(uri)) {

case BOOK_DIR:

updatedRows = db.update("Book", values, selection, selectionArgs);

break;

case BOOK_ITEM:

String bookId = uri.getPathSegments().get(1);

updatedRows = db.update("Book", values, "id = ?", new String[]

{ bookId });

break;

case CATEGORY_DIR:

updatedRows = db.update("Category", values, selection,

selectionArgs);

break;

case CATEGORY_ITEM:

String categoryId = uri.getPathSegments().get(1);

updatedRows = db.update("Category", values, "id = ?", new String[]

{ categoryId });

break;

default:

break;

}

return updatedRows;

}

@Override

public int delete(Uri uri, String selection, String[] selectionArgs) {

// 刪除數(shù)據(jù)

SQLiteDatabase db = dbHelper.getWritableDatabase();

int deletedRows = 0;

switch (uriMatcher.match(uri)) {

case BOOK_DIR:

deletedRows = db.delete("Book", selection, selectionArgs);

break;

case BOOK_ITEM:

String bookId = uri.getPathSegments().get(1);

deletedRows = db.delete("Book", "id = ?", new String[] { bookId });

break;

case CATEGORY_DIR:

deletedRows = db.delete("Category", selection, selectionArgs);

break;

case CATEGORY_ITEM:

String categoryId = uri.getPathSegments().get(1);

deletedRows = db.delete("Category", "id = ?", new String[]

{ categoryId });

break;

default:

break;

}

return deletedRows;

}

@Override

public String getType(Uri uri) {

switch (uriMatcher.match(uri)) {

case BOOK_DIR:

return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.book";

case BOOK_ITEM:

return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.book";

case CATEGORY_DIR:

return "vnd.android.cursor.dir/vnd.com.example.databasetest.provider.category";

case CATEGORY_ITEM:

return "vnd.android.cursor.item/vnd.com.example.databasetest.provider.category";

}

return null;

}

}

接下來就是每個(gè)抽象方法的具體實(shí)現(xiàn)了脐往,先來看下onCreate()方法休吠,這個(gè)方法的代碼很短,就是創(chuàng)建了一個(gè)MyDatabaseHelper 的實(shí)例钙勃,然后返回true 表示內(nèi)容提供器初始化成功蛛碌,這時(shí)數(shù)據(jù)庫(kù)就已經(jīng)完成了創(chuàng)建或升級(jí)操作。接著看一下query()方法辖源,在這個(gè)方法中先獲取到了SQLiteDatabase 的實(shí)例蔚携,然后根據(jù)傳入的Uri 參數(shù)判斷出用戶想要訪問哪張表,再調(diào)用SQLiteDatabase 的query()進(jìn)行查詢克饶,并將Cursor 對(duì)象返回就好了酝蜒。注意當(dāng)訪問單條數(shù)據(jù)的時(shí)候有一個(gè)細(xì)節(jié),這里調(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è)內(nèi)容URI 解析成Uri 對(duì)象星爪,當(dāng)然這個(gè)內(nèi)容URI 是以新增數(shù)據(jù)的id 結(jié)尾的。

接下來就是update()方法了粉私,相信這個(gè)方法中的代碼已經(jīng)完全難不倒你了顽腾。也是先獲取SQLiteDatabase 的實(shí)例,然后根據(jù)傳入的Uri 參數(shù)判斷出用戶想要更新哪張表里的數(shù)據(jù)诺核,再調(diào)用SQLiteDatabase 的update()方法進(jìn)行更新就好了崔泵,受影響的行數(shù)將作為返回值返回秒赤。

下面是delete()方法,是不是感覺越到后面越輕松了憎瘸?因?yàn)槟阋呀?jīng)漸入佳境入篮,真正地找到竅門了。這里仍然是先獲取到SQLiteDatabase 的實(shí)例幌甘,然后根據(jù)傳入的Uri 參數(shù)判斷出用戶想要?jiǎng)h除哪張表里的數(shù)據(jù)潮售,再調(diào)用SQLiteDatabase 的delete()方法進(jìn)行刪除就好了,被刪除的行數(shù)將作為返回值返回锅风。

最后是getType()方法酥诽,這個(gè)方法中的代碼完全是按照上一節(jié)中介紹的格式規(guī)則編寫的,相信已經(jīng)沒有什么解釋的必要了皱埠。

這樣我們就將內(nèi)容提供器中的代碼全部編寫完了肮帐,不過離實(shí)現(xiàn)跨程序數(shù)據(jù)共享的功能還差了一小步,因?yàn)檫€需要將內(nèi)容提供器在AndroidManifest.xml 文件中注冊(cè)才可以边器。

<application

android:allowBackup="true"

android:icon="@drawable/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

……

<provider

android:name="com.example.databasetest.DatabaseProvider"

android:authorities="com.example.databasetest.provider" >

</provider>

</application>

2训枢,項(xiàng)目二,調(diào)用自定義的內(nèi)容提供者

還是先來編寫一下布局文件吧忘巧,修改activity_main.xml 中的代碼恒界,如下所示:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:orientation="vertical" >

<Button

android:id="@+id/add_data"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Add To Book" />

<Button

android:id="@+id/query_data"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Query From Book" />

<Button

android:id="@+id/update_data"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Update Book" />

<Button

android:id="@+id/delete_data"

android:layout_width="match_parent"

android:layout_height="wrap_content"

android:text="Delete From Book" />

</LinearLayout>

布局文件很簡(jiǎn)單,里面放置了四個(gè)按鈕砚嘴,分別用于添加十酣、查詢、修改和刪除數(shù)據(jù)的际长。然后修改MainActivity 中的代碼耸采,如下所示:

public class MainActivity extends Activity {

private String newId;

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

Button addData = (Button) findViewById(R.id.add_data);

addData.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

// 添加數(shù)據(jù)

Uri uri = Uri.parse("content://com.example.databasetest.provider/book");

ContentValues values = new ContentValues();

values.put("name", "A Clash of Kings");

values.put("author", "George Martin");

values.put("pages", 1040);

values.put("price", 22.85);

Uri newUri = getContentResolver().insert(uri, values);

newId = newUri.getPathSegments().get(1);

}

});

Button queryData = (Button) findViewById(R.id.query_data);

queryData.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

// 查詢數(shù)據(jù)

Uri uri = Uri.parse("content://com.example.databasetest.

provider/book");

Cursor cursor = getContentResolver().query(uri, null, null,

null, null);

if (cursor != null) {

while (cursor.moveToNext()) {

String name = cursor.getString(cursor.

getColumnIndex("name"));

String author = cursor.getString(cursor.

getColumnIndex("author"));

int pages = cursor.getInt(cursor.getColumnIndex

("pages"));

double price = cursor.getDouble(cursor.

getColumnIndex("price"));

Log.d("MainActivity", "book name is " + name);

Log.d("MainActivity", "book author is " + author);

Log.d("MainActivity", "book pages is " + pages);

Log.d("MainActivity", "book price is " + price);

}

cursor.close();

}

}

});

Button updateData = (Button) findViewById(R.id.update_data);

updateData.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

// 更新數(shù)據(jù)

Uri uri = Uri.parse("content://com.example.databasetest.

provider/book/" + newId);

ContentValues values = new ContentValues();

values.put("name", "A Storm of Swords");

values.put("pages", 1216);

values.put("price", 24.05);

getContentResolver().update(uri, values, null, null);

}

});

Button deleteData = (Button) findViewById(R.id.delete_data);

deleteData.setOnClickListener(new OnClickListener() {

@Override

public void onClick(View v) {

// 刪除數(shù)據(jù)

Uri uri = Uri.parse("content://com.example.databasetest.

provider/book/" + newId);

getContentResolver().delete(uri, null, null);

}

});

}

}

可以看到,我們分別在這四個(gè)按鈕的點(diǎn)擊事件里面處理了增刪改查的邏輯工育。添加數(shù)據(jù)的時(shí)候虾宇,首先調(diào)用了Uri.parse()方法將一個(gè)內(nèi)容URI 解析成Uri 對(duì)象,然后把要添加的數(shù)據(jù)都存放到ContentValues 對(duì)象中翅娶,接著調(diào)用ContentResolver 的insert()方法執(zhí)行添加操作就可以了文留。注意insert()方法會(huì)返回一個(gè)Uri 對(duì)象好唯,這個(gè)對(duì)象中包含了新增數(shù)據(jù)的id竭沫,我們通過getPathSegments()方法將這個(gè)id 取出,稍后會(huì)用到它骑篙。查詢數(shù)據(jù)的時(shí)候蜕提,同樣是調(diào)用了Uri.parse()方法將一個(gè)內(nèi)容URI 解析成Uri 對(duì)象,然后調(diào)用ContentResolver 的query()方法去查詢數(shù)據(jù)靶端,查詢的結(jié)果當(dāng)然還是存放在Cursor 對(duì)象中的谎势。之后對(duì)Cursor 進(jìn)行遍歷凛膏,從中取出查詢結(jié)果,并一一打印出來脏榆。

更新數(shù)據(jù)的時(shí)候猖毫,也是先將內(nèi)容URI 解析成Uri 對(duì)象,然后把想要更新的數(shù)據(jù)存放到ContentValues 對(duì)象中须喂,再調(diào)用ContentResolver 的update()方法執(zhí)行更新操作就可以了吁断。注意這里我們?yōu)榱瞬幌胱孊ook 表中其他的行受到影響,在調(diào)用Uri.parse()方法時(shí)坞生,給內(nèi)容URI的尾部增加了一個(gè)id仔役,而這個(gè)id 正是添加數(shù)據(jù)時(shí)所返回的。這就表示我們只希望更新剛剛添加的那條數(shù)據(jù)是己,Book 表中的其他行都不會(huì)受影響又兵。

刪除數(shù)據(jù)的時(shí)候,也是使用同樣的方法解析了一個(gè)以id 結(jié)尾的內(nèi)容URI卒废,然后調(diào)用ContentResolver 的delete()方法執(zhí)行刪除操作就可以了沛厨。由于我們?cè)趦?nèi)容URI 里指定了一個(gè)

id,因此只會(huì)刪掉擁有相應(yīng)id 的那行數(shù)據(jù)升熊,Book 表中的其他數(shù)據(jù)都不會(huì)受影響俄烁。

七,總結(jié)

bt2和bt5兩種刪除方式都可成功

通過bt2和bt5的對(duì)比级野,可以證明路徑uri和約束條件是相輔相成的页屠。

既可以在路徑中帶上參數(shù),約束條件為空蓖柔,再由接口提供者做邏輯處理辰企;

也可以設(shè)置約束條件,在調(diào)用方做具體的邏輯處理况鸣。

類似于客戶端調(diào)用服務(wù)端牢贸,即可以在客戶端做數(shù)據(jù)處理,也可以在服務(wù)端做邏輯處理镐捧。事先約定好即可潜索。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市懂酱,隨后出現(xiàn)的幾起案子竹习,更是在濱河造成了極大的恐慌,老刑警劉巖列牺,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件整陌,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)泌辫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門随夸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人震放,你說我怎么就攤上這事宾毒。” “怎么了殿遂?”我有些...
    開封第一講書人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵伍俘,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我勉躺,道長(zhǎng)癌瘾,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任饵溅,我火速辦了婚禮妨退,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蜕企。我一直安慰自己咬荷,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開白布轻掩。 她就那樣靜靜地躺著幸乒,像睡著了一般。 火紅的嫁衣襯著肌膚如雪唇牧。 梳的紋絲不亂的頭發(fā)上罕扎,一...
    開封第一講書人閱讀 51,125評(píng)論 1 297
  • 那天,我揣著相機(jī)與錄音丐重,去河邊找鬼腔召。 笑死,一個(gè)胖子當(dāng)著我的面吹牛扮惦,可吹牛的內(nèi)容都是我干的臀蛛。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼崖蜜,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼浊仆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起豫领,我...
    開封第一講書人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤抡柿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后氏堤,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體沙绝,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年鼠锈,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闪檬。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡购笆,死狀恐怖粗悯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情同欠,我是刑警寧澤样傍,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站铺遂,受9級(jí)特大地震影響衫哥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜襟锐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一撤逢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧粮坞,春花似錦蚊荣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至筝闹,卻和暖如春媳叨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背关顷。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來泰國(guó)打工肩杈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人解寝。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓扩然,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親聋伦。 傳聞我的和親對(duì)象是個(gè)殘疾皇子夫偶,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容