如何自定义一个优雅的ContentProvider

如何自定义一个优雅的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
        // 数据库升级的时候 这边的代码 不写了,看各自的业务逻辑了,一般建议大家在这个地方多打一些日志
    }

}
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值