最近在code review的时候发现很多人的provider定义的不是很好,写的很粗糙 以至于代码健壮性不够好,可读性也不强

但是你既然写了content provider 就是要给别人调用的,如果provider写的漏洞百出的话  还不如不写,

要么别让别的app 对你的数据进行crud,要么就让自己的app 直接用db 来操作数据,既然要写provider,就要写的标准

优雅~~放一个provider的实例在这里,有大量注释 告诉你为什么要这么写。跟我一样有代码洁癖的人可以参考下。

 package com.example.providertest;

 import android.net.Uri;
import android.provider.BaseColumns; /**
* 常量类
*/
public final class StudentProfile { /**
* 一般来说 我们的authority都是设置成 我们这个常量类的包名+类名
*/
public static final String AUTHORITY = "com.example.providertest.StudentProfile"; /**
* 注意这个构造函数 是私有的 目的就是让他不能被初始化
*/
private StudentProfile() { } /**
* 实现了这个BaseColumns接口 可以让我们少写几行代码
*
*/
public static final class Students implements BaseColumns {
/**
* 这个类同样也是不能被初始化的
*/
private Students() { } // 定义我们的表名
public static final String TABLE_NAME = "students"; /**
* 下面开始uri的定义
*/ // uri的scheme部分 这个部分是固定的写法
private static final String SCHEME = "content://"; // 部分学生
private static final String PATH_STUDENTS = "/students"; // 某一个学生
private static final String PATH_STUDENTS_ID = "/students/"; /**
* path这边的第几个值是指的位置 我们设置成第一个位置
*/
public static final int STUDENT_ID_PATH_POSITION = 1; // 这个表的基本的uri格式
public static final Uri CONTENT_URI = Uri.parse(SCHEME + AUTHORITY
+ PATH_STUDENTS);
// 某一条数据的基本uri格式 这个通常在自定義的provider的insert方法里面被调用
public static final Uri CONTENT_ID_URI_BASE = Uri.parse(SCHEME
+ AUTHORITY + PATH_STUDENTS_ID); /**
* 定义一下我们的mime类型 注意一下mime类型的写法
*
* 一般都是后面vnd.应用程序的包名.表名
*/ // 多行的mime类型
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.com.example.providertest.students";
// 单行的mime类型
public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/vnd.google.com.example.providertest.students"; /**
* 既然provider提供了查询的方法 我们肯定要设置一个默认的排序方式 这里我们就默认让他根据创建的时间 来降序排序
*/
public static final String DEFAULT_SORT_ORDER = "created DESC"; /**
* 下面就是表的列定义了
*/ // 学生的名字
public static final String COLUMN_NAME_NAME = "name";
// 学生的年龄
public static final String COLUMN_NAME_AGE = "age";
// 学生的学号
public static final String COLUMN_NAME_NUMBER = "number";
// 这个学生创建的时间
public static final String COLUMN_NAME_CREATE_DATE = "created";
// 这个学生入库以后修改的时间
public static final String COLUMN_NAME_MODIFICATION_DATE = "modified"; } }
 package com.example.providertest;

 import java.util.HashMap;

 import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.text.TextUtils;
