近日來學習ContentProvider相關的知識,作了一個demo,想和網友分享下。html
首先說一點相關的知識:android
一:做用sql
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 typeide
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端已向系統註冊,下文會講)
1 <uses-permission android:name="com.example.cpserver.permission" />
Step1.在「增刪改查」以前須要判斷將要請求的Uri的有效性,代碼以下:
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語言的一個封裝,具體每一個方法的返回值,參數意義不展開講了,不然很長,可留言詢問。
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標籤裏面去。
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的數據存儲庫
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的子類,而且擴展抽象方法,代碼註釋很詳細。
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