ContentProvider操作数据库—一项古老的Android技术

一、杂谈

本文结构:
1、杂谈。
2、上代码,阉割版的demo。
3、总结一下封装的思想。

本文旨在回味几年前的技术,同时对封装功底进行夯实。毕竟最近一段时间都没有写代码。封装的思路,要清晰,明白,明白的是这个思路,这个想法,而不是照抄。

好久没更新CSDN了,因为最近有点懵懵的,周末也不知道在干啥,没打错字,不是萌萌的,是真懵懵的。

之前找工作阶段的时候,面试过一家公司,叫xx拼车,名字也是蹭滴滴的热度,整了个滴答。O(∩_∩)O哈哈~。人事是个挺漂亮的年轻小姐姐,所以说这家公司不错。

面试这家公司的时候,原定是组长面我,因为我和另一个人一起来的,他先答完了题(by phone),所以经理去面他了,然后找了个人来面我。这个人是个员工,一个有强烈竞争感的员工。

问了我上家公司采用的一些技术,其中涉及到数据库部分的,我答,用的是ContentProvider。接下来他的反应令我懵逼了

它以一种嘲笑的语气,半笑着问我,ContentProvider不是用来数据共享的吗,怎么能操作数据库呢?

我(⊙o⊙)…了一下。然后回答,ContentProvider数据共享的时候,不也是操作了数据库吗?那么他一定可以操作自己的数据库啊。

他很执着的说,ContentProvider是用来共享数据的,不是操作数据的啊。

我(⊙o⊙)…了一下。然后说可以的,是可以的,我可以给你说说。

然后他又重复的说了上一句,ContentProvider是用来共享数据的,不是操作数据的啊。

我心里想,尼玛,这么一根筋吗,你就当做共享了自己的数据行不?我说,ContentProvider可以共享,也可以操作数据库,插入查找删除啥的都有,都可以的。

我刚想给他详细的说一下,代码怎么实现,但这时候突然间,他好像魔怔了一般,选择性失聪并且复读。一直在重复着那一句话,“ContentProvider怎么操作数据库,ContentProvider怎么操作数据库,ContentProvider怎么操作数据库”,令我无从插嘴。恨自己交流能力太弱。

这时候我想到了,在山的那边,雪的那边,有一个男精灵,他名字叫欧阳锋,他执着又聪明。

一个练功练魔怔了的男人,他俩此时的状态居然像极,因为对于心法中有地方想不明白,就走火入魔了。

最后我还是简单说了一下ContentProvider操作数据的过程,然后我们进行了下一话题。

面试完毕之后,我觉得我很有戏,因为我说到了一个他不会的知识点,虐了一下面试官,一般这种情况,面试官都会录取你的。

故事的结局很意外。他们说我们在考虑一下之后,就一直考虑到了现在,大概已经有三四个月了吧,而我早就入职别的公司了。

我到现在还挺愧疚的,我要不要告诉他们我已经入职了啊,要不然他们还在考虑可怎么办啊。

哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈哈。


之前公司数据库操作用的SQLiteOpenHelper和ContentProvider,现在入职的公司,是OrmLite和ContentProvider一起用的。是的都是比较老的框架。

毕竟这两个项目,一个六年了,一个N(N<=4)年了。
(6年前的Android项目你见过吗?古董)

简单说一下吧,ContentProvider操作数据库的好处,那就是定义统一的接口,以接口的形式访问表啥的。不管你表结构怎么改,我这不用改,挺方便是吧。但是,据我个人所知,现在类似与GreenDao这种框架的流行,对数据操作的优化以及编程时也不用自己写多少,所以ContentProvider没有多少公司再用了。

有用的,要么是项目很久都没有被重构,要么是做项目的程序员所掌握的技能很久都没有被重构。

牛逼吹完了,上上代码,这里以SQLiteOpenHelper为例,回顾一下几年前主流的数据库操作。(老程序员可以哭了,现在还有菜鸟学你们之前的用法)

卧槽,写到这发现有点不对啊。别人都是学新的技术,新技术一出,就像疯狗一样去学,生怕屎凉了,哦不,是新技术过时了(没办法,Android技术更新太快),为啥我越学越往回学了啊?

二、SQLiteOpenHelper

SQLiteOpenHelper略。

打我啊。

三、BaseContentProvider

先写个ContentProvider,实现了先。内部定义SQLIteOpenHelper。

/**
 * ContentProvider基础类.
 *
 */
public abstract class BaseContentProvider extends ContentProvider {
    //略一堆常量

    private SQLiteOpenHelper mOpenHelper;


    /**
     * 子类应该通过本方法设置SQLiteOpenHelper
     *
     * @param helper
     */
    public void setSQLiteOpenHelper(SQLiteOpenHelper helper) {
        mOpenHelper = helper;
    }
    
