解讀ContentResolver和ContentProvider

轉自:http://cthhqu.blog.51cto.com/7598297/1281217html

 

1. ContentProvider的概述
ContentProvider:
(Official Definition)Content providers manage access to a structured set of data. They encapsulate the data, and provide mechanisms for defining data security. Content providers are the standard interface that connects data in one process with code running in another process.
       由官方的定義咱們能夠得知它是一個管理訪問結構化數據的機制。咱們系統中有些數據很重要,不能讓人隨便訪問,可是由於比較有價值,因此不少應用程序須要用到它,這是就可經過ContentProvider這個機制,壓縮數據,提供安全定義、訪問數據的機制。該機制提供一個藉口,使得應用程序能從該進程訪問另一個應用程序的數據。
       不只是系統重要的數據,若是咱們開發過程當中有數據也是比較重要,可是須要提供給多個程序訪問,這個時候也能夠用到ContentProvider機制,把咱們的數據的增刪改查分裝在ContentProvider的增刪改查中,並增長相應的安全機制,使得用戶可咱們規定的安全機制下訪問咱們的數據。
 ContentProvider還有一個重要的特色就是它是可使得某些數據能夠被跨進程訪問,通常咱們的數據庫是不可跨進程被訪問,由於數據庫通常的數據是屬於某個應用程序的,若是其餘程序能夠隨意訪問其數據庫,這是很危險的,可是若是該應用程序的數據想分享給其餘應用程序,那麼就能夠經過創建一個ContentProvider,規定一些安全機制,屏蔽一些比較重要的數據被訪問,或是規定訪問權限,好比只可讀不可寫等,使其餘應用程序在有限制的前提下訪問咱們的數據。這樣作就會起到對數據進行保護同時又能使得有用的數據能被分享的做用。
2. ContentResolver和ContentProvider的使用方法
   2.1 ContentResolver的使用方法:
       首先,咱們得了解如何經過ContentResolver來對系統或咱們自定義的ContentProvider進行交互,一般,咱們經常使用到的是訪問系統的數據,常見的有通信錄、日曆、短信、多媒體等,下面以經過ContentResolver獲取系統的通信錄爲例子,簡單演示是如何使用ContentResolver的:
使用方法概括:
一、從當前Activity獲取系統的ContentResolver;
二、使用ContentProvider的insert、delete、update、query方法對ContentProvider的內容進行增刪改查;
三、若是是使用query是的到一個Cursor的結果集,經過該結果集能夠得到咱們查詢的結果。
 
源代碼示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package cth.android.contentprovide;
 
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.util.Log;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
 
 
/**
* @author CTH
*  
*該類是演示利用ContentProvider,獲取手機的聯繫人信息。
*使用ContentProvide的步驟:
*一、從當前Activity獲取系統的ContentResolver;
*二、使用ContentProvider的insert、delete、update、query方法對ContentProvider的內容進行增刪改查;
*三、若是是使用query是的到一個Cursor的結果集,經過該結果集能夠得到咱們查詢的結果。
*
*/
public class MainActivity  extends Activity {
 
private ListView contactsList;
 
@SuppressLint ( "InlinedApi" )
protected void onCreate(Bundle savedInstanceState) {
super .onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);  //不使用inflate XML文件方法,而是使用動態生成控件。
 
contactsList =  new ListView(MainActivity. this );  
 
ContentResolver cr = getContentResolver();   //獲取ContentResolver
Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI,  null ,
null null null );    //查詢系統的聯繫人信息
Log.i( "cth" , cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME) +  "" );
@SuppressWarnings ( "deprecation" )
ListAdapter la =  new SimpleCursorAdapter(MainActivity. this ,
android.R.layout.simple_list_item_1, cursor,
new String[] {ContactsContract.Contacts.DISPLAY_NAME_PRIMARY },
new int [] { android.R.id.text1 });    //創建列表適配器,把cursor關聯進來
 
 
contactsList.setAdapter(la);         //把ListVIew與適配器綁定
 
setContentView(contactsList);        //動態生成ListView
}
}
2.2 經常使用的系統URI
       訪問系統提供的其餘數據方法基本相似,只是傳入的URI有所區別,這些URI都是API裏面提供的,經過系統規定的不一樣URI可訪問系統不一樣的數據。系統經常使用的URI以下:

