數據源組件ContentProvider與其他組件不同,數據源組件並不包括特定的功能邏輯。它只是負責為應用提供數據訪問的接口。Android內置的許多數據都是使用ContentProvider形式,供開發者調用的(如視頻,音頻,圖片,通訊錄等)。如果把第三方應用比作一個黑盒子的話,ContentProvider就像是從里面延伸出來的管道,從這個管道,應用可以把一些數據共享出來,我們也可以往里面輸送數據。但是里面怎么處理數據我們看不到,也管不着。並且這個管道是有規范標准的,不是它規定的數據你塞不進這個管道。
一、ContentProvider的特征
- 我們為什么使用ContentProvider?像上幾篇寫的博客中就有好幾種方法可以跨應用來讀取數據,但ContentProvider的特點不僅僅如此。首先它作為Android中四大組件之一,(我們都知道組件的信息會被android統一管理),提供數據的跨進程無縫隙訪問,並會在進程中提供本地緩存。跨進程調用是需要時間和資源消耗的,而通過緩存可以有效的提高效率。再則ContentProvider規定了數據訪問結構,嚴謹不容易發生錯誤。然后,應用調用接口進行操作時,是一個同步的過程,也就是說,所有對數據源組件對象中的數據操作都是在消息隊列中串行執行的,我們開發者就不需要考慮復雜的並發情形。最后,數據源組件中數據存儲的方式沒有任何的限制,可以通過數據庫、文件等任意方式實現。
- 通過什么方式找到想要的ContentProvider?它是通過URI進行定位。URI,就是全局統一定位標志,通過一個結構化的字符串,唯一標識數據源的地址信息,而每個數據源組件都有一個唯一的URI標識。