    //未完。。。。。。

然后重写COntentProvider中的一些方法,例如增删改查的方法。这里用到一个类,SqlArguments ,这个是自己封装的,Android SDK没有,别问我你怎么找不到。
就是根据传来的uri以及其他参数,来返回不同的参数。。。对参数的一个封装,就是查询时需要的表名,以及其他条件。

重写getType:

//就是根据uri返回个表名
 @Override
    public String getType(Uri uri) {
        SqlArguments args = new SqlArguments(uri, null, null);
        if (TextUtils.isEmpty(args.where)) {
            return "xx.xx.xx/" + args.table;
        } else {
            return "yy.yy.yy/" + args.table;
        }
    }

重写查询,这里有个setNotificationUri的方法,这个方法就是在数据库改了之后,通知我们注册的观察者,这个观察这是在cursorAdapter中注册的,想详细了解这里的话,自行百度吧。这里上链接会处于审核中,而一直不过关的。有病。


    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder) {

        SqlArguments args = new SqlArguments(uri, selection, selectionArgs);
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        qb.setTables(args.table);

        SQLiteDatabase db = null;
        Cursor result = null;
        try {
            db = mOpenHelper.getWritableDatabase();// .getReadableDatabase();
            result = qb.query(db, projection, args.where, args.args, null, null, sortOrder);
            if (result != null) {
                result.setNotificationUri(getContext().getContentResolver(), uri);
            }
        } catch (SQLiteDiskIOException e) {
            Log.i(TAG, "query : " + e.toString());
            if (result != null) {
                result.close();
                result = null;
            }
        }

        return result;
    }

再写个插入,批量插入。这里用到了数据库的事务,要插一起插,完事的先等着,没完事的插完了一起拔。(o)/~

@Override
    public int bulkInsert(Uri uri, ContentValues[] values) {
        SqlArguments args = new SqlArguments(uri);

        SQLiteDatabase db = mOpenHelper.getWritableDatabase();
        db.beginTransaction();
        try {
            int numValues = values.length;
            for (int i = 0; i < numValues; i++) {
                if (db.insert(args.table, null, values[i]) < 0)
                    return 0;
            }
            db.setTransactionSuccessful();
        } finally {
            db.endTransaction();
        }
        sendNotify(uri);
        return values.length;
    }
//通知一下子观察者,数据库变了。
private void sendNotify(Uri uri) {
        String notify = uri.getQueryParameter(PARAMETER_NOTIFY);
        if (notify == null || "true".equals(notify)) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
    }

更新和删除就不写了,和插入差不多。返回int类型,更新了几条,删除了几条。

这样,一个BaseContentProvider就写好了。然后写一个类,继承我们的BaseContentProvider就好了。

四、封装思想及过程

上学那时候,学什么课程都要做个学生信息管理系统。后来我腻了,老子来学计算机是打算做游戏的,天天做个破管理系统干毛啊?

后来老师一想,恩,就让我做了个跟游戏有关的玩家信息管理系统。。。

OMG,后来拷贝代码的时候,没改名,玩家有一个表叫做课程表。交作业的时候,老师问我,玩家为什么要有课程?

我答,这是本游戏的特色,玩家除了玩游戏以外还可以学习。我天猫的是一本正经的那种表情和语气当着全班同学的面回答的啊。回答完自己都憋不住笑。同学们都说我是个弱智。

所以这里创建的表还是Course

然后说一下封装的思想。封装,就是将一系列操作,打包,暴露给外部使用的时候,十分简单。而正因为这种封装的封闭性,所以我们封装的一定要严谨,一定要不能出错。层层判断是必须的,现在你拿出任何一个知名的APP出来,你看他的源代码,都是判断的十分严谨,恨不得所有地方都写上if。对数据的来源,不信任,不关心,这是Java的一个封装最基本的思想,哪怕你知道数据的来源可靠。如果代码里这种判断都没有,那么这个代码一定是个垃圾。

划重点:对数据的来源,不信任且不关心,哪怕你知道这数据是从哪来的,并且传的是什么,那也要加上判断。

CourseContentProvider

/**
 * Created by WenCh on 2017/11/5.
 */

public class CourseContentProvider extends BaseContentProvider {

    public static final int DATABASE_VERSION = Constants.DATABASE_VERSION;

    public static final String DATABASE_NAME = "netease_vopen.db";

    public static final String AUTHORITY = Constants.DATABASE_AUTHORITY;

    public static final Uri BASE_URI = Uri.parse("content://" + AUTHORITY);

    public static final String TABLE_All_DTTA = "t_vopen_all_data";

    @Override
    public boolean onCreate() {
        setSQLiteOpenHelper(new com.contentprovidertest.CourseHelper(getContext()));
        return true;
    }