聯繫人的URI:
ContactsContract.Contacts.CONTENT_URI 管理聯繫人的Uri
ContactsContract.CommonDataKinds.Phone.CONTENT_URI 管理聯繫人的電話的Uri
ContactsContract.CommonDataKinds.Email.CONTENT_URI 管理聯繫人的Email的Uri
(注:Contacts有兩個表,分別是rawContact和Data,rawContact記錄了用戶的id和name,java

其中id欄名稱 爲:ContactsContract.Contacts._ID,name名稱欄爲ContactContract.Contracts.DISPLAY_NAME,android

電話信息表的外鍵id爲 ContactsContract.CommonDataKinds.Phone.CONTACT_ID,sql

電話號碼欄名稱爲:ContactsContract.CommonDataKinds.Phone.NUMBER。數據庫

data表中Email地址欄名稱爲:ContactsContract.CommonDataKinds.Email.DATA
其外鍵欄爲:ContactsContract.CommonDataKinds.Email.CONTACT_ID)api


多媒體的ContentProvider的Uri以下:
MediaStore.Audio.Media.EXTERNAL_CONTENT_URI  存儲在sd卡上的音頻文件
MediaStore.Audio.Media.INTERNAL_CONTENT_URI  存儲在手機內部存儲器上的音頻文件安全

MediaStore.Audio.Images.EXTERNAL_CONTENT_URI SD卡上的圖片文件內容
MediaStore.Audio.Images.INTERNAL_CONTENT_URI 手機內部存儲器上的圖片
MediaStore.Audio.Video.EXTERNAL_CONTENT_URI SD卡上的視頻
MediaStore.Audio.Video.INTERNAL_CONTENT_URI  手機內部存儲器上的視頻app

