如何自定义一个优雅的ContentProvider

最近在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 }

 

posted @ 2015-06-01 15:33  希尔瓦娜斯女神  阅读(2463)  评论(0编辑  收藏  举报