一、廢話
好不容易完成出差任務,又被派到另一個地方出差!苦逼的我!年終獎還不給我發,再不發的話我都快沒錢坐車回家了!
二、正文
今天內容是ContentProvider——如果做過電話薄應用程序的人肯定都用過這個類,那ContentProvider到底是個什么東西,有什么用,如何用呢?
1、 ContentProvider是個啥?
ContentProvider——內容提供者。它是一個類,這個類主要是對Android系統中進行共享的數據進行包裝,並提供了一組統一的訪問接口供其他程序調用。這些被共享的數據,可以使系統自己的也可以使我們個人應用程序中的數據。
2、 為什么要有ContentProvider這個類?
在Android中,數據的存儲有很多種方式,最常用的就是SQLite和XML文件方式。在不同的應用程序間,其實數據是不能直接被相互訪問和操作的,在這種情況下,ContentProvider很好的被用來解決了不同應用程序間數據共享的問題。
其實在Android系統中,已經為我們提供了許多ContentProvider,如:Contacts、Browser、CallLog、Settings等等。那么,Android系統中提供了這么多的ContentProvider,另外還有我們自己公開的共享數據,我們在寫程序的時候,怎么才能讓我們的應用程序知道去哪兒取、如何取這些數據呢?我們自然的會想到URI。
3、 URI是個啥?在ContentProvider中有什么用處?URI中的幾個方法。
URI(Uniform Resource Identifier)——統一資源定位符,URI在ContentProvider中代表了要操做的數據。
在Android系統中通常的URI格式為:content://LiB.cprovider.myprovider.Users/User/21
在萬維網訪問時通常用的URI格式為:http://www.XXXX.com/AAA/123
-
- content://——schema,這個是Android中已經定義好的一個標准。我個人一直認為這和我們的http://有異曲同工之妙,都是代表的協議。
- LiB.cprovider.myprovider.Users——authority(主機名),用於唯一標識這個ContentProvider,外部調用者通過這個authority來找到它。相當於www.XXXX.com,代表的是我們ContentProvider所在的”域名”,這個”域名”在我們Android中一定要是唯一的,否則系統怎么能知道該找哪一個Provider呢?所以一般情況下,建議采用完整的包名加類名來標識這個ContentProvider的authority。
- /User/21——路徑,用來標識我們要操作的數據。/user/21表示的意思是——找到User中id為21的記錄。其實這個相當於/AAA/123。
綜上所述,content://LiB.cprovider.myprovider.Users/User/21所代表的URI的意思為:標識LiB.cprovider.myprovider中Users表中_ID為21的User項。
通過上面的介紹,我想大家都知道URI到底是如何標識我們的ContentProvider了,我們就來看看Android系統中為我們提供了些有關操作URI的方法吧。
-
-
ContentUris類——操作URI
-
withAppendedId(builder, id)——在builder最后加上id組成URI。
Uri uri = ContentUris.withAppendedId(User.CONTENT_URI, id);
parseId(uri)——用於從URI中提取出ID值。
long returnID = ContentUris.parseId(User.CONTENT_URI);
-
-
UriMatcher類——匹配URI
-
addURI(authority, path, code)——為UriMatcher添加規則,並在匹配成功后返回code值,否則返回構造函數中傳入的默認值,一般為UriMatcher.NO_MATCH
參數解釋:authority——主機名,path——路徑,code——匹配后返回的值
uriMatcher.addURI(Users.AUTHORITY, "User", USER);
4、 ContentProvider中公開的幾個方法
-
- public boolean onCreate():該方法在ContentProvider創建后就會被調用,Android系統運行后,ContentProvider只有在被第一次使用它時才會被創建。
- public Uri insert(Uri uri, ContentValues values):外部應用程序通過這個方法向 ContentProvider添加數據。
- uri——標識操作數據的URI
- values——需要添加數據的鍵值對
- public int delete(Uri uri, String selection, String[] selectionArgs):外部應用程序通過這個方法從 ContentProvider中刪除數據。
- uri——標識操作數據的URI
- selection——構成篩選添加的語句,如"id=1" 或者 "id=?"
- selectionArgs——對應selection的兩種情況可以傳入null 或者 new String[]{"1"}
- public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs):外部應用程序通過這個方法對 ContentProvider中的數據進行更新。
- values——對應需要更新的鍵值對,鍵為對應共享數據中的字段,值為對應的修改值
- 其余參數同delete方法
- public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder):外部應用程序通過這個方法從ContentProvider中獲取數據,並返回一個Cursor對象。
- projection——需要從Contentprovider中選擇的字段,如果為空,則返回的Cursor將包含所有的字段。
- sortOrder——默認的排序規則
- 其余參數同delete方法
- public String getType(Uri uri):該方法用於返回當前Url所代表數據的MIME類型。
- 如果操作的數據屬於集合類型,那么MIME類型字符串應該以vnd.android.cursor.dir/開頭,例如:要得到所有user記錄的Uri為content:// LiB.cprovider.myprovider.Users /User,那么返回的MIME類型字符串應該為:"vnd.android.cursor.dir/user"。
- 如果要操作的數據屬於非集合類型數據,那么MIME類型字符串應該以vnd.android.cursor.item/開頭,例如:得到id為21的user記錄,Uri為content:// LiB.cprovider.myprovider.Users /User/21,那么返回的MIME類型字符串為:"vnd.android.cursor.item/user"。
5、 如何公開我自己的數據?
看了那么多,如何才能夠把我們自己的數據公開出來給別的程序使用呢?
首先,繼承ContentProvider並重寫它的幾個抽象方法