    public static class CourseHelper {
        /**===================数据库字段==================*/
        /**课程id*/
        public static final String COURSE_ID = "course_id";
        /**课程名称*/
        public static final String COURSE_NAME = "course_name";
        /**课程tag*/
        public static final String COURSE_TAG = "course_tag";

        /**===================数据库字段==================*/

        public static Uri getUri() {
            return Uri.parse("content://" + CourseContentProvider.AUTHORITY + "/"
                    + TABLE_All_DTTA);
        }
    }
}

//这里也可以写上其他表的Helper,我就不写了,略了,略略略

DBApi

/**
 * Created by WenCh on 2017/11/5.
 */

public class DBApi {
    public static final int DB_OPERATION_FAILED = -1;

    /*************************************************************
     * table name :TABLE_All_DTTA = "t_vopen_all_data"
     *************************************************************/
    //该表数据字段
    public static class DBCourseInfo {
        public String mCourseId;
        public String mCourseName;
        public String mCourseTag;
    }

    /**
     * 向表所有数据表中插入一项
     *
     * @param context 不瞎都知道
     * @param course  不傻都知道
     * @return 不是沙比都知道
     * @deprecated ^_^
     */
    public static Uri insertCourse(Context context, DBCourseInfo course) {
        if (null == course) {
            return null;
        }
        ContentValues initialValues = new ContentValues();
        if (!("判断一下相应的条件," +
                "比如说数据库里有其他字段,这个字段是标识的type," +
                "因为数据库里有很多表,所以根据这个type来判断是哪个表,否可能插入错误," +
                "这里是demo,就不写了").isEmpty())
            initialValues.put(CourseContentProvider.CourseHelper.COURSE_TAG, course.mCourseTag);
        return context.getContentResolver().insert(CourseContentProvider.CourseHelper.getUri(), initialValues);
    }
}

//这里也可以像其他表插入,比如插入学生。。。。
//insertStudent,可以这样命名,然后调用的CourseContentProvider中
//的别的Helper,比如StudentHelper。懂了吗?

懂了

DBUtils

这个类里,可以写一些对对条件的判断,对数据库的操作,调用DBApi。来实现更强大的功能。

/**
 * Created by WenCh on 2017/11/5.
 * 

* 该类用来处理数据库操作后的封装 */ public class DBUtils { private static final String TAG = "DBUtils"; /** * 查询与某一门课程相似的课程列表。 * * @param context * @param info * @return */ public static Cursor getRelativeCourses(Context context, BeanCourse info) { String[] projection = new String[]{CourseContentProvider.CourseHelper.COURSE_CONTENT, "_id"}; StringBuilder selectionBuilder = new StringBuilder(); List selectionArgsList = new ArrayList(); //不包括自己 selectionBuilder.append(CourseContentProvider.CourseHelper.COURSE_ID + "<> ?"); selectionArgsList.add(info.plid); //有相同的tag String tags = info.tags; if (!TextUtils.isEmpty(tags)) { selectionBuilder.append(" AND "); String[] tagTokens = tags.split(","); selectionBuilder.append("("); for (int i = 0; i < tagTokens.length; i++) { String tag = tagTokens[i]; selectionBuilder.append(CourseContentProvider.CourseHelper.COURSE_TAG + " LIKE ? " + " OR "); selectionArgsList.add("%" + tag + "%"); } selectionBuilder.setLength(selectionBuilder.length() - 4); selectionBuilder.append(")"); } String selection = null; if (selectionBuilder.length() > 0) { selection = selectionBuilder.toString(); } String[] selectionArgs = null; if (selectionArgsList.size() > 0) { selectionArgs = selectionArgsList.toArray(new String[0]); } String sortOrder = CourseContentProvider.CourseHelper.COURSE_NAME + " DESC"; return DBApi.queryCourse(context, projection, selection, selectionArgs, sortOrder); } } //这里也可以加上其他方法,例如,模糊插入,判断表里有没有,有就更新,没有就插入等等。

怎么样,经过这一层层封装,是不是可以完成很强大的功能?而逻辑还是清晰的?如果你什么都写一个类里,那么你一定特别乱。

五、封装思想总结

BaseContentProvider完成一些ContentProvider的操作。实现。

CourseContentProvider继承自BaseContentProvider,内部含有很多表的Helper,Helper包含一些字段,uri等。

DBApi是完成对相应的表的数据库操作,注意,是相应的表的。可以insertStudent,也可以insertCourse。调用CourseContentProvider以及其各种Helper。

DBUtils则是通过DBApi来完成更强大的功能,模糊查询,模糊插入等。当然了,最基本的插入啥的一定会有的。

周末愉快。卧槽,写完这博客已经0:12了。。。。。。

周一愉快。

你可能感兴趣的:(人生如此,你活得痛苦,却没人在乎)