解讀ContentResolver和ContentProvider


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

 

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的:
使用方法歸納:
1、從當前Activity獲取系統的ContentResolver;
2、使用ContentProvider的insert、delete、update、query方法對ContentProvider的內容進行增刪改查;
3、如果是使用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的步驟:
*1、從當前Activity獲取系統的ContentResolver;
*2、使用ContentProvider的insert、delete、update、query方法對ContentProvider的內容進行增刪改查;
*3、如果是使用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,

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

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

電話號碼欄名稱為:ContactsContract.CommonDataKinds.Phone.NUMBER。

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


多媒體的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  手機內部存儲器上的視頻

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

短信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());
}
 
}
}

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM