概述
SQLite
在介绍LitePal之前还是要先介绍一下SQLite,也就是我们通常所说的数据库,开发中多多少少会用到,不过原生的SQLiteDatabase,只要写过你就知道,写Demo还是可以的,但是在实际项目中就不够灵活了,因为Java作为面向对象的语言,我们在实际开发的过程中操作的大部分都是对象,如果使用SQLiteDatabase,我们进行CRUD操作的时候需要写SQL语句,查询的也是一个Cursor。所以需要根据就跟网络请求一样,如果是简单的网络请求,你可以用HttpURLConnection或者OKHttpURLConnection,但是真正的项目开发的时候,也是各种框架用地飞起,所以就有人对DB的CRUD操作进行了封装,于是就产生了很多的ORM框架,LitePal便是其中的一种。
LitePal
LitePal的作者是郭霖,使用方式比较简洁,从名字来看LitePal比较轻,翻译过来是,'轻的朋友',GitHub上面有很多的ORM(Object Relational Mapping),也就是通常所说的关系型数据库框架。常见有greenDAO,LitePal,OrmLite等等,下面看一下主流的ORM框架在GitHub的使用情况
ORM | Initial Commit | Star | Fork | Contributors |
---|---|---|---|---|
greenDAO | 2011-07 | 9366 | 2537 | 6 |
Realm | 2012-04 | 9018 | 1427 | 76 |
LitePal | 2013-03 | 4361 | 1088 | 1 |
Sugar | 2011-08 | 2484 | 596 | 57 |
Ormlite | 2010-09 | 1296 | 343 | 6 |
上面的表格是按照GitHub上的Star数来排序的,可以看到LitePal是排第三,Fork数跟Star数量基本上是正相关的,但是由于LitePal推出的比较晚,而且是作者一人在维护(Contributors数据摘自GitHub),所以郭神还是相当厉害的。
LitePal的使用很简单,下面以官方的Sample举例,可以在asset下的literpal.xml中配置DB的版本以及存储的数据对象,存储路径,:
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<dbname value="sample" />
<version value="1" />
<list>
<mapping class="org.litepal.litepalsample.model.Album" />
<mapping class="org.litepal.litepalsample.model.Song" />
<mapping class="org.litepal.litepalsample.model.Singer" />
</list>
<storage value="external" />
</litepal>
本文分析的是GitHub上LitePal的最新版本1.6.1,使用方式比较简单,并且作者专门开了一个专栏:Android数据库高手秘籍,关于LItePal的使用可以看一下作者的专栏,还有GitHub上的Sample也很详细地介绍了这个框架,下面主要从源码的角度来解读一下Litepal的实现原理,其实分析起来还是很简单的,就是注释有些翻译起来很痛苦,强烈建议来一个中文版的注释。
正文
工作流程
Litepal是个ORM框架,所以不像AsncTask,Volley,Picasso那样流程比较复杂,以及线程切换等,它的中心在于让DB操作更加简单跟高效,基本上跟数据库打过交道都知道,数据库的主要操作就是CRUD,然后稍微麻烦点的就是DB的创建,升级等,说白了就是编写SQL语句比较麻烦,毕竟做Android客户端开发不像后台天天跟数据库打交道,随手一个SQL语句信手拈来,LitePal将DB的操作封装了对象的操作,也就是我们通常所说的ORM操作,这样操作起来就会比较方便了,我们不需要担心SQL语句编写错误,平时怎么操作对象,现在早怎么操作数据库,同时Litepal也保留了原始的SQLite语句查询方式。
下面从跟随Sample中的示例代码,LitePal的Save操作,跟着源码来追一下Litepal的工作流程
Singer是一个继承自DataSupport的类,调用一下save方法,即可触发DB的存储造作,跟一下源码
Singer singer = new Singer();
singer.setName(mSingerNameEdit.getText().toString());
singer.setAge(Integer.parseInt(mSingerAgeEdit.getText().toString()));
singer.setMale(Boolean.parseBoolean(mSingerGenderEdit.getText().toString()));
singer.save();
依然是方法调用,调用了Singer的saveThrows方法,继续跟
public synchronized boolean save() {
try {
saveThrows();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
saveThrows是一个同步方法,在这里通过Connector获取到SQLiteDatabase的实例db,然后开启事务,创建了一个SaveHandler的实例,并且在构造方法中传入了SQLiteDatabase,调用了Singer的onSave方法,传入了当前对象的实例,如果执行此方法没有异常,那么就说明存储成功,关闭事务,否则会抛出异常,事务失败。那么继续看SaveHandler中的onSave方法,并且传入了自身对象,所以继续跟onSave方法
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(), e);
} finally {
db.endTransaction();
}
}
onSave方法,逻辑稍微复杂一下,下面通过一行一行的代码来分析一下
void onSave(DataSupport baseObj) throws SecurityException, IllegalArgumentException,
NoSuchMethodException, IllegalAccessException, InvocationTargetException {
//拿到对象的类名
String className = baseObj.getClassName();
//根据className,通过反射获取到LitePal支持的普通成员变量
List<Field> supportedFields = getSupportedFields(className);
//根据className,通过反射获取到LitePal支持的泛型变量
List<Field> supportedGenericFields = getSupportedGenericFields(className);
//根据className,通过反射获得到数据库的映射关系
Collection<AssociationsInfo> associationInfos = getAssociationInfo(className);
if (!baseObj.isSaved()) {
//通过Id判断是否是首次存储
if (!ignoreAssociations) {
//看表的映射关系是否需要处理
analyzeAssociatedModels(baseObj, associationInfos);
}
//存储该列数据
doSaveAction(baseObj, supportedFields, supportedGenericFields);
if (!ignoreAssociations) {
analyzeAssociatedModels(baseObj, associationInfos);
}
} else {
//更新操作
if (!ignoreAssociations) {
analyzeAssociatedModels(baseObj, associationInfos);
}
//更新表中的字段
doUpdateAction(baseObj, supportedFields, supportedGenericFields);
}
}
跟一下doSaveAction
private void doSaveAction(DataSupport baseObj, List<Field> supportedFields, List<Field> supportedGenericFields)
throws SecurityException, IllegalArgumentException, NoSuchMethodException,
IllegalAccessException, InvocationTargetException {
values.clear();//清空上一次CRUD操作的ContentValue
//将Singer中的数据转换成ContentValues
beforeSave(baseObj, supportedFields, values);
//保存数据,并且获取Id
long id = saving(baseObj, values);
//对保存的module赋予Id,进行加密等操作
afterSave(baseObj, supportedFields, supportedGenericFields, id);
}
跟一下saving这个方法,发现已经到头了,直接调用了SQLiteDataBase的insert方法
private long saving(DataSupport baseObj, ContentValues values) {
if (values.size() == 0) {
values.putNull("id");
}
return mDatabase.insert(baseObj.getTableName(), null, values);
}
好像整个流程到这里基本上完成了一次保存或者更新的操作,还是比较简单的。不过上面主要是在分析流程,有很多细节没有深入,涉及到的几个类有DataSupport,SaveHandler,Connector,由于只是进行了保存操作,所以还有很多类没有涉及到,类似typechange,LitePalBase,AsyncExecutor,DataHandler等,这些会接下来的LitePal架构中进行具体的分析。
LitePal架构
由于LitePal分了很多包,而且是通过功能进行划分的,为了便于直观的展示,我将包转化成了思维导图,这样可以更加直观地了解整个LitePal的架构。
Litepal
其实观察一下可以发现,crud包跟tablemanager包是整个框架的核心,因为其实这两个包有些东西是有关联的,所以没法具体的进行划分,所以现在选取了三个抽象类LitePalBase,AsyncExecutor,OrmChange因为大部分核心类都是继承自这三个抽象类的。
LitePal
注释
LitePal is an Android library that allows developers to use SQLite database extremely easy.You can initialized it by calling {@link #initialize(Context)} method to make LitePal ready to work. Also you can switch the using database by calling {@link #use(LitePalDB)} and {@link #useDefault()} methods.
LitePal是一个Android库,开发者可以用这个库很容易地操作数据库。你可以通过调用initialize(Contetext)来初始化LitePal,当然你也可以通过调用use(LitePalDB)来使用指定的数据库或者调用useDefault来使用litepal.xml中默认的数据库。
aesKey
设置AES加密的key
public static void aesKey(String key) {
CipherUtil.aesKey = key;
}
isDefaultDatabase
//判断数据库是否是默认的数据库
private static boolean isDefaultDatabase(String dbName) {
if (BaseUtility.isLitePalXMLExists()) {
if (!dbName.endsWith(Const.Config.DB_NAME_SUFFIX)) {
dbName = dbName + Const.Config.DB_NAME_SUFFIX;
}
LitePalConfig config = LitePalParser.parseLitePalConfiguration();
String defaultDbName = config.getDbName();
if (!defaultDbName.endsWith(Const.Config.DB_NAME_SUFFIX)) {
defaultDbName = defaultDbName + Const.Config.DB_NAME_SUFFIX;
}
return dbName.equalsIgnoreCase(defaultDbName);
}
return false;
}
removeVersionInSharedPreferences
//移除SP中指定的数据库版本
private static void removeVersionInSharedPreferences(String dbName) {
if (isDefaultDatabase(dbName)) {
SharedUtil.removeVersion(null);
} else {
SharedUtil.removeVersion(dbName);
}
}
LitePalApplication
主要用来给LitePal操作数据库添加Context
public LitePalApplication() {
sContext = this;
}
//已经被遗弃
@Deprecated
public static void initialize(Context context) {
sContext = context;
}
//获取Context
public static Context getContext() {
if (sContext == null) {
throw new GlobalException(GlobalException.APPLICATION_CONTEXT_IS_NULL);
}
return sContext;
}
LitePalDB
注释
Configuration of LitePal database. It's similar to litepal.xml configuration, but allows to configure database details at runtime. This is very important when comes to support multiple databases functionality.