import android.util.Log; public class StudentProfileProvider extends ContentProvider { // tag 打日志用
private static final String TAG = "StudentProfileProvider"; // 数据库的名字
private static final String DATABASE_NAME = "students_info.db"; // 数据库版本号
private static final int DATABASE_VERSION = 1; /**
* A UriMatcher instance
*/
private static final UriMatcher sUriMatcher; // 匹配成功的返回值 这里代表多行匹配成功
private static final int STUDENTS = 1; // 匹配成功的返回值 这里代表多单行匹配成功
private static final int STUDENTS_ID = 2; /**
* 注意看一下这个哈希表 这个哈希表实际上是主要为了SQLiteQueryBuilder这个类的 setProjectionMap这个方法使用的
*
* 他的值的初始化我放在静态代码块里面,这个地方实际上主要是为了多表查询而存在的
*
* 比如你要多表查询的时候 你有2个表 一个表A 一个表B 你join的时候 肯定需要重命名某个表的某个列
*
* 比如你要把表A的 name1 这个列名重命名成 a.name1 那你就可以add一个key value对,key为name1
*
* value 为a.name1 即可。当然咯 如果你不想重命名或者只是单表查询那就只需要吧key 和value
*
* 的值都写成 一样的即可
*
*/
private static HashMap<String, String> sStudentsProjectionMap; // 定义数据库helper.
private DatabaseHelper mOpenHelper; // 静态代码块执行
static { // 先构造urimatcher
sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH); sUriMatcher.addURI(StudentProfile.AUTHORITY, "students", STUDENTS); // #代表任意数字 *一般代表任意文本
sUriMatcher.addURI(StudentProfile.AUTHORITY, "students/#", STUDENTS_ID); // 因为我们这里是单表查询 所以这个地方key和value的值都写成固定的就可以了
sStudentsProjectionMap = new HashMap<String, String>(); sStudentsProjectionMap.put(StudentProfile.Students._ID,
StudentProfile.Students._ID); sStudentsProjectionMap.put(StudentProfile.Students.COLUMN_NAME_AGE,
StudentProfile.Students.COLUMN_NAME_AGE); sStudentsProjectionMap.put(
StudentProfile.Students.COLUMN_NAME_CREATE_DATE,
StudentProfile.Students.COLUMN_NAME_CREATE_DATE); sStudentsProjectionMap.put(
StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE,
StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE); sStudentsProjectionMap.put(StudentProfile.Students.COLUMN_NAME_NAME,
StudentProfile.Students.COLUMN_NAME_NAME); sStudentsProjectionMap.put(StudentProfile.Students.COLUMN_NAME_NUMBER,
StudentProfile.Students.COLUMN_NAME_NUMBER);
} @Override
public boolean onCreate() {
// TODO Auto-generated method stub
mOpenHelper = new DatabaseHelper(getContext());
return true;
} /**
* 对于自定义contentprovider来说CRUD的这几个方法的写法 要尽量保证 代码优美 和 容错性高
*
*/ @Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(StudentProfile.Students.TABLE_NAME); // 先匹配uri
switch (sUriMatcher.match(uri)) {
// 多行查询
case STUDENTS:
qb.setProjectionMap(sStudentsProjectionMap);
break;
// 单行查询
case STUDENTS_ID:
qb.setProjectionMap(sStudentsProjectionMap);
qb.appendWhere(StudentProfile.Students._ID
+ "="
+ uri.getPathSegments().get(
StudentProfile.Students.STUDENT_ID_PATH_POSITION));
break;
default:
throw new IllegalArgumentException("Unknown uri" + uri);
} // 如果没有传orderby的值过来 那我们就使用默认的
String orderBy;
if (TextUtils.isEmpty(sortOrder)) {
orderBy = StudentProfile.Students.DEFAULT_SORT_ORDER;
} else {
// 如果传过来了 就使用传来的值
orderBy = sortOrder;
} // 开始操作数据库
SQLiteDatabase db = mOpenHelper.getReadableDatabase(); Cursor c = qb.query(db, projection, selection, selectionArgs, null,
null, orderBy); // 这个地方要解释一下 这句语句的作用,很多人自定义provider的时候 在query方法里面都忘记
// 写这句话,有的人写了也不知道这句话是干嘛的,实际上这句话就是给我们的cursor加了一个观察者
// 有兴趣的可以看一下sdk里面这个函数的源码,非常简单。那么他的实际作用就是如果返回的cursor
// 被用在SimpleCursorAdapter 类似的这种adapter的话,一旦uri所对应的provider数据发生了变化
// 那么这个adapter里的数据是会自己变化刷新的。这句话起的就是这个作用 有兴趣的可以自己写代码
// 验证一下 如果把这句话删除掉的话 adapter里的数据是不会再uri更新的时候 自动更新的
c.setNotificationUri(getContext().getContentResolver(), uri);
return c;
} /**
* 这个地方的返回值 一定要和manifest你配置activity的时候data 字段的值相同 不然会报错
*/
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
case STUDENTS:
return StudentProfile.Students.CONTENT_TYPE;
case STUDENTS_ID:
return StudentProfile.Students.CONTENT_ITEM_TYPE;
default:
// 注意这个地方记得不匹配的时候抛出异常信息 这样当比人调用失败的时候会知道哪里不对
throw new IllegalArgumentException("Unknown uri" + uri);
} } @Override
public Uri insert(Uri uri, ContentValues initialValues) { if (sUriMatcher.match(uri) != STUDENTS) {
throw new IllegalArgumentException("Unknown URI " + uri);
} ContentValues values; if (initialValues != null) {
values = new ContentValues(initialValues);
} else {
values = new ContentValues();
} // 下面几行代码实际上就是告诉我们对于某些表而言 默认的字段的值 可以在insert里面自己写好
// 不要让调用者去手动再做重复劳动,我们应该允许调用者写入最少的字段的值 来完成db的insert
// 操作
Long now = Long.valueOf(System.currentTimeMillis()); if (values.containsKey(StudentProfile.Students.COLUMN_NAME_CREATE_DATE) == false) {
values.put(StudentProfile.Students.COLUMN_NAME_CREATE_DATE, now);
}
if (values
.containsKey(StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE) == false) {
values.put(StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE,
now);
} SQLiteDatabase db = mOpenHelper.getWritableDatabase(); long rowId = db.insert(StudentProfile.Students.TABLE_NAME,
StudentProfile.Students.COLUMN_NAME_NAME, values); if (rowId > 0) {
Uri stuUri = ContentUris.withAppendedId(
StudentProfile.Students.CONTENT_ID_URI_BASE, rowId);
// 用于通知所有观察者数据已经改变
getContext().getContentResolver().notifyChange(stuUri, null);
return stuUri;
} // 如果插入失败也最好抛出异常 通知调用者
throw new SQLException("Failed to insert row into " + uri); } @Override
public int delete(Uri uri, String where, String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
String finalWhere; int count; switch (sUriMatcher.match(uri)) { case STUDENTS:
count = db.delete(StudentProfile.Students.TABLE_NAME, where,
whereArgs);
break; case STUDENTS_ID:
finalWhere = StudentProfile.Students._ID
+ " = "
+ uri.getPathSegments().get(
StudentProfile.Students.STUDENT_ID_PATH_POSITION); if (where != null) {
finalWhere = finalWhere + " AND " + where;
} count = db.delete(StudentProfile.Students.TABLE_NAME, finalWhere,
whereArgs);
break; default:
throw new IllegalArgumentException("Unknown URI " + uri);
} getContext().getContentResolver().notifyChange(uri, null); return count;
} @Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {
SQLiteDatabase db = mOpenHelper.getWritableDatabase();
int count;
String finalWhere; switch (sUriMatcher.match(uri)) { case STUDENTS: count = db.update(StudentProfile.Students.TABLE_NAME, values,
where, whereArgs);
break; case STUDENTS_ID: finalWhere = StudentProfile.Students._ID
+ " = "
+ uri.getPathSegments().get(
StudentProfile.Students.STUDENT_ID_PATH_POSITION); if (where != null) {
finalWhere = finalWhere + " AND " + where;
} count = db.update(StudentProfile.Students.TABLE_NAME, values,
finalWhere, whereArgs);
break;
default:
throw new IllegalArgumentException("Unknown URI " + uri);
} getContext().getContentResolver().notifyChange(uri, null); return count;
} // 自定义helper
static class DatabaseHelper extends SQLiteOpenHelper { DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
} @Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
db.execSQL("CREATE TABLE " + StudentProfile.Students.TABLE_NAME
+ " (" + StudentProfile.Students._ID
+ " INTEGER PRIMARY KEY,"
+ StudentProfile.Students.COLUMN_NAME_NAME + " TEXT,"
+ StudentProfile.Students.COLUMN_NAME_NUMBER + " TEXT,"
+ StudentProfile.Students.COLUMN_NAME_AGE + " INTEGER,"
+ StudentProfile.Students.COLUMN_NAME_CREATE_DATE
+ " INTEGER,"
+ StudentProfile.Students.COLUMN_NAME_MODIFICATION_DATE
+ " INTEGER" + ");");
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub
// 数据库升级的时候 这边的代码 不写了,看各自的业务逻辑了,一般建议大家在这个地方多打一些日志
} }
}

