學習筆記|AS入門(八) 元件篇之ContentProvider

釐米姑娘發表於2017-12-21

終於又回到元件篇,Android中非常重要的四大元件--Activity、ContentProvider、BroadcastReceiver和Service,它們分工明確,共同構成了可重用、靈活、低耦合的安卓系統。通過之前的學習,我們知道Activity主要負責UI載入和頁面跳轉,接下來幾篇就依次介紹後三種元件,本篇先學習ContentProvider,目錄見下:

  • ContentProvider概要
  • 從系統提供的Provider訪問資料
    • 內容URI的組成
    • ContentResolve類
    • 例子:讀取聯絡人的電話
  • 建立自己的Provider
    • UriMater類
    • 自定義一個Provider的步驟
    • 例子:為student.db建立MyProvider

1.ContentProvider概要

ContentProvider也有儲存資料的功能,但與上一篇中學習的那三種資料儲存方法不同的是,後者儲存下的資料只能被該應用程式使用,而前者可以讓不同應用程式之間進行資料共享,它還可以選擇只對哪一部分資料進行共享,從而保證程式中的隱私資料不會有洩漏風險。所以元件ContentProvider主要負責儲存和共享資料。

ContentProvider有兩種形式:可以使用現有的內容提供者來讀取和操作相應程式中的資料,也可以建立自己的內容提供者給這個程式的資料提供外部訪問介面。下面分別學習一下。

2.從系統提供的Provider訪問資料

既然ContentProvider有對外共享資料的功能,換句話說,其他應用程式可以通過ContentProvider對應用中的資料進行增刪改查,說到這裡是否感到熟悉?上篇學習SQLite資料儲存的時候就提到過可以實現增刪改查的各種輔助性方法,實際上ContentProvider是對SQLiteOpenHelper的進一步封裝,因此它們使用的方法太像了,只不過不再用單純的表名指明被操作的表,畢竟現在是其他程式訪問它,而是用有一定格式規範的內容URI來代替。下面先來學習URI的組成。

(1)內容URI的組成

以上篇最後做的關於資料庫的demo為例,它的包名是com.example.myapplication,如果其他程式想訪問該程式student.db中的student表,那麼需要的內容URI如圖所示:

學習筆記|AS入門(八) 元件篇之ContentProvider

可以看出內容 URI 可以非常清楚地表達出我們想要訪問哪個程式中哪張表裡的資料,但還沒完,還需要將它解析成 Uri 物件才可以作為引數傳入。通過呼叫 Uri.parse()方法,就可以將內容 URI 字串解析成 Uri 物件了,程式碼如下:

學習筆記|AS入門(八) 元件篇之ContentProvider

(2)ContentResolve類

現在有了酷似“表名”的Uri,類似的,在ContentResolver類中提供的一系列用於對資料進行增刪改查操作的方法也酷似SQLiteDatabase的那些輔助性方法: insert() 方法用於新增資料, update() 方法用於更新資料, delete() 方法用於刪除資料, query() 方法用於查詢資料。它們不僅方法名一樣,連提供的引數都非常相似,見下圖,紅色部分是區別:

學習筆記|AS入門(八) 元件篇之ContentProvider

所以其他程式若想要訪問ContentProvider中共享的資料的方法是:

第一步:通過Context 中的 getContentResolver() 方法例項化一個ContentResolve物件。

第二步:呼叫該物件的增刪改查方法去操作ContentProvider中的資料。 接下來通過讀取聯絡人電話的小例子體驗這個過程:

(3)例子:讀取聯絡人的電話

先在虛擬機器上手動新增兩個聯絡人和電話:

學習筆記|AS入門(八) 元件篇之ContentProvider

這樣準備工作就做好了。然後建個佈局,這裡我們希望讀取出來的聯絡人的資訊能夠在ListView中顯示:

學習筆記|AS入門(八) 元件篇之ContentProvider

在活動中的onCreate()方法中,首先獲取ListView控制元件的例項,並給它設定介面卡,用ArrayAdapter就可以;然後呼叫init()方法去實現讀取聯絡人電話的需求。

