本文结构:
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略。
打我啊。
先写个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的一个封装最基本的思想,哪怕你知道数据的来源可靠。如果代码里这种判断都没有,那么这个代码一定是个垃圾。
划重点:对数据的来源,不信任且不关心,哪怕你知道这数据是从哪来的,并且传的是什么,那也要加上判断。
/**
* 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,我就不写了,略了,略略略
/**
* 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。懂了吗?
懂了
这个类里,可以写一些对对条件的判断,对数据库的操作,调用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了。。。。。。
周一愉快。