LitePal源代码分析

概述

SQLite

在介绍LitePal之前还是要先介绍一下SQLite,也就是我们通常所说的数据库,开发中多多少少会用到,不过原生的SQLiteDatabase,只要写过你就知道,写Demo还是可以的,但是在实际项目中就不够灵活了,因为Java作为面向对象的语言,我们在实际开发的过程中操作的大部分都是对象,如果使用SQLiteDatabase,我们进行CRUD操作的时候需要写SQL语句,查询的也是一个Cursor。所以需要根据就跟网络请求一样,如果是简单的网络请求,你可以用HttpURLConnection或者OKHttpURLConnection,但是真正的项目开发的时候,也是各种框架用地飞起,所以就有人对DB的CRUD操作进行了封装,于是就产生了很多的ORM框架,LitePal便是其中的一种。

LitePal

LitePal的作者是郭霖,使用方式比较简洁,从名字来看LitePal比较轻,翻译过来是,'轻的朋友',GitHub上面有很多的ORM(Object Relational Mapping),也就是通常所说的关系型数据库框架。常见有greenDAOLitePalOrmLite等等,下面看一下主流的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框架,所以不像AsncTaskVolleyPicasso那样流程比较复杂,以及线程切换等,它的中心在于让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();

依然是方法调用,调用了SingersaveThrows方法,继续跟

public synchronized boolean save() {
   try {
      saveThrows();
      return true;
   } catch (Exception e) {
      e.printStackTrace();
      return false;
   }
}

saveThrows是一个同步方法,在这里通过Connector获取到SQLiteDatabase的实例db,然后开启事务,创建了一个SaveHandler的实例,并且在构造方法中传入了SQLiteDatabase,调用了SingeronSave方法,传入了当前对象的实例,如果执行此方法没有异常,那么就说明存储成功,关闭事务,否则会抛出异常,事务失败。那么继续看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这个方法,发现已经到头了,直接调用了SQLiteDataBaseinsert方法

private long saving(DataSupport baseObj, ContentValues values) {
       if (values.size() == 0) {
           values.putNull("id");
       }
   return mDatabase.insert(baseObj.getTableName(), null, values);
}

好像整个流程到这里基本上完成了一次保存或者更新的操作,还是比较简单的。不过上面主要是在分析流程,有很多细节没有深入,涉及到的几个类有DataSupportSaveHandlerConnector,由于只是进行了保存操作,所以还有很多类没有涉及到,类似typechangeLitePalBaseAsyncExecutorDataHandler等,这些会接下来的LitePal架构中进行具体的分析。

LitePal架构

由于LitePal分了很多包,而且是通过功能进行划分的,为了便于直观的展示,我将包转化成了思维导图,这样可以更加直观地了解整个LitePal的架构。

Litepal

其实观察一下可以发现,crud包跟tablemanager包是整个框架的核心,因为其实这两个包有些东西是有关联的,所以没法具体的进行划分,所以现在选取了三个抽象类LitePalBaseAsyncExecutorOrmChange因为大部分核心类都是继承自这三个抽象类的。

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.

LitePal DB的配置信息,类似于litepal.xml,不过可以动态地配置DB,在需要添加多个数据库的时候这个功能非常重要

通过litepal.xml配置的数据库的时候只能添加一个,通过LitePalDB可以支持多个数据库。

成员变量


private int 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值