1 package LiB.cprovider;
2
3 import java.util.HashMap;
4
5 import LiB.cprovider.Users.User;
6 import android.content.ContentProvider;
7 import android.content.ContentUris;
8 import android.content.ContentValues;
9 import android.content.UriMatcher;
10 import android.database.Cursor;
11 import android.database.sqlite.SQLiteDatabase;
12 import android.database.sqlite.SQLiteQueryBuilder;
13 import android.net.Uri;
14 import android.text.TextUtils;
15
16 public class myprovider extends ContentProvider {
17
18 private DBHelper dbHelper;
19 private static final UriMatcher uriMatcher;
20 private static final int USER = 1;
21 private static final int USER_ID = 2;
22 private static HashMap<String, String> maps;
23 static {
24 // 當沒有匹配成功是時,返回NO_MATCH的值
25 uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
26 // 匹配Users表中的所有User,匹配成功后返回USER整數值
27 // content://LiB.cprovider.myprovider.Users/User
28 uriMatcher.addURI(Users.AUTHORITY, "User", USER);
29 // 匹配Users表中指定ID的User項,匹配成功后返回USER_ID整數值
30 // content://LiB.cprovider.myprovider.Users/User/21
31 uriMatcher.addURI(Users.AUTHORITY, "User/#", USER_ID);
32
33 maps = new HashMap<String, String>();
34 maps.put(User._ID, User._ID);
35 maps.put(User.NAME, User.NAME);
36 maps.put(User.SEX, User.SEX);
37 maps.put(User.AGE, User.AGE);
38 }
39
40 @Override
41 public int delete(Uri uri, String selection, String[] selectionArgs) {
42 SQLiteDatabase db = dbHelper.getWritableDatabase();
43 int count = 0;
44 switch (uriMatcher.match(uri)) {
45 case USER:
46 count = db.delete(DBHelper.DATATABLE_NAME, selection, selectionArgs);
47 break;
48 case USER_ID:
49 String noteId = uri.getPathSegments().get(1);
50 count = db.delete(DBHelper.DATATABLE_NAME, User._ID
51 + "="
52 + noteId
53 + (!TextUtils.isEmpty(selection) ? " AND (" + selection
54 + ')' : ""), selectionArgs);
55 break;
56 default:
57 throw new IllegalArgumentException();
58 }
59 this.getContext().getContentResolver().notifyChange(uri, null);
60 return count;
61 }
62
63 @Override
64 public String getType(Uri uri) {
65 return null;
66 }
67
68 @Override
69 public Uri insert(Uri uri, ContentValues values) {
70 SQLiteDatabase db = dbHelper.getWritableDatabase();
71 // User.NAME不能為空
72 long _id = db.insert(DBHelper.DATATABLE_NAME, User.NAME, values);
73 if (_id > 0) {
74 Uri uri1 = ContentUris.withAppendedId(User.CONTENT_URI, _id);
75 this.getContext().getContentResolver().notifyChange(uri1, null);
76 return uri1;
77 }
78 return null;
79 }
80
81 @Override
82 public boolean onCreate() {
83 dbHelper = new DBHelper(this.getContext());
84 return true;
85 }
86
87 @Override
88 public Cursor query(Uri uri, String[] projection, String selection,
89 String[] selectionArgs, String sortOrder) {
90 SQLiteQueryBuilder sqb = new SQLiteQueryBuilder();
91 switch (uriMatcher.match(uri)) {
92 case USER:
93 sqb.setTables(DBHelper.DATATABLE_NAME);
94 sqb.setProjectionMap(maps);
95 break;
96 case USER_ID:
97 sqb.setTables(DBHelper.DATATABLE_NAME);
98 sqb.setProjectionMap(maps);
99 sqb.appendWhere(User._ID + "=" + uri.getPathSegments().get(1));
100 break;
101 default:
102 throw new IllegalArgumentException();
103 }
104 SQLiteDatabase db = dbHelper.getReadableDatabase();
105 Cursor cursor = sqb.query(db, projection, selection, selectionArgs,
106 null, null, null);
107 cursor.setNotificationUri(getContext().getContentResolver(), uri);
108 return cursor;
109 }
110
111 @Override
112 public int update(Uri uri, ContentValues values, String selection,
113 String[] selectionArgs) {
114 SQLiteDatabase db = dbHelper.getWritableDatabase();
115 int count;
116 switch (uriMatcher.match(uri)) {
117 case USER:
118 count = db.update(DBHelper.DATATABLE_NAME, values, selection,
119 selectionArgs);
120 break;
121 case USER_ID:
122 String noteId = uri.getPathSegments().get(1);
123 count = db.update(DBHelper.DATATABLE_NAME, values, User._ID
124 + "="
125 + noteId
126 + (!TextUtils.isEmpty(selection) ? " AND (" + selection+ ')' : ""), selectionArgs);
127 break;
128 default:
129 throw new IllegalArgumentException();
130 }
131 getContext().getContentResolver().notifyChange(uri, null);
132 return count;
133 }
134 }
然后,定義我們自己的數據存儲(記住,在我們公開的數據中,一定要有個_ID字段,因為在Android中,數據的存儲方式是以一個表格的形式存儲的,_ID字段唯一標示了每項數據,所以常規做法是將我們的類繼承自BaseColumns類,關於這個類請查看官方幫助文檔)

