綜述
Content providers是一個Android應用程式的主要部分,主要是為應用程式提供內容。它對資料進行封裝然後通過單一的ContentResolver介面提供給應用程式。只有需要在多個應用程式間共用資料時,content provider才是必須的。例如:有多個應用程式(例如:打電話程式和發簡訊程式等等)都需要使用連絡人的資料,因此我們必須將這些資料存放在content provider。如果不需要在多個應用程式中共用資料,那麼可以直接在應用程式中使用SQLiteDatabase。
當程式通過一個ContentResolver介面請求Content provider時,系統就會檢查傳遞進來的URI的許可權,並且將請求傳遞給擁有這些許可權的content provider。content provider可以以任意的方式解析URI。UriMatcher類對於解析URIs是非常有協助的。
對於content provider主要要實現的方法有如下幾個:
onCreate():主要是初始化provider;
query(Uri, String[], String, String[], String):講資料返回給它的調用者
insert(Uri, COntentValues):插入新的資料到content provider
update(Uri, COntentValues, String, String[]):更新content provider中的資料
delete(Uri, String, String[])刪除content provider中的資料
getType(Uri):返回content provider中資料的MIME類型
資料存取方法(例如:insert(Uri, ContentValues)和update(Uri, ContentValues, String, String[]))可能會在同一時間被多個線程調用,並且是安全執行緒的。其他的方法(例如:onCreate())僅僅被程式的主線程調用,而且必須要避免在其中進行耗時的操作。具體的請看方法的描述。
對於ContentResolver將會自動的傳給合適的ContentProvider執行個體,因此子類不需要擔心誇進程調用的細節。
開發人員指南
對於如何使用content provider的細節,請閱讀Content Providers開發人員指南。
下面是Content Providers開發人員指南:
Content Providers存取資料,並且使這些資料對於所有的程式都是可用的。Android系統中並沒有對任何程式都可使用的共同儲存地區,因此,這是在多個應用程式間共用資料的唯一方法。
Android系統包含許多的對常見類型資料(音頻、視頻、圖片以及個人聯絡資訊等等)的content providers。你可以在android.provider包中看到一部分的content provider。你可以查詢這個content provider中包含的資料(儘管對於其中的一部分資料,你必須要擁有合適的許可權)。
如果你想讓自己的資料對外部公開,你有兩個選擇:
1.你可以建立你自己的content provider(一個Content Provider子類)
2.你可以將自己的資料添加到已經存在的content provide,如果存在包含著和你想公開資料的相同類型資料的content provider,並且你擁有許可權,你就可以將這些資料寫入到這個content provider中。
這篇文檔是介紹如何使用content provider。在簡潔的討論了一下基本後,這些討論覆蓋了如何查詢一個content provider中的內容,如何修改一個content provider中包含的資料,如何為自己建立一個content provider。
Content Provider 基礎知識
content provider如何儲存資料是依賴於它的設計的。但是所有的content provider都實現了一個共同的介面用於查詢provider並且返回結果--以及:添加,修改和刪除資料等。
用戶端不會直接使用這個介面,幾乎都是通過ContentResolver對象。你可以通過實現在Activity中或其它應用程式組件中的getContentResolver()方法來獲得ContentResolver.
ContentResolver cr = getContentResolver();
你可以通過ContentResolver的方法和感興趣的content providers進行互動。
當一個查詢操作初始化後,Android系統識別查詢要進行的操作對象,並且確保它處於運行狀態。系統執行個體化了所有的Contentprovider對象:你根本不需要進行這樣的操作。實際上,你從來就不會直接和ContentProvider打交道。實際上,對於每一種ContentProvider,系統僅僅有一個執行個體。但是這一個ContentProvider執行個體卻可以和處於多個程式、多個進程中的ContentResolver對象進行通訊。多個進程間的互動是通過ContentResolver類和ContentProvider類進行的。
資料模型
Content providers是像資料庫中的表的模型一樣暴露自己所包含的資料,每一行都是一條記錄,每一列都是一個特定的類型並且有特定的意義。例如:連絡人的資訊以及他們的電話號碼就可能像如下一樣儲存:
在表中的每一條記錄都含有是個數值型的_ID域,每一條記錄的該域都是唯一的,用以標示這條記錄。IDs可以用來匹配關聯表中的記錄,例如:在一個表中找到一個連絡人的電話號碼,在另一個表中找到該連絡人的圖片。
查詢操作可以返回一個Cursor對象,通過這個Cursor對象你可以從一條記錄移動到另一條記錄,從一個列移動到另一個列來讀取每個列值。
對於每一種類型的資料,他都有特定的方法讀取。因此,要讀取一個域的值,你必須要知道這個域的值的資料類型(關於這方面的詳細內容會在查詢操作以及Cursor對象中介紹)。
URIs
每一個content provider都暴露了一個公用的URI(封裝成一個Uri對象)用來唯一標示它包含的資料集。對於那些包含多資料集(多個表)的content provider它會為每一個資料集提供一個URI。所有由provider暴露的URIs都是以字串"content://"開頭的。"content:"表明這個資料集是由一個content provider提供的。
如果你定義了一個content provider,那麼為它的URI定義一個常量是一個比較好的做法,這樣就可以簡化用戶端的編程並且使得將來的更新更加的徹底。Android隨著平台一起為所有的providers定義了CONTENT_URI這個常量,例如:對於連絡人電話號碼的表的URI和儲存連絡人圖片的表的URI(兩者都是被Contacts content provider控制)分別如下:
android:provider.Contacts.Phone.CONTENT_URI
android:provider.Contacts.Photos.CONTENT_URI
URI常量在所有與content provider的互動操作中都會被使用到。每一個ContentResolver的方法的第一個參數都是URI。它代表著ContentResolver將會與哪一個provider通訊,竟會操作provider中的哪一個表。
查詢一個Content Provider
你需要三方面的資訊來查詢一個xontent provider:
1.標示provider的URI;
2.你希望接收的資料域的名稱;
3.你希望操作的資料域的資料類型。
如果你是查詢特定的記錄,那麼你還需要唯一標示這條記錄的ID。
執行查詢操作
要查詢一個content provider,你將會使用到ContentResolver.query(0方法或者是Activity.managedQuery()方法。這兩個方法使用同樣的參數,都返回一個Cursor對象。然而,managedQuery()方法會使得由activity來管理返回的Cursor對象的生命週期。一個被管理的Cursor對象必須處理所有的細節,例如:在activity處於暫停狀態時就要卸載自己,當activity處於重啟時,又要開始執行查詢操作。 你可以調用Activity.startManagingCursor()方法來管理一個還未被管理的Cursor對象。
query()和managedQuery()方法的第一參數都是provider的URI——CONTENT_URI常量標示了一個特定的ContentProvider以及其資料集。
要嚴格的查詢到一條記錄,你必須追加記錄的_ID到URI上——也就是,將代表ID的字串放置在URI的最後欄位。例如:如果ID是23,那麼URI就如下所示:
content://. . . ./23
這裡有一些協助的方法,特別是ContentUris.withAppendedId()和Uri.withAppendedPath()方法,他們使得追加一個ID到URI上變得更加容易。這兩個靜態方法都返回追加了ID的Uri對象。因此,如果你想在連絡人的資料庫中尋找到標示為23的這條記錄,你可以像下面這樣構建一個查詢器:
import android.provider.Contacts.People;
import android.content.ContentUris;
import android.net.Uri;
import android.database.Cursor;
// Use the ContentUris method to produce the base URI for the contact with _ID == 23.
Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
// Alternatively, use the Uri method to produce the base URI.
// It takes a string rather than an integer.
Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");
// Then query for this specific record:
Cursor cur = managedQuery(myPerson, null, null, null, null);
import android.provider.Contacts.People;
import android.content.ContentUris;
import android.net.Uri;
import android.database.Cursor;
// Use the ContentUris method to produce the base URI for the contact with _ID == 23.
Uri myPerson = ContentUris.withAppendedId(People.CONTENT_URI, 23);
// Alternatively, use the Uri method to produce the base URI.
// It takes a string rather than an integer.
Uri myPerson = Uri.withAppendedPath(People.CONTENT_URI, "23");
// Then query for this specific record:
Cursor cur = managedQuery(myPerson, null, null, null, null);
對於query()方法和managedQuery()方法的其他參數的詳細介紹如下:
1.要返回的列的名稱。如果指定為null,那麼就會返回所有的列。否則,只有那些別列出的名字的列才會被返回。所有的content provider都有平台定義的列常量。例如:android.provider.Contacts.Phones類定義了phone表中的列的名字,就像之前介紹的_ID,NUMBER,NUMBER_KEY,NAME等等。
2.對於要返回的行的過濾器,如同SQL語句中的WHERE語句(除掉WHERE自身)。若指定為N,則返回所有的行(除非指定了只查詢特定的單個記錄)。
3.進行選擇的參數
4.一個代表返回的所有的行排序的參數,如同SQL語句中的QRDER BY語句(除了ORDER BY自身)。若指定為null,那麼就按照記錄在表中的記錄返回,有可能是無序的。
讓我們看看一個查詢並擷取連絡人名字和他們電話號碼的例子:
import android.provider.Contacts.People;
import android.database.Cursor;
// Form an array specifying which columns to return.
String[] projection = new String[] {
People._ID,
People._COUNT,
People.NAME,
People.NUMBER
};
// Get the base URI for the People table in the Contacts content provider.
Uri contacts = People.CONTENT_URI;
// Make the query.
Cursor managedCursor = managedQuery(contacts,
projection, // Which columns to return
null, // Which rows to return (all rows)
null, // Selection arguments (none)
// Put the results in ascending order by name
People.NAME + " ASC");
import android.provider.Contacts.People;
import android.database.Cursor;
// Form an array specifying which columns to return.
String[] projection = new String[] {
People._ID,
People._COUNT,
People.NAME,
People.NUMBER
};
// Get the base URI for the People table in the Contacts content provider.
Uri contacts = People.CONTENT_URI;
// Make the query.
Cursor managedCursor = managedQuery(contacts,
projection, // Which columns to return
null, // Which rows to return (all rows)
null, // Selection arguments (none)
// Put the results in ascending order by name
People.NAME + " ASC");這個查詢操作從Contacts content provider的People表中獲得資料。它獲得了連絡人姓名和他們的主要電話號碼以及每一條記錄的ID。他也將查詢到的總的記錄數通過_COUNT域返回。
對於每個列的名字的常量是定義在不同的介面中的——_ID和_COUNT在BaseColumns。NAME是在PeopleColumns,NUMBER是在PhoneColumns。Contacts.People類實現了所有的這些介面,這也就是為什麼上面的樣本中我們可以僅僅通過類名就引用這些方法。
一個查詢操作的傳回值
一個查詢操作會返回0條或者是更多的資料庫記錄。列的名稱,它們預設的順序,以及他們對於每個contect provider的資料類型。每一個provider都有一個數值型的_ID列,也就是每條記錄的唯一標示ID。每一個provider也可以通過_COUNT列來返回這次查詢操作總共返回了多少條記錄,對於返回的每條記錄,_COUNT域的值都是一樣的。
下面是通過查詢操作返回的上面例子的結果的樣本:
擷取的資料是通過一個Cursor對象來呈現的,通過這個Cursor對象,我們可以通過這個結果集來重複的向前向後操作。你僅僅可以通過這個Cursor對象來讀取資料。要想添加、修改或者是刪除資料,你必須使用ContentReslover對象。
讀取檢索出來的資料
我們可以通過查詢providers返回的Cursor對象來操作返回的資料集。如果在查詢的時候指定了ID,那麼這個結果集中僅僅包含一條記錄。否則他就會包含多條記錄(如果沒有匹配的資料,那麼返回的也有可能為空白)。你可以讀取記錄的特定域,但是你必須知道你要讀取的域存放的資料的資料類型,因為Cursor對象對於每一種類型的資料都有不同的方法——例如:getString(),getInt()和getFloat()。(然而,對於大多數的類型,如果你調用讀取字串的方法,那麼Cursor對象就會將代表資料的字串形式返回給你)。Cursor對象允許你從列的索引中回去列名,或者是從列名中獲得索引號。
下面的例子示範了從前面的查詢操作中讀取連絡人的名字和電話號碼:
import android.provider.Contacts.People;
private void getColumnData(Cursor cur){
if (cur.moveToFirst()) {
String name;
String phoneNumber;
int nameColumn = cur.getColumnIndex(People.NAME);
int phoneColumn = cur.getColumnIndex(People.NUMBER);
String imagePath;
do {
// Get the field values
name = cur.getString(nameColumn);
phoneNumber = cur.getString(phoneColumn);
// Do something with the values.
...
} while (cur.moveToNext());
}
}
import android.provider.Contacts.People;
private void getColumnData(Cursor cur){
if (cur.moveToFirst()) {
String name;
String phoneNumber;
int nameColumn = cur.getColumnIndex(People.NAME);
int phoneColumn = cur.getColumnIndex(People.NUMBER);
String imagePath;
do {
// Get the field values
name = cur.getString(nameColumn);
phoneNumber = cur.getString(phoneColumn);
// Do something with the values.
...
} while (cur.moveToNext());
}
}如果一個查詢操作可以返回位元據,例片或者是聲音,那麼這些資料可能會直接記錄在表中,或者是對於這類型資料可能會特殊指定一個字串content:URI指定表的入口來允許我們對這類型的資料進行存取。一般地,少量的資料(從20K到50K或者更少的)幾乎都是直接記錄在表中的並且可以通過Cursor.getBlob()方法讀取,他返回一個byte類型的數組。
如果表的入口是一個content:URI,你不應該直接開啟或者讀取(因為一件事,許可權問題可能導致這個操作失敗)。此時,你應該使用ContentRecesolver.openInputStream()方法獲得一個InputStream對象,再通過這個InputStream對象來讀去資料。
摘自 chenlong12580的專欄