如何自定义一个优雅的ContentProvider的更多相关文章

  1. 在PostgreSQL自定义一个“优雅”的type

    是的,又是我,不要脸的又来混经验了.我们知道PostgreSQL是一个高度可扩展的数据库,这次我聊聊如何在PostgreSQL里创建一个优雅的type,如何理解优雅?大概就是不仅仅是type本身,其它 ...

  2. SpringMVC 自定义一个拦截器

    自定义一个拦截器方法,实现HandlerInterceptor方法 public class FirstInterceptor implements HandlerInterceptor{ /** * ...

  3. jQuery Validate 表单验证插件----自定义一个验证方法

    一.下载依赖包 网盘下载:https://yunpan.cn/cryvgGGAQ3DSW  访问密码 f224 二.引入依赖包 <script src="../../scripts/j ...

  4. Spring自定义一个拦截器类SomeInterceptor,实现HandlerInterceptor接口及其方法的实例

    利用Spring的拦截器可以在处理器Controller方法执行前和后增加逻辑代码,了解拦截器中preHandle.postHandle和afterCompletion方法执行时机. 自定义一个拦截器 ...

  5. JSTL,自定义一个标签的功能案例

    1.自定义一个带有两个属性的标签<max>,用于计算并输出两个数的最大值: 2.自定义一个带有一个属性的标签<lxn:readFile  src=“”>,用于输出指定文件的内容 ...

  6. 自定义View(7)官方教程:自定义View(含onMeasure),自定义一个Layout(混合组件),重写一个现有组件

    Custom Components In this document The Basic Approach Fully Customized Components Compound Controls ...

  7. Volley HTTP库系列教程(5)自定义一个Volley请求

    Implementing a Custom Request Previous  Next This lesson teaches you to Write a Custom Request parse ...

  8. 在String()构造器不存在的情况下自定义一个MyString()函数,实现如下内建String()方法和属性:

    在String()构造器不存在的情况下自定义一个MyString()函数,实现如下内建String()方法和属性: var s = new MyString("hello"); s ...

  9. ExtJs5_继承自定义一个控件

    Extjs的开发都可以遵循OOP的原则,其对类的封装也很完善了.自定义一个控件最简单的办法就是继承一个已有的控件.根据上一节的需要,我做了一个Button的子类.首先根据目录结构,在app目录下建立一 ...

随机推荐

  1. mysql之select(二)

    union 联合 作用: 把2次或多次查询结果合并起来. 要求:两次查询的列数一致.推荐:查询的每一列,相对应得列类型也一样. 可以来自于多张表.多次sql语句取出的列名可以不一致,此时,以第1个sq ...

  2. sql openrowset

    select * from openrowset('sqloledb','ip';'user';'pwd','exec 库..过程') 

  3. java中静态代理跟动态代理之间的区别

    文章转载于:http://www.cnblogs.com/xiaoluo501395377/p/3383130.html 在学习Spring的时候,我们知道Spring主要有两大思想,一个是IoC,另 ...

  4. kill -9 和kill

    kill pid 在kill进程的同时,会将包删掉该进程所在webapps目录下的文件夹,如iexpense文件夹 kill -9 pid 会强制删掉进程,但是不是删掉该进程所在webapps目录下的 ...

  5. 一些时间的概念与区分(UTC、GMT、LT、TAI等)

    UT - 世界时 Universal Time世界时是最早的时间标准.在1884年,国际上将1s确定为全年内每日平均长度的1/8.64×104.以此标准形成的时间系统,称为世界时,即 UT1.1972 ...

  6. 对话框上右下角显示resize icon(可以拖动改变对话框的大小)(在WM_CREATE的时候,增加WS_THICKFRAME风格)

    CStatusBar m_StatusBar;  // 成员变量 // 全局变量 static UINT auIDStatusBar[] = { ID_SEPARATOR }; //在对话框类的WM_ ...

  7. 禁用和启用链接(a元素|LinkButton)的js方法

    4 function disableLink(link) {5     //设置href属性6     link.href = "javascript:void(0);";7    ...

  8. USACO Section 4.2: Drainage Ditches

    最大流的模板题 /* ID: yingzho1 LANG: C++ TASK: ditch */ #include <iostream> #include <fstream> ...

  9. Ubuntu14.04安装和配置Tomcat8.0.12

    Ubuntu14.04长的好看,所以一时间很感兴趣,研究各种软件的安装和开发环境的配置.今天先把安装的tomcat 8.0.12的教程分享给大家.如果你需要,请收藏!!!   官方网站下载最新的tom ...

  10. JavaScript —— 对象的取值与赋值

    可能是因为用惯了 Java ,对一个对象取值/赋值喜欢用 setXXX() 和 getXXX() . 在 JavaScript 中使用 setValue() 时,遇到了个奇怪的问题,所以查了下 Jav ...