Android四大組件之Content Provider
**一、概念**
Content Provider 作為Android應用程序四大組件之一,為存儲和查詢數據提供統一的接口,實現程序間數據的共享。Android系統內一些常見的數據如音樂、視頻、圖像等都內置了一系列的Content Provider。
? ??
應用程序間共享數據有兩種方式:
一是創建子類繼承于Content Provider,重寫該類用于數據存儲和查詢的方法。
二是直接使用已經存在的Content Provider,如聯系人等。
在Content Provider中數據的是以表的形式存儲,在數據表中每一行為一條記錄,每一列為類型和數據。每一條記錄都包括一個唯一的名叫_ID的數值字段,這個字段唯一標識一條數據記錄,在創建表的時候用 INTEGER?PRIMARY KEY AUTOINCREMENT來標識此字段。
??????
**二、相關類介紹**
**類URI:**
每一個Content Provider為其管理的多個數據集,分配一個URI,這個URI對外提供了一個能夠唯一標識自己數據集的字符串。這樣別的應用程序就可以通過這個URI來訪問這個數據集。Android中所有的Content Provider的URI都有固定格式:content://**開頭,一般可分為4個部分:
**標準前綴:**用來標識一個Content Provider,固定Content://
**URI標識:**定義了是哪個Content Provider提供這些數據。一般是定義該ContentProvider的包類的名字。和AndroidManifest.xml中定義的authorities屬性相同。
**路徑:**標識URI下的某一個Item。
**記錄的ID:**如果URI中包含表示某個記錄的ID,則返貨該id對應的數據。否則表示返回全部。
注意:"content://com.android.people.provider/contacts/#" 這里#表示匹配任意數字"content://com.android.people.provider/contacts/*"表示匹配任意文本
**UriMatcher:**Uri標識了要操作的數據,而UriMatcher即使Android提供給我們用于操作Uri這個數據的工具類。
常用方法:
??????? public void addURI(String authority, String path, int code) 往UriMatcher對象里面添加Uri。
??????? public int match(Uri uri)?與UriMatcher對象的Uri進行匹配,如果成功返回上面傳入的code值,否則返回-1.
**類ContentUris:**類似于UriMatcher,也是一個操作Uri數據的工具類,用于在Uri后面追加一個ID或解析出傳入的Uri對象的ID值。
常用方法:
??????? public static Uri withAppendId(Uri contentUri, long id)? 為前面contentUri加上ID部分
??????? public static long parseId(Uri contentUri)?從contentUri中獲取ID部分
**類ContentProvider:**常用方法:
~~~
public abstract boolean onCreate();
public abstract Uri insert(Uri uri, ContentValues values)
public abstract int delete(Uri uri, String selection, String[] selectionArsg);
public abstract int update(Uri uri, ContentValues values, String selection, String[] selectionArgs);
public abstract Cursor query(Uri uri, String[] peojection, String selection, String[] selectionArgs, String sortOrder)
public abstract String getType(Uri uri)
~~~
這些都是抽象方法需要子類去實現。**類 ContentValues:**
?????? android.content.ContentValues 這個用于存儲ContentResolver能處理的數據的集合。
構造函數:
???????ContentValues()???????? // 創建一個空的ContentValues對象,初始化默認大小
?????? ContentValues(int size)
?????? ContentValues(ContentValues from)
常用方法:????????
~~~
void clear()
boolean containKey(String key)
Object get(String key)
void put(String key, Type value) // Type: Byte Integer Float Short byte[] String Double Long Boolean
int size()
Type getAsTypeArray(String key) // Type: Object Boolean Byte byte[] Double Float Integer Long Short String
~~~
**類android.content.ContentResolver:**
一個程序可以通過實現一個ContentProvider的抽象接口將自己的數據以類似數據庫中表的方式完全暴露出去,那么外界其他應用程序怎么從數據庫中獲取數據呢,這就需要ContentResolver了,通過URI表示外界訪問需要的數據庫。???????????這個類為我們定義了一系列的方法包括:插入、刪除、修改、查詢等,與ContentProvider基本類似,主要根據傳入的參數Uri找到對應的Content Provider,調用其相應的方法。
構造函數:
??????? public ContentResolver(Context context)
一般在代碼中我們直接通過: MainActivity.this.getContentResolver()獲得當前應用程序的一個ContentResolver實例。??????
這里我們需要考慮一個問題,就是如果多個程序同時通過ContentResolver共享訪問一個ContentProvider,會不會不同步,尤其是數據寫入的時候這就需要在AndroidManifest.xml中定義ContentProvider的時候加上:<provider>元素的multiprocess屬性。同時Android在ContentResolver中為我們提供了
notifyChange()接口,在數據發生改變時通知其他的ContentObserver。
~~~
final void registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
final void unregisterContentObserver(ContentObserver observer)
void notifyChange(Uri uri, ContentObserver observer, boolean syncToNetwork)
void notifyChange(Uri uri, ContentObserver observer)
~~~
**三、使用已經存在的ContentProvider**
使用已經存在的ContentProvider包括兩種情況:一是從已經存在的ContentProvider里面讀取數據,如獲得
手機里面聯系人的信息。二是將自己的數據加入到ContentProvider里面,讓其他程序能共享次數據,這就需要獲得對這個
ContentProvider的寫權限了。
Android中電話簿就是通過ContentProvider實現數據共享的,系統中有很多已經存在的共享URI,我們可以使用ContentResolver通過Uri來操作不同的標的數據。如Contacts.People.CONTENT_URI
在Android中為我們提供了兩種方法來查詢Content Provider:一是使用ContentResolver的query()方法,二是使用Activity對象的manageQuery()方法,他們的參數都相同,而且都返回Cursor對象。但是使用manageQuery()方法返回的Cursor對象的生命周期自動被Activity來管理,被管理的Cursor對象在Activity進入暫停狀態的時候調用自己的deactivate()方法卸載,在Activity回到運行狀態的時候調用自己的requery()方法重新查詢生成的Cursor。而使用ContentResolver的query()方法返回的Cursor對象需要手動加入Activity來管理,這是通過Activity的startManagingCursor()方法來實現的。
**四、創建自己的ContentProvider**
(1) 創建一個繼承于ContentProvider的類MyContentProvider
(2) 定義一個public static final Uri 類型變量CONTENT_URI
如:public static final Uri CONTENT_URI = Uri.parse("content://com.android.MyContentProvider")
(3) 定義需要返回給客戶端的數據列名,如果使用到數據庫SQLite,必須定義一個_id,表示記錄的唯一性。
(4) 創建數據存儲系統,如文件系統或數據庫SQLite系統。
(5) 如果存儲字節型數據,如位圖文件等,數據列其實是一個表示實際保存文件的URI字符串,用來讀取對應的實際文件數據。?處理這種數據類型的Content Provider需要實現一個名為_data的字段,該字段列出了該文件在Android系統的實際路?徑,客戶端可以通過調用方法ContentResolver.
openOutputStream()來處理該URI指向的文件資源。
(6) 查詢返回一個Cursor類型對象,所有執行寫操作的方法如insert()、update()及delete()都將被監聽,可以通過Content
Resolver().notifyChange()來通過監聽器關于數據更新的消息。
(7) 在AndroidManifest.xml中使用<provider>標簽來設置Content Provider信息,如:android:authorities、android:name
android:permission等。
**Demo:**
**數據庫類DatabaseHelper用來存儲個人信息,名字(name)對應年齡(age)**
~~~
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "personInfo.db";
private static final String TB_NAME = "person";
private static final int VERSION = 1;
private static final String creat_cmd = "create table IF NOT EXISTS " + TB_NAME + " (_id integer PRIMARY KEY autoincrement, name text, age integer)";
private static final String upgrade_cmd = "alert table " + TB_NAME + " add sex varchar(8)";
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, VERSION);
// TODO Auto-generated constructor stub
}
@Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL(creat_cmd);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
db.execSQL(upgrade_cmd);
}
}
~~~
**創建自己的ContentProvider**
~~~
// 創建一個MyProvider繼承于ContentProvider
public class MyProvider extends ContentProvider {
private DatabaseHelper dbHelper; // 數據庫類
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private static final int ALL_PERSON = 1;
private static final int PERSON = 2;
private static final String TAG = "MyProvider";
private static final String TABLE_NAME = "person";
// 定義自己的URI
private static final String AUTHORITY = "com.myAndroid.myProvider";
public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
static {
URI_MATCHER.addURI(AUTHORITY, "person", ALL_PERSON);
URI_MATCHER.addURI(AUTHORITY, "person/#", PERSON);
}
// ContentProvider的入口,初始化
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
Log.i(TAG, "----onCreate----");
dbHelper = new DatabaseHelper(this.getContext());
return false;
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
// TODO Auto-generated method stub
Log.i(TAG, "----delete----");
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON:
count = db.delete(TABLE_NAME, arg1, arg2);
case PERSON:
long id = ContentUris.parseId(arg0);
String where = "_id=" + id;
if(arg1 != null && !"".equals(arg1)) {
where += " and " + arg1;
}
count = db.delete(TABLE_NAME, where, arg2);
default:
throw new IllegalArgumentException("Unknown Uri:" + arg0.toString());
}
getContext().getContentResolver().notifyChange(arg0, null); // 通知注冊客戶端數據發生改變
return count;
}
@Override
public String getType(Uri arg0) {
// TODO Auto-generated method stub
Log.i(TAG, "----getType----");
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON:
return "com.android.cursor.dir/person";
case PERSON:
return "com.android.cursor.item/person";
default:
throw new IllegalArgumentException("Unknow Uri" + arg0.toString());
}
}
@Override
public Uri insert(Uri uri, ContentValues arg1) {
// TODO Auto-generated method stub
Log.i(TAG, "----insert----");
SQLiteDatabase db = dbHelper.getWritableDatabase();
Uri insertUri = null;
switch (URI_MATCHER.match(uri)) {
case ALL_PERSON:
long rowId = db.insert(TABLE_NAME, "name", arg1);
Log.d(TAG, "insert:"+arg1.toString()+" Id:"+rowId);
insertUri = ContentUris.withAppendedId(uri, rowId);
// this.getContext().getContentResolver().notifyChange(insertUri, null);
break;
default:
throw new IllegalArgumentException("Unknown Uri:" + uri.toString());
}
getContext().getContentResolver().notifyChange(insertUri, null);
return insertUri;
}
// 處理查詢,返回Cursor
@Override
public Cursor query(Uri arg0, String[] arg1, String arg2, String[] arg3,
String arg4) {
// TODO Auto-generated method stub
Log.i(TAG, "----query----");
SQLiteDatabase db = dbHelper.getReadableDatabase();
Cursir cursor = null;
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON: // query all the person info
cursor = db.query(TABLE_NAME, arg1, arg2, arg3, null, null, arg4);
case PERSON: // query only one person info from a given ID
long id = ContentUris.parseId(arg0);
String where = " _id=" + id;
if( (!"".equals(arg2)) && (arg2 != null)) {
where += " and " + arg2 ;
}
cursor = db.query(TABLE_NAME, arg1, where, arg3, null, null, arg4);
default:
throw new IllegalArgumentException("unknow uri" + arg0.toString());
}
if(cursor != null) {
// 注冊該Uri對應的數據發生改變時,向客戶端發送通知
cursor.setNotificationUri(getContext().getContentResolver(), uri);
}
return cursor;
}
@Override
public int update(Uri arg0, ContentValues arg1, String arg2, String[] arg3) {
// TODO Auto-generated method stub
Log.i(TAG, "----update----");
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;
switch (URI_MATCHER.match(arg0)) {
case ALL_PERSON:
count = db.update(TABLE_NAME, arg1, arg2, arg3);
break;
case PERSON:
long id = ContentUris.parseId(arg0);
String where = "_id=" + id;
if( (arg2 != null) && (!"".equals(arg2))) {
where += " and " + arg2;
}
count = db.update(TABLE_NAME, arg1, where, arg3);
break;
default:
throw new IllegalArgumentException("Unknow Uri:"+arg0.toString());
}
getContext().getContentResolver().notifyChange(arg0, null);
return count;
}
}
~~~
**客戶端使用Content Provider:**
~~~
// 定義自己的URI
private static final String TABLE_NAME = "person";
private static final String AUTHORITY = "com.myAndroid.myProvider";
public static final Uri CONTENT_URI = Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME);
// 插入
ContentResolver contenResolver = MainActivity.this.getContentResolver();
ContentValues values = new ContentValues();
values.put("name", "hello");
values.put("age", 25);
Uri resultUri = contentResolver.insert(CONTENT_URI, values);
if(ContentUris.parseId(resultUri) > 0) { Log.i(TAG, "OK"); }
// 查詢
String columns[] = new String[] ("_id", "name", "age");
Cursor cursor = contentResolver.query(CONTENT_URI, columns, null, null, "_id");
if(cursor.moveToFirst()) {
do {
Log.i(TAG, "_id:"+cursor.getInt(cursor.getColumnIndex("_id")));
Log.i(TAG, "name:"+cursor.getString(cursor.getColumnIndex("name")));
Log.i(TAG, "age:"+cursor.getInt(cursor.getColumnIndex("age")));
} while(cursor.moveToNext());
cursor.close();
}
// 刪除 ID為1的記錄
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), null, null);
// 修改ID為 1 的記錄
ContentValues values = new ContentValues();
values.put("name", "world");
values.put("age", 32);
contentResolver.update(Uri.parse("content://"+AUTHORITY+"/"+TABLE_NAME+"/1"), values, null, null);
~~~
最后我們還需要在AndroidManifest.xml中定義我們的provider屬性:
~~~
<provider android:name=".MyProvider" android:authorities="com.myAndroid.myProvider" />
~~~
**使用游標:**
這里我們關注下在query()方法中,我們查詢數據庫的時候使用到了一個Cursor對象,這個Cursor對象就是游標,它包含0個或多個記錄。列名稱、順序和類型都是特定于ContentProvider的,但是返回的每行都包涵啊一個名為_id的默認咧,表示該行的唯一ID。
?1、游標是一個行集合 ? ??
?2、讀取數據之前,需要使用moveToFirst()將游標移動到第一行之前
?3、需要知道列名稱和列類型
?4、所有字段的訪問都是基于列編號,所有必須首先將該列名稱轉換為列編號
?5,、游標可以隨意移動(往前、往后,跳過一段距離等)
主要方法:
~~~
boolean moveToFirst()
boolean isBeforeFirst()
boolean isAfterLast()
boolean isClosed()
~~~
**使用Where查詢:**
上面代碼中使用的manageQuery()的簽名為:
public final Cursor manageQuery(Uri uri, String[] projectin, ?String selection, String[] selectionArgs, String sortOrder);
參數:
Uri: 給定的URI
projection: 聲明要返回的行屬性,傳遞null 返回給定URI的所有行
selection 表示過濾器,以SQL Where字句(不含Where本身)的格式聲明,可以使用?,將被替換為selectionArgs中的值,按照在列表中出現的順序顯示。
selectionArgs: 過濾器殘數
sortOrder: 排序方式
如:查詢id為23的筆記:
~~~
manageQuery("Content://com.google.provider.NotePad/notes/23",
null, null, null, null);
manageQuery("Content://com.google.provider.Notepad/notes",
null, "_id=?", new String[] {23}, null);
~~~
- 前言
- Android開發之serviceManager分析
- Android啟動之init.c文件main函數分析
- Android開發之ProcessState和IPCThreadState類分析
- Android開發之MediaPlayerService服務詳解(一)
- Android系統五大布局詳解Layout
- Android四大組件之Content Provider
- Android四大組件之Service
- Android四大組件之BroadcastReceiver
- Android系統中的消息處理Looper、Handler、Message
- Android EditText/TextView使用SpannableString顯示復合文本
- Android關鍵資源詳解
- Android常用適配器分析(如何制作簡易Launcher)
- Android常用列表控件