(注:圖片的顯示名欄:Media.DISPLAY_NAME,圖片的詳細描述欄爲:Media.DESCRIPTION  圖片的保存位置:Media.DATAide

短信URI: Content://sms函數

發送箱中的短信URI: Content://sms/outbox

2.3 ContentProvider的使用方法:
       那麼咱們是如何自定義ContentProvider來對其餘程序提供訪問咱們數據的接口的呢?
 
       通常咱們若是有數據想被跨進程共享,就能夠定義個ContentProvider,並在該ContentProvider定義訪問該數據的方法,這些方法只容許外界傳入一個URI和其餘附加信息,該URI用於定位想操做數據的對象,而附加信息通常就是操做對象裏具體數據的條件(例如SQL裏面的where語句或者是SharedPreferences的鍵值對)。這樣,就能作到對外界屏蔽了訪問數據的方法,單純開發URI和附加信息的接口,使得其餘應用程序是在咱們規定的機制下訪問數據。這樣既能作到跨進程數據共享,又能消除訪問不一樣類型數據時訪問方法的差別性,用戶可不用管訪問的數據類型是數據庫或是其餘類型而單純用URI和附加信息定位操做數據對象,同時有提升了安全性。
2.3.1 URI簡介:
       安卓中系統和用戶自定義ContentProvider不止一個,那麼當咱們想訪問某個ContentProvider時,咱們是怎麼定位到的呢?由上文可知咱們是經過URI定位到咱們具體想訪問的數據,這時咱們得先了解用於定位ContentProvider的URI的構成以及各部分的意義。官網API文檔是這樣介紹的:
Content URIs have the syntax

content://authority/path/id

content:
The scheme portion of the URI. This is always set to  ContentResolver.SCHEME_CONTENT (value content://).  
authority
A string that identifies the entire content provider. All the content URIs for the provider start with this string.
To guarantee a unique authority, providers should consider using an authority that is the same as the provider class' package identifier.  
path
Zero or more segments, separated by a forward slash ( /), that identify some subset of the provider's data.  
Most providers use the path part to identify individual tables. Individual segments in the path are often called "directories"  
although they do not refer to file directories. The right-most segment in a path is often called a "twig"  
id
A unique numeric identifier for a single row in the subset of data identified by the preceding path part.  
Most providers recognize content URIs that contain an id part and give them special handling.
A table that contains a column named  _ID often expects the id part to be a particular value for that column.

       由上述咱們能夠獲得該URI分爲四部分:

第一部分:「content://」是系統規定的;

第二部分:authority是一個標誌整個內容提供者的字符串,也就是該內容提供者的標識符,至關於咱們每一個人都有的姓名;

第三部分:是內容提供者提供數據的某個子集,由於內容提供者有時會提供多個數據,咱們具體是要訪問那個子集,須要把具體路徑標出來;

第四部分:是一個標誌數據子集中具體某一行數據的數字,通常咱們數據庫都會有ID這一項,經過該字段可直接定位到表中的某一行。若是不知道或用不到該項可不填。

       Android裏面提供了兩個類對URI進行操做,一個是UriMatcher,該類專用於在ContentProvider中創建匹配器,從安卓的API文檔咱們得知該匹配器用於在ContentProvider類的最開始創建匹配器並添加匹配規則,在後面的方法覆蓋過程,須要用到該類的match方法進行匹配,返回匹配到的標誌位。

void addURI(String authority, String path, int code)   //  三個參數,第一個是受權信息,第二個是路徑,第三個就是當匹配該規則是返回的標誌位
Add a URI to match, and the code to return when this URI is matched.

       另一個是ContentUri類,該類僅有的三個方法都是靜態類,它是一個提供對URI和ID進行操做的工具類。經常使用的主要是parseId:用於取得URI中的Id,其實內部實現方法就是取得Uri PATH部分後面的值;withAppendedId用於把ID拼接到一個Uri的後面。

Public Methods
static Uri.Builder appendId(Uri.Builder builder, long id)
Appends the given ID to the end of the path.
                   
static long parseId(Uri contentUri)
Converts the last path segment to a long.
                   
static Uri withAppendedId(Uri contentUri, long id)
Appends the given ID to the end of the path.
                   

 

    2.3.2 ContentProvider的使用方法:

       瞭解了什麼是URI就能夠自定義一個ContentProvider,對外提供訪問咱們數據的接口了。下面以ContentProvider最常封裝的數據類型——數據庫爲例子進行說明。

       整個自定義ContentProvider的過程分爲兩大步,第一步固然是創建數據源,第二部則是創建訪問數據源的內容提供者,這裏以SQL數據庫爲例:

第一步:首先咱們須要先建一個SQLiteOpenHelper的子類:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
 
public class StuDbHelper  extends SQLiteOpenHelper {
 
private static String DbName =  "student.db" ;
 
public StuDbHelper(Context context, int version) {
super (context, DbName,  null , version);
}
 
@Override
public void onCreate(SQLiteDatabase db) {
String sql =  "create table student (id integer primary key,name varchar(20),age integer)" //在數據庫中建立一張表
db.execSQL(sql);
}
 
@Override
public void onUpgrade(SQLiteDatabase db,  int oldVersion,  int newVersion) {
 
}
}

第二步:有了數據源,咱們接下來就得創建訪問數據源的ContentProvider,創建ContentProvider的步驟一般分爲:創建匹配器並添加匹配規則,重寫各類操做數據的方法。添加匹配規則必需在全部方法和構造函數的執行前執行,由於有時候咱們須要在匹配成功後才創建數據庫的SQLiteOpenHelper。因此這裏必須使用靜態代碼塊添加匹配規則。而重寫訪問數據的方法主要有增刪改查四種,重寫的步驟基本遵循: 對Uri進行匹配 --> 獲取讀或寫數據庫對象  -->  根據匹配結果利用switch語句判斷該執行哪一種操做  -->  返回結果。一下是ContentProvider的源碼示例:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package cth.android.contentprovider;
 
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
 
public class StuDbCP  extends ContentProvider {
 
private static final UriMatcher URI_MATCHER =  new UriMatcher(UriMatcher.NO_MATCH);   //建立該內容提供者的匹配器
private static final String AUTHORITY =  "cth.android.contentprovider.StuDbCP" ;    //定義受權信息,用於獲取該內容提供者的標識
private static final String PATH =  "student" ;                        //路徑,表示訪問內容提供者的具體路徑,通常用代表表示訪問該數據庫的具體哪張表
private static final int STU =  1 ;     //設定標誌位,STU表示單條信息
private static final int STUS =  2 ;    //STUS表示多條信息
static {
URI_MATCHER.addURI(AUTHORITY, PATH +  "/#" , STU);    //給匹配器加入匹配規則
URI_MATCHER.addURI(AUTHORITY, PATH, STUS);
}
 
private StuDbHelper stuDbHepler =  null ;
private SQLiteDatabase stuDb =  null ;
@Override
public boolean onCreate() {
boolean flag =  false ;
stuDbHepler =  new StuDbHelper(getContext(), 1 );   //建立SQLiteOpenHelper對象,版本爲1
if (stuDb !=  null ) flag =  true ;
return flag;
}
 
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
int flag = URI_MATCHER.match(uri);     //匹配傳入的Uri
Cursor selectResult =  null ;
SQLiteDatabase db = stuDbHepler.getReadableDatabase();
String whereClause =  null ;
switch (flag) {
case STU:
whereClause =  "id = " + ContentUris.parseId(uri);   //若是匹配第一種方式表示後面跟了id,因此要先提取id
if (selection !=  null && selection.equals( "" )) {
whereClause +=  " and " + selection;    //把id加到where條件子句(下面幾種方法此步驟做用同理)
}
selectResult = db.query( "student" , projection, whereClause, selectionArgs,  null null null );
break ;
case STUS:
selectResult = db.query( "student" , projection,selection,selectionArgs,  null null null );
}
 
return selectResult;
}
 
/*返回uri的路徑擴展部分的信息,通常單個條目返回字段爲vnd.android.cursor.item/對應的PATH 而多個條目的以vnd.android.cursor.dir/對應的PATH。*/
@Override
public String getType(Uri uri) {  
 
int flag = URI_MATCHER.match(uri);
switch (flag) {
case STU:
return "vnd.android.cursor.item/student" ;
 
case STUS:
return "vnd.android.cursor.dir/students" ;
}
return null ;
}
 
@Override
public Uri insert(Uri uri, ContentValues values) {
int flag = URI_MATCHER.match(uri);
Uri resultUri =  null ;
switch (flag) {
case STUS :
stuDb = stuDbHepler.getWritableDatabase();    //獲取寫數據庫
long id = stuDb.insert(PATH,  null , values);    //插入數據
resultUri = ContentUris.withAppendedId(uri, id);   //創建插入的數據的URI
break ;
}
 
return resultUri;
}
 
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
int flag = URI_MATCHER.match(uri);
String whereClause =  null ;
int rowCount = - 1 ;
SQLiteDatabase db = stuDbHepler.getWritableDatabase();
switch (flag) {
case STU:
whereClause =  "id = " + ContentUris.parseId(uri);
if (selection !=  null && selection.equals( "" )) {
whereClause +=  " and " + selection ;
}
rowCount = db.delete( "student" , whereClause, selectionArgs);
break ;
 
case STUS:
rowCount = db.delete( "student" , selection, selectionArgs);
 
 
default break ;
}
return rowCount;
}
 
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
int flag = URI_MATCHER.match(uri);
int count = - 1 ;
SQLiteDatabase db = stuDbHepler.getWritableDatabase();
String whereClause =  null ;
switch (flag) {
case STU:
whereClause =  "id = " + ContentUris.parseId(uri);
if (selection !=  null && selection.equals( "" )) {
whereClause +=  " and " + selection ;
}
count = db.update( "student" , values, whereClause, selectionArgs);
break ;
case STUS:
count = db.update( "student" , values, selection, selectionArgs);
break ;
}
return count;
}
 
}

 

       最後,ContentProvider也是安卓的四大組件之一,因此咱們要在Manifest文件的application標籤中對其進行註冊。這樣,一個內容提供者就創建好了。咱們能夠另外創建一個測試工程,對其進行跨進程訪問,不過須要注意的是咱們對其進行訪問前必須肯定該ContentProvider是存在的,因此必須先運行ContentProvider的工程,而後就可使用測試工程對其進行訪問了。這裏須要注意的是,當咱們把ContentProvider進程關閉後,咱們仍是能夠對其數據進行訪問的。一下是測試工程的代碼:

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.test.AndroidTestCase;
import android.util.Log;
 
