• 如何自定义一个优雅的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 }
  • 相关阅读:
    flask
    ORACLE EXP不能导出空表的原因分析及解决方法
    安装sqlService需要重启解决方法
    sqlserver2008日志已满解决方法
    Oracle中删除用户下所有对象
    sqlserver生成表行数据insert语句
    对回调函数的理解
    github笔记
    Ubuntu摆脱QQ版本过旧问题!安装 wineinternational版 qq
    Servlet完全教程
  • 原文地址:https://www.cnblogs.com/punkisnotdead/p/4544076.html
Copyright © 2020-2023  润新知