數據存儲與ContentProvider
數據存儲
典型的桌面系統會提供通用文件系統,任何應用程序都可以通過文件系統存儲文件,其它應用程序可以來讀取這些文件(可能需要某些訪問權限設置)。Android使用不同的系統:在Android上,應用程序的所有數據都是其私有的。但是,Android仍然提供了一套標準方法——通過Content Provider,使一個應用程序可以把它的私有數據開發給其它應用程序訪問。Content Provider對應用程序來說是一個可選的組建組件,它暴露了應用程序數據的讀/寫訪問,可以對其設置任何限制。Content Provider提供了請求和修改數據的標準語法和讀取返回數據的標準機制。Android爲標準的數據類型提供了一些Content Provider,如圖像,視頻和音頻文件,以及個人通訊錄信息。更多關於Content Provider使用的信息,請參考Content Provider。
無論是否需要把應用程序的數據輸出給其他應用程序,我們都需要一種方法來保存這些數據。Android提供了四種機制來存儲和獲取數據:Preferences, Files, Databases, 和 Network。
Preferences
Preferences是存取原始數據類型鍵-值對的一種輕量級機制。Preferences的經典應用於存儲應用程序的參數選項,如應用程序啓動時候的缺省問候語和加載的文本字體。通過調用Context.getSharedPreferences()方法來讀/寫值。爲Preferences集合賦予一個名稱就可以與同一個應用程序中其它的組件共享這些Preferences,或者使用不帶名字的Activity.getPreferences()方法保存這些Preferences對調用它們的Activity的私密性。除非通過content provider,Preferences不能跨應用程序共享。
下面的例子是計算器設置按鍵靜音模式的用戶參數選項:
import android.app.Activity;
import android.content.SharedPreferences;
public class Calc extends Activity {
public static final String PREFS_NAME = "MyPrefsFile";
. . .
@Override
protected void onCreate(Bundle state){
super.onCreate(state);
. . .
// Restore preferences
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
boolean silent = settings.getBoolean("silentMode", false);
setSilent(silent);
}
@Override
protected void onStop(){
super.onStop();
// Save user preferences. We need an Editor object to
// make changes. All objects are from android.context.Context
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putBoolean("silentMode", mSilentMode);
// Don't forget to commit your edits!!!
editor.commit();
}
}
Files
應用程序可以直接在移動設備或可移動存儲介質上保存文件,缺省情況下,其它應用程序是不能訪問這些文件的。
通過調用Context.openFileInput()函數,傳遞文件的本地名稱和路徑,就可以讀取其中的數據。這個函數返回一個標準java FileInputStream的對象。調用Context.openFileOutput()函數,傳遞文件的名稱和路徑參數可以寫一個文件,這個函數返回FileOutputStream對象。通過傳遞文件名稱和路徑調用上述的方法對其它應用程序是無效的,只能訪問本身的文件。
如果在編譯時有一個靜態文件一同與應用程序打包在一塊兒,就可以把這個文件保存在中的res/raw/myDataFile,然後通過Resources.openRawResource (R.raw.myDataFile)函數打開它,返回的是用於讀取文件的InputStream對象。
Databases
Android API支持創建和使用SQLite數據庫。數據庫對於創建它的應用程序來說是私有的。
SQLiteDatabase對象代表了一個數據庫以及與數據庫交互的方法——訪問和管理數據。
調用SQLiteDatabase.create()函數和SQLiteOpenHelper的子類,創建數據庫。
作爲支持SQLite數據庫系統的一部分,Android提供了數據庫管理功能,通過這些功能我們可以保存包裝在有用對象中的複雜數據集。例如,Android爲通訊錄信息定義了一種數據類型,它包含了很多域,包括姓和名(字符串),地址和電話號碼(字符串),照片(bitmap圖片),以及其它更多的描述一個人的信息。
sqlite3工具同Android是一同發佈的,通過這個工具我們可以瀏覽數據庫內容、運行SQL命令、執行其它SQLite數據庫上的有用的功能。
SQLite及其它所有的數據庫都存儲在設備上的/data/data/package_name/databases 中。
討論創建多少個數據表、表包含什麼字段,表之間如何關聯,這些問題超出了本文的範圍。但是,Android在標準SQLite概念之外沒有強加任何限制。我們強烈建議包含一個能夠作爲唯一ID,並快速定一條位記錄的自增值字段。關於這方面更多內容參考Content Provider文檔。NotePad例子代碼中的NotePadProvider類提供了怎樣創建和使用新數據庫的示例。創建的任何數據庫都可以通過名稱被應用程序中的其它類訪問,但是對外部應用程序來說是不可訪問的。
Network
如果網絡可用,我們也可以通過網絡保存和獲取數據。網絡存取要用到下面的包中的類:
Content Providers
Content providers可以存取數據,並且可以使所有應用程序訪問這些數據。Content providers是應用程序之間共享數據的唯一方式;不存在所有Android包都能夠訪問的共用存儲區。
Android包含了一些常用數據類型的Content providers(音頻、視頻、圖像、個人通訊錄等),在android.provider包中可以找到它們。可以查詢這些Provider包含的數據(對某些數據來說,我們必須獲得必要的授權)。
本文介紹怎樣使用Content providers。基本概念的簡要討論之後,我們將探討怎樣查詢一個Content Provider,怎樣修改一個Provider控制的數據,以及怎樣創建自己的Content Provider。
Content Provider基礎知識
content provider實際上是怎樣把數據存儲在表面之下,這取決於它的設計。但是,所有的content providers都會實現一個通用接口來查詢provider並返回結果,除此之外,還要添加、改變和刪除數據。
大多數情況下,客戶端可以通過ContentResolver對象,直接使用接口。在Activity或其它應用程序組件的實現代碼中,通過調用getContentResolver()函數,就可以得到ContentResolver。
ContentResolver cr = getContentResolver();
然後,通過ContentResolver提供的方法,跟任何感興趣的content provider進行互動。
查詢初始化時,Android系統會確定作爲查詢目標的那個content provider,並且會確保它啓動運行。系統會把所有ContentProvider對象實例化,用戶不需要自己做這些。實際上,用戶從來根本不需要同ContentProvider對象直接打交道。通常,對於每一種類型的ContentProvider,只有一個實例,但是這個實例可以與在不同的程序或進程中的多個ContentResolver對象進行通訊。進程之間的互動是由ContentResolver 和 ContentProvider類處理的。
數據模型
Content provider把它的數據作爲數據庫模型上的單一數據表提供出來,在數據表中,每一行是一條記錄,每一列代表某一特定類型的值。例如,聯繫人信息及其電話號碼會像下面這樣輸出。
_ID |
NUMBER |
NUMBER_KEY |
LABEL |
NAME |
TYPE |
13 |
(425) 555 6677 |
425 555 6677 |
Kirkland office |
Bully Pulpit |
TYPE_WORK |
44 |
(212) 555-1234 |
212 555 1234 |
NY apartment |
Alan Vain |
TYPE_HOME |
45 |
(212) 555-6657 |
212 555 6657 |
Downtown office |
Alan Vain |
TYPE_MOBILE |
53 |
201.555.4433 |
201 555 4433 |
Love Nest |
Rex Cars |
TYPE_HOME |
每條記錄包含了“numeric _ID”域,它是在表中可唯一標識該條記錄的。ID也可以用於在相關表中匹配激勵——例如,在一個表中查找一個人的電話號碼,在另一個表中獲取其照片。
查詢會返回一個Cursor 對象,Cursor 可以從一條記錄移到另一條記錄,一列一列地讀取每個字段的數據。讀取每種類型的數據都有特定的方法,所有,如果要讀取一個字段,必須知道該字段所包含的數據的類型。
URIs
每個content provider 都會對外提供一個公共的URI(包裝成Uri對象),URI唯一確定 這個content provider的數據集。控制多個數據集(多個表)的content provider會爲每一個數據集提供獨立的URI。Provider的URI都是以字符串“content://”開頭的。content:表示這些數據是被content provider控制的。
如果我們自己定義一個content provider,同時爲這個content provider的URI定義一個常量是一個很好的想法,這樣做不僅可以簡化客戶端的代碼,而且可以使將來的升級更簡潔。所有與Android平臺一同發佈的provider,系統都爲它們定義了CONTENT_URI常量。例如,匹配電話號碼和聯繫人的表的URI,以及保存聯繫人照片表的URI如下(這兩個表都是由Contacts的content provider控制的):
android.provider.Contacts.Phones.CONTENT_URI
android.provider.Contacts.Photos.CONTENT_URI
類似的,通話記錄表的URI和日程表的URI分別是:
android.provider.CallLog.Calls.CONTENT_URI
android.provider.Calendar.CONTENT_URI
所有與其content provider互動的過程中都可以使用這個URI 常量。每個ContentResolver 都把URI作爲其第一個參數,它決定了ContentResolver 將與哪一個provider對話,這個provider瞄準的是哪一個數據表。
構造查詢
查詢Content Provider需要三塊兒信息:
· 標識provider的URI
· 想要獲取的數據字段的名稱
· 這些字段的數據類型
如果訪問一條特殊記錄,也是需要它的ID的。
通過調用 ContentResolver.query()函數 或者調用Activity.managedQuery()函數來查詢一個Content Provider。 這兩個函數攜帶形同的參數,並且均返回一個Cursor對象。但是, managedQuery()函數使activity管理Cursor的生命週期。被管理的Cursor負責處理所有具體事務,例如當activity暫停的時候,Cursor負責把自己卸載下去,當activity重新啓動的時候,重新查詢。通過調用 Activity.startManagingCursor()函數,我們可以讓activity管理一個沒有被管理的Cursor對象。
傳給query() 或managedQuery()的第一個參數是這個Provider的URI——CONTENT_URI常量標識了一個特定的ContentProvider及其數據集。
在URI後面附上記錄的_ID值,就可以把查詢限制到一條記錄上——即,把匹配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);
query()和 managedQuery()函數的其他參數更詳細的界定了查詢,它們是:
· 返回的列的名稱。null 表示返回所有列。否則,只返回列出名字的列。所有Android平臺提供的content providers都爲它們自己的列定義了常量。例如, android.provider.Contacts.Phones類 爲phone數據表中的列名定義了常量_ID, NUMBER, NUMBER_KEY, NAME等。
· 過濾器的格式如同SQL的Where語句,它定義了返回那些行。 如果URI沒有顯示產尋某一單一記錄,null值表示返回查到的所有行。
· 選擇參數。
· 返回行的排序,格式定義如同SQL 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");
這個查詢從通訊錄Content Provider的人員表中獲取數據,它得到每個聯繫人的姓名、電話和唯一的記錄ID,它也報告了記錄的數量作爲每條記錄的_COUNT字段返回。
列名的常量在不同的接口中定義——在BaseColumns中定義了_ID 和_COUNT,在PeopleColumns中定義了NAME,在PhoneColumns中定義了NUMBER。Contacts.People類實現了所有這些接口,這就是爲什麼上面的例子代碼能夠通過類名引用它們。
查詢返回什麼
一條查詢返回的是一組零或數據庫記錄的集合。列名、缺省排序、數據類型對每個Content Provider是特定的,但是每個Provider都有一個_ID列,它爲每條記錄保存一個數字ID值。每個Provider都可以把返回的記錄數量作爲_COUNT列報告出來,這個值對所有的行來說是相同的。
下面是前面的查詢列子中的結果數據集:
_ID |
_COUNT |
NAME |
NUMBER |
44 |
3 |
Alan Vain |
212 555 1234 |
13 |
3 |
Bully Pulpit |
425 555 6677 |
53 |
3 |
Rex Cars |
201 555 4433 |
取回的數據由Cursor對象提供出來,Cursor對象用於在結果集中前向或後向列舉記錄,Cursor對象只能用來讀數據。增加、修改和刪除數據必須使用ContentResolver對象。
讀取取回的數據
查詢返回的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());
}
}
如果一個查詢返回諸如圖像和聲音的二進制數據,那麼在數據表中可以被直接存儲或者數據的表入口是指定一個Content的字符串:URI,通過這個URI可以得到數據。一般說來,小數量的數據(20K到50K或者更少)常常在表中直接存儲,通過Cursor.getBlob()函數讀取,這個函數返回的是一個二進制數組。
如果數據表入口是一個Content:URI,就不要直接打開和讀取文件(一個原因是權限問題),而是通過調用ContentResolver.openInputStream()函數來得到一個可以用來讀數據的InputStream對象。
修改數據
可以通過下面的方法修改content provider控制的數據:
· 增加新記錄
· 對存在的記錄添加新值
· 批量更新存在的記錄
· 刪除記錄
調用 ContentResolver 的方法可以完成數據的修改。 某些content providers寫數據需要比讀數據更嚴格的權限,如果沒有寫Content Provider的權限, ContentResolver的方法將失效。
添加數據
要想向Content Provider添加一條新紀錄,首先要在ContentValues對象中創建一個鍵-值對的映射,在這個映射中,每個值對應content provider中列的名字,相應的值就是新紀錄在對應列中的值;然後,調用以Provider的URI和ContentValues映射作爲參數ContentResolver.insert()函數。這個函數返回值是新記錄的完整URI——即Provider的RUI加上新紀錄的ID。可以通過這個URI查詢和得到新紀錄的Cursor,並且可以進一步修改這個新增加的記錄。下面是一個例子;
import android.provider.Contacts.People;
import android.content.ContentResolver;
import android.content.ContentValues;
ContentValues values = new ContentValues();
// Add Abraham Lincoln to contacts and make him a favorite.
values.put(People.NAME, "Abraham Lincoln");
// 1 = the new contact is added to favorites
// 0 = the new contact is not added to favorites
values.put(People.STARRED, 1);
Uri uri = getContentResolver().insert(People.CONTENT_URI, values);
添加新值
如果一條記錄已經存在,我們可以爲其增加新信息或者修改現有的信息。例如,上面例子的下一步是爲這個新紀錄增加聯絡信息——電話號、IM或電子郵件地址。
爲聯繫人數據庫中增加一條新紀錄最佳方法是把新數據所去的表明添加到該記錄的URI後面,然後使用修正後的RUI來添加新數據值。每個聯繫人表都開放了一個名字作爲CONTENT_DIRECTORY常量。下面的代碼接續前面的例子,爲剛剛創建的記錄增加電話號碼和電郵地址。
Uri phoneUri = null;
Uri emailUri = null;
// Add a phone number for Abraham Lincoln. Begin with the URI for
// the new record just returned by insert(); it ends with the _ID
// of the new record, so we don't have to add the ID ourselves.
// Then append the designation for the phone table to this URI,
// and use the resulting URI to insert the phone number.
phoneUri = Uri.withAppendedPath(uri, People.Phones.CONTENT_DIRECTORY);
values.clear();
values.put(People.Phones.TYPE, People.Phones.TYPE_MOBILE);
values.put(People.Phones.NUMBER, "1233214567");
getContentResolver().insert(phoneUri, values);
// Now add an email address in the same way.
emailUri = Uri.withAppendedPath(uri, People.ContactMethods.CONTENT_DIRECTORY);
values.clear();
// ContactMethods.KIND is used to distinguish different kinds of
// contact methods, such as email, IM, etc.
values.put(People.ContactMethods.KIND, Contacts.KIND_EMAIL);
values.put(People.ContactMethods.DATA, "[email protected]");
values.put(People.ContactMethods.TYPE, People.ContactMethods.TYPE_HOME);
getContentResolver().insert(emailUri, values);
通過調用ContentValues.put()函數可以把一小段二進制數據放到數據表中,ContentValues.put()函數攜帶一個二進制數組。這種方法適合於小的圖標類圖片或者一小段音頻剪輯。如果要添加大量的二進制數據。例如照片,一整首歌曲,在數據表中保存該數據的content: URI,然後調用文件URI的ContentResolver.openOutputStream()函數(這會使ContentProvider把數據保存在一個文件裏,,把這個文件的路徑保存在記錄的一個隱含字段中)。
就這方面來說,MediaStore Content Provider是分配圖像、視頻和音頻數據的主要的Provider。MediaStore Content Provider採用特殊的規則:用於query() 或 managedQuery()來獲取二進制數據的元信息(如照片的標題或者攝製日期)的URI,同樣用於openInputStream()來獲取二進制數據本身。類似的,用於insert()函數來把元信息放入MediaStore記錄的URI,也用於保存二進制數據本身的openOutputStream()函數,下面的這段代碼展示了這種規則:
import android.provider.MediaStore.Images.Media;
import android.content.ContentValues;
import java.io.OutputStream;
// Save the name and description of an image in a ContentValues map.
ContentValues values = new ContentValues(3);
values.put(Media.DISPLAY_NAME, "road_trip_1");
values.put(Media.DESCRIPTION, "Day 1, trip to Los Angeles");
values.put(Media.MIME_TYPE, "image/jpeg");
// Add a new record without the bitmap, but with the values just set.
// insert() returns the URI of the new record.
Uri uri = getContentResolver().insert(Media.EXTERNAL_CONTENT_URI, values);
// Now get a handle to the file for that record, and save the data into it.
// Here, sourceBitmap is a Bitmap object representing the file to save to the database.
try {
OutputStream outStream = getContentResolver().openOutputStream(uri);
sourceBitmap.compress(Bitmap.CompressFormat.JPEG, 50, outStream);
outStream.close();
} catch (Exception e) {
Log.e(TAG, "exception while writing image", e);
}
批量更新數據
想要批量更新一組記錄,例如把所有字段的"NY"改成 "New York",就要調用ContentResolver.update()函數,用想要改變的列和值作爲參數。
刪除記錄
調用ContentResolver.delete()函數,以特定行的Uri爲參數,可以刪除一條記錄。
調用ContentResolver.delete()函數,以需要刪除的記錄類型的Uri(如android.provider.Contacts.People.CONTENT_URI)爲參數,可以刪除多行數據,SQL的WHERE語句可以指定刪除哪兒些行。
注意:如果要刪除一個通用類型,一定要確定WHERE語句是有效的,否則將會面臨刪掉比預計更多的記錄的風險。
創建Content Provider
要創建一個Content Provider,必須:
· 建立保存數據的系統。大多數content providers使用Android的文件或SQLite數據庫來存儲數據,但是我們可以以我們想要的任何方式存儲數據。 Android提供了 SQLiteOpenHelper來幫助我們創建數據庫,提供了 SQLiteDatabase類來管理數據庫。
· 擴展ContentProvider類來提供訪問數據的方法。.
· 在應用程序的AndroidManifest.xml文件中聲明這個Content Provider。
下面各節將討論上面任務的後兩項
定義ContentProvider類的子類把數據開放給使用ContentResolver和Cursor期望規則的其它應用程序,這主要是要實現在ContentProvider類中定義的六個虛函數:
query()
insert()
update()
delete()
getType()
onCreate()
query()方法必須返回一個能夠遍歷所求數據的Cursor對象。Cursor本身也是接口,但是Android一些可用的Cursor對象,例如,SQLiteCursor可以遍歷保存在SQLite數據庫中的數據。通過調用任何SQLiteDatabase類的query()方法就可以得到Cursor對象,還有其它爲不是保存在數據庫中的數據而設的Cursor實現,諸如MatrixCursor。
由於在不同的進程和線程中的ContentResolver 對象都可以調用ContentProvider的方法,必須在線程安全模式下使用。
當對數據修改時,處於善意,會調用call ContentResolver.notifyChange()函數來通知監聽者。
除了定義子類,還要採取其他步驟來簡化客戶端的工作,使這個類更容易使用:
· 定義 public static final Uri命名爲 CONTENT_URI,它是一個字符串,代表了content provider能夠處理的全部內容。 必須爲這個值定義唯一的字符串。最佳的方法是使用content provider的完全類名 (小寫)。例如, TransportationProvider類的定義如下的URI
- public static final Uri CONTENT_URI = Uri.parse("content://com.example.codelab.transporationprovider");
如果Provider有子表,爲每一個子表都要定義CONTENT_URI常量。這些URIs 應該具備相同的權限,只能通過它們的路徑來區分。例如:
content://com.example.codelab.transporationprovider/train
content://com.example.codelab.transporationprovider/air/domestic
content://com.example.codelab.transporationprovider/air/international
· 定義Content Provider返回給客戶端的列名。 如果使用數據庫,這些列名通常與SQL數據庫類名同名。還要定義public static字符串常量,客戶端可以在查詢或其它指令中指定列
要確保爲記錄的ID提供一個命名爲"_id" (常量 _ID)的整數列。無論是否還有其它字段(如URI)能夠唯一標識記錄,都應該定義這個ID字段。如果使用SQLite數據庫,_ID應爲下面的類型:
INTEGER PRIMARY KEY AUTOINCREMENT
AUTOINCREMENT描述符是可選的。 如果沒有它,SQLite載ID字段添加比現存ID列中最大的數大一的數。如果刪除了最後一行,那麼增加的下一行將使用與被刪除行相同的ID。AUTOINCREMENT使SQLite增加到目前最大值的下一個值,而無論這個最大值是否被刪除。
· 仔細考證每個列的數據類型,客戶端將使用這個信息來讀數據。
· 如果要處理新的數據類型,必須在ContentProvider.getType()函數的實現中定義一種新的MIME類型返回。這種數據類型部分依賴於提交給getType()函數的content: URI 是否侷限於請求某一特定記錄。針對單一記錄有一種MIME類型的格式,針對多記錄則是另外一種格式。利用 Uri 方法有助於確定請求的是什麼。下面是每種類型的通用格式:
o 對單一記錄:
vnd.android.cursor.item/vnd.yourcompanyname.contenttype
例如,請求train記錄122的URI格式如下:
content://com.example.transportationprovider/trains/122
返回的MIME類型:
vnd.android.cursor.item/vnd.example.rail
o 對多記錄:vnd.android.cursor.dir/vnd.yourcompanyname.contenttype
例如,請求所有train記錄的URI格式如下
content://com.example.transportationprovider/trains
返回的MIME類型:
vnd.android.cursor.dir/vnd.example.rail
· 如果輸出的字節數據太大以至於不能放在數據表中,如大的bitmap文件,那麼向客戶端輸出的字段實際上應該包含一個content: URI字符串。這個字段提供給客戶端訪問數據文件的方式。記錄中也要包含名爲“_data”的字段。 “_data”列出了該文件在設備上的確切的文件路徑,它不是提供給客戶端訪問的,而是由ContentResolver讀取。客戶端可以在面向用戶字段上調用 ContentResolver.openInputStream() ,這個字段保存條目的URI。 ContentResolver會請求記錄的"_data"字段。由於ContentResolver比起客戶端來說有更高的權限,所以ContentResolver 可以直接訪問文件,向客戶端返回一個read wrapper。
content provider實現的例子,參考隨同SDK一同發佈的Notepad 示例應用程序中NodePadProvider 類。
聲明content provider
爲了讓Android系統知道我們開發的content provider,需要在應用程序的AndroidManifest.xml文件中聲明一個<provider>元素。沒有在mainfest中聲明的Content providers對Android系統來說是不可見的。
Name屬性是能夠完全確定ContentProvider子類的屬性;authorities屬性是標識provider的content: URI的權限部分。例如,ContentProvider的子類AutoInfoProvider的<provider>元素定義如下:
<provider name="com.example.autos.AutoInfoProvider"
authorities="com.example.autos.autoinfoprovider"
. . . />
</provider>
注意:authorities屬性忽略了content: URI的路徑部分。例如,如果AutoInfoProvider爲不同類型的汽車或製造商管理不同的子表
content://com.example.autos.autoinfoprovider/honda
content://com.example.autos.autoinfoprovider/gm/compact
content://com.example.autos.autoinfoprovider/gm/suv
這些路徑不要在manifest中聲明,authority是標識provider的,provider能夠解釋所選擇的URI的路徑部分。
其它<provider>屬性可以設置讀寫數據的權限,提供可以顯示給用戶的圖標或文本,使provider有效或無效等等。如果在content provider多個運行版本之間不需要數據的同步,把multiprocess屬性設置爲“true”,這允許在每個客戶進程中創建provider實例,消除執行IPC的需求。
Content URI 總結
下面重溫一下content URI的重要部分:
content://com.example.transportationprovider/trains/122
v/:* {behavior:url(#default#VML);} o/:* {behavior:url(#default#VML);} w/:* {behavior:url(#default#VML);} .shape {behavior:url(#default#VML);} Normal 0 7.8 磅 0 2 false false false MicrosoftInternetExplorer4 /* Style Definitions */ table.MsoNormalTable {mso-style-name:普通表格; mso-tstyle-rowband-size:0; mso-tstyle-colband-size:0; mso-style-noshow:yes; mso-style-parent:""; mso-padding-alt:0cm 5.4pt 0cm 5.4pt; mso-para-margin:0cm; mso-para-margin-bottom:.0001pt; mso-pagination:widow-orphan; font-size:10.0pt; font-family:"Times New Roman"; mso-ansi-language:#0400; mso-fareast-language:#0400; mso-bidi-language:#0400;}
A: 標準前綴,指明數據是由一個content provider控制的,永遠不要修改這個前綴。
B: URI的權柄部分,它指明瞭content provider。對第三方應用程序來說,這應當是一個完全限定類名(小寫),以確保其唯一性。在<provider>元素的authorities屬性中聲明這個權柄。
<provider name=".TransportationProvider"
authorities="com.example.transportationprovider"
. . . >
C: content provider用來確定請求哪種類型數據的路徑。這部分可能有零個或多個段長。如果content provider只輸出一種類型的數據(例如,只有trains),這部分是可以不要的;如果provider輸出幾種類型的數據,包括子類型,那麼這部分可能是幾個段長——例如,"land/bus", "land/train", "sea/ship",和 "sea/submarine"提供了四種可能性。
D: 如果有的話,這部分是被請求的具體記錄的ID,它是被請求記錄的_ID值。如果不是請求單一的記錄,這部分以及斜槓線都可以省略,如:
content://com.example.transportationprovider/trains