苦心人天不負(fù)臥薪嘗膽三千越甲可吞吳,有志者天不負(fù)釜底抽薪百二秦川終屬楚。這是一對非常勵志的名言,每當(dāng)讀這句話都會被震撼一下,然后接著頹廢,哈哈,最近的工作比較忙,也在這里提醒自己,一定要堅持下去,一定要堅持一件對自己有益的事情。
裝逼到此進(jìn)入正題,今天要討論的主要內(nèi)容是ContentProvider(內(nèi)容提供者),ContentProvider也是android的四大組件之一,可見其在android中的重要性,可能大家用ContentProvider比其他三個組件用的少一點,但是ContentProvider同樣的非常重要,有的人知道怎么使用ContentProvider,但是對于ContentProvider的原理等,并沒有搞清楚,沒關(guān)系,通過本篇博客相信你會對ContentProvider有一個全新的認(rèn)識。
通過本篇博客你將學(xué)到以下知識
①什么是內(nèi)容提供者
②為什么會有內(nèi)容提供者
③怎樣使用內(nèi)容提供者
④ContentProvider中的Uri的詳細(xì)介紹
⑤ContentResolver講解
⑥UriMatch用法介紹
⑦ContentObserver用法詳解
⑧通過一個案例來講解自定義ContentProvider的執(zhí)行過程(下一篇將給大家?guī)碚{(diào)用系統(tǒng)的ContentProvider)
1.什么是內(nèi)容提供者?
首先我們必須要明白的是ContentProvider(內(nèi)容提供者)是android中的四大組件之一,但是在一般的開發(fā)中,可能使用比較少。ContentProvider為不同的軟件之間數(shù)據(jù)共享,提供統(tǒng)一的接口。而且ContentProvider是以類似數(shù)據(jù)庫中表的方式將數(shù)據(jù)暴露,也就是說ContentProvider就像一個“數(shù)據(jù)庫”。那么外界獲取其提供的數(shù)據(jù),也就應(yīng)該與從數(shù)據(jù)庫中獲取數(shù)據(jù)的操作基本一樣,只不過是采用URI來表示外界需要訪問的“數(shù)據(jù)庫”。至于如何從URI中識別出外界需要的是哪個“數(shù)據(jù)庫”這就是Android底層需要做的事情了,也就是說,如果我們想讓其他的應(yīng)用使用我們自己程序內(nèi)的數(shù)據(jù),就可以使用ContentProvider定義一個對外開放的接口,從而使得其他的應(yīng)用可以使用我們自己應(yīng)用中的文件、數(shù)據(jù)庫內(nèi)存儲的信息。當(dāng)然,自己開發(fā)的應(yīng)用需要給其他應(yīng)用共享信息的需求可能比較少見,但是在Android系統(tǒng)中,很多數(shù)據(jù)如:聯(lián)系人信息、短信信息、圖片庫、音頻庫等,這些信息在開發(fā)中還是經(jīng)常用到的,這些信息谷歌工程師已經(jīng)幫我們封裝好了,我們可以使用谷歌給我的Uri去直接訪問這些數(shù)據(jù)。所以對于ContentProvider我們還是需要認(rèn)真的學(xué)習(xí)的,在遇到獲取聯(lián)系人信息,圖片庫,音視頻庫等需求的時候,才能更好的實現(xiàn)功能。
它的作用是在ContentProvider添加一個用于匹配的Uri,當(dāng)匹配成功時返回code。Uri可以是精確的字符串,Uri中帶有*表示可匹配任意text,#表示只能匹配數(shù)字。
③public int match(Uri uri) 這里的Uri就是傳過來的要進(jìn)行驗證,匹配的Uri假如傳過來的是:content://com.example.test/student/#,則content://com.example.test/student/10可以匹配成功,這里的10可以使任意的數(shù)字。
public final void registerContentObserver (Uri uri, boolean notifyForDescendents, ContentObserver observer)
注冊一個觀察者實例,當(dāng)指定的Uri發(fā)生改變時,這個實例會回調(diào)實例對象做相應(yīng)處理。
參數(shù):uri:需要觀察的Uri
notifyForDescendents:如果為true表示以這個Uri為開頭的所有Uri都會被匹配到,
如果為false表示精確匹配,即只會匹配這個給定的Uri。
舉個例子,假如有這么幾個Uri:
①content://com.example.studentProvider/student
②content://com.example.studentProvider/student/#
③content://com.example.studentProvider/student/10
④content://com.example.studentProvider/student/teacher
假如觀察的Uri為content://com.example.studentProvider/student,當(dāng)notifyForDescendents為true時則以這個Uri開頭的Uri的數(shù)據(jù)變化時都會被捕捉到,在這里也就是①②③④的Uri的數(shù)據(jù)的變化都能被捕捉到,當(dāng)notifyForDescendents為false時則只有①中Uri變化時才能被捕捉到。
看到registerContentObserver 這個方法,根據(jù)語言基礎(chǔ)我想大家能夠想到ContentResolver中的另一個方法
public final voidunregisterContentObserver(ContentObserverobserver)它的作用就是取消對注冊的那個Uri的觀察,這里傳進(jìn)去的就是在registerContentObserver中傳遞進(jìn)去的ContentObserver對象。到這關(guān)于注冊和解除注冊的ContentObserver可能大家都比較清楚了,那么問題來了,怎么去寫一個ContentObserver呢?其實它的實現(xiàn)很簡單,直接創(chuàng)建一個類繼承ContentObserver需要注意的是這里必須要實現(xiàn)它的構(gòu)造方法
public ContentObserver(Handlerhandler)
這里傳進(jìn)去的是一個Handler對象,這個Handler對象的作用一般要依賴于ContentObserver的另一個方法即
public void onChange(boolean selfChange)
這個方法的作用就是當(dāng)指定的Uri的數(shù)據(jù)發(fā)生變化時會回調(diào)該方法,此時可以借助構(gòu)造方法中的Handler對象將這個變化的消息發(fā)送給主線程,當(dāng)主線程接收到這個消息之后就可以按照我們的需求來完成相應(yīng)的操作,比如上面提到的類似于Adapter的notifyDataSetChanged()的作用,下面的案例也是完成了這個功能,準(zhǔn)備工作完成之后來看一個案例,相信這個案例會讓你對以上知識了解的更加深入。
- package com.example.contentproviderpractice;
- import android.content.ContentProvider;
- import android.content.ContentUris;
- import android.content.ContentValues;
- import android.content.UriMatcher;
- import android.database.Cursor;
- import android.database.sqlite.SQLiteDatabase;
- import android.net.Uri;
- public class PeopleContentProvider extends ContentProvider {
- //這里的AUTHORITY就是我們在AndroidManifest.xml中配置的authorities,這里的authorities可以隨便寫
- private static final String AUTHORITY = "com.example.studentProvider";
- //匹配成功后的匹配碼
- private static final int MATCH_ALL_CODE = 100;
- private static final int MATCH_ONE_CODE = 101;
- private static UriMatcher uriMatcher;
- private SQLiteDatabase db;
- private DBOpenHelper openHelper;
- private Cursor cursor = null;
- //數(shù)據(jù)改變后指定通知的Uri
- private static final Uri NOTIFY_URI = Uri.parse("content://" + AUTHORITY + "/student");
- //在靜態(tài)代碼塊中添加要匹配的 Uri
- static {
- //匹配不成功返回NO_MATCH(-1)
- uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
- /**
- * uriMatcher.addURI(authority, path, code); 其中
- * authority:主機(jī)名(用于唯一標(biāo)示一個ContentProvider,這個需要和清單文件中的authorities屬性相同)
- * path:路徑路徑(可以用來表示我們要操作的數(shù)據(jù),路徑的構(gòu)建應(yīng)根據(jù)業(yè)務(wù)而定)
- * code:返回值(用于匹配uri的時候,作為匹配成功的返回值)
- */
- uriMatcher.addURI(AUTHORITY, "student", MATCH_ALL_CODE);// 匹配記錄集合
- uriMatcher.addURI(AUTHORITY, "student/#", MATCH_ONE_CODE);// 匹配單條記錄
- }
- @Override
- public boolean onCreate() {
- openHelper = new DBOpenHelper(getContext());
- db = openHelper.getWritableDatabase();
- return false;
- }
- @Override
- public int delete(Uri uri, String selection, String[] selectionArgs) {
- switch (uriMatcher.match(uri)) {
- /**
- * 這里如果匹配是uriMatcher.addURI(AUTHORITY, "student",
- * MATCH_SUCCESS_CODE);中的Uri,則我們可以在這里對這個ContentProvider中的數(shù)據(jù)庫
- * 進(jìn)行刪除等操作。這里如果匹配成功,我們將刪除所有的數(shù)據(jù)
- */
- case MATCH_ALL_CODE:
- int count=db.delete("personData", null, null);
- if(count>0){
- notifyDataChanged();
- return count;
- }
- break;
- /**
- * 這里如果匹配是uriMatcher.addURI(AUTHORITY,
- * "student/#",MATCH_ONE_CODE);中的Uri,則說明我們要操作單條記錄
- */
- case MATCH_ONE_CODE:
- // 這里可以做刪除單條數(shù)據(jù)的操作。
- break;
- default:
- throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
- }
- return 0;
- }
- @Override
- public String getType(Uri uri) {
- return null;
- }
- /**
- * 插入 使用UriMatch的實例中的match方法對傳過來的 Uri進(jìn)行匹配。 這里通過ContentResolver傳過來一個Uri,
- * 用這個傳過來的Uri跟在ContentProvider中靜態(tài)代碼塊中uriMatcher.addURI加入的Uri進(jìn)行匹配
- * 根據(jù)匹配的是否成功會返回相應(yīng)的值,在上述靜態(tài)代碼塊中調(diào)用uriMatcher.addURI(AUTHORITY,
- * "student",MATCH_CODE)這里的MATCH_CODE
- * 就是匹配成功的返回值,也就是說假如返回了MATCH_CODE就表示這個Uri匹配成功了
- * ,我們就可以按照我們的需求就行操作了,這里uriMatcher.addURI(AUTHORITY,
- * "person/data",MATCH_CODE)加入的Uri為:
- * content://com.example.studentProvider/student
- * ,如果傳過來的Uri跟這個Uri能夠匹配成功,就會按照我們設(shè)定的步驟去執(zhí)行相應(yīng)的操作
- */
- @Override
- public Uri insert(Uri uri, ContentValues values) {
- int match=uriMatcher.match(uri);
- if(match!=MATCH_ALL_CODE){
- throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
- }
- long rawId = db.insert("personData", null, values);
- Uri insertUri = ContentUris.withAppendedId(uri, rawId);
- if(rawId>0){
- notifyDataChanged();
- return insertUri;
- }
- return null;
- }
- /**
- * 查詢 如果uri為
- * content://com.example.studentProvider/student則能匹配成功,然后我們可以按照需求執(zhí)行匹配成功的操作
- */
- @Override
- public Cursor query(Uri uri, String[] projection, String selection,
- String[] selectionArgs, String sortOrder) {
- switch (uriMatcher.match(uri)) {
- /**
- * 如果匹配成功,就根據(jù)條件查詢數(shù)據(jù)并將查詢出的cursor返回
- */
- case MATCH_ALL_CODE:
- cursor = db.query("personData", null, null, null, null, null, null);
- break;
- case MATCH_ONE_CODE:
- // 根據(jù)條件查詢一條數(shù)據(jù)。。。。
- break;
- default:
- throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
- }
- return cursor;
- }
- @Override
- public int update(Uri uri, ContentValues values, String selection,
- String[] selectionArgs) {
- switch (uriMatcher.match(uri)) {
- case MATCH_ONE_CODE:
- long age = ContentUris.parseId(uri);
- selection = "age = ?";
- selectionArgs = new String[] { String.valueOf(age) };
- int count = db.update("personData", values, selection,selectionArgs);
- if(count>0){
- notifyDataChanged();
- }
- break;
- case MATCH_ALL_CODE:
- // 如果有需求的話,可以對整個表進(jìn)行操作
- break;
- default:
- throw new IllegalArgumentException("Unkwon Uri:" + uri.toString());
- }
- return 0;
- }
- //通知指定URI數(shù)據(jù)已改變
- private void notifyDataChanged() {
- getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
- }
- }
可以看到在onCreate()方法中創(chuàng)建了一個數(shù)據(jù)庫,關(guān)于數(shù)據(jù)庫的操作大家可以看此博客http://blog.csdn.net/dmk877/article/details/44876805。這里就不多做介紹了。注意這個案例牽扯到兩個項目,一個是包含我們自定義的ContentProvider,另一個項目是訪問這個包含ContentProvider項目中的數(shù)據(jù)。- <provider
- android:name="com.example.contentproviderpractice.PeopleContentProvider"
- android:authorities="com.example.student"
- android:exported="true" >
- </provider>
- package com.example.otherapplication;
- import java.util.ArrayList;
- import com.example.otherapplication.adapter.MyAdapter;
- import com.example.otherapplication.bean.Student;
- import com.example.otherapplication.observer.PersonOberserver;
- import android.net.Uri;
- import android.os.Bundle;
- import android.os.Handler;
- import android.app.Activity;
- import android.content.ContentResolver;
- import android.content.ContentUris;
- import android.content.ContentValues;
- import android.database.Cursor;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.ListView;
- public class MainActivity extends Activity implements OnClickListener {
- private ContentResolver contentResolver;
- private ListView lvShowInfo;
- private MyAdapter adapter;
- private Button btnInit;
- private Button btnInsert;
- private Button btnDelete;
- private Button btnUpdate;
- private Button btnQuery;
- private Cursor cursor;
- private static final String AUTHORITY = "com.example.studentProvider";
- private static final Uri STUDENT_ALL_URI = Uri.parse("content://" + AUTHORITY + "/student");
- protected static final String TAG = "MainActivity";
- private Handler handler=new Handler(){
- public void handleMessage(android.os.Message msg) {
- //在此我們可以針對數(shù)據(jù)改變后做一些操作,比方說Adapter.notifyDataSetChanged()等,根據(jù)業(yè)務(wù)需求來定。。
- cursor = contentResolver.query(STUDENT_ALL_URI, null, null, null,null);
- adapter.changeCursor(cursor);
- };
- };
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- lvShowInfo=(ListView) findViewById(R.id.lv_show_info);
- initData();
- }
- private void initData() {
- btnInit=(Button) findViewById(R.id.btn_init);
- btnInsert=(Button) findViewById(R.id.btn_insert);
- btnDelete=(Button) findViewById(R.id.btn_delete);
- btnUpdate=(Button) findViewById(R.id.btn_update);
- btnQuery=(Button) findViewById(R.id.btn_query);
- btnInit.setOnClickListener(this);
- btnInsert.setOnClickListener(this);
- btnDelete.setOnClickListener(this);
- btnUpdate.setOnClickListener(this);
- btnQuery.setOnClickListener(this);
- contentResolver = getContentResolver();
- //注冊內(nèi)容觀察者
- contentResolver.registerContentObserver(STUDENT_ALL_URI,true,new PersonOberserver(handler));
- adapter=new MyAdapter(MainActivity.this,cursor);
- lvShowInfo.setAdapter(adapter);
- }
- @Override
- public void onClick(View v) {
- switch (v.getId()) {
- //初始化
- case R.id.btn_init:
- ArrayList<Student> students = new ArrayList<Student>();
- Student student1 = new Student("蒼老師",25,"一個會教學(xué)的好老師");
- Student student2 = new Student("柳巖",26,"大方");
- Student student3 = new Student("楊冪",27,"漂亮");
- Student student4 = new Student("張馨予",28,"不知道怎么評價");
- Student student5 = new Student("范冰冰",29,"。。。");
- students.add(student1);
- students.add(student2);
- students.add(student3);
- students.add(student4);
- students.add(student5);
- for (Student Student : students) {
- ContentValues values = new ContentValues();
- values.put("name", Student.getName());
- values.put("age", Student.getAge());
- values.put("introduce", Student.getIntroduce());
- contentResolver.insert(STUDENT_ALL_URI, values);
- }
- break;
- //增
- case R.id.btn_insert:
- Student student = new Student("小明", 26, "帥氣男人");
- //實例化一個ContentValues對象
- ContentValues insertContentValues = new ContentValues();
- insertContentValues.put("name",student.getName());
- insertContentValues.put("age",student.getAge());
- insertContentValues.put("introduce",student.getIntroduce());
- //這里的uri和ContentValues對象經(jīng)過一系列處理之后會傳到ContentProvider中的insert方法中,
- //在我們自定義的ContentProvider中進(jìn)行匹配操作
- contentResolver.insert(STUDENT_ALL_URI,insertContentValues);
- break;
- //刪
- case R.id.btn_delete:
- //刪除所有條目
- contentResolver.delete(STUDENT_ALL_URI, null, null);
- //刪除_id為1的記錄
- Uri delUri = ContentUris.withAppendedId(STUDENT_ALL_URI,1);
- contentResolver.delete(delUri, null, null);
- break;
- //改
- case R.id.btn_update:
- ContentValues contentValues = new ContentValues();
- contentValues.put("introduce","性感");
- //更新數(shù)據(jù),將age=26的條目的introduce更新為"性感",原來age=26的introduce為"大方".
- //生成的Uri為:content://com.example.studentProvider/student/26
- Uri updateUri = ContentUris.withAppendedId(STUDENT_ALL_URI,26);
- contentResolver.update(updateUri,contentValues, null, null);
- break;
- //查
- case R.id.btn_query:
- //通過ContentResolver獲得一個調(diào)用ContentProvider對象
- Cursor cursor = contentResolver.query(STUDENT_ALL_URI, null, null, null,null);
- //CursorAdapter的用法,參考此博客:http://blog.csdn.net/dmk877/article/details/44983491
- adapter=new MyAdapter(MainActivity.this,cursor);
- lvShowInfo.setAdapter(adapter);
- cursor = contentResolver.query(STUDENT_ALL_URI, null, null, null,null);
- adapter.changeCursor(cursor);
- break;
- }
- }
- }
- package com.example.otherapplication.observer;
- import android.database.ContentObserver;
- import android.os.Handler;
- import android.os.Message;
- public class PersonOberserver extends ContentObserver {
- private Handler handler;
- public PersonOberserver(Handler handler) {
- super(handler);
- this.handler=handler;
- }
- @Override
- public void onChange(boolean selfChange) {
- super.onChange(selfChange);
- //向handler發(fā)送消息,更新查詢記錄
- Message msg = new Message();
- handler.sendMessage(msg);
- }
- }
可以看到,在構(gòu)造方法中接收了Handler然后當(dāng)監(jiān)聽到指定的Uri的數(shù)據(jù)變化時就會通過Handler消息機(jī)制發(fā)送一條消息,然后的操作就由我們自行完成了。