如何自定义一个优雅的ContentProvider
最近在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的更多相关文章
- 在PostgreSQL自定义一个“优雅”的type
是的,又是我,不要脸的又来混经验了.我们知道PostgreSQL是一个高度可扩展的数据库,这次我聊聊如何在PostgreSQL里创建一个优雅的type,如何理解优雅?大概就是不仅仅是type本身,其它 ...
- SpringMVC 自定义一个拦截器
自定义一个拦截器方法,实现HandlerInterceptor方法 public class FirstInterceptor implements HandlerInterceptor{ /** * ...
- jQuery Validate 表单验证插件----自定义一个验证方法
一.下载依赖包 网盘下载:https://yunpan.cn/cryvgGGAQ3DSW 访问密码 f224 二.引入依赖包 <script src="../../scripts/j ...
- Spring自定义一个拦截器类SomeInterceptor,实现HandlerInterceptor接口及其方法的实例
利用Spring的拦截器可以在处理器Controller方法执行前和后增加逻辑代码,了解拦截器中preHandle.postHandle和afterCompletion方法执行时机. 自定义一个拦截器 ...
- JSTL,自定义一个标签的功能案例
1.自定义一个带有两个属性的标签<max>,用于计算并输出两个数的最大值: 2.自定义一个带有一个属性的标签<lxn:readFile src=“”>,用于输出指定文件的内容 ...
- 自定义View(7)官方教程:自定义View(含onMeasure),自定义一个Layout(混合组件),重写一个现有组件
Custom Components In this document The Basic Approach Fully Customized Components Compound Controls ...
- Volley HTTP库系列教程(5)自定义一个Volley请求
Implementing a Custom Request Previous Next This lesson teaches you to Write a Custom Request parse ...
- 在String()构造器不存在的情况下自定义一个MyString()函数,实现如下内建String()方法和属性:
在String()构造器不存在的情况下自定义一个MyString()函数,实现如下内建String()方法和属性: var s = new MyString("hello"); s ...
- ExtJs5_继承自定义一个控件
Extjs的开发都可以遵循OOP的原则,其对类的封装也很完善了.自定义一个控件最简单的办法就是继承一个已有的控件.根据上一节的需要,我做了一个Button的子类.首先根据目录结构,在app目录下建立一 ...
随机推荐
- (一)初探HTML!
想自己动手做一个个人网站,因此,最近在自学PHP,主要看韩顺平老师的教学视频..将自己学习的点点滴滴记录在博客园,希望数月之后,自己可以熟练的运用PHP,也希望各位PHP高手们给予指点,不胜感激!! ...
- awk过滤统计不重复的行
awk以‘\t’为分隔符区分列 cat logs | grep IconsendRedirect | grep 1752 | awk -F'\t' '{print $8}'| wc -l awk过滤统 ...
- Card Game Cheater---hdu1528(扑克建图求二分匹配)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1528 题意就是给有两个人有n张牌第二个人知道第一个人的牌的序列: 然后第二个人尽可能的让自己得更高的分 ...
- Linux配置自建 YUM 软件存储库
yum软件仓库的搭建方式有三种,分别是本地yum源,网络yum源,第三方软件仓库. 以下示例演示了搭建本地yum仓库的方法: 1. 删除 /etc/yum.repos.d/dvd.repo 这个仓库文 ...
- Hadoop HDFS文件系统通过java FileSystem 实现上传下载等
package linlintest; import java.io.File; import java.io.FileOutputStream; import java.io.IOException ...
- OpenCV4Android开发之旅
http://blog.csdn.net/yanzi1225627/article/details/16917961
- php数据库操作常用相关函数
MySQL访问函数都需要有相应的权限才能运行.常用的相关函数介绍如下: (1)integer mysql_connect(主机,用户名,口令); 此函数开始一个对指定主机上的MySQL数据库的连接.若 ...
- 《OD学Flume》20160806Flume和Kafka
一.Flume http://flume.apache.org/FlumeUserGuide.html Flume是一个分布式的,可靠的,可用的,非常有效率的对大数据量的日志数据进行收集.聚集.移动信 ...
- JUnit 4
本文是转载的, 主要介绍 Junit 4 ( 搭建在 eclipse 中 ) JUnit4 初体验 Eclipse: 下载 Ant, 基于java的开源构建工具, 你可以在 http://ant.ap ...
- create-maximum-number(难)
https://leetcode.com/problems/create-maximum-number/ 这道题目太难了,花了我很多时间.最后还是参考了别人的方法.还少加了个greater方法.很难. ...