讀前思考
學習一門技術或者看一篇文章最好的方式就是帶著問題去學習,這樣才能在過程中有茅塞頓開、燈火闌珊的感覺,記憶也會更深刻。
- ContentProvider 是什麼?
- ContentProvider 如何使用?
- ContentProvider 和其他通訊方式比有什麼區別與優點?
- 什麼是 URI ?怎麼書寫?有什麼含義?
認識 URI
通用資源標誌符(Universal Resource Identifier, 簡稱 "URI")
Uri 代表要操作的資料,Android 上可用的每種資源、影像、視訊片段等都可以用 Uri 來表示。Uri 唯一標識每種資源。
Uri一般由三部分組成: 1.訪問資源的命名機制。 2.存放資源的主機名。 3.資源自身的名稱,由路徑表示。
android 的 Uri 由以下三部分組成:
"content://"、資料的路徑、標示 ID(可選)
一個標準的內容 URI 寫法是這樣的:
content://com.example.app.provider/table1
//這就表示呼叫方期望訪問的是 com.example.app 這個應用的 table1 表中的資料。
複製程式碼
除此之外,我們還可以在這個內容 URI 的後面加上一個 id,如下所示:
content://com.example.app.provider/table1/1
//這就表示呼叫方期望訪問的是 com.example.app 這個應用的 table1 表中 id 為 1 的資料。
複製程式碼
內容 URI 的格式主要就只有以上兩種,以路徑結尾就表示期望訪問該表中所有的資料,以 id 結尾就表示期望訪問該表中擁有相應 id 的資料。
我們可以使用萬用字元的方式來分別匹 配這兩種格式的內容 URI,規則如下。
- *:表示匹配任意長度的任意字元
- #:表示匹配任意長度的數字
所以,一個能夠匹配任意表的內容 URI 格式就可以寫成:
content://com.example.app.provider/*
複製程式碼
而一個能夠匹配 table1 表中任意一行資料的內容 URI 格式就可以寫成:
content://com.example.app.provider/table1/#
複製程式碼
什麼是 ContentProvider ?
作為四大元件之一,ContentProvider 主要負責儲存和共享資料。與檔案儲存、SharedPreferences 儲存、SQLite 資料庫儲存這幾種資料儲存方法不同的是,後幾者儲存下的資料只能被該應用程式使用,而前者可以讓不同應用程式之間進行資料共享,它還可以選擇只對哪一部分資料進行共享,從而保證程式中的隱私資料不會有洩漏風險。
ContentProvider 有兩種形式:
- 可以使用現有的內容提供者來讀取和操作相應程式中的資料。
- 也可以建立自己的內容提供者給這個程式的資料提供外部訪問介面。
從系統提供的 Provider 訪問資料
例子:讀取聯絡人的電話
1.清單檔案中新增讀取許可權
<uses-permission android:name="android.permission.READ_CONTACTS" />
複製程式碼
- 針對 6.0+ 系統動態許可權申請
//判斷是否有讀取聯絡人許可權
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.READ_CONTACTS}, 1);
} else {
readContacts();
}
複製程式碼
- 重寫 onRequestPermissionsResult( ) 方法獲取許可權申請返回
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 1:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
readContacts();
} else {
Toast.makeText(this, "獲取許可權失敗!", Toast.LENGTH_SHORT);
}
break;
default:
break;
}
}
複製程式碼
- 獲取手機通訊錄
private void readContacts() {
List contactsList=null;
Cursor cursor=null;
try {
contactsList=new ArrayList();
//查詢聯絡人資料,使用了getContentResolver().query方法來查詢系統的聯絡人的資料
//CONTENT_URI就是一個封裝好的Uri,是已經解析過得常量
cursor=getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
//對cursor進行遍歷,取出姓名和電話號碼
if (cursor!=null){
while (cursor.moveToNext()){
//獲取聯絡人姓名
String displayName=cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME
));
//獲取聯絡人手機號
String number=cursor.getString(cursor.getColumnIndex(
ContactsContract.CommonDataKinds.Phone.NUMBER
));
//把取出的兩類資料進行拼接,中間加換行符,然後新增到listview中
contactsList.add(displayName+"\n"+number);
LogUtils.i("姓名:"+displayName+"\n"+"電話:"+number);
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
//記得關掉cursor
if (cursor!=null){
cursor.close();
}
}
}
複製程式碼
- 最後的結果輸出
com.keven.jianshu I/TAG: 姓名:孫**
電話:+86157****429
com.keven.jianshu I/TAG: 姓名:井**
電話:+861836****299
com.keven.jianshu I/TAG: 姓名:張**
電話:+861866****830
com.keven.jianshu I/TAG: 姓名:釘**
電話:0105****898
com.keven.jianshu I/TAG: 姓名:彩**
電話:0108****604
複製程式碼
建立自己的 Provider
- 自定義類繼承 ContentProvider,重寫六個方法
public class Part1dMyProvider extends ContentProvider {
/**
* 初始化內容提供器的時候呼叫。通常會在這裡完成對資料庫的建立和升級等操作,
* 返回 true 表示內容提供器初始化成功,返回 false 則表示失敗。注意,只有
* 當存在 ContentResolver 嘗試訪問我們程式中的資料時,內容提供器才會被初始化。
*/
@Override
public boolean onCreate() {
return false;
}
/**
* 從內容提供器中查詢資料。使用 uri 引數來確定查詢哪張表,projection 引數用
* 於確 定查詢哪些列,selection 和 selectionArgs 引數用於約束查詢哪些行,
* sortOrder 引數用於 對結果進行排序,查詢的結果存放在 Cursor 物件中返回。
*/
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
return null;
}
/**
* 向內容提供器中新增一條資料。使用 uri 引數來確定要新增到的表,待新增的資料
* 儲存在 values 引數中。新增完成後,返回一個用於表示這條新記錄的 URI。
*/
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
/**
* 更新內容提供器中已有的資料。使用 uri 引數來確定更新哪一張表中的資料,新數
* 據儲存在 values 引數中,selection 和 selectionArgs 引數用於約束更新哪些行,
* 受影響的 行數將作為返回值返回。
*/
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
return 0;
}
/**
* 從內容提供器中刪除資料。使用 uri 引數來確定刪除哪一張表中的資料,selection
* 和 selectionArgs 引數用於約束刪除哪些行,被刪除的行數將作為返回值返回。
*/
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
/**
* 根據傳入的內容 URI 來返回相應的 MIME 型別。 可以看到,幾乎每一個方法都會
* 帶有 Uri 這個引數,這個引數也正是呼叫 ContentResolver的增刪改查方法時傳
* 遞過來的。而現在,我們需要對傳入的 Uri 引數進行解析,從中分析出 呼叫方
* 期望訪問的表和資料。
*/
@Override
public String getType(Uri uri) {
return null;
}
}
複製程式碼
- 使用 UriMatcher
public class Part1dMyProvider extends ContentProvider {
public static final int TABLE1_DIR = 0;
public static final int TABLE1_ITEM = 1;
public static final int TABLE2_DIR = 2;
public static final int TABLE2_ITEM = 3;
private static UriMatcher uriMatcher; static {
uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
uriMatcher.addURI("com.keven.jianshu.provider", "table1", TABLE1_DIR);
uriMatcher.addURI("com.keven.jianshu.provider ", "table1/#", TABLE1_ITEM);
uriMatcher.addURI("com.keven.jianshu.provider ", "table2", TABLE2_ITEM);
uriMatcher.addURI("com.keven.jianshu.provider ", "table2/#", TABLE2_ITEM);
}
......
}
複製程式碼
- 以 query( ) 方法為例示範(insert()、update()、delete() 實現類似)
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
// 查詢table1表中的所有資料
break;
case TABLE1_ITEM:
// 查詢table1表中的單條資料
break;
case TABLE2_DIR:
// 查詢table2表中的所有資料
break;
case TABLE2_ITEM:
// 查詢table2表中的單條資料
break;
default:
break;
}
……
}
……
}
複製程式碼
除此之外,還有一個方法你會比較陌生,即 getType() 方法。它是所有的內容提供器都必 須提供的一個方法,用於獲取 Uri 物件所對應的 MIME 型別。一個內容 URI 所對應的 MIME 字串主要由三部分組分,Android 對這三個部分做了如下格式規定。
- 必須以 vnd 開頭。
- 如果內容 URI 以路徑結尾,則後接 android.cursor.dir/,如果內容 URI 以 id 結尾, 則後接 android.cursor.item/。
- 最後接上 vnd..
。
所以,對於 content://com.example.app.provider/table1 這個內容 URI,它所對應的 MIME 型別就可以寫成:
vnd.android.cursor.dir/vnd.com.example.app.provider.table1
複製程式碼
對於 content://com.example.app.provider/table1/1 這個內容 URI,它所對應的 MIME 型別 就可以寫成:
vnd.android.cursor.item/vnd.com.example.app.provider.table1
複製程式碼
則我們的自定義 Provider 可以完善為
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)) {
case TABLE1_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table1";
case TABLE1_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table1";
case TABLE2_DIR:
return "vnd.android.cursor.dir/vnd.com.example.app.provider.table2";
case TABLE2_ITEM:
return "vnd.android.cursor.item/vnd.com.example.app.provider.table2";
default:
break;
}
return null;
}
複製程式碼
- 在清單檔案中註冊
<provider
android:authorities="com.keven.jianshu.Part1dMyProvider"
android:name=".part1.Part1dMyProvider"
android:exported="true"/>
複製程式碼
- name:ContentProvider 的全稱類名。
- authorities:唯一標識了一個 ContentProvider,外部應用通過該屬性值來訪問我們的 ContentProvider。因此該屬性值必須是唯一的,建議在命名時以包名為字首。
- exported:表明是否允許其他應用呼叫 ContentProvider,true 表示支援,false 表示不支援。預設值根據開發者的屬性設定而會有所不同,如果包含 Intent-Filter 則預設值為 true,否則為 false。
到這裡,一個完整的內容提供器就建立完成了,現在任何一個應用程式都可以使用 ContentResolver 來訪問我們程式中的資料。
文章已經讀到末尾了,不知道最初的幾個問題你都會了嗎?如果不會的話?可以再針對不會的問題進行精讀哦!答案都在文中,相信你肯定可以解決的!