Xamarin.Android之ContentProvider
一、前言
掌握瞭如何使用SQLiteOpenHelper之後,我們就可以進行下一步的學習。本章我們將會學習如何使用ContentProvider來將資料庫方面的操作封裝起來,同時它還可以供其他應用訪問並操作資料庫。
二、概念
首先我們不會急於寫程式碼,而是要搞懂如何利用ContentProvider對資料庫進行操作,因為我們不會直接操作資料庫物件,而是通過URI來操作資料庫。這就好比你要獲取User表的全部內容,那麼這個URI就是content://base/user其中base是自己命名的,最好是能夠唯一。因為我們需要依靠這個區分資料庫,然後就是user是用來區分操作的是哪個表,當然你也可以不用命名為user可以是其他的名稱,最終反正要依靠程式碼去判斷的。這樣我們就可以避免在活動中直接對資料庫物件操作,也方便對資料庫進行統一的維護。
三、實際操作
1、搭建基本框架
新建一個LocationContentProvider類,並且繼承自ContentProvider,還要重寫該類的OnCreate、Delete、GetType、Insert、Query和Update方法。
2、設計資料庫以及URI
下面是筆者設計好的結構:
1 public static string PROVIDER_NAME = "xamarin-cn.location"; 2 3 private const string DATABASE_NAME = "location"; 4 private const intDATABASE_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。
3、初始化UriMatcher
我們需要通過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,其中有針對整張表的,還有針對表中某條資料的。
4、建立資料庫
這裡我就不一一介紹了直接放出程式碼:
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 }
5.初始化ContentProvider
首先我們需要在OnCreate方法中將dbHelper初始化:
1 public override bool OnCreate() 2 { 3 dbHelper = new LocationSqliteOpenHelper(Context); 4 return true; 5 }
6.完善Query方法
為了能夠方便的組織查詢語句這裡筆者使用了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進行比較,都是一樣的只是少了一部分引數。
7.完善Insert
因為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返回,這樣就可以方便以後的查詢。
8.完善Update
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 }
這裡跟查詢一樣,需要判斷是針對某條資料還是整個表。
9.完善Delete
理解了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方法,只要返回空字串即可。
四、操作ContentProvider
現在我們回到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);