博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何自定义一个优雅的ContentProvider
阅读量:6840 次
发布时间:2019-06-26

本文共 14322 字,大约阅读时间需要 47 分钟。

最近在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 HashMap
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
(); 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 }

 

转载于:https://www.cnblogs.com/punkisnotdead/p/4544076.html

你可能感兴趣的文章
如何提高Linux系统应对短连接的负载能力
查看>>
Django 数据库表多对多的创建和增删改查
查看>>
大數據下的決策思考
查看>>
hadoop作业分片处理以及任务本地性分析(源码分析第一篇)
查看>>
又睡不着了。。
查看>>
RHEL在VLAN Trunk模式下的IP地址配置
查看>>
RHCE 学习笔记(38 ) - Shell
查看>>
WEB服务器-Nginx之虚拟主机、日志、认证及优化
查看>>
常用的两种数据分区方法(以Teradata为例)
查看>>
Nginx Rewrite正则表达式案例
查看>>
一个新兴的日志管理产品
查看>>
WindowsServer2012史记-安装和配置HyperV
查看>>
Delphi开发的IOCP测试Demo以及使用说明。
查看>>
10分钟学会 Python 函数基础知识
查看>>
应届毕业生求职之我见
查看>>
FAQ:configuration manager未找到站点来管理此客户端
查看>>
微博商城开启社会化电商之路
查看>>
通过脚本案例学习shell(三) --- 通过交互式脚本自动创建Apache虚拟主机
查看>>
在大二,我是怎么月收入5000
查看>>
校验顺序和短路
查看>>