內(nèi)容提供器(Content Provider)主要用于在不同的應(yīng)用程序之間實(shí)現(xiàn)數(shù)據(jù)數(shù)據(jù)共享的功能往衷,同時(shí)這也是Android實(shí)現(xiàn)跨程序共享數(shù)據(jù)的標(biāo)準(zhǔn)方式肢藐。盡管之前學(xué)習(xí)的SharedPreference的數(shù)據(jù)存儲(chǔ)也能夠?qū)崿F(xiàn)讀寫(xiě)操作,但是內(nèi)容提供器可以選擇對(duì)哪一部分的數(shù)據(jù)進(jìn)行共享,因此內(nèi)容提供器有著更好的保護(hù)隱私的作用。
使用系統(tǒng)內(nèi)容提供器
如果一個(gè)應(yīng)用程序通過(guò)內(nèi)容提供器對(duì)其數(shù)據(jù)提供了外部訪問(wèn)借接口雪位,那么其他的程序就能夠通過(guò)該接口來(lái)對(duì)這些數(shù)據(jù)進(jìn)行訪問(wèn)。Android系統(tǒng)中自帶的電話簿梨撞、相冊(cè)雹洗、短信等應(yīng)用都設(shè)置內(nèi)容提供器香罐,所以我們的程序都可以來(lái)利用這些數(shù)據(jù)。
1.內(nèi)容URI的組成
由于是其他的程序在訪問(wèn)內(nèi)容提供器中的數(shù)據(jù)时肿,所以需要用有一定格式規(guī)范的內(nèi)容URI來(lái)代替庇茫,我們先來(lái)了解一下什么是URI吧。
內(nèi)容URI給內(nèi)容提供器中的數(shù)據(jù)建立了唯一的表示符螃成,它主要是由兩部分組成:authority和path旦签。authority是用于對(duì)不同的應(yīng)用程序做區(qū)分的,而path則是用于對(duì)不同的表做區(qū)分的寸宏。舉例來(lái)說(shuō)假設(shè)某個(gè)程序中有兩張表table 1和table 2宁炫,該程序的包為com.example.app,那該程序的兩個(gè)內(nèi)容URI的標(biāo)準(zhǔn)寫(xiě)法就是:
content://com.example.app.provider/table 1
content://cmom.example.app.provider/table 2
在得到了內(nèi)容URI之后氮凝,我們還需要將其解析為Uri對(duì)象才可以作為參數(shù)來(lái)進(jìn)行傳入羔巢,具體的解析方法是:
Uri uri1 = Uri.parse("content://com.example.app.provider/table 1");
Uri uri2 = Uri.parse("content://com.example.app.provider/table 2");
我們只用調(diào)用Uri中的parse()方法即可將其解析為Uri對(duì)象,之后我們就可以對(duì)該對(duì)象進(jìn)行操作了罩阵。
2.ContentResolve類
如果想要訪問(wèn)內(nèi)容提供器中的內(nèi)容竿秆,我們就需要使用ContentResolve類,使用該類的getContentResolve()方法就會(huì)返回一個(gè)該類的實(shí)例永脓。有了該類的實(shí)例我們就可以對(duì)其進(jìn)行CRUD操作袍辞,具體的方法仍然是查詢query()鞋仍、添加insert()常摧、更新update()和刪除delete()。但是這里的CRUD操作所使用的參數(shù)和之前的SQLite有所不同威创,我們這里不再用單純的表名指明被操作的表落午,而是使用內(nèi)容URI來(lái)進(jìn)行來(lái)當(dāng)參數(shù)進(jìn)行操作了。具體操作的方法和SQLite中的很相似肚豺,就用一張圖來(lái)概括:
3.實(shí)踐:讀取系統(tǒng)聯(lián)系人
知道了使用方法溃斋,下面就來(lái)具體的實(shí)踐一下吧。我們這里是用我們的程序來(lái)調(diào)用系統(tǒng)的電話簿中的聯(lián)系人并將他們的姓名和電話號(hào)碼顯示在我們的程序當(dāng)中吸申。先創(chuàng)建一個(gè)空項(xiàng)目梗劫,然后為布局加上一個(gè)ListView,然后修改主代碼:
package com.example.yzbkaka.contasttest;
import android.Manifest;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.provider.ContactsContract;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends AppCompatActivity {
List<String> contactList = new ArrayList<String>();
ArrayAdapter<String> adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView contaseView = (ListView)findViewById(R.id.list_view);
adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,contactList);
contaseView.setAdapter(adapter);
if(ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,new String[] {Manifest.permission.READ_CONTACTS},1);
}
else{
readContast();
}
}
private void readContast(){
Cursor cursor = null;
try{
cursor = getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
if(cursor != null){
while(cursor.moveToNext()){
String displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number = cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
contactList.add(displayName+"\n"+number);
}
adapter.notifyDataSetChanged();
}
}catch(Exception e){
e.printStackTrace();
}finally {
if(cursor != null){
cursor.close();
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode,String[] permission,int[] grantResults){
switch(requestCode){
case 1:
if(grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED){
readContast();
}
else{
Toast.makeText(this, "you denied the permission", Toast.LENGTH_SHORT).show();
}
}
}
}
這個(gè)代碼看起來(lái)很長(zhǎng)截碴,但是其中的onRequestPermissionsResult()方法和中間申請(qǐng)權(quán)限的代碼都在技能總結(jié)篇(2)中見(jiàn)到過(guò)梳侨。這里主要是看一下readContast()中的操作,我們先是使用getContentResolver()方法返回一個(gè)Cursor對(duì)象日丹,接著是使用query()方法來(lái)進(jìn)行查詢走哺,這個(gè)方法中我們傳入的ContactsContract.Common DataKinds.Phone.CONTENT_URI類是已經(jīng)幫我們封裝好的Uri對(duì)象。接著我們就對(duì)cursor對(duì)象進(jìn)行便利哲虾,將其中的聯(lián)系人的姓名和號(hào)碼讀取出來(lái)丙躏,在使用getColumnIndex()方法時(shí)我們傳入的參數(shù)仍然是系統(tǒng)為我們封裝好的常量择示,我們直接使用即可。最后就是將這些信息傳入到List中展示出來(lái)晒旅。
最后要記住危險(xiǎn)權(quán)限的聲明一定要在AndroidManifest.xml中進(jìn)行注冊(cè):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yzbkaka.contasttest">
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
創(chuàng)建自己的內(nèi)容提供器
創(chuàng)建自己的內(nèi)容提供器可以讓我們的程序中的數(shù)據(jù)共享給其他的程序栅盲,我們這里使用的數(shù)據(jù)庫(kù)是上一節(jié)中使用的Book.dp,我們的創(chuàng)建將會(huì)以它為基礎(chǔ)來(lái)進(jìn)行废恋。打開(kāi)那個(gè)項(xiàng)目剪菱,然后新建一個(gè)DatabaseProvider類,并讓它繼承自ContentProvider類拴签,接著我們?cè)陬惖拈_(kāi)始新建幾個(gè)變量:
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";
public static UriMatcher uriMatcher;
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);
}
我們首先定義了4個(gè)常量孝常,BOOK_DIR表示訪問(wèn)Book表中的所有數(shù)據(jù),BOOK_ITEM表示訪問(wèn)Book表中的單條數(shù)據(jù)蚓哩,CATEGORY_DIR表示訪問(wèn)Category中的全部數(shù)據(jù)构灸,CATEGORY_ITEM表示訪問(wèn)Category中的單條數(shù)據(jù)。接著我們?cè)陟o態(tài)代碼塊中定義了一個(gè)UriMatcher實(shí)例岸梨,利用它的addURI()方法我們可以將期望匹配的幾種URI格式添加了進(jìn)去喜颁,這個(gè)方法需要傳入三個(gè)參數(shù),可以分別把a(bǔ)uthority曹阔、path和一個(gè)自定義的代碼傳進(jìn)去半开。這樣當(dāng)調(diào)用UriMatcher的match()方法時(shí)就可以將一個(gè)Uri對(duì)象傳入,而返回值就是一個(gè)之前自定義的代碼赃份,利用這個(gè)代碼我們就可以判斷出調(diào)用方需要的是那一項(xiàng)數(shù)據(jù)了寂拆。
接著我們來(lái)分別實(shí)現(xiàn)繼承過(guò)來(lái)的方法,首先是onCreate()方法:
@Override
public boolean onCreate() {
dbHelper = new MyDatabaseHelper(getContext(),"BookStore.db",null,2);
return true;
}
在這一步中我們是定義了dbHelper抓韩,并且讓它創(chuàng)建了一個(gè)BookStore.db的數(shù)據(jù)庫(kù)纠永,然后是返回true表示內(nèi)容提供器初始化成功。接著來(lái)寫(xiě)query()方法:
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = null;
switch(uriMatcher.match(uri)){
case BOOK_DIR:
cursor = db.query("Book",projection,selection,selectionArgs,null,null,sortOrder);
break;
case BOOK_ITEM:
cursor = db.query("Book",projection,selection,selectionArgs,null,null,sortOrder);
break;
case CATEGORY_DIR:
cursor = db.query("Category",projection,selection,selectionArgs,null,null,sortOrder);
break;
case CATEGORY_ITEM:
cursor = db.query("Category",projection,selection,selectionArgs,null,null,sortOrder);
break;
default:
break;
}
return cursor;
}
在這個(gè)方法中需要我們傳入5個(gè)參數(shù)谒拴,第一個(gè)是Uri對(duì)象尝江,第二個(gè)是查詢的列,第三個(gè)和第四個(gè)是用于約束查詢哪些行英上,第五個(gè)是對(duì)結(jié)果的排序方式炭序。我們?cè)谶@個(gè)方法里面首先是得到了一個(gè)SQLiteDatabase對(duì)象,并定義了一個(gè)Cursor苍日,之后我們?cè)龠M(jìn)行匹配惭聂,當(dāng)匹配成功時(shí),我們就使用SQLiteDatabase中的query()方法來(lái)進(jìn)行查詢易遣,該方法會(huì)返回一個(gè)Cursor對(duì)象彼妻。在最后我們會(huì)返回一個(gè)Cursor對(duì)象。
接著來(lái)看insert()方法:
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri uriReturn = null;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
case BOOK_ITEM:
long newBookId = db.insert("Book",null,contentValues);
uriReturn = Uri.parse("content://"+AUTHORITY+"/book/"+newBookId);
break;
case CATEGORY_DIR:
case CATEGORY_ITEM:
long newCategoryId = db.insert("Category",null,contentValues);
uriReturn = Uri.parse("context://"+AUTHORITY+"/category/"+newCategoryId);
break;
default:
break;
}
return uriReturn;
}
在這個(gè)方法中也是先獲取SQLiteDatabase對(duì)象,然后再來(lái)根據(jù)uri進(jìn)行匹配侨歉。但是要注意的是這個(gè)方法要求返回一個(gè)Uri的對(duì)象屋摇,因此我們需要使用parse()方法來(lái)進(jìn)行轉(zhuǎn)換,不過(guò)這個(gè)內(nèi)容的URI是以新增數(shù)據(jù)的id結(jié)尾的幽邓。
接下來(lái)就是udata()方法:
@Override
public int update(Uri uri, ContentValues contentValues, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int updateDows = 0;
switch (uriMatcher.match(uri)){
case BOOK_DIR:
updateDows = db.update("Book",contentValues,selection,selectionArgs);
break;
case BOOK_ITEM:
String newBookId = uri.getPathSegments().get(1);
updateDows = db.update("Book",contentValues,"id=?",new String[] {newBookId});
break;
case CATEGORY_DIR:
updateDows = db.update("Book",contentValues,selection,selectionArgs);
break;
case CATEGORY_ITEM:
String newCategoryId = uri.getPathSegments().get(1);
updateDows = db.update("Book",contentValues,"id=?",new String[]{newCategoryId});
break;
default:
break;
}
return updateDows;
}
updata()方法也是和前面幾個(gè)相似炮温,先是獲取SQLiteDatabase對(duì)象,然后再來(lái)根據(jù)uri進(jìn)行匹配牵舵。這個(gè)方法需要返回一個(gè)整數(shù)代表的事更新的行數(shù)柒啤,所以在匹配的時(shí)候我們使用了一個(gè)getPathSegments()的方法,該方法可以把URI中的的權(quán)限之后的部分以“/”分割畸颅,并把分割后的結(jié)果放在一個(gè)字符串列表中担巩,所以這個(gè)列表第0個(gè)位置存放的就是路徑,第1個(gè)位置存放的就是id了没炒。
然后是delete()方法了:
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
int deleteRows = 0;
switch(uriMatcher.match(uri)){
case BOOK_DIR:
deleteRows = db.delete("Book",selection,selectionArgs);
break;
case BOOK_ITEM:
String bookId = uri.getPathSegments().get(1);
deleteRows = db.delete("Book","id = ?",new String[] {bookId});
break;
case CATEGORY_DIR:
deleteRows = db.delete("Category",selection,selectionArgs);
break;
case CATEGORY_ITEM:
String categoryId = uri.getPathSegments().get(1);
deleteRows = db.delete("Category","id = ?",new String[]{categoryId});
break;
default:
break;
}
return deleteRows;
}
這個(gè)方法也很簡(jiǎn)單涛癌,就不多說(shuō)了。
最后就是getType()方法了:
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case BOOK_DIR:
return "vnd.android.cursor.dir/vand.com.example.databasetest.provider.book";
case BOOK_ITEM:
return "vnd.android.cursor.dir/vand.com.example.databasetest.provider.book";
case CATEGORY_DIR:
return "vnd.android.cursor.dir/vand.com.example.databasetest.provider.CATEGORY";
case CATEGORY_ITEM:
return "vnd.android.cursor.dir/vand.com.example.databasetest.provider.CATEGORY";
}
return null;
}
這個(gè)方法是用于獲取Uri對(duì)象所對(duì)應(yīng)的MIME類型送火,一個(gè)內(nèi)容Uri所對(duì)應(yīng)的MIME字符串主要是由三部分構(gòu)成:Android對(duì)這三個(gè)部分做了以下格式規(guī)定:必須以vnd開(kāi)頭拳话;如果內(nèi)容URI以路徑結(jié)尾,則后接android.cursor.dir/种吸,如果內(nèi)容URI以id結(jié)尾弃衍,則后接android.cursor.item/;最后接上vnd.< authority>.< path>坚俗。所以我們把這四個(gè)添加進(jìn)去镜盯。
最后內(nèi)容提供器一定要在AndroidManifst.xml中進(jìn)行注冊(cè):
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.yzbkaka.databasetest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:authorities="com.example.databasttest.provider"
android:name=".DatabaseProvider"
android:exported="true"
android:enabled="true">
</provider>
</application>
</manifest>
我們?cè)?lt;application>標(biāo)簽中添加了<provider>,其中authorities就是指它的authority坦冠,name指定了DatabaseProvider的類名形耗,exported表示是否運(yùn)行外部程序訪問(wèn)我們的內(nèi)容提供器哥桥,enable表示是否啟用該內(nèi)容提供器辙浑。
最后我們運(yùn)行程序,然后退出程序拟糕,再寫(xiě)一個(gè)調(diào)用該程序的測(cè)試程序判呕,先在布局文件里面添加4個(gè)按鈕分別表示CRUE,然后修改主代碼:
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
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 View.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", 55.55);
Uri newUri = getContentResolver().insert(uri, values);
newId = newUri.getPathSegments().get(1);
}
});
Button queryData = (Button) findViewById(R.id.query_data);
queryData.setOnClickListener(new View.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 View.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 View.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);
}
});
}
}
最后運(yùn)行程序送滞,調(diào)用數(shù)據(jù)成功O啦荨!犁嗅!