學習筆記|AS入門(八) 元件篇之ContentProvider

在init()方法中使用了ContentResolver的query()方法來查詢系統的聯絡人資料。不過這裡傳入的 Uri 引數是一個常量ContactsContract.Contacts.CONTENT_URI,這是因為ContactsContract.Contacts類已經幫我們做好了封裝,而這個常量就是使用 Uri.parse()方法解析出來的結果。接下來的過程就很熟悉了:用Cursor 物件進行遍歷,先取出聯絡人姓名,這一列對應的常量是是ContactsContract.Contacts.DISPLAY_NAME,之後取出聯絡人的電話時,又進行一次遍歷,這是因為一個聯絡人可能有多個電話,所以需要用ID唯一標識某個聯絡人然後到另一個表找他的所有電話。等名字和電話都取出之後,將它們拼接起來再新增到 ListView裡。最後千萬不要忘記將Cursor物件關閉掉。

學習筆記|AS入門(八) 元件篇之ContentProvider

因為讀取系統聯絡人也是需要宣告許可權的,一定在配置檔案中宣告好:

學習筆記|AS入門(八) 元件篇之ContentProvider

另外,Android6.0以上的系統要求部分許可權還要手動申請,因此在活動中務必要新增一段程式碼:

學習筆記|AS入門(八) 元件篇之ContentProvider

這段程式碼很通用,不同許可權只要更換名稱就可用了:

 final private int REQUEST_CODE_ASK_PERMISSIONS = 123;

  int hasWriteContactsPermission = ContextCompat.checkSelfPermission(this,Manifest.permission.READ_CONTACTS);
        if(hasWriteContactsPermission != PackageManager.PERMISSION_GRANTED) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {

                requestPermissions(new String[]{Manifest.permission.READ_CONTACTS},REQUEST_CODE_ASK_PERMISSIONS);
            }
            return;
        }
複製程式碼

需要執行時許可權的就是下圖這幾個:

學習筆記|AS入門(八) 元件篇之ContentProvider

執行程式跑一下看看吧!下圖展示的內容就是上面一段程式碼的作用,選擇ALLOW:

學習筆記|AS入門(八) 元件篇之ContentProvider

現在就能看到之前新增的兩個聯絡人的姓名和電話了!

學習筆記|AS入門(八) 元件篇之ContentProvider

3.建立自己的Provider

(1)UriMater類

UriMater類有匹配內容URI的功能,在這裡常用它的兩個方法:一個是 addURI() 方法用來傳入URI,它接收三個引數(許可權,路徑,一個自定義程式碼);另一個是 match() 方法用來匹配URI,接受一個Uri物件,返回值是某個能夠匹配這個Uri物件所對應的自定義程式碼,利用這個自定義程式碼,就可以判斷出呼叫方期望訪問的是哪張表中的資料了。

(2)自定義一個Provider的步驟

步驟一:新建一個類去繼承ContentProvider。

步驟二:重寫ContentProvider的六個抽象方法,方法及含義如圖:

學習筆記|AS入門(八) 元件篇之ContentProvider

步驟三:在配置檔案中進行註冊,並註明屬性:

android:authorities即Provider的許可權,形式是包名.provider

android:name即Provider的全名,形式是包名.類名

android:exported="true" 指明該Provider可被其它程式訪問。

以上這些知識還是有點抽象,那還是再來個例子更深刻感受一下吧!

(3)例子:為student.db建立MyProvider

接下里還是給上篇的資料庫demo建立一個自定義提供器MyProvider,然後在別的應用程式中通過MyProvider去操作student.db中的資料。

先修改MyHelper,將Toast提示語句都去掉,因為跨程式訪問時我們不能直接使用 Toast。

學習筆記|AS入門(八) 元件篇之ContentProvider