1 package LiB.cprovider;
2
3 import android.net.Uri;
4 import android.provider.BaseColumns;
5 //在BaseColumns中就已經包含了我們所必須的_ID字段,具體的可以查看幫助文檔
6 //下面的類組成了一個含有User項的Users表,每一項就是一個User對象,每個對象含有name,sex,age等屬性
7 public final class Users
8 {
9 //請務必記住,這個AUTHORITY一定要和你在配置文件中配置的一樣
10 public static final String AUTHORITY="LiB.cprovider.myprovider.Users";
11 private Users(){}
12 //包含的項
13 public static final class User implements BaseColumns{
14 private User(){}
15 //標識URI
16 public static final Uri CONTENT_URI=Uri.parse("content://"+AUTHORITY+"/User");
17 //類型
18 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.LiB.Users";
19 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.LiB.Users";
20 // 表字段常量
21 public static final String NAME = "name"; // 姓名
22 public static final String SEX= "sex"; // 性別
23 public static final String AGE = "age"; //年齡
24 }
25 }
然后在AndroidManifest.xml中對我們的Provider進行注冊

1 <?xml version="1.0" encoding="utf-8"?>
2 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 package="LiB.cprovider" android:versionCode="1" android:versionName="1.0">
4 <uses-sdk android:minSdkVersion="9" />
5
6 <application android:icon="@drawable/icon" android:label="@string/app_name">
7 <activity android:name=".Android_ContentProviderDemoActivity"
8 android:label="@string/app_name">
9 <intent-filter>
10 <action android:name="android.intent.action.MAIN" />
11 <category android:name="android.intent.category.LAUNCHER" />
12 </intent-filter>
13 </activity>
14 <provider
15 android:name=".myprovider"
16 android:authorities="LiB.cprovider.myprovider.Users">
17 </provider>
18 </application>
19 </manifest>
在AndroidManifest.xml文件中,配置provider節時一定要注意android:name為你的繼承自ContentProvider的那個類的類名,android:authorities與你在定義自己的數據存儲時所定義的AUTHORITIES字段一定要相等,雖然這個字段值可以隨意,但是非常強烈的建議你采用包名加類名的方式類命名。
6、 怎么操作我自己的數據呢?
為了方便我們操作ContentProvider,Android系統為我們提供了ContentResolver類。我們可以使用getContentResolver()方法返回一個ContentResolver對象,並使用它與ContentProvider對應的方法來進行操作。

