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.
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值