然後開始自定義提供器吧!一開始定義了四個常量,分別表示訪問student表中的所有資料、訪問student表中的單條資料(student/#用於表示student表中任意一行記錄)、訪問course表中的所有資料和訪問course表中的單條資料。然後在靜態程式碼塊裡對UriMatcher進行了初始化操作,將期望匹配的幾種URI格式新增了進去。

學習筆記|AS入門(八) 元件篇之ContentProvider

接下來就是六個抽象方法的具體實現了,先看onCreate()方法,這裡建立了一個MyHelper的例項,然後返回true表示內容提供器初始化成功,現在資料庫就已經完成了建立或升級操作。

學習筆記|AS入門(八) 元件篇之ContentProvider

接下來是 getType()方法,需要返回一個MIME字串。一個內容URI所對應的MIME字串主要由三部分組分,Android對這三個部分做了以下格式規定:必須以vnd開頭;如果內容URI以路徑結尾,則後接android.cursor.dir/,如果內容URI以id結尾,則後接android.cursor.item/;最後接上vnd.< authority>.< path>。所以四個內容URI對應的MIME字串分別是:

學習筆記|AS入門(八) 元件篇之ContentProvider

在query()方法裡先獲取到SQLiteDatabase的例項,然後根據傳入的Uri引數判斷出使用者想要訪問哪張表,再呼叫SQLiteDatabase的query()進行查詢並將Cursor物件返回就好了。注意當訪問的是單條資料時呼叫了Uri物件的getPathSegments()方法,它會將內容URI許可權之後的部分以“/”符號進行分割,並把分割後的結果放入到一個字串列表中,那這個列表的第0個位置存放的就是路徑,第1個位置存放的就是id了。得到了id之後,再通過selection和selectionArgs引數進行約束,就實現了查詢單條資料的功能。

學習筆記|AS入門(八) 元件篇之ContentProvider

再看insert()方法,同樣的,先獲取到SQLiteDatabase的例項,然後根據傳入的Uri引數判斷出使用者想要往哪張表裡新增資料,再呼叫SQLiteDatabase的insert()方法進行新增就可以了。注意insert()方法要求返回一個能夠表示這條新增資料的 URI,所以還需要呼叫Uri.parse()方法將一個以新增資料的id結尾的內容URI解析成Uri物件。

學習筆記|AS入門(八) 元件篇之ContentProvider

再來看delete()方法,和前面一樣的,不同的是這裡需要在呼叫SQLiteDatabase的delete()方法刪除特定記錄的同時還要把被刪除的行數作為返回值返回。

學習筆記|AS入門(八) 元件篇之ContentProvider

終於到了最後一個方法update(),和delete()相似的,在呼叫SQLiteDatabase的 update()方法進行更新的同時還要把受影響的行數作為返回值返回。

學習筆記|AS入門(八) 元件篇之ContentProvider

最後將MyProvider在AndroidManifest.xml檔案中註冊,一個自定義內容提供器終於完成了!

學習筆記|AS入門(八) 元件篇之ContentProvider

現在需要做的是將該程式從模擬器中解除安裝防止之前產生的遺留資料對後面操作有干擾,然後再執行一下重新安裝在模擬器上,啟動後直接關閉掉。接下來建立一個新的module,注意包要不同,代表其他程式。新建一個佈局test.xml並放四個按鈕:

學習筆記|AS入門(八) 元件篇之ContentProvider

接下來在活動分別處理四個按鈕的點選事件。到目前為止這是第三次用增刪改出方法去運算元據了,相信這些程式碼已經不難理解了!呼叫Uri.parse()將一個內容URI解析成Uri物件,這裡希望操作student.db中的student表。又獲取到ContentResolver物件就可以進行CRUD操作了,這裡插入兩條記錄,並且通過第一條記錄的insert()方法得到一個Uri物件,這個物件中包含了新增記錄的id,呼叫getPathSegments()方法將它取出,之後利用這個id合成新的內容URI和Uri物件方便給該條記錄進行更改和刪除的操作。查詢操作完成的列印出表中所有的資料。

學習筆記|AS入門(八) 元件篇之ContentProvider

現在執行這個程式,分別進行以下幾個測試,觀察列印出的資料的變化,和預想是一樣的!

學習筆記|AS入門(八) 元件篇之ContentProvider

關於ContentProvider的知識點就到這裡~

>下一篇預告:元件篇之BroadcastReceiver

相關文章