近日來學習ContentProvider相關的知識,做了一個demo,想和網友分享下。
首先說一點相關的知識:
一:作用
ContentProvider是不同應用程式共享資料的接口,跟共享資料的别的方法相比,ContentProvider更好地提供了資料共享接口的統一性。CP(ContentProvider的簡稱)通過一張或者多張表的形式向外部應用程式提供資料(就像關系型資料庫中看見的表那樣)。
二:Content URIS
URI(Uniform Resource Identifier)統一資源标示符,能表示provider中的資料。由三部分組成,分别是scheme,authority,path.它的scheme,Android系統規定為"content://",authority唯一表示了要通路的CP名字,而path表示了要通路的CP中的資料。在使用ContentResolver對象通路CP時,需要一個Uri參數。構造Uri時可能會用到三個類:
Uri.Builder類可用來通過String建構Uri
ContentUris.withAppendedId()可在Uri後面加一個id
UriMatcher:在CP中這個類可幫你從接受到Uri中選擇出要執行的ACTION
三:MIME type
MIME,多用途網際網路郵件擴充,是一種網際網路标準。它的作用是:伺服器會通過MIME值來告訴用戶端所傳送的多媒體資料的類型。CP也是一種C/S架構,是以需要使用到這個值。它的格式為:type/subtype,比如text/html,代表所傳輸的文本為html的格式。在Android中MIME可提供兩類值:統一的MIME資料類型和定制的MIME類型字元串。在CP中前者在傳輸檔案時會用到,而後者更常用,發送結構化的資料,譬如SQLiteDatabase時會使用定制的MIME類型字元串。在android中後者有自己的規定:
如果傳回單行資料,type被設定為"vnd.android.cursor.item"
如果傳回多行資料,type被設定為"vnd.android.cursor.dir"
而subtype部分由CP類自己設定。
接下來進入正題,說代碼環節,這篇代碼是通過結構化資料來闡述CP的。
一:用戶端
在用戶端,是通過一個ContentResolver對象作為client和CP互動的。ContentResolver對象可調用CP中的同名方法,可提供“增删改查”的功能。
Step1:在用戶端的manifest檔案申請要通路的CP的權限(該權限在CP端已向系統注冊,下文會講)
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLrN2bsJEZlR3YhJHdu92Qvw1cy9GdhNWak5WSn5WaulGb0V3TvwVbvNmLzd2bsJmbj5ycldWYtl2Lc9CX6MHc0RHaiojIsJye.gif)
1 <uses-permission android:name="com.example.cpserver.permission" />
View Code
Step1.在“增删改查”之前需要判斷将要請求的Uri的有效性,代碼如下:
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLrN2bsJEZlR3YhJHdu92Qvw1cy9GdhNWak5WSn5WaulGb0V3TvwVbvNmLzd2bsJmbj5ycldWYtl2Lc9CX6MHc0RHaiojIsJye.gif)
1 //判斷所要使用的Provider是否有效
2 private boolean checkValidProvider(Uri uri)
3 {
4 ContentProviderClient client = getContentResolver().acquireContentProviderClient(uri);
5 if(client == null)
6 {
7 System.out.println("provider is invalid!");
8 return false;
9 }
10 else
11 {
12 client.release();
13 return true;
14 }
15 }
Step3:"增删改查"功能,這些方法都是SQL中DDL語言的一個封裝,具體每個方法的傳回值,參數意義不展開講了,否則很長,可留言詢問。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLrN2bsJEZlR3YhJHdu92Qvw1cy9GdhNWak5WSn5WaulGb0V3TvwVbvNmLzd2bsJmbj5ycldWYtl2Lc9CX6MHc0RHaiojIsJye.gif)
1 private int insert()
2 {
3 if(!checkValidProvider(Contract.CONTENT_URI))
4 return -1;
5 ContentValues values = new ContentValues();
6 values.put(Contract.COLUMN_NAME_1, "小衛的春天");
7 values.put(Contract.COLUMN_NAME_2, "翟衛華");
8 values.put(Contract.COLUMN_NAME_3, "100");
9 Uri uri = getContentResolver().insert(Contract.CONTENT_URI, values);
10 String lastPath = uri.getLastPathSegment();
11 if(TextUtils.isEmpty(lastPath))
12 {
13 System.out.println("insert failure!");
14 }
15 else
16 {
17 System.out.println("insert success! the id is " + lastPath);
18 }
19 return Integer.parseInt(lastPath);
20 }
21
22 //删除所有行
23 private int delete()
24 {
25 if(!checkValidProvider(Contract.CONTENT_URI))
26 return -1;
27 int count = getContentResolver().delete(Contract.CONTENT_URI, null, null);
28 return count;
29 }
30
31 //将所有行的資料進行一個修改
32 private int update()
33 {
34 if(!checkValidProvider(Contract.CONTENT_URI))
35 return -1;
36 ContentValues values = new ContentValues();
37 values.put(Contract.COLUMN_NAME_1,"小寶的春天");
38 values.put(Contract.COLUMN_NAME_2, "翟小寶");
39 values.put(Contract.COLUMN_NAME_3, "200");
40 int count = getContentResolver().update(Contract.CONTENT_URI, values, null, null);
41 if(count == 0)
42 {
43 System.out.println("update failed!");
44 }
45 return count;
46 }
47
48 //row:要查找的行号, "-1"代表查找多行
49 private void query(int row)
50 {
51 if(!checkValidProvider(Contract.CONTENT_URI))
52 return;
53 Cursor cursor = null;
54 if(row != -1)
55 {
56
57 }
58 else
59 {
60 cursor = getContentResolver().query(Contract.CONTENT_URI,
61 new String[]{Contract.COLUMN_NAME_1,Contract.COLUMN_NAME_2,Contract.COLUMN_NAME_3},null, null, null);
62 }
63 if(cursor == null)
64 System.out.println("query failure!");
65 else
66 {
67 String strDisplay = getDataFromCursor(cursor);
68 tvDisplay.setText(strDisplay);
69 cursor.close();
70 }
71 }
二:CP端
在CP端要通過繼承ContentProvider來實作。CP是Android的四大元件之一,比較重要,而且系統本身就能提供很多的ContentProvider供開發者使用,比如可通過CP請求道所有的圖檔,音頻,視訊等資料。在Android系統中CP預設是可被别的任何應用程式請求到的,如果你不設定權限加以限制的話。是以第一步應該設定一個permission字段,并把它添加到provider标簽裡面去。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLrN2bsJEZlR3YhJHdu92Qvw1cy9GdhNWak5WSn5WaulGb0V3TvwVbvNmLzd2bsJmbj5ycldWYtl2Lc9CX6MHc0RHaiojIsJye.gif)
1 <permission
2 android:name="com.example.cpserver.permission"
3 android:label="Example Data"
4 android:protectionLevel="signature" />
5
6 <provider
7 android:name="provider.TestProvider"
8 android:authorities="com.example.cpserver.provider"
9 android:permission="com.example.cpserver.permission"
10 android:exported="true"
11 android:enabled="true" />
Step1:實作一個SQLiteOpenHelper作為Provider的資料存儲庫
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLrN2bsJEZlR3YhJHdu92Qvw1cy9GdhNWak5WSn5WaulGb0V3TvwVbvNmLzd2bsJmbj5ycldWYtl2Lc9CX6MHc0RHaiojIsJye.gif)
1 package db;
2
3 import com.example.cpserver.Contract;
4
5 import android.content.Context;
6 import android.database.sqlite.SQLiteDatabase;
7 import android.database.sqlite.SQLiteOpenHelper;
8 /*
9 * 實作一個SQLiteOpenHelper作為Provider的資料存儲庫
10 */
11 public final class MainDatabaseHelper extends SQLiteOpenHelper {
12
13 //建立一張表的SQL語句
14 private static final String SQL_CREATE_MAIN = "CREATE TABLE " +
15 Contract.TABLE_NAME + "(" +
16 " _ID INTEGER PRIMARY KEY, " +
17 Contract.COLUMN_NAME_1 + " TEXT," +
18 Contract.COLUMN_NAME_2 + " TEXT," +
19 Contract.COLUMN_NAME_3 + " INTEGER )";
20
21 public MainDatabaseHelper(Context context)
22 {
23 super(context, Contract.DB_NAME, null, 1);
24 }
25
26 /*
27 *當Provider設法打開資料存儲庫,并且資料庫不存在時該方法被調用
28 */
29 @Override
30 public void onCreate(SQLiteDatabase db) {
31 // Creates the main table
32 db.execSQL(SQL_CREATE_MAIN);
33 }
34
35 @Override
36 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
37 // TODO Auto-generated method stub
38
39 }
40 }
Step2:實作ContentProvider的子類,并且擴充抽象方法,代碼注釋很詳細。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiZpdmLrN2bsJEZlR3YhJHdu92Qvw1cy9GdhNWak5WSn5WaulGb0V3TvwVbvNmLzd2bsJmbj5ycldWYtl2Lc9CX6MHc0RHaiojIsJye.gif)
1 package provider;
2
3 import com.example.cpserver.Contract;
4 import db.MainDatabaseHelper;
5 import android.content.ContentProvider;
6 import android.content.ContentUris;
7 import android.content.ContentValues;
8 import android.content.UriMatcher;
9 import android.database.Cursor;
10 import android.database.SQLException;
11 import android.database.sqlite.SQLiteDatabase;
12 import android.net.Uri;
13 import android.text.TextUtils;
14
15 /*
16 * 1.除了onCreate方法,别的方法都可能會被多線程調用,是以這些方法要設計成線程安全的
17 * 2.在onCreate中避免耗時操作
18 * 3.雖然以下方法都要被繼承,但不必重寫每個方法,除了getType
19 */
20 public class TestProvider extends ContentProvider
21 {
22 //建立一個UriMatcher對象,該對象幫你從接受的URI中選擇出要執行的動作
23 private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
24 private MainDatabaseHelper mOpenHelper;
25 private SQLiteDatabase db = null;
26
27 //調用addURI方法添加provider可以識别的所有URI類型
28 static
29 {
30 sUriMatcher.addURI(Contract.authority, Contract.TABLE_NAME, 1);
31 sUriMatcher.addURI(Contract.authority, Contract.TABLE_NAME+"/#", 2);
32 }
33 /*
34 * 該方法作用:初始化該Provider
35 * 注意:直到一個ContentResolver對象通路時,該方法才被調用
36 * 這個方法裡不應做耗時的操作,因為這樣可能延緩Provider對别的應用程式的相應
37 */
38 @Override
39 public boolean onCreate() {
40 mOpenHelper = new MainDatabaseHelper(getContext());
41 return true;
42 }
43
44 @Override
45 public Cursor query(Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder)
46 {
47 int type = sUriMatcher.match(uri);
48 switch (type)
49 {
50 //多行請求的URI
51 case 1:
52 if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC";
53 break;
54 case 2:
55 //單行請求的URI
56 selection = selection + "_ID = " + uri.getLastPathSegment();
57 break;
58 default:
59 //如果URI不比對,做一些錯誤提示,傳回null或者抛出異常
60 throw new IllegalArgumentException("Unknown URI " + uri);
61 }
62 if(db == null)
63 db = mOpenHelper.getWritableDatabase();
64 Cursor cursor = db.query(Contract.TABLE_NAME, projection, selection, selectionArgs, null, null,sortOrder);
65 return cursor;
66 }
67
68 //傳回content URI相應的MIME 類型
69 @Override
70 public String getType(Uri uri)
71 {
72 int type = sUriMatcher.match(uri);
73 switch (type)
74 {
75 case 1:
76 return Contract.CONTENT_TYPE;
77 case 2:
78 return Contract.CONTENT_ITEM_TYPE;
79 default:
80 throw new IllegalArgumentException("Unknown URI " + uri);
81 }
82 }
83
84 @Override
85 public Uri insert(Uri uri, ContentValues initialValues)
86 {
87 int type = sUriMatcher.match(uri);
88 if(type != 1)
89 throw new IllegalArgumentException("Unknown URI " + uri);
90
91 //建立一個可寫資料庫,将調用MainDatabaseHelper的onCreate方法,如果資料庫還不存在的話
92 if(db == null)
93 db = mOpenHelper.getWritableDatabase();
94
95 //確定所有的域都被設定
96 ContentValues values;
97 if (initialValues != null)
98 values = new ContentValues(initialValues);
99 else
100 values = new ContentValues();
101 if (values.containsKey(Contract.COLUMN_NAME_1) == false) {
102 values.put(Contract.COLUMN_NAME_1, "");
103 }
104 if (values.containsKey(Contract.COLUMN_NAME_2) == false) {
105 values.put(Contract.COLUMN_NAME_2, "");
106 }
107 if (values.containsKey(Contract.COLUMN_NAME_3) == false) {
108 values.put(Contract.COLUMN_NAME_3, "");
109 }
110
111 long rowId = db.insert(Contract.TABLE_NAME,null, values);
112 if(rowId > 0)
113 {
114 Uri noteUri = ContentUris.withAppendedId(Contract.CONTENT_URI, rowId);
115 getContext().getContentResolver().notifyChange(noteUri, null);
116 return noteUri;
117 }
118 throw new SQLException("Failed to insert row into " + uri);
119 }
120
121 @Override
122 public int delete(Uri uri, String selection, String[] selectionArgs)
123 {
124 if(db == null)
125 db = mOpenHelper.getWritableDatabase();
126 int type = sUriMatcher.match(uri);
127 int count;
128 switch (type)
129 {
130 case 1:
131 count = db.delete(Contract.TABLE_NAME, selection, selectionArgs);
132 break;
133 case 2:
134 String noteId = uri.getLastPathSegment();
135 count = db.delete(Contract.TABLE_NAME, "_ID" + "=" + noteId +
136 (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs);
137 break;
138 default:
139 throw new IllegalArgumentException("Unknown URI " + uri);
140 }
141 getContext().getContentResolver().notifyChange(uri, null);
142 return count;
143 }
144
145 @Override
146 public int update(Uri uri, ContentValues values, String selection,String[] selectionArgs)
147 {
148 if(db == null)
149 db = mOpenHelper.getWritableDatabase();
150 int type = sUriMatcher.match(uri);
151 int count;
152 switch (type)
153 {
154 case 1:
155 count = db.update(Contract.TABLE_NAME, values, selection, selectionArgs);
156 break;
157 case 2:
158 String noteId = uri.getLastPathSegment();
159 count = db.update(Contract.TABLE_NAME, values,"_ID" + "=" + noteId +
160 (!TextUtils.isEmpty(selection) ? " AND (" + selection + ')' : ""), selectionArgs);
161 break;
162 default:
163 throw new IllegalArgumentException("Unknown URI " + uri);
164 }
165 getContext().getContentResolver().notifyChange(uri, null);
166 return count;
167 }
168
169 //如果Provider提供file資料,要用這個方法傳回MIME類型
170 @Override
171 public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
172 // TODO Auto-generated method stub
173 return super.getStreamTypes(uri, mimeTypeFilter);
174 }
175
176 }
我認為CP之是以不那麼容易了解,是因為它涉及到的東西較多,還涉及到計算機網絡中的的URI,MIME等概念,确實不是那麼容易,作為碼農的我們隻能去啃了,無别的辦法。總結一下,CP機制中主要涉及到這麼幾點知識:permission權限,uri,MIME,使用UriMatcher來比對uri的類型,還要掌握一些基本的SQL語言等。關于使用CP來實作File分享,又是很長的一個篇幅,等得空再研究吧。我的項目用百度雲做個下載下傳連結吧,有需求的朋友可拿去用。歡迎留言交流。
http://pan.baidu.com/s/1gdkPia3
Author:Andy Zhai
2014-02-11 20:44:00