Android学习——LitePal源码分析
原创技术博客,请认准Azzssss的原文http://www.cnblogs.com/Azzssss/p/4147704.html。
这两天项目终于上线了,松了一口气,虽然还是很不稳定,见一步走一步吧。反正总算能抽出时间来写博客了。在项目中用到了LitePal,LitePal是什么鬼东西呢?它的Github主页是什么介绍的:“LitePal是一个开源的android库,允许开发者极其方便地去使用sqlite数据库。通过它,你甚至可以不写一行SQL语句来完成大部分的数据库操作,包括创建和更新表,crud操作,聚合函数等等。LitePal的配置同样很简单,5分钟就可以整合进你的项目。”
我第一次知道LitePal是通过郭林的博客,郭先生一连写了八篇博客介绍LitePal。这篇博客主要是想深入了解LitePal的源码,如果还没使用过LitePal,建议先移步郭先生的博客,这是他关于LitePal的第一篇博客《Android数据库高手秘籍(零)——前言》。
现在,先看看LitePal的save操作,太简单了,忍不住再次称赞一下。
public class Album extends DataSupport { private String name; private float price; private List<Song> songs = new ArrayList<Song>(); // generated getters and setters. ... } Album album = new Album(); album.setName("album"); album.setPrice(10.99f); album.save();
是不是很简单?想起自己写过的sqlite语句是不是嗷的一声昏过去了?这里我先讲一下大概的思路吧,是这样实现的,每一个实体类都要继承DataSupport这个类,通过反射找到实体类中所有的属性(只能是基本数据类型或者String),通过反射将对象属性的值put到ContentValues里面,然后就调用原生SQLiteDatabase的方法insert进去。当然,这里面还涉及到关联数据的东西,这个有点复杂,稍后分析。
我们先来看看LitePal的增删改查操作的类图
可以看出,主要就是SaveHandler,DeleteHandler,UpdateHandler,QueryHandler,另外还有一个AssociationsAnalyzer,这是一个关联的分析器,分析关联关系。LitePal可以让我们很轻松地处理关联关系,就是通过这个东西。
下面我们看看,这个save操作究竟进行了什么,走起~
我们点进去DataSupport的save方法,发现是这样的(我是不是应该画个类图啊)
public synchronized boolean save() { //调用了saveThrows()方法 代码上的注释是这么说的,如果这个是一条新的记录,就会在数据库中create,否则就更新现有的数据。 try { //它怎么区分这条数据是否在已经在数据库中了?答案是根据JavaBean里地id或者_id属性(必须为int类型或者long类型),这是一个约定,如果实体类 saveThrows(); //中有这个属性,这个id的值将会由数据库创建并在调用save方法后自动指派给它(原理我稍后看看),并且判断这个id属性是否有值区分insert和update操作 return true; } catch (Exception e) { e.printStackTrace(); return false; } }
接下来我们看saveThrows()方法
public synchronized void saveThrows() { SQLiteDatabase db = Connector.getDatabase(); //获取数据库 db.beginTransaction(); //这个估计是开启事务?先放着 try { SaveHandler saveHandler = new SaveHandler(db); //这是关键咯 saveHandler.onSave(this); clearAssociatedData(); //不知道这个是什么鬼,清理外键数据啥的 db.setTransactionSuccessful(); } catch (Exception e) { throw new DataSupportException(e.getMessage()); } finally { db.endTransaction(); } }
这里LitePal把储存的操作封装在SaveHandler里面了,其实增删改查的操作还分别封装了DeleteHandler,UpdateHandler,QueryHandler,这四个类继承自DataHandler
SaveHandler的onSave方法是这样的
void onSave(DataSupport baseObj) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
String className = baseObj.getClassName(); //获取类名,例子里就是Album类名,其实调用的就是getClass().getName(); List<Field> supportedFields = getSupportedFields(className); //找出这个实体类中的所有属性,但是只支持基本数据类型和String类型 Collection<AssociationsInfo> associationInfos = getAssociationInfo(className); //这里涉及到关联数据,先不理 if (!baseObj.isSaved()) { //判断模型已经创建与否,也就是说这条数据是否在数据库中了,就一行return baseObjId > 0,所以说实体类的id值不要自己手动去设 analyzeAssociatedModels(baseObj, associationInfos); //关联数据相关,等一下再分析 doSaveAction(baseObj, supportedFields); analyzeAssociatedModels(baseObj, associationInfos); //关联数据相关,等一下再分析 } else { analyzeAssociatedModels(baseObj, associationInfos); //关联数据相关,等一下再分析 doUpdateAction(baseObj, supportedFields); //如果baseObjId不为空,更新 } }
先说一下doSaveAction
private void doSaveAction(DataSupport baseObj, List<Field> supportedFields) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
ContentValues values = new ContentValues(); beforeSave(baseObj, supportedFields, values); //beforeSave 主要功能是通过反射把实体类的数据存入ContentValues,动态地获取数据类型,并调用相 //应的put方法 long id = saving(baseObj, values); //保存数据到表中,并且返回一个long类型的id afterSave(baseObj, supportedFields, id); //afterSave }
现在看看这行代码 long id = saving(baseObj, values); 这里点进去,就发现是调用SQLiteDataBase的insert操作了,之前我们不是把通过反射把ContentValues的数据都put好了吗,接着调用SQLiteDataBase的insert方法,这个nullColumnHack参数是为了防止插入空行,底层数据库不允许插入一个空行
public long insert(String table, String nullColumnHack, ContentValues values)
好了,看看afterSave()方法,插入后的操作
private void afterSave(DataSupport baseObj, List<Field> supportedFields, long id) { throwIfSaveFailed(id); //如果id为-1,抛出异常,插入数据失败 assignIdValue(baseObj, getIdField(supportedFields), id); //指派id给JavaBean,就是那个Album对象 updateAssociatedTableWithFK(baseObj); //更新关联的表,先不要理,关联那块另外说 insertIntermediateJoinTableValue(baseObj, false); //这个也涉及到关联数据,多对多关系下的中间表操作,先放着 }
好像差不多了,SaveHandler里面还有update的方法没提到,还有一小部分关于关联的操作。先吃个夜宵再写。。。。
下面我们来分析LitePal的关联关系是怎么实现的,在onSave()里面首先有这么一个getAssociationInfo
protected Collection<AssociationsInfo> getAssociationInfo(String className) { //通过类名获取关联信息 if (mAssociationInfos == null) { mAssociationInfos = new HashSet<AssociationsInfo>(); } mAssociationInfos.clear(); analyzeClassFields(className, GET_ASSOCIATION_INFO_ACTION); return mAssociationInfos; }
什么是关联信息呢?其实很简单,就是修饰符是private,以及不是基本数据类型的,主要通过这个方法判断
private boolean isPrivateAndNonPrimitive(Field field) { return Modifier.isPrivate(field.getModifiers()) && !field.getType().isPrimitive(); }
然后分别有oneToAnyConditions,以及manyToAnyConditions两个方法来分析,这个类中的一对多和多对多关系。
oneToAnyConditions方法其实很简单,具体思路是这样的。首先,通过反射,获得这个类(from class)的所有申明的字段reverseFields
,然后遍历这些字段,通过reverse字段得到它的类名(defined class),然后根据这个reverseFieldTypeClass,反射得到其所有申明的字段,查看
1、If there's the from class name in the defined class, they are one2one bidirectional associations. 中文还真不到怎么表达,看不明白可以先阅读郭霖的Android数据库高手秘籍(四)——使用LitePal建立表关联。通俗一点表达就是,如果reverseFields中也有你这个from class name,那么就是一对一关系,而且是双边的一对一关系
2、 If there's the from class Set or List in the defined class, they are many2one bidirectional associations.
如果reverseFields中有这个from class name,并且是以List或者Set的形式存在,那么就是多对一关系
3、If there's no from class in the defined class, they are one2one unidirectional associations.
如果在from class 不在defined class 中,他们是一对一关系,只是单边的一对一关系
嗯,感觉没说明白。。一步一步来吧,我们看看关联数据的值是怎么赋的。
在刚才提到的beforeSave的方法中
private void beforeSave(DataSupport baseObj, List<Field> supportedFields, ContentValues values) throws SecurityException, IllegalArgumentException, NoSuchMethodException, IllegalAccessException, InvocationTargetException { putFieldsValue(baseObj, supportedFields, values); putForeignKeyValue(values, baseObj); //将foreignKey的值设置到ContentValue中 }
点进去putForeignKeyValue
private void putForeignKeyValue(ContentValues values, DataSupport baseObj) { Map<String, Long> associatedModelMap = baseObj.getAssociatedModelsMapWithoutFK(); //得到存着关联模型的一个Map,key是字段名,value是外键的id for (String associatedTableName : associatedModelMap.keySet()) { values.put(getForeignKeyColumnName(associatedTableName), //put到ContentValue里面去 associatedModelMap.get(associatedTableName)); } }