最近在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 + AUTHORITY58 + PATH_STUDENTS);59 // 某一条数据的基本uri格式 这个通常在自定義的provider的insert方法里面被调用60 public static final Uri CONTENT_ID_URI_BASE = Uri.parse(SCHEME61 + 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 HashMapsStudentsProjectionMap; 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 (); 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 @Override108 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 // 先匹配uri114 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._ID123 + "="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里面这个函数的源码,非常简单。那么他的实际作用就是如果返回的cursor149 // 被用在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 @Override160 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 @Override174 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的insert190 // 操作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 (values197 .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 @Override221 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._ID236 + " = "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 @Override258 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._ID275 + " = "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 // 自定义helper296 static class DatabaseHelper extends SQLiteOpenHelper {297 298 DatabaseHelper(Context context) {299 super(context, DATABASE_NAME, null, DATABASE_VERSION);300 }301 302 @Override303 public void onCreate(SQLiteDatabase db) {304 // TODO Auto-generated method stub305 db.execSQL("CREATE TABLE " + StudentProfile.Students.TABLE_NAME306 + " (" + StudentProfile.Students._ID307 + " 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_DATE312 + " INTEGER,"313 + StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE314 + " INTEGER" + ");");315 }316 317 @Override318 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {319 // TODO Auto-generated method stub320 // 数据库升级的时候 这边的代码 不写了,看各自的业务逻辑了,一般建议大家在这个地方多打一些日志321 }322 323 }324 }