Android 四大元件之 ContentProvider
讀前思考
學習一門技術或者看一篇文章最好的方式就是帶著問題去學習,這樣才能在過程中有茅塞頓開、燈火闌珊的感覺,記憶也會更深刻。
- 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,規則 如下。
1.*表示匹配任意長度的任意字元
2.#:表示匹配任意長度的數字
所以,一個能夠匹配任意表 的內容 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.<authority>.<path>。
所以,對於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; }
到這裡,一個完整的內容提供器就建立完成了,現在任何一個應用程式都可以使用 ContentResolver來訪問我們程式中的資料。
文章已經讀到末尾了,不知道最初的幾個問題你都會了嗎?如果不會的話?可以再針對不會的問題進行精讀哦!答案都在文中,相信你肯定可以解決的!