Android元件系列----ContentProvider內容提供者
【宣告】
歡迎轉載,但請保留文章原始出處→_→
生命壹號:http://www.cnblogs.com/smyhvae/
文章來源:http://www.cnblogs.com/smyhvae/p/4108017.html
【正文】
一、ContentProvider簡介:
ContentProvider內容提供者(四大元件之一)主要用於在不同的應用程式之間實現資料共享的功能。
ContentProvider可以理解為一個Android應用對外開放的介面,只要是符合它所定義的Uri格式的請求,均可以正常訪問執行操作。其他的Android應用可以使用ContentResolver物件通過與ContentProvider同名的方
Android附帶了許多有用的ContentProvider,但是本文暫時不涉及到這麼多(本文將學習如何建立自己的ContentProvider)。Android中自帶的ContentProvider包括:
- Browser:儲存如瀏覽器的資訊。
- CallLog:儲存通話記錄等資訊。
- Contacts Provider:儲存聯絡人(通訊錄)等資訊。
- MediaStore:儲存媒體檔案的資訊。
- Settings:儲存裝置的設定和首選項資訊。
此外,還有日曆、
ContentProvider的方法:
如果要建立自己的內容提供者,需要新建一個類繼承抽象類ContentProvider,並重寫其中的抽象方法。抽象方法如下:
boolean onCreate() 初始化提供者 Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 查詢資料,返回一個數據Cursor物件。其中引數selection和selectionArgs是外部程式提供的查詢條件 Uri insert(Uri uri, ContentValues values) 插入一條資料。引數values是需要插入的值int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 根據條件更新資料 int delete(Uri uri, String selection, String[] selectionArgs) 根據條件刪除資料 String getType(Uri uri) 返回MIME型別對應內容的URI
除了onCreate()和getType()方法外,其他的均為CRUD操作,這些方法中,Uri引數為與ContentProvider匹配的請求Uri,剩下的引數可以參見SQLite的CRUD操作,基本一致。
備註:還有兩個非常有意思的方法,必須要提一下,call()和bulkInsert()方法,使用call,理論上可以在ContentResolver中執行ContentProvider暴露出來的任何方法,而bulkInsert()方法用於插入多條資料。
Uri:
在Android中,Uri是一種比較常見的資源訪問方式。而對於ContentProvider而言,Uri也是有固定格式的:<srandard_prefix>://<authority>/<data_path>/<id>
- <srandard_prefix>:ContentProvider的srandard_prefix始終是content://。
- <authority>:ContentProvider的名稱。
- <data_path>:請求的資料型別。
- <id>:指定請求的特定資料。
在ContentProvider的CRUD操作,均會傳遞一個Uri物件,通過這個物件來匹配對應的請求。那麼如何確定一個Uri執行哪項操作呢?需要用到一個UriMatcher物件,這個物件用來幫助內容提供者匹配Uri。它所提供的方法非常簡單,僅有兩個:
- void addURI(String authority,String path,int code):新增一個Uri匹配項,authority為AndroidManifest.xml中註冊的ContentProvider中的authority屬性;path為一個路徑,可以設定萬用字元,#表示任意數字,*表示任意字元;code為自定義的一個Uri程式碼。
- int match(Uri uri):匹配傳遞的Uri,返回addURI()傳遞的code引數。
二、程式碼舉例:
最終所有工程檔案的目錄結構如下:
PersonDao是增刪改查資料庫的工具類,並在PersonContentProvider中得到呼叫。DBHelper用於初始化SQLite資料庫。
PersonContentProvider用於向外提供增刪改查的介面。並最終在ContentResolverTest的MyTest.java中進行單元測試,實現CRUD。
本文的核心類是:PersonContentProvider和MyTest
下面來看一下具體的實現步驟。
新建工程檔案ContetProviderTest01。
(1)新建類PersonDao:用於進行對SQLite的CRUD操作。程式碼如下:
PersonDao.java:
1 package com.example.contentprovidertest01.dao; 2 3 import android.content.ContentValues; 4 import android.content.Context; 5 import android.database.Cursor; 6 import android.database.sqlite.SQLiteDatabase; 7 8 import com.example.contentprovidertest01.db.DBHelper; 9 10 public class PersonDao { 11 private DBHelper helper = null; 12 13 public PersonDao(Context context) { 14 helper = new DBHelper(context); 15 } 16 17 //方法:插入操作,返回的long型別為:插入當前行的行號 18 public long insertPerson(ContentValues values) { 19 long id = -1; 20 SQLiteDatabase database = null; 21 try { 22 database = helper.getWritableDatabase(); 23 id = database.insert("person", null, values); 24 } catch (Exception e) { 25 e.printStackTrace(); 26 } finally { 27 if (database != null) { 28 database.close(); 29 } 30 } 31 return id; 32 } 33 34 public int deletePerson(String whereClause, String[] whereArgs) { 35 int count = -1; 36 SQLiteDatabase database = null; 37 try { 38 database = helper.getWritableDatabase(); 39 count = database.delete("person", whereClause, whereArgs); 40 } catch (Exception e) { 41 e.printStackTrace(); 42 } finally { 43 if (database != null) { 44 database.close(); 45 } 46 } 47 return count; 48 } 49 50 public int updatePerson(ContentValues values, String whereClause, 51 String[] whereArgs) { 52 SQLiteDatabase database = null; 53 int count = -1; 54 try { 55 database = helper.getWritableDatabase(); 56 count = database.update("person", values, whereClause, whereArgs); 57 } catch (Exception e) { 58 e.printStackTrace(); 59 } finally { 60 if (null != database) { 61 database.close(); 62 } 63 } 64 return count; 65 } 66 67 public Cursor queryPersons(String selection, String[] selectionArgs) { 68 SQLiteDatabase database = null; 69 Cursor cursor = null; 70 try { 71 database = helper.getReadableDatabase(); 72 cursor = database.query(true, "person", null, selection, 73 selectionArgs, null, null, null, null); 74 } catch (Exception e) { 75 e.printStackTrace(); 76 } finally { 77 if (null != database) { 78 // database.close(); 79 } 80 } 81 return cursor; 82 } 83 84 }
(2)新建類DBHelper:用於初始化SQLiate資料庫
DBHelper.java:
1 package com.example.contentprovidertest01.db; 2 3 import android.content.Context; 4 import android.database.sqlite.SQLiteDatabase; 5 import android.database.sqlite.SQLiteOpenHelper; 6 7 public class DBHelper extends SQLiteOpenHelper { 8 9 private static String name = "mydb.db"; // 資料庫的名字 10 private static int version = 1; // 資料庫的版本 11 12 public DBHelper(Context context) { 13 super(context, name, null, version); 14 } 15 16 @Override 17 public void onCreate(SQLiteDatabase db) { 18 // 只能支援基本資料型別:varchar int long float boolean text blob clob 19 // 建表語句執行 20 String sql = "create table person(id integer primary key autoincrement,name varchar(64),address varchar(64))"; 21 db.execSQL(sql); 22 } 23 24 @Override 25 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 26 // TODO Auto-generated method stub 27 String sql = "alter table person add sex varchar(8)"; 28 db.execSQL(sql); 29 } 30 31 }
(3)【核心】新建類PersonContentProvider,繼承ContetProvider
PersonContentProvider.java:
1 package com.example.contentprovidertest01; 2 3 import com.example.contentprovidertest01.dao.PersonDao; 4 5 import android.content.ContentProvider; 6 import android.content.ContentUris; 7 import android.content.ContentValues; 8 import android.content.UriMatcher; 9 import android.database.Cursor; 10 import android.net.Uri; 11 import android.os.Bundle; 12 import android.util.Log; 13 14 public class PersonContentProvider extends ContentProvider { 15 16 private final String TAG = "PersonContentProvider"; 17 private PersonDao personDao = null; 18 private static final UriMatcher URI_MATCHER = new UriMatcher( 19 UriMatcher.NO_MATCH);// 預設的規則是不匹配的 20 private static final int PERSON = 1; // 操作單行記錄 21 private static final int PERSONS = 2; // 操作多行記錄 22 // 往UriMatcher中新增匹配規則。注意,這裡面的url不要寫錯了,我就是因為寫錯了,半天沒調試出來。哎··· 23 static { 24 // 新增兩個URI篩選 25 URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider", 26 "person", PERSONS); 27 // 使用萬用字元#,匹配任意數字 28 URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider", 29 "person/#", PERSON); 30 } 31 32 public PersonContentProvider() { 33 34 } 35 36 @Override 37 public boolean onCreate() { 38 // 初始化一個數據持久層 39 personDao = new PersonDao(getContext()); 40 //Log.i(TAG, "--->>onCreate()被呼叫"); 41 return true; 42 } 43 44 @Override 45 public Uri insert(Uri uri, ContentValues values) { 46 Uri resultUri = null; 47 // 解析Uri,返回Code 48 int flag = URI_MATCHER.match(uri); 49 switch (flag) { 50 case PERSONS: 51 //呼叫資料庫的訪問方法 52 long id = personDao.insertPerson(values); //執行插入操作的方法,返回插入當前行的行號 53 resultUri = ContentUris.withAppendedId(uri, id); 54 Log.i(TAG,"--->>插入成功, id=" + id); 55 Log.i(TAG,"--->>插入成功, resultUri=" + resultUri.toString()); 56 System.out.println("insert success"); 57 break; 58 } 59 return resultUri; 60 } 61 62 //方法:刪除記錄。注:引數:selection和selectionArgs是查詢的條件,是由外部(另一個應用程式)傳進來的 63 @Override 64 public int delete(Uri uri, String selection, String[] selectionArgs) { 65 int count = -1; //影響資料庫的行數 66 try { 67 int flag = URI_MATCHER.match(uri); 68 switch (flag) { 69 case PERSON: 70 // delete from student where id=? 71 // 單條資料,使用ContentUris工具類解析出結尾的Id 72 long id = ContentUris.parseId(uri); 73 String where_value = "id = ?"; 74 String[] args = { String.valueOf(id) }; 75 count = personDao.deletePerson(where_value, args); 76 break; 77 case PERSONS: 78 count = personDao.deletePerson(selection, selectionArgs); 79 break; 80 } 81 } catch (Exception e) { 82 e.printStackTrace(); 83 } 84 Log.i(TAG, "--->>刪除成功,count=" + count); 85 return count; 86 } 87 88 @Override 89 public int update(Uri uri, ContentValues values, String selection, 90 String[] selectionArgs) { 91 int count = -1; 92 try { 93 int flag = URI_MATCHER.match(uri); 94 switch (flag) { 95 case PERSON: 96 long id = ContentUris.parseId(uri); 97 String where_value = " id = ?"; 98 String[] args = { String.valueOf(id) }; 99 count = personDao.updatePerson(values, where_value, args); 100 break; 101 case PERSONS: 102 count = personDao 103 .updatePerson(values, selection, selectionArgs); 104 break; 105 } 106 } catch (Exception e) { 107 e.printStackTrace(); 108 } 109 Log.i(TAG, "--->>更新成功,count=" + count); 110 return count; 111 } 112 113 @Override 114 public Cursor query(Uri uri, String[] projection, String selection, 115 String[] selectionArgs, String sortOrder) { 116 Cursor cursor = null; 117 try { 118 int flag = URI_MATCHER.match(uri); 119 switch (flag) { 120 case PERSON: 121 long id = ContentUris.parseId(uri); 122 String where_value = " id = ?"; 123 String[] args = { String.valueOf(id) }; 124 cursor = personDao.queryPersons(where_value, args); 125 break; 126 case PERSONS: 127 cursor = personDao.queryPersons(selection, selectionArgs); 128 break; 129 } 130 } catch (Exception e) { 131 e.printStackTrace(); 132 } 133 Log.i(TAG, "--->>查詢成功,Count=" + cursor.getCount()); 134 return cursor; 135 } 136 137 @Override 138 public String getType(Uri uri) { 139 int flag = URI_MATCHER.match(uri); 140 switch (flag) { 141 case PERSON: 142 return "vnd.android.cursor.item/person"; // 如果是單條記錄,則為vnd.android.cursor.item/ 143 // + path 144 145 case PERSONS: 146 return "vnd.android.cursor.dir/persons"; // 如果是多條記錄,則為vnd.android.cursor.dir/ 147 // + path 148 } 149 return null; 150 } 151 152 @Override 153 public Bundle call(String method, String arg, Bundle extras) { 154 Log.i(TAG, "--->>" + method); 155 Bundle bundle = new Bundle(); 156 bundle.putString("returnCall", "call被執行了"); 157 return bundle; 158 } 159 }
18行的UriMatcher類的作用是:匹配內容uri,預設的規則是不匹配的。UriMatcher提供了一個addURI方法:
- void android.content.UriMatcher.addURI(String authority, String path, int code)
這三個引數分別代表:許可權、路徑、和一個自定義程式碼。一般第一個引數是uri(包名.內容提供者的類名),第二個引數一般是資料庫的表名。
27行:匹配規則的解釋:*表示匹配任意字元,#表示匹配任意數字。注:如果內部的匹配規則越多,越容易訪問。
138行的getType(Uri uri)方法:所有的內容提供者都必須提供的一個方法。用於獲取uri物件所對應的MIME型別。
然後,每編寫一個內容提供者,都必須在清單檔案中進行宣告。在AndroidManifest.xml中<application>節點中增加,格式如下:
<provider android:name=".內容提供者的類名" android:authorities="包名.內容提供者的類名" > </provider>
第3行表示的是uri路徑,畢竟Contet Provider是通過路徑來訪問的。
所以在本程式中,在AndroidManifest.xml的<application>節點中增加如下程式碼:
<provider android:name=".PersonContentProvider" android:authorities="com.example.contentprovidertest01.PersonContentProvider" > </provider>
(4)單元測試類:
這裡需要涉及到另外一個知識:ContentResolver內容訪問者。
要想訪問ContentProvider,則必須使用ContentResolver。可以通過ContentResolver來操作ContentProvider所暴露處理的介面。一般使用Content.getContentResolver()方法獲取ContentResolver物件。第一段中已經提到:ContentProvider有很多對外可以訪問的方法,在ContentResolver中均有同名的方法,是一一對應的。所以它也存在insert、query、update、delete等方法。於是單元測試類可以這樣寫:(注:單元測試如果不清楚,可以參考另外一篇文章: JUnit單元測試的使用)
MyTest.java:
1 package com.example.contentresolvertest; 2 3 import android.content.ContentResolver; 4 import android.content.ContentValues; 5 import android.database.Cursor; 6 import android.net.Uri; 7 import android.os.Bundle; 8 import android.test.AndroidTestCase; 9 import android.util.Log; 10 11 public class MyTest extends AndroidTestCase { 12 13 public MyTest() { 14 // TODO Auto-generated constructor stub 15 16 } 17 18 public void calltest() { 19 ContentResolver contentResolver = getContext().getContentResolver(); 20 Uri uri = Uri 21 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 22 Bundle bundle = contentResolver.call(uri, "method", null, null); 23 String returnCall = bundle.getString("returnCall"); 24 Log.i("main", "-------------->" + returnCall); 25 } 26 27 //測試方法:向資料庫中新增記錄。如果之前沒有資料庫,則會自動建立 28 public void insert() { 29 // 使用內容解析者ContentResolver訪問內容提供者ContentProvider 30 ContentResolver contentResolver = getContext().getContentResolver(); 31 ContentValues values = new ContentValues(); 32 values.put("name", "生命貳號"); 33 values.put("address", "湖北"); 34 // content://authorities/person 35 // http:// 36 Uri uri = Uri 37 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 38 contentResolver.insert(uri, values); 39 } 40 41 //測試方法:刪除單條記錄。如果要刪除所有記錄:content://com.example.contentprovidertest01.PersonContentProvider/person 42 public void delete() { 43 ContentResolver contentResolver = getContext().getContentResolver(); 44 Uri uri = Uri 45 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/2");//刪除id為1的記錄 46 contentResolver.delete(uri, null, null); 47 } 48 49 //測試方法:根據條件刪除記錄。 50 public void deletes() { 51 ContentResolver contentResolver = getContext().getContentResolver(); 52 Uri uri = Uri 53 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 54 String where = "address=?"; 55 String[] where_args = { "HK" }; 56 contentResolver.delete(uri, where, where_args); //第二個引數表示查詢的條件"address=?",第三個引數表示佔位符中的具體內容 57 } 58 59 //方法:根據id修改記錄。注:很少有批量修改的情況。 60 public void update() { 61 ContentResolver contentResolver = getContext().getContentResolver(); 62 Uri uri = Uri 63 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/2"); 64 ContentValues values = new ContentValues(); 65 values.put("name", "李四"); 66 values.put("address", "上海"); 67 contentResolver.update(uri, values, null, null); 68 } 69 70 //方法:根據條件來修改記錄。 71 public void updates() { 72 ContentResolver contentResolver = getContext().getContentResolver(); 73 Uri uri = Uri 74 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person/student"); 75 ContentValues values = new ContentValues(); 76 values.put("name", "王五"); 77 values.put("address", "深圳"); 78 String where = "address=?"; 79 String[] where_args = { "beijing" }; 80 contentResolver.update(uri, values, where, where_args); 81 } 82 83 //測試方法:查詢所有記錄。如果要查詢單條記錄:content://com.example.contentprovidertest01.PersonContentProvider/person/1 84 public void query() { 85 ContentResolver contentResolver = getContext().getContentResolver(); 86 Uri uri = Uri 87 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 88 Cursor cursor = contentResolver.query(uri, null, null, null, null); 89 while (cursor.moveToNext()) { 90 Log.i("MyTest", 91 "--->>" 92 + cursor.getString(cursor.getColumnIndex("name"))); 93 } 94 } 95 96 //測試方法:根據條件查詢所有記錄。 97 public void querys() { 98 ContentResolver contentResolver = getContext().getContentResolver(); 99 Uri uri = Uri 100 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 101 String where = "address=?"; 102 String[] where_args = { "深圳" }; 103 Cursor cursor = contentResolver.query(uri, null, where, where_args, 104 null); 105 while (cursor.moveToNext()) { 106 Log.i("main", 107 "-------------->" 108 + cursor.getString(cursor.getColumnIndex("name"))); 109 } 110 } 111 112 }
既然ContetProvider實現的是跨應用訪問資料,那這個測試類Test.java就應該寫在另一個應用程式中才行。於是,我們新建另外一個工程檔案ContentResolverTest,在裡面新增單元測試,裡面的程式碼其實和上方的Test.java的程式碼是一模一樣的。執行單元測試,依然能在ContentResolverTest中實現對ContentProviderTest01中的CRUD.核心在於:使用應用1中的內容解析者ContentResolver訪問應用2中的內容提供者ContentProvider
現在執行ContentProviderTest01中的單元測試類:
1、執行insert()方法,實現插入操作。後臺列印如下:
上圖中紅框部分表明,這個uri就是代表內容提供者中,person表中,id為1的資料。
此時,開啟file Explorer,進行檢視,發現確實多了個檔案:
注意:如果SQLite中之前沒有mydb.db這個資料庫,當實現插入操作時,會自動建立mydb.db這個資料庫,並自動建立person表(因為在PersonDao類中執行了getWritableDatabase()方法)。
現在將上圖中的mydb.db匯出,然後用SQLiteExpert軟體開啟,輸入sql查詢語句,就可以看到person表中的資料了:
如果再執行insert()方法,又會繼續新增一條記錄(id是自動增長的)。
2、執行query()方法,查詢所有記錄(目前一共兩條記錄)。後臺輸出效果如下:
經測試,其他方法也都是可以執行的。
事實證明,新建的另外一個工程檔案ContentResolverTest中,在裡面執行單元測試,也是可以執行的(單元測試的程式碼不變,實現的CRUD功能也一模一樣),也就是說,能夠對ContentProviderTest01中的SQLite進行CRUD操作。例如,執行query()方法,後臺輸出如下:
這樣,我們的目的也就達到了。
【特別注意】
需要特別注意的是,程式碼中uri不要寫錯了,這些錯誤一旦發生,很難被發現。具體表現在:
1、清單檔案中:
<provider android:name=".內容提供者的類名" android:authorities="包名.內容提供者的類名" > </provider>
如:
<provider android:name=".PersonContentProvider" android:authorities="com.example.contentprovidertest01.PersonContentProvider" > </provider>
2、ContentProvider類中的UriMatcher中的uri:
1 private static final UriMatcher URI_MATCHER = new UriMatcher( 2 UriMatcher.NO_MATCH);// 預設的規則是不匹配的 3 private static final int PERSON = 1; // 操作單行記錄 4 private static final int PERSONS = 2; // 操作多行記錄 5 // 往UriMatcher中新增匹配規則。注意,這裡面的url不要寫錯了,我就是因為寫錯了,半天沒調試出來。哎··· 6 static { 7 // 新增兩個URI篩選 8 URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider", 9 "person", PERSONS); 10 // 使用萬用字元#,匹配任意數字 11 URI_MATCHER.addURI("com.example.contentprovidertest01.PersonContentProvider", 12 "person/#", PERSON); 13 }
3、ContentProvider類中的getType()方法裡面的程式碼:
1 @Override 2 public String getType(Uri uri) { 3 int flag = URI_MATCHER.match(uri); 4 switch (flag) { 5 case PERSON: 6 return "vnd.android.cursor.item/person"; // 如果是單條記錄,則為vnd.android.cursor.item/ 7 // + path 8 case PERSONS: 9 return "vnd.android.cursor.dir/persons"; // 如果是多條記錄,則為vnd.android.cursor.dir/ 10 // + path 11 } 12 return null; 13 }
4、ContentResolver類中的uri:(以insert()方法為例)
1 //測試方法:向資料庫中新增記錄。如果之前沒有資料庫,則會自動建立 2 public void insert() { 3 // 使用內容解析者ContentResolver訪問內容提供者ContentProvider 4 ContentResolver contentResolver = getContext().getContentResolver(); 5 ContentValues values = new ContentValues(); 6 values.put("name", "生命貳號"); 7 values.put("address", "湖北"); 8 // content://authorities/person 9 // http:// 10 Uri uri = Uri 11 .parse("content://com.example.contentprovidertest01.PersonContentProvider/person"); 12 contentResolver.insert(uri, values); 13 }
【工程檔案】
連結:http://pan.baidu.com/s/1hq7VO12
密碼:0a49