最近在code review的時候發現很多人的provider定義的不是很好,寫的很粗糙 以至於程式碼健壯性不夠好,可讀性也不強
但是你既然寫了content provider 就是要給別人呼叫的,如果provider寫的漏洞百出的話 還不如不寫,
要麼別讓別的app 對你的資料進行crud,要麼就讓自己的app 直接用db 來運算元據,既然要寫provider,就要寫的標準
優雅~~放一個provider的例項在這裡,有大量註釋 告訴你為什麼要這麼寫。跟我一樣有程式碼潔癖的人可以參考下。
1 package com.example.providertest; 2 3 import android.net.Uri; 4 import android.provider.BaseColumns; 5 6 /** 7 * 常量類 8 */ 9 public final class StudentProfile { 10 11 /** 12 * 一般來說 我們的authority都是設定成 我們這個常量類的包名+類名 13 */ 14 public static final String AUTHORITY = "com.example.providertest.StudentProfile"; 15 16 /** 17 * 注意這個建構函式 是私有的 目的就是讓他不能被初始化 18 */ 19 private StudentProfile() { 20 21 } 22 23 /** 24 * 實現了這個BaseColumns介面 可以讓我們少寫幾行程式碼 25 * 26 */ 27 public static final class Students implements BaseColumns { 28 /** 29 * 這個類同樣也是不能被初始化的 30 */ 31 private Students() { 32 33 } 34 35 // 定義我們的表名 36 public static final String TABLE_NAME = "students"; 37 38 /** 39 * 下面開始uri的定義 40 */ 41 42 // uri的scheme部分 這個部分是固定的寫法 43 private static final String SCHEME = "content://"; 44 45 // 部分學生 46 private static final String PATH_STUDENTS = "/students"; 47 48 // 某一個學生 49 private static final String PATH_STUDENTS_ID = "/students/"; 50 51 /** 52 * path這邊的第幾個值是指的位置 我們設定成第一個位置 53 */ 54 public static final int STUDENT_ID_PATH_POSITION = 1; 55 56 // 這個表的基本的uri格式 57 public static final Uri CONTENT_URI = Uri.parse(SCHEME + AUTHORITY 58 + PATH_STUDENTS); 59 // 某一條資料的基本uri格式 這個通常在自定義的provider的insert方法裡面被呼叫 60 public static final Uri CONTENT_ID_URI_BASE = Uri.parse(SCHEME 61 + AUTHORITY + PATH_STUDENTS_ID); 62 63 /** 64 * 定義一下我們的mime型別 注意一下mime型別的寫法 65 * 66 * 一般都是後面vnd.應用程式的包名.表名 67 */ 68 69 // 多行的mime型別 70 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.example.providertest.students"; 71 // 單行的mime型別 72 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.google.com.example.providertest.students"; 73 74 /** 75 * 既然provider提供了查詢的方法 我們肯定要設定一個預設的排序方式 這裡我們就預設讓他根據建立的時間 來降序排序 76 */ 77 public static final String DEFAULT_SORT_ORDER = "created DESC"; 78 79 /** 80 * 下面就是表的列定義了 81 */ 82 83 // 學生的名字 84 public static final String COLUMN_NAME_NAME = "name"; 85 // 學生的年齡 86 public static final String COLUMN_NAME_AGE = "age"; 87 // 學生的學號 88 public static final String COLUMN_NAME_NUMBER = "number"; 89 // 這個學生建立的時間 90 public static final String COLUMN_NAME_CREATE_DATE = "created"; 91 // 這個學生入庫以後修改的時間 92 public static final String COLUMN_NAME_MODIFICATION_DATE = "modified"; 93 94 } 95 96 }
1 package com.example.providertest; 2 3 import java.util.HashMap; 4 5 import android.content.ContentProvider; 6 import android.content.ContentUris; 7 import android.content.ContentValues; 8 import android.content.Context; 9 import android.content.UriMatcher; 10 import android.database.Cursor; 11 import android.database.SQLException; 12 import android.database.sqlite.SQLiteDatabase; 13 import android.database.sqlite.SQLiteOpenHelper; 14 import android.database.sqlite.SQLiteQueryBuilder; 15 import android.net.Uri; 16 import android.text.TextUtils; 17 import android.util.Log; 18 19 public class StudentProfileProvider extends ContentProvider { 20 21 // tag 打日誌用 22 private static final String TAG = "StudentProfileProvider"; 23 24 // 資料庫的名字 25 private static final String DATABASE_NAME = "students_info.db"; 26 27 // 資料庫版本號 28 private static final int DATABASE_VERSION = 1; 29 30 /** 31 * A UriMatcher instance 32 */ 33 private static final UriMatcher sUriMatcher; 34 35 // 匹配成功的返回值 這裡代表多行匹配成功 36 private static final int STUDENTS = 1; 37 38 // 匹配成功的返回值 這裡代表多單行匹配成功 39 private static final int STUDENTS_ID = 2; 40 41 /** 42 * 注意看一下這個雜湊表 這個雜湊表實際上是主要為了SQLiteQueryBuilder這個類的 setProjectionMap這個方法使用的 43 * 44 * 他的值的初始化我放在靜態程式碼塊裡面,這個地方實際上主要是為了多表查詢而存在的 45 * 46 * 比如你要多表查詢的時候 你有2個表 一個表A 一個表B 你join的時候 肯定需要重新命名某個表的某個列 47 * 48 * 比如你要把表A的 name1 這個列名重新命名成 a.name1 那你就可以add一個key value對,key為name1 49 * 50 * value 為a.name1 即可。當然咯 如果你不想重新命名或者只是單表查詢那就只需要吧key 和value 51 * 52 * 的值都寫成 一樣的即可 53 * 54 */ 55 private static HashMap<String, String> sStudentsProjectionMap; 56 57 // 定義資料庫helper. 58 private DatabaseHelper mOpenHelper; 59 60 // 靜態程式碼塊執行 61 static { 62 63 // 先構造urimatcher 64 sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); 65 66 sUriMatcher.addURI(StudentProfile.AUTHORITY, "students", STUDENTS); 67 68 // #代表任意數字 *一般代表任意文字 69 sUriMatcher.addURI(StudentProfile.AUTHORITY, "students/#", STUDENTS_ID); 70 71 // 因為我們這裡是單表查詢 所以這個地方key和value的值都寫成固定的就可以了 72 sStudentsProjectionMap = new HashMap<String, String>(); 73 74 sStudentsProjectionMap.put(StudentProfile.Students._ID, 75 StudentProfile.Students._ID); 76 77 sStudentsProjectionMap.put(StudentProfile.Students.COLUMN_NAME_AGE, 78 StudentProfile.Students.COLUMN_NAME_AGE); 79 80 sStudentsProjectionMap.put( 81 StudentProfile.Students.COLUMN_NAME_CREATE_DATE, 82 StudentProfile.Students.COLUMN_NAME_CREATE_DATE); 83 84 sStudentsProjectionMap.put( 85 StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE, 86 StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE); 87 88 sStudentsProjectionMap.put(StudentProfile.Students.COLUMN_NAME_NAME, 89 StudentProfile.Students.COLUMN_NAME_NAME); 90 91 sStudentsProjectionMap.put(StudentProfile.Students.COLUMN_NAME_NUMBER, 92 StudentProfile.Students.COLUMN_NAME_NUMBER); 93 } 94 95 @Override 96 public boolean onCreate() { 97 // TODO Auto-generated method stub 98 mOpenHelper = new DatabaseHelper(getContext()); 99 return true; 100 } 101 102 /** 103 * 對於自定義contentprovider來說CRUD的這幾個方法的寫法 要儘量保證 程式碼優美 和 容錯性高 104 * 105 */ 106 107 @Override 108 public Cursor query(Uri uri, String[] projection, String selection, 109 String[] selectionArgs, String sortOrder) { 110 SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); 111 qb.setTables(StudentProfile.Students.TABLE_NAME); 112 113 // 先匹配uri 114 switch (sUriMatcher.match(uri)) { 115 // 多行查詢 116 case STUDENTS: 117 qb.setProjectionMap(sStudentsProjectionMap); 118 break; 119 // 單行查詢 120 case STUDENTS_ID: 121 qb.setProjectionMap(sStudentsProjectionMap); 122 qb.appendWhere(StudentProfile.Students._ID 123 + "=" 124 + uri.getPathSegments().get( 125 StudentProfile.Students.STUDENT_ID_PATH_POSITION)); 126 break; 127 default: 128 throw new IllegalArgumentException("Unknown uri" + uri); 129 } 130 131 // 如果沒有傳orderby的值過來 那我們就使用預設的 132 String orderBy; 133 if (TextUtils.isEmpty(sortOrder)) { 134 orderBy = StudentProfile.Students.DEFAULT_SORT_ORDER; 135 } else { 136 // 如果傳過來了 就使用傳來的值 137 orderBy = sortOrder; 138 } 139 140 // 開始運算元據庫 141 SQLiteDatabase db = mOpenHelper.getReadableDatabase(); 142 143 Cursor c = qb.query(db, projection, selection, selectionArgs, null, 144 null, orderBy); 145 146 // 這個地方要解釋一下 這句語句的作用,很多人自定義provider的時候 在query方法裡面都忘記 147 // 寫這句話,有的人寫了也不知道這句話是幹嘛的,實際上這句話就是給我們的cursor加了一個觀察者 148 // 有興趣的可以看一下sdk裡面這個函式的原始碼,非常簡單。那麼他的實際作用就是如果返回的cursor 149 // 被用在SimpleCursorAdapter 類似的這種adapter的話,一旦uri所對應的provider資料發生了變化 150 // 那麼這個adapter裡的資料是會自己變化重新整理的。這句話起的就是這個作用 有興趣的可以自己寫程式碼 151 // 驗證一下 如果把這句話刪除掉的話 adapter裡的資料是不會再uri更新的時候 自動更新的 152 c.setNotificationUri(getContext().getContentResolver(), uri); 153 return c; 154 } 155 156 /** 157 * 這個地方的返回值 一定要和manifest你配置activity的時候data 欄位的值相同 不然會報錯 158 */ 159 @Override 160 public String getType(Uri uri) { 161 switch (sUriMatcher.match(uri)) { 162 case STUDENTS: 163 return StudentProfile.Students.CONTENT_TYPE; 164 case STUDENTS_ID: 165 return StudentProfile.Students.CONTENT_ITEM_TYPE; 166 default: 167 // 注意這個地方記得不匹配的時候丟擲異常資訊 這樣當比人呼叫失敗的時候會知道哪裡不對 168 throw new IllegalArgumentException("Unknown uri" + uri); 169 } 170 171 } 172 173 @Override 174 public Uri insert(Uri uri, ContentValues initialValues) { 175 176 if (sUriMatcher.match(uri) != STUDENTS) { 177 throw new IllegalArgumentException("Unknown URI " + uri); 178 } 179 180 ContentValues values; 181 182 if (initialValues != null) { 183 values = new ContentValues(initialValues); 184 } else { 185 values = new ContentValues(); 186 } 187 188 // 下面幾行程式碼實際上就是告訴我們對於某些表而言 預設的欄位的值 可以在insert裡面自己寫好 189 // 不要讓呼叫者去手動再做重複勞動,我們應該允許呼叫者寫入最少的欄位的值 來完成db的insert 190 // 操作 191 Long now = Long.valueOf(System.currentTimeMillis()); 192 193 if (values.containsKey(StudentProfile.Students.COLUMN_NAME_CREATE_DATE) == false) { 194 values.put(StudentProfile.Students.COLUMN_NAME_CREATE_DATE, now); 195 } 196 if (values 197 .containsKey(StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE) == false) { 198 values.put(StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE, 199 now); 200 } 201 202 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 203 204 long rowId = db.insert(StudentProfile.Students.TABLE_NAME, 205 StudentProfile.Students.COLUMN_NAME_NAME, values); 206 207 if (rowId > 0) { 208 Uri stuUri = ContentUris.withAppendedId( 209 StudentProfile.Students.CONTENT_ID_URI_BASE, rowId); 210 // 用於通知所有觀察者資料已經改變 211 getContext().getContentResolver().notifyChange(stuUri, null); 212 return stuUri; 213 } 214 215 // 如果插入失敗也最好丟擲異常 通知呼叫者 216 throw new SQLException("Failed to insert row into " + uri); 217 218 } 219 220 @Override 221 public int delete(Uri uri, String where, String[] whereArgs) { 222 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 223 String finalWhere; 224 225 int count; 226 227 switch (sUriMatcher.match(uri)) { 228 229 case STUDENTS: 230 count = db.delete(StudentProfile.Students.TABLE_NAME, where, 231 whereArgs); 232 break; 233 234 case STUDENTS_ID: 235 finalWhere = StudentProfile.Students._ID 236 + " = " 237 + uri.getPathSegments().get( 238 StudentProfile.Students.STUDENT_ID_PATH_POSITION); 239 240 if (where != null) { 241 finalWhere = finalWhere + " AND " + where; 242 } 243 244 count = db.delete(StudentProfile.Students.TABLE_NAME, finalWhere, 245 whereArgs); 246 break; 247 248 default: 249 throw new IllegalArgumentException("Unknown URI " + uri); 250 } 251 252 getContext().getContentResolver().notifyChange(uri, null); 253 254 return count; 255 } 256 257 @Override 258 public int update(Uri uri, ContentValues values, String where, 259 String[] whereArgs) { 260 SQLiteDatabase db = mOpenHelper.getWritableDatabase(); 261 int count; 262 String finalWhere; 263 264 switch (sUriMatcher.match(uri)) { 265 266 case STUDENTS: 267 268 count = db.update(StudentProfile.Students.TABLE_NAME, values, 269 where, whereArgs); 270 break; 271 272 case STUDENTS_ID: 273 274 finalWhere = StudentProfile.Students._ID 275 + " = " 276 + uri.getPathSegments().get( 277 StudentProfile.Students.STUDENT_ID_PATH_POSITION); 278 279 if (where != null) { 280 finalWhere = finalWhere + " AND " + where; 281 } 282 283 count = db.update(StudentProfile.Students.TABLE_NAME, values, 284 finalWhere, whereArgs); 285 break; 286 default: 287 throw new IllegalArgumentException("Unknown URI " + uri); 288 } 289 290 getContext().getContentResolver().notifyChange(uri, null); 291 292 return count; 293 } 294 295 // 自定義helper 296 static class DatabaseHelper extends SQLiteOpenHelper { 297 298 DatabaseHelper(Context context) { 299 super(context, DATABASE_NAME, null, DATABASE_VERSION); 300 } 301 302 @Override 303 public void onCreate(SQLiteDatabase db) { 304 // TODO Auto-generated method stub 305 db.execSQL("CREATE TABLE " + StudentProfile.Students.TABLE_NAME 306 + " (" + StudentProfile.Students._ID 307 + " INTEGER PRIMARY KEY," 308 + StudentProfile.Students.COLUMN_NAME_NAME + " TEXT," 309 + StudentProfile.Students.COLUMN_NAME_NUMBER + " TEXT," 310 + StudentProfile.Students.COLUMN_NAME_AGE + " INTEGER," 311 + StudentProfile.Students.COLUMN_NAME_CREATE_DATE 312 + " INTEGER," 313 + StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE 314 + " INTEGER" + ");"); 315 } 316 317 @Override 318 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 319 // TODO Auto-generated method stub 320 // 資料庫升級的時候 這邊的程式碼 不寫了,看各自的業務邏輯了,一般建議大家在這個地方多打一些日誌 321 } 322 323 } 324 }