ContentProvider內部如何保存數據由其設計者決定。但是所有的ContentProvider都實現一組通用的方法,用來提供數據的增刪改查功能。
客戶端通常不會直接使用這些方法,大多數是通過ContentResolver對象實現對ContentProvider的操作,開發人員可以通過調用Activity或者其他應用程序組建的實現類中的getContentResolver()方法來獲得ContentProvider對象。
使用ContentResolver提供的方法可以獲得ContentProvider中任何感興趣的數據。
當開始查詢時,Android系統確認查詢的目標ContentProvider並確保它正在運行。系統會初始化所有的ContentProvider類的對象,開發人員不必完成此類操作。實際上,開發人員根本不會直接使ContentProvider類的對象。通常,每個類型的ContentProvider僅有一個單獨的實例。但是該實例能與位於不同應用程序和進程的多個ContentProvider類對象通信。不同進程之間的通信由ContentProvider類和ContentResolver類處理
1.1數據模型
ContentResolver使用基於數據庫模型的簡單表格來提供其中的 數據,這裏每行代表一條記錄,每列代表特定類型和含義的數據。例如,聯繫人的信息可能以表所示的方式提供。
——ID | NAME | NUMBER | |
001 | 王xx | 123****** | 123**@163.com |
002 | 張xx | 333****** | 333******@163.com |
查詢返回一個Cursor對象,它能遍歷各行各列來讀取各個字段的值,對於各個類型的數據,它都提供了專用的方法。因此,爲了讀取字段的數據,開發人員必須知道當前字段包含的數據類型。
1.2URI的用法
每個ContentProvider提供公共的URI(使用Uri類包裝)來唯一標識其數據集。管理多個數據集(多個表格)的ContentProvider爲每個數據集提供了單獨的URI。所有爲provider提供的URI都以“content://"作爲前綴,”content://“模式表示數據由ContentProvider來管理。
如果自定義ContentProvider,則應該爲其URI也定義一個常量,來簡化客戶端代碼並讓日後更新更加簡潔。Android爲當前平臺提供的ContentProvider定義了CONTENT_URI常量。匹配電話號碼到聯繫人表格的URI和匹配保存聯繫人照片表格的URI分別如下:
android.provider.Contacts.Phones.CONTENT_URI;
android.provider.Contacts.Photos.CONTETN_URI;
URI常量用於所有與ContentProvider的交互中。每個ContentProvider位方法使用URI作爲其第一個參數,它標識ContentProvider應該使用哪個provider及其中的哪個表格。
下面是ContentUri重要部分的總結:
content://com.example.tofun/dba/001
1.紅色(content://):標準的前綴,用於標識改數據由ContentProvider管理。它永遠不用修改。
2.藍色(com.example.tofun):URI的authority部分,它標識ContentProvider。對於第三方應用,該部分應該是完整的類名(使用小寫形式)來保證唯一性。在<provider>元素的authorities屬性中聲明authorty。
3.綠色(/dba):ContentProvider的路徑部分,用於決定哪類數據被請求。如果ContentProvider僅提供一種數據類型,這部分可以省略。如果provider提供幾種數據類型,包括子類型,這部分可以由幾部分組成。
4.黃色(/001):被請求的特定記錄的ID值。這是被請求記錄的ID值。如果請求不僅限於單條記錄,則該部分應該刪除。
2.預定義ContentProvider
Android系統爲常用數據類型提供了很多預定義的ContentProvider(聲音,視頻,圖片,聯繫人等),它們大都位於android.provider包中。開發人員可以查詢這些provider以獲得其中包含的信息(儘管有些需要適當的權限來讀取數據)。Android系統提供的常見ContentProvider說明如下:
1.Browser:讀取或修改書籤,瀏覽歷史或網絡搜索。
2.CallLog:查看或更新通話歷史。
3.Contacts:獲取,修改或保存聯繫人信息。
4.LiveFolders:由ContentProvider提供內容的特定文件夾。
5.MediaStore:訪問聲音,視頻和圖片。
6.Setting:查看和獲取藍牙設置,鈴聲和其他設備偏好。
7.SearchRecentSuggestions:能被配置以使用查找意見provider操作。
8.SyncStateContact:用於使用數據數組賬號關聯數據的ContentProvider約束。希望使用標準方式保存數據的provider可以使用它。
9.UserDictionary:在可預測文本輸入誰,提供用戶定義單詞給輸入法使用。應用程序和輸入法能增加數據到該字典。單詞能關聯頻率信息和本地化信息。
2.1查詢數據
開發人員需要下面3條信息才能查詢Contentprovider中的數據:
1.標識改ContentProvider的URI。
2.需要查詢的數據字段名稱。
3.字段中數據的類型。
如果查詢特定的記錄,則還需要提供該記錄的ID值。
爲了查詢ContentProvider中do數據,開發人員需要使用ContentResolver.query()或Activity.ManagedQuery()方法。這兩個方法使用相同的參數,並且都返回Cursor對象。然而,managedQuery()方法導致Activity管理Cursor的聲明週期。託管的Cursor處理所有細節,例如當Activity暫停時卸載自身,當Activity重啓時加載自身。調用Activity.startManagingCursor()方法可以讓Activity管理未託管的Cursor對象。
query()和managedQuery()方法的第一個參數是provider的URI,級標識特定ContentProvider和數據集的CONTENT_URI常量。
爲了限制返回一條記錄,可以再URI結尾增加改記錄的_ID值,即將匹配ID值的字符串作爲URI路徑部分的結尾片段。例如,ID值是10,URI將是:
content://..../10
有些輔助方法,特別是ContentUris.withAppednedId()和Uri.withAppednedPath(),能輕鬆將ID增加到URI。這兩個方法都是靜態方法並返回一個增加了ID的URI對象。
query和managedQuery()方法的其他參數用來更加細緻地限制查詢結果,它們是:
1.應該返回的數據列名稱。null值表示返回全部列:否則,僅返回列出的列。全部預定義ContentProvider都爲其列定義了常量。例如,android.provider.Contacts.Phones類定義了_ID,NUMBER,NUMBER_KEY,NAME等常量
2.決定哪些行被返回的過濾器,格式類似SQL的WHERE語句(但是不包含WHERE自身)。null表示返回全部行(除非URI限制查詢結果爲單行記錄)。
3.選擇參數。
4.返回記錄的排序器,格式類似SQL的ORDER BY語句(但是不包含ORDER BY自身)。null表示以默認順序返回,可能是無序的。
查詢返回一組零條或多條數據庫記錄。列明,默認順序和數據類型對每個ContentProvider都是特別的。但是每個provider都有一個_ID列,它爲每條記錄保存唯一的數組ID。每個provider也能使用_COUNT報告返回結果中記錄的行數,改制在各行都是相同的。
獲得數據使用Cursor對象處理,它能向前或者向後遍歷整個結果集。
2.2增加記錄
爲了向ContentProvider中增加新數據,首先需要在ContentValues對象中建立鍵值對映射,這裏每個鍵匹配ContentProvider中的列名,每個值是該列中希望增加的值。然後調用ContentResolver.insert()方法並傳遞給它provider的URI參數和ContentValues的映射。該方法返回新紀錄的完整URI,既增加了新紀錄的ID的URI。
2.3增加新值
一旦記錄存在,開發人員可以向其增加新信息或者修改已經存在的信息。增加記錄到Contacts數據庫的最佳方式是增加保存新數據的表名到代表記錄的URI,然後使用組裝好的URI來增加新數據。每個Contacts表格以CONTENT_DIRECTORY常量的方式提供名稱作爲該用途。
3.自定義ContentProvider
如果開發人員希望共享自己的數據,則有兩個選擇:
1.創建自定義的ContentProvider(一個ContentProvider的子類)
2.如果有預定義的ContentProvider,管理相同的數據類型並且有寫入權限,則可以向其中增加數據。
如果自定義Contentprovider,則需要完成以下操作:
1.建立數據存儲系統。大多數ContentProvider使用Android文件存儲方法或者SQLite數據庫保存數據,但是開發人員可以使用任何方式存儲。Android提供了SQLiteOpenHelper類幫助創建數據庫,SQLiteDatabase類管理數據庫。
2.繼承ContentProvider類來提供數據訪問方式。
3.在應用程序清單文件中聲明ContentProvider
下面介紹後兩個任務。
3.1繼承ContentProvider類
定義ContentProvider類的子類,以便使用ContentResolver和Cursor類帶來的便捷來共享數據。原則上,這意味着需要實現ContentProvider類定義的6個方法。
1.public boolean onCreate():用於初始化provider
2.public Cursor query(Uri uri,String[] projection,String,selection,String[] selectionArgs,String sortOrder):返回數據給調用者
3.public Uri insert(Uri uri,ContentValues values):插入
4.public int update(Uri uri,ContentValues values,String selection,String [] selectionArgs):更新
5.public int delete(Uri uri,String selection,String[] selectionArgs):刪除
6.public String getType(Uri uri):返回ContentProvider數據的MIME類型。
3.2聲明ContentProvider
在AndroidManifest.xml文件中定義<provider>標籤。沒有在清單文件中聲明,自定義的ContentProvider對於Android系統不可見。
name屬性的值是ContentProvider類的子類的完整名稱;authorities屬性是provider定義的content:URI中authority部分,例如
<provider android:name="com.example.EmployeeProvider" android:authorities="com.exmaple.employeeprovider"/>
其他<provider>屬性能設置讀寫數據的權限,提供顯示給用戶的圖標或文本,棄用或禁用provider等。如果數據不需要在多個運行着的ContentProvider間同步,則設置multiprocess爲true。這允許在各個客戶端進程之間創建一個provider實例,從而避免執行IPC。
下面一個讀取聯繫人ID和姓名的例子
public class MainActivity extends Activity {
private TextView tv;
String[] columns = {Contacts._ID,Contacts.DISPLAY_NAME};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView)findViewById(R.id.tv);
tv.setText(getQueryData());
}
private String getQueryData() {
StringBuffer sb = new StringBuffer();
ContentResolver resolver = this.getContentResolver();
Cursor cursor = resolver.query(Contacts.CONTENT_URI, columns, null, null, null);
int idIndex = cursor.getColumnIndex(columns[0]);
int nameIndex = cursor.getColumnIndex(columns[1]);
for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext()){
int id = cursor.getInt(idIndex);
String name = cursor.getString(nameIndex);
sb.append(id+":"+name+"\n");
}
return sb.toString();
}
}
需要聲明讀取聯繫人的權限
<uses-permission android:name="android.permission.READ_CONTACTS"/>