/*
* 新建一個工程,創建一個測試類來測試是否可以跨進程訪問數據。分別實現增刪改查四種方法。
* */
 
public class TestCP  extends AndroidTestCase {
 
public void insertData() {
ContentResolver cr = getContext().getContentResolver();
ContentValues values =  new ContentValues();
values.put( "id" 3 );
values.put( "name" "Jiky" );
values.put( "age" 18 );
Uri resultUri = cr.insert(uri, values);
if (resultUri !=  null ) {
Log.i( "cth" ,resultUri.toString());
else {
Log.e( "cth" , "插入失敗" );
}
}
 
 
public void deleteData() {
ContentResolver cr = getContext().getContentResolver();
String where =  "id = ?" ;
int deleteRowNum = cr.delete(uri, where,  new String[] { "123" });
Log.i( "cth" , "deleteRowNum is " + deleteRowNum );
}
 
public void updateData() {
ContentResolver cr = getContext().getContentResolver();
Uri uri = Uri.parse( "content://cth.android.contentprovider.StuDbCP/student/" ); //直接把要修改的id加在PATH後,也可按一下方式。
ContentValues values =  new ContentValues();
values.put( "name" , "Mike" );
values.put( "age" 11 );
int rowId = cr.update(uri, values,  "id = ?" new String[]{ "1" });
if (rowId ==  0 ) {
Log.e( "cth" , "找不到匹配項。" );
else {
Log.i( "cth" , "rowId = " + rowId);
}
 
}
 
public void selectData() {
ContentResolver cr = getContext().getContentResolver();
 
Cursor cursor = cr.query(uri, new String[]{ "name" , "id" },  null null null );
 
while (cursor.moveToNext()) {
Log.i( "cth" ,cursor.getString(cursor.getColumnIndex( "id" )) +  " count = " + cursor.getCount());
}
 
}
}
相關文章
相關標籤/搜索