如何自定義一個優雅的ContentProvider

希爾瓦娜斯女神發表於2015-06-01

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

 

相關文章