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

終於又回到組件篇,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如圖所示:

可以看出內容 URI 可以非常清楚地表達出我們想要訪問哪個程序中哪張表裏的數據,但還沒完,還需要將它解析成 Uri 對象纔可以作爲參數傳入。通過調用 Uri.parse()方法,就可以將內容 URI 字符串解析成 Uri 對象了,代碼如下:

(2)ContentResolve類

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

所以其他程序若想要訪問ContentProvider中共享的數據的方法是:
第一步:通過Context 中的getContentResolver()方法實例化一個ContentResolve對象。
第二步:調用該對象的增刪改查方法去操作ContentProvider中的數據。
接下來通過讀取聯繫人電話的小例子體驗這個過程:

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

先在虛擬機上手動添加兩個聯繫人和電話:

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

在活動中的onCreate()方法中,首先獲取ListView控件的實例,並給它設置適配器,用ArrayAdapter就可以;然後調用init()方法去實現讀取聯繫人電話的需求。

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

因爲讀取系統聯繫人也是需要聲明權限的,一定在配置文件中聲明好:

另外,Android6.0以上的系統要求部分權限還要手動申請,因此在活動中務必要添加一段代碼:

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

 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;
        }

需要運行時權限的就是下圖這幾個:

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

現在就能看到之前添加的兩個聯繫人的姓名和電話了!

3.創建自己的Provider

(1)UriMater類

UriMater類有匹配內容URI的功能,在這裏常用它的兩個方法:一個是addURI()方法用來傳入URI,它接收三個參數(權限,路徑,一個自定義代碼);另一個是match()方法用來匹配URI,接受一個Uri對象,返回值是某個能夠匹配這個Uri對象所對應的自定義代碼,利用這個自定義代碼,就可以判斷出調用方期望訪問的是哪張表中的數據了。

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

步驟一:新建一個類去繼承ContentProvider。
步驟二:重寫ContentProvider的六個抽象方法,方法及含義如圖:

步驟三:在配置文件中進行註冊,並註明屬性:
android:authorities即Provider的權限,形式是包名.provider
android:name即Provider的全名,形式是包名.類名
android:exported=”true”指明該Provider可被其它程序訪問。

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

(3)例子:爲student.db創建MyProvider

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

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

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

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

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

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

再看insert()方法,同樣的,先獲取到SQLiteDatabase的實例,然後根據傳入的Uri參數判斷出用戶想要往哪張表裏添加數據,再調用SQLiteDatabase的insert()方法進行添加就可以了。注意insert()方法要求返回一個能夠表示這條新增數據的 URI,所以還需要調用Uri.parse()方法將一個以新增數據的id結尾的內容URI解析成Uri對象。

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

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

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

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

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

現在運行這個程序,分別進行以下幾個測試,觀察打印出的數據的變化,和預想是一樣的!

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

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章