1 // 刪除方法
2 private void delete(int id) {
3 Uri uri = ContentUris.withAppendedId(User.CONTENT_URI, id);
4 // 使用getContentResolver()獲取ContentResolver對象,並使用它的delete()方法進行數據刪除操作
5 getContentResolver().delete(uri, null, null);
6 System.out.println("del");
7 }
8
9 // 更新
10 private void update(int id) {
11 Uri uri = ContentUris.withAppendedId(User.CONTENT_URI, id);
12 ContentValues values = new ContentValues();
13 values.put(User.NAME, "LiB");
14 values.put(User.SEX, "male");
15 values.put(User.AGE, 20+id);
16 // 使用getContentResolver()獲取ContentResolver對象,並使用它的update()方法進行數據更新操作
17 getContentResolver().update(uri, values, null, null);
18 System.out.println("update...");
19 }
20
21 // 查詢
22 private void query() {
23 // 需要查詢的列名
24 String[] PROJECTION = new String[] {
25 User._ID,
26 User.NAME,
27 User.SEX,
28 User.AGE
29 };
30 // 此處也可以使用Cursor c = managedQuery(User.CONTENT_URI, PROJECTION, null,
31 // null, null);方法來進行查詢操作
32 Cursor c = getContentResolver().query(User.CONTENT_URI, PROJECTION,
33 null, null, null);
34 if (c.moveToFirst()) {
35 for (int i = 0; i < c.getCount(); i++) {
36 c.moveToPosition(i);
37 String name = c.getString(1);
38 String sex = c.getString(2);
39 int age = c.getInt(3);
40 // 輸出日志
41 Log.i("query", name + ":" + sex + ":" + age);
42 }
43 }
44 System.out.println("query...");
45 }
46
47 // 插入
48 private void insert(int id) {
49 Uri uri = User.CONTENT_URI;
50 ContentValues values = new ContentValues();
51 values.put(User.NAME, "ZMR_"+id);
52 values.put(User.SEX, "female");
53 values.put(User.AGE, 20+id);
54 // 使用getContentResolver()獲取ContentResolver對象,並使用它的insert()方法進行數據插入操作
55 getContentResolver().insert(uri, values);
56 System.out.println("insert");
57 }
三、總結
其實在Android中,系統已經為我們提供了許多的ContentProvider了,我們通常情況下是不需要去自定義ContentProvider的。但是,為了更好的理解ContentProvider,自己來實現一個ContentProvider是非常必要的。同時,在Android中,數據的存儲方式有很多種,在上面的例子中提到了有SQLite和XML方式,下一篇筆記將介紹如何操作XML文件。