ContentProvider的scheme已經由Android所規定, scheme為:content://
主機名(或叫Authority)用於唯一標識這個ContentProvider,外部調用者可以根據這個標識來找到它。
路徑(path)可以用來表示我們要操作的數據,路徑的構建應根據業務而定,如下:
要操作person表中id為10的記錄,可以構建這樣的路徑:/person/10
要操作person表中id為10的記錄的name字段, person/10/name
要操作person表中的所有記錄,可以構建這樣的路徑:/person
要操作xxx表中的記錄,可以構建這樣的路徑:/xxx
當然要操作的數據不一定來自數據庫,也可以是文件、xml或網絡等其他存儲方式,如下:
要操作xml文件中person節點下的name節點,可以構建這樣的路徑:/person/name
如果要把一個字符串轉換成Uri,可以使用Uri類中的parse()方法,如下:
Uri uri = Uri.parse("content://com.ljq.provider.personprovider/person")
二、ContentProvider的實例
我們還是通過一個實例來了解它吧。利用ContentProvider來對第三方的數據庫進行操作。
- 首先我們建一個DBHelper的類繼承SQLiteOpenHelper
package com.example.database; import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteDatabase.CursorFactory; public class DBHelper extends SQLiteOpenHelper{ private static final int VERSION=1; /** * 在SQLiteOpenHelper的子類當中,必須有該構造函數 * @param context 上下文對象 * @param name 數據庫名稱 * @param factory * @param version 當前數據庫的版本,值必須是整數並且是遞增的狀態 */ public DBHelper(Context context,String name,CursorFactory factory,int version){ super(context,name,factory,version); } public DBHelper(Context context, String name, int version){ this(context,name,null,version); } public DBHelper(Context context, String name){ this(context,name,VERSION); } @Override public void onCreate(SQLiteDatabase db) { // 數據庫首次構造時,會調用該函數,可以在這里構造表、索引,等等 System.out.println("create a database"); //execSQL用於執行SQL語句 db.execSQL("create table notebook(_id integer primary key autoincrement,title varchar(20),content text,time long)"); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 如果給定的當前數據庫版本高於已有數據庫版本,調用該函數 System.out.println("upgrade a database"); } }
這一步沒什么好解釋的,不懂的可以看一看我寫的上一篇關於數據庫操作的博文。
- 接下來我們就要新建一個MyProvider的類繼承ContentProvider
package com.example.database; 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 MyProvider extends ContentProvider { private DBHelper dh = null;// 數據庫管理對象 private SQLiteDatabase db;//獲取其中的數據庫 //UriMatcher:Creates the root node of the URI tree. //按照官方解釋,UriMatcher是一顆Uri的樹,然后利用addURI()方法往里面添加枝干,通過match()函數來查找枝干。 private static final UriMatcher MATCHER = new UriMatcher( UriMatcher.NO_MATCH); //設定匹配碼 private static final int NOTEBOOK = 1; private static final int NOTEBOOKS = 2; static { //添加枝干,並給它們加上唯一的匹配碼,以方便查找 //如果match()方法匹配content://com.example.database/notebook路徑,返回匹配碼為1 MATCHER.addURI("com.example.database", "notebook", NOTEBOOKS); //如果match()方法匹配content://com.example.database/notebook/#路徑,返回匹配碼為2 //其中#號為通配符。 MATCHER.addURI("com.example.database", "notebook/#", NOTEBOOK); } @Override public boolean onCreate() { // 創建ContentProvider對象時會調用這個函數 dh = new DBHelper(this.getContext(),"note.db");// 數據庫操作對象 db = dh.getReadableDatabase(); return false; } /** * 查詢,返回Cursor **/ @Override public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { //通過match函數,獲取匹配碼 switch (MATCHER.match(uri)) { case NOTEBOOKS: //返回數據庫操作的結果 return db.query("notebook", projection, selection, selectionArgs, null, null, sortOrder); case NOTEBOOK: //因為添加 了id,所以要把id加到where條件中 long id = ContentUris.parseId(uri); String where = "_id=" + id; if (selection != null && !"".equals(selection)) { where = selection + " and " + where; } return db.query("notebook", projection, where, selectionArgs, null, null, sortOrder); default: throw new IllegalArgumentException("Unkwon Uri:" + uri.toString()); } } //獲取Uri的類型 @Override public String getType(Uri uri) { // TODO Auto-generated method stub switch (MATCHER.match(uri)) { case NOTEBOOKS: return "com.example.Database.all/notebook"; case NOTEBOOK: return "com.example.Database.item/notebook"; default: throw new IllegalArgumentException("Unkwon Uri:" + uri.toString()); } } //插入數據 @Override public Uri insert(Uri uri, ContentValues values) { // TODO Auto-generated method stub switch (MATCHER.match(uri)) { case NOTEBOOKS: //調用數據庫的插入功能 // 特別說一下第二個參數是當title字段為空時,將自動插入一個NULL。 long rowid = db.insert("notebook", "title", values); Uri insertUri = ContentUris.withAppendedId(uri, rowid);// 得到代表新增記錄的Uri this.getContext().getContentResolver().notifyChange(uri, null); return insertUri; default: throw new IllegalArgumentException("Unkwon Uri:" + uri.toString()); } } //刪除數據 @Override public int delete(Uri uri, String selection, String[] selectionArgs) { // TODO Auto-generated method stub int count; switch (MATCHER.match(uri)) { case NOTEBOOKS: count = db.delete("notebook", selection, selectionArgs); return count; case NOTEBOOK: long id = ContentUris.parseId(uri); String where = "_id=" + id; if (selection != null && !"".equals(selection)) { where = selection + " and " + where; } count = db.delete("notebook", where, selectionArgs); return count; default: throw new IllegalArgumentException("Unkwon Uri:" + uri.toString()); } } //更新數據 @Override public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { // TODO Auto-generated method stub int count = 0; switch (MATCHER.match(uri)) { case NOTEBOOKS: count = db.update("notebook", values, selection, selectionArgs); return count; case NOTEBOOK: long id = ContentUris.parseId(uri); String where = "_id=" + id; if (selection != null && !"".equals(selection)) { where = selection + " and " + where; } count = db.update("notebook", values, where, selectionArgs); return count; default: throw new IllegalArgumentException("Unkwon Uri:" + uri.toString()); } } }
因為Uri代表了要操作的數據,所以我們經常需要解析Uri,並從Uri中獲取數據。Android系統提供了兩個用於操作Uri的工具類,分別為UriMatcher和ContentUris 。掌握它們的使用,會便於我們的開發工作。
看上去這個類很像我上次寫的DBManager類吧。其實這可以算是一個很簡單的數據操作類,關鍵地方就在於它放在了ContentProvider這個“容器”上,讓第三方應用也能訪問到己方的數據。所以想要吃透這個組件,只要透徹理解什么是Uri,怎么操作Uri就八九不離十了。 - 最后,不要忘記在配置文件中為ContentProvider注冊,因為這也是一個組件,所以無法避免了~
<provider android:name=".MyProvider" android:authorities="com.example.database" />
前面的是你的類名,后面則是關鍵地方,它是要寫在Uri中的,所以不要弄錯了。
到此,一個可以供其他應用訪問的工程就建好了,接下來我們來寫個測試工程來檢驗效果吧。
三、調用ContentProvider
在使用其他應用為你提供的ContentProvider時,你必須要知道的有兩點:(1)它的authorities值,在我這里的是“com.example.database”;(2)數據文件的結構,比如我這里要使用的是數據庫中的booknote表,它里面有着(_id,title,content,time)這些字段。只有知道了這些你才能操作ContentProvider。
- 好的,我們先新建一個工程,設置一下布局文件,效果如下

activity_main.xml<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <LinearLayout android:layout_width="match_parent" android:layout_height="90dp" android:orientation="horizontal" > <LinearLayout android:layout_width="120dp" android:layout_height="match_parent" android:orientation="vertical" > <TextView android:id="@+id/textView1" android:layout_width="match_parent" android:layout_height="45dp" android:text="Title:" android:textAlignment="center" android:textSize="25sp" /> <TextView android:id="@+id/textView2" android:layout_width="match_parent" android:layout_height="45dp" android:text="Content:" android:textAlignment="center" android:textSize="25sp" /> </LinearLayout> <LinearLayout android:layout_width="200dp" android:layout_height="match_parent" android:orientation="vertical" > <EditText android:id="@+id/editText1" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="25sp" > <requestFocus /> </EditText> <EditText android:id="@+id/editText2" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="25sp" /> </LinearLayout> </LinearLayout> <ListView android:id="@+id/listView1" android:layout_width="match_parent" android:layout_height="196dp" > </ListView> <LinearLayout android:layout_width="match_parent" android:layout_height="90dp" android:layout_weight="0.20" > <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="獲取全部信息" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="插入一條信息" /> <Button android:id="@+id/button3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="刪除一條信息" /> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="更新一條信息" /> </LinearLayout> </LinearLayout>
item.xml<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" > <TextView android:layout_width="80dip" android:layout_height="wrap_content" android:text="_id" android:id="@+id/id" /> <TextView android:layout_width="80dip" android:layout_height="wrap_content" android:text="title" android:id="@+id/title" /> <TextView android:layout_width="100dip" android:layout_height="wrap_content" android:text="content" android:id="@+id/content" /> <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="time" android:id="@+id/time" /> </LinearLayout>
- 接下來在MainActivity添加代碼
import android.view.View; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.Button; import android.widget.EditText; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.Toast; import android.app.Activity; import android.content.ContentResolver; import android.content.ContentValues; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; public class MainActivity extends Activity implements OnClickListener { private ListView listView; private SimpleCursorAdapter adapter; private Button button_query, button_insert, button_delete, button_update; private EditText editText_title, editText_content; private int CurItem; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); editText_title = (EditText) this.findViewById(R.id.editText1); editText_content = (EditText) this.findViewById(R.id.editText2); listView = (ListView) this.findViewById(R.id.listView1); listView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ListView lView = (ListView) parent; Cursor data = (Cursor) lView.getItemAtPosition(position); int _id = data.getInt(data.getColumnIndex("_id")); Toast.makeText(MainActivity.this, _id + "", Toast.LENGTH_SHORT) .show(); CurItem = _id; editText_title.setText(data.getString(data.getColumnIndex("title"))); editText_content.setText(data.getString(data.getColumnIndex("content"))); } }); button_query = (Button) this.findViewById(R.id.button1); button_query.setOnClickListener(this); button_insert = (Button) this.findViewById(R.id.button2); button_insert.setOnClickListener(this); button_delete = (Button) this.findViewById(R.id.button3); button_delete.setOnClickListener(this); button_update = (Button) this.findViewById(R.id.button4); button_update.setOnClickListener(this); } @Override public void onClick(View v) { //ContentResolver它是ContentProvider提供的一個接口,它能夠調用ContentProvider里面的所有方法。 ContentResolver contentResolver; // TODO Auto-generated method stub switch (v.getId()) { case R.id.button1: contentResolver = getContentResolver(); //Uri.parse()能將字符串轉換成Uri格式。 Uri selectUri = Uri.parse("content://com.example.database/notebook"); Cursor cursor = contentResolver.query(selectUri, null, null, null, null); adapter = new SimpleCursorAdapter(this, R.layout.item, cursor, new String[] { "_id", "title", "content", "time" }, new int[] { R.id.id, R.id.title, R.id.content, R.id.time }, 1); listView.setAdapter(adapter); break; case R.id.button2: contentResolver = getContentResolver(); Uri insertUri = Uri .parse("content://com.example.database/notebook"); ContentValues values = new ContentValues(); values.put("title", editText_title.getText().toString()); values.put("content", editText_content.getText().toString()); values.put("time", System.currentTimeMillis()); Uri uri = contentResolver.insert(insertUri, values); Toast.makeText(this, uri.toString() + "添加完成", Toast.LENGTH_SHORT) .show(); break; case R.id.button3: contentResolver = getContentResolver(); Uri deleteUri = Uri .parse("content://com.example.database/notebook/"+CurItem); int d = contentResolver.delete(deleteUri, null,null); Toast.makeText(this, CurItem+"刪除完成", Toast.LENGTH_SHORT) .show(); break; case R.id.button4: contentResolver = getContentResolver(); Uri updateUri = Uri .parse("content://com.example.database/notebook/"+CurItem); ContentValues updatevalues = new ContentValues(); updatevalues.put("title", editText_title.getText().toString()); updatevalues.put("content", editText_content.getText().toString()); updatevalues.put("time", System.currentTimeMillis()); int u = contentResolver.update(updateUri, updatevalues,null,null); Toast.makeText(this, CurItem+"更新完成", Toast.LENGTH_SHORT) .show(); break; } } }
兩個應用之間的流程圖大概就是這樣了(手挫,不要嫌棄~)

- 最后,將兩個應用安裝好,打開實踐一下。那么我們看看運行結果吧

正常運行。那么今天就到此結束,收工了~
四、結束語
理論上來說,數據源組件並沒有所謂的生命周期,因為數據源組件的狀態並不作為判定進程優先級的依據。所以系統回收進程資源時,並不會將數據源組件的銷毀事件告訴開發者。但構造ContentProvider組件時還是會調用onCreate()函數。所以,不要在數據源組件中部署延遲寫入等寫優化策略,當被系統默默回收時,一些未持久化的數據會丟失。一旦數據源組件被構造出來,就會保持長期運行的狀態至其所在的進程被系統回收。所以,也不要在數據源組件中緩存過多的數據,以免占用內存空間。
到此,Android的四大組件已經介紹完畢,他們各有各的的特色和用法,同時也是我們開發時候不可缺少的工具。希望通過這些介紹能讓大家體會到Android設計的巧妙和特征,學會正確的使用Android應用的框架。那么接下來可能會帶來的是Android的Intent機制,盡請期待~
參考文章:(1)ContentProvider和Uri詳解 http://www.cnblogs.com/linjiqin/archive/2011/05/28/2061396.html
(2) ContentProvider淺析 http://bbs.51cto.com/thread-1024108-1.html
(3) 從頭學Android之ContentProvider http://blog.csdn.net/worker90/article/details/7016430
下載:demo下載
========================================
作者:cpacm
出處:(http://www.cpacm.net/2015/03/22/Android開發日記(七)——Android四大組件之ContentProvider/)
