掌握了如何使用SQLiteOpenHelper之後,我們就可以進行下一步的學習。本章我們將會學習如何使用ContentProvider來將數據庫方面的操作封裝起來,同時它還可以供其他應用訪問並操作數據庫。
首先我們不會急於寫代碼,而是要搞懂如何利用ContentProvider對數據庫進行操作,因為我們不會直接操作數據庫對象,而是通過URI來操作數據庫。這就好比你要獲取User表的全部內容,那麼這個URI就是content://base/user其中base是自己命名的,最好是能夠唯一。因為我們需要依靠這個區分數據庫,然後就是user是用來區分操作的是哪個表,當然你也可以不用命名為user可以是其他的名稱,最終反正要依靠代碼去判斷的。這樣我們就可以避免在活動中直接對數據庫對象操作,也方便對數據庫進行統一的維護。
新建一個LocationContentProvider類,並且繼承自ContentProvider,還要重寫該類的OnCreate、Delete、GetType、Insert、Query和Update方法。
下面是筆者設計好的結構:
1 public static string PROVIDER_NAME = "xamarin-cn.location";
2
3 private const string DATABASE_NAME = "location";
4 private const int DATABASE_VERSION = 1;
5
6 private const string USERTABLE_NAME = "tuser";
7 private const int USER = 1;
8 private const int USER_ID = 2;
9 private static IDictionary<string, string> userProjectionMap;
10 public class UserTable
11 {
12 public static Android.Net.Uri CONTENT_URI = Android.Net.Uri.Parse("content://" + PROVIDER_NAME + "/user");
13 public const string _ID = "_id";
14 public const string UserName = "username";
15 public const string UserPwd = "userpwd";
16 public const string Age = "age";
17 }
其中 PROVIDER_NAME 是URI的基址,DATABASE_NAME和DATABASE_VERSION是數據庫的命名的和版本號,下面就是tuser表的信息,分別是表名、URI標識1、URI標識2、表結構鍵值對。UserTable類中的就是該表公開的屬性,其中包含表的字段以及查詢該表的URI路徑。我們可以看到該表的URI路徑為content://xamarin-cn.location/user。
我們需要通過UriMatcher這個類來判斷URI操作的是哪個數據庫的哪個表,這樣就不需要我們自己通過字符串進行判斷,具體的初始化可以見如下代碼:
1 private static UriMatcher uriMatcher;
2 static LocationContentProvider()
3 {
4 userProjectionMap = new Dictionary<string, string>();
5 userProjectionMap.Add(UserTable._ID, UserTable._ID);
6 userProjectionMap.Add(UserTable.UserName, UserTable.UserName);
7 userProjectionMap.Add(UserTable.UserPwd, UserTable.UserPwd);
8 userProjectionMap.Add(UserTable.Age, UserTable.Age);
9 uriMatcher = new UriMatcher(UriMatcher.NoMatch);
10 uriMatcher.AddURI(PROVIDER_NAME, "user", USER);
11 uriMatcher.AddURI(PROVIDER_NAME, "user/#", USER_ID);
12 }
這裡我們通過UriMatcher的AddURI方法添加的不同類型URI,其中有針對整張表的,還有針對表中某條數據的。
這裡我就不一一介紹了直接放出代碼:
1 private LocationSqliteOpenHelper dbHelper;
2 class LocationSqliteOpenHelper : SQLiteOpenHelper
3 {
4 public LocationSqliteOpenHelper(Context context)
5 : base(context, DATABASE_NAME, null, DATABASE_VERSION)
6 {
7 }
8
9 public override void OnCreate(SQLiteDatabase db)
10 {
11 StringBuilder strSql = new StringBuilder();
12 strSql.AppendFormat("CREATE TABLE {0} (", USERTABLE_NAME);
13 strSql.AppendFormat("{0} INTEGER PRIMARY KEY NOT NULL,", UserTable._ID);
14 strSql.AppendFormat("{0} TEXT NOT NULL,", UserTable.UserName);
15 strSql.AppendFormat("{0} TEXT NOT NULL,", UserTable.UserPwd);
16 strSql.AppendFormat("{0} INTEGER NOT NULL)", UserTable.Age);
17 db.ExecSQL(strSql.ToString());
18 }
19
20 public override void OnUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
21 {
22 db.ExecSQL("DROP TABLE IF EXISTS " + USERTABLE_NAME);
23 OnCreate(db);
24 }
25 }
首先我們需要在OnCreate方法中將dbHelper初始化:
1 public override bool OnCreate()
2 {
3 dbHelper = new LocationSqliteOpenHelper(Context);
4 return true;
5 }
為了能夠方便的組織查詢語句這裡筆者使用了SQLiteQueryBuilder對象來組織,下面就是查詢的代碼:
1 public override Android.Database.ICursor Query(Android.Net.Uri uri, string[] projection, string selection, string[] selectionArgs, string sortOrder)
2 {
3 SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
4 qb.Tables = USERTABLE_NAME;
5 //根據uri判斷查詢的是某條數據還是針對整個表,從而決定條件語句
6 switch (uriMatcher.Match(uri))
7 {
8 case USER:
9 {
10 //設置需要獲取的字段
11 //userProjectionMap默認包含的所有字段
12 qb.SetProjectionMap(userProjectionMap);
13 }
14 break;
15 case USER_ID:
16 {
17 qb.SetProjectionMap(userProjectionMap);
18 //拼接條件語句
19 //其中uri.PathSegments.ElementAt(1) 將會獲取第二個片段,
20 //就是第二個“/”後台的內容,如果後面還存在“/”則獲取他們之間的內容
21 qb.AppendWhere(UserTable._ID + "=" + uri.PathSegments.ElementAt(1));
22 }
23 break;
24 }
25 SQLiteDatabase db = dbHelper.ReadableDatabase;
26 ICursor c = qb.Query(db, projection, selection, selectionArgs, null, null, " _id desc");
27 c.SetNotificationUri(Context.ContentResolver, uri);
28 return c;
29 }
代碼中的注釋已經將重點部分都介紹了,關於Query的參數可以跟數據庫對象的Query進行比較,都是一樣的只是少了一部分參數。
因為SQLite規定了id只能是數據庫自動生成的,所以在插入數據庫這塊只需要判斷操作的是哪個表,介於筆者這裡只有一個表所以沒有該項操作,下面是具體的代碼:
1 public override Android.Net.Uri Insert(Android.Net.Uri uri, ContentValues values)
2 {
3 SQLiteDatabase db = dbHelper.WritableDatabase;
4 long rowId = db.Insert(USERTABLE_NAME, null, values);
5 //拼接最終形成的URI
6 Android.Net.Uri result = ContentUris.WithAppendedId(UserTable.CONTENT_URI, rowId);
7 Context.ContentResolver.NotifyChange(result, null);
8 return result;
9 }
唯一要說明的就是在添加完數據之後要將這條數據組成的uri返回,這樣就可以方便以後的查詢。
1 public override int Update(Android.Net.Uri uri, ContentValues values, string selection, string[] selectionArgs)
2 {
3 SQLiteDatabase db = dbHelper.WritableDatabase;
4 int count = 0;
5 switch (uriMatcher.Match(uri))
6 {
7 case USER:
8 {
9 count = db.Update(USERTABLE_NAME, values, selection, selectionArgs);
10 }
11 break;
12 case USER_ID:
13 {
14 String userid = uri.PathSegments.ElementAt(1);
15 string select = "";
16 //如果還有附加的查詢語句則拼接上去
17 if (selection != null)
18 select = " AND(" + selection + ")";
19 count = db.Update(USERTABLE_NAME, values, UserTable._ID + "=" + userid + select, selectionArgs);
20 }
21 break;
22 }
23 Context.ContentResolver.NotifyChange(uri, null);
24 return count;
25 }
這裡跟查詢一樣,需要判斷是針對某條數據還是整個表。
理解了Update,刪除就簡單了,只是將db.Update方法改寫成Delete即可,代碼如下所示:
1 public override int Delete(Android.Net.Uri uri, string selection, string[] selectionArgs)
2 {
3 SQLiteDatabase db = dbHelper.WritableDatabase;
4 int count = 0;
5 switch (uriMatcher.Match(uri))
6 {
7 case USER:
8 {
9 count = db.Delete(USERTABLE_NAME, selection, selectionArgs);
10 }
11 break;
12 case USER_ID:
13 {
14 String userid = uri.PathSegments.ElementAt(1);
15 string select = "";
16 if (selection != null)
17 select = " AND(" + selection + ")";
18 count = db.Delete(USERTABLE_NAME,
19 UserTable._ID + "=" + userid + select,
20 selectionArgs);
21 }
22 break;
23 }
24 Context.ContentResolver.NotifyChange(uri, null);
25 return count;
26 }
最後就是GetType方法,只要返回空字符串即可。
現在我們回到MainActivity中使用ContentProvider對數據庫進行操作,其中最關鍵的是ContentResolver是不是跟ContentProvider是配對的?通過ContentResolver我們就可以通過URI來操作數據庫,而不需要關注具體的數據庫對象,比如下面的代碼我們進行了插入、查詢、更新和刪除操作,代碼量要比使用SQLiteOpenHelper更少,同時也便於後期的維護:
1 ContentValues values = new ContentValues(); 2 values.Put(LocationContentProvider.UserTable.UserName, "yzf"); 3 values.Put(LocationContentProvider.UserTable.UserPwd, "123"); 4 values.Put(LocationContentProvider.UserTable.Age, 23); 5 Android.Net.Uri uri = ContentResolver.Insert(LocationContentProvider.UserTable.CONTENT_URI, values); 6 7 var c = ContentResolver.Query(uri, null, null, null, null); 8 c.MoveToFirst(); 9 string username = c.GetString(c.GetColumnIndex(LocationContentProvider.UserTable.UserName)); 10 11 values.Clear(); 12 values.Put(LocationContentProvider.UserTable.UserName, "zn"); 13 ContentResolver.Update(uri, values, null, null); 14 15 c = ContentResolver.Query(uri, null, null, null, null); 16 c.MoveToFirst(); 17 username = c.GetString(c.GetColumnIndex(LocationContentProvider.UserTable.UserName)); 18 19 ContentResolver.Delete(uri, null, null);
示例項目下載請點我
ContentProvider 在android中的作用是對外共享數據,也就是說你可以通過ContentProvider把應用中的數據共享給其他應用訪問,其他應用可以通過ContentProvider 對你應用中的數據進行添刪改查。關於數據共享,以前我們學習過文件操作模式,知道通過指定文件的操作模式為Context.MODE_WORLD_READABLE 或Context.MODE_WORLD_WRITEABLE同樣也可以對外共享數據。那麼,這裡為何要使用ContentProvider 對外共享數據呢?是這樣的,如果采用文件操作模式對外共享數據,數據的訪問方式會因數據存儲的方式而不同,導致數據的訪問方式無法統一,如:采用xml文件對外共享數據,需要進行xml解析才能讀取數據;采用sharedpreferences共享數據,需要使用sharedpreferences API讀取數據。
使用ContentProvider對外共享數據的好處是統一了數據的訪問方式。
當應用需要通過ContentProvider對外共享數據時,第一步需要繼承ContentProvider並重寫下面方法:
public class PersonContentProvider extends ContentProvider{
public boolean onCreate()
public Uri insert(Uri uri, ContentValues values)
public int delete(Uri uri, String selection, String[] selectionArgs)
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs)
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)
public String getType(Uri uri)}
第二步需要在AndroidManifest.xml使用<provider>對該ContentProvider進行配置,為了能讓其他應用找到該ContentProvider , ContentProvider 采用了authorities(主機名/域名)對它進行唯一標識,你可以把 ContentProvider看作是一個網站(想想,網站也是提供數據者),authorities 就是他的域名:
<manifest .... >
<application android:icon="@drawable/icon" android:label="@string/app_name">
<provider android:name=".PersonContentProvider" android:authorities="cn.ccy.providers.personprovider"/>
</application>
</manifest>
只是直接通過應用A提......余下全文>>
我這裡有一個讀通信錄中郵箱的代碼,至於讀電話吧類似了:
private ArrayList<String> getEmailAddress() {
emailAddress = new ArrayList<String>();
Cursor c = getContentResolver()
.query(
ContactsContract.Data.CONTENT_URI,null,Data.MIMETYPE
+ "='"
+ ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE
+ "'", null, null);
if (c.getCount() > 0) {
while (c.moveToNext()) {
int mailindex = c.getColumnIndex(Data.DATA1);
String mail = c.getString(mailindex);
Log.v("minetype", mail);
emailAddress.add(mail);
}
}
c.close();
return emailAddress;
}
另外 在AndroidManifest.xml中加入:
<uses-permission android:name="android.permission.READ_CONTACTS" />,這樣才能讀通訊錄,當然要讀電話本的話不是這個了,你自己查查api吧!