ActiveAndroid 源码阅读笔记 (1)

134 篇文章 0 订阅
79 篇文章 0 订阅

新公司忙成狗,不过hold过来,但是因为自己之前android涉猎contentprovider和SQLiteDB很少,

因此有些碰壁,决定读一遍ActiveAndroid这个DB操作封装集成框架的源码,补补这一块。

ActiveAndroid源码解析:
首先需要对数据库本身有一定的了解,
很多类本身对应的就是SQL的一个组件/概念/操作。
1. Annoation分析:
   java用  @interface Annotation{ } 定义一个注解 @Annotation,一个注解是一个类。
   在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。   
   (1)Table @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME)
   TYPE说明可以用于描述类、接口(包括注解类型) 或enum声明,
   而RetentionPolicy.RUNTIME 则说明此注解在Runtime时都是存在的。
   有name()<表名称> 和 id()<使用了default指定了"Id"为默认值>两个配置参数。
   (2)Column @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME),
   用于修饰描述域<基本可以等同为类的内部成员>,依然是运行期有效.
   有如下的配置参数:
     两个枚举的Action类,本身不作为配置参数,作为配置参数的可配值:
     <1> ConflictAction, 在操作此Column出现conflict时采取何种措施:
     <2> ForeignKeyAction, 外链相关操作.
     上面两个枚举类的值大多都对应着SQL的某种操作.
     
     配置参数除了属性值<name/length/unique...>以外,还有动作属性<onDelete/onUniqueConflict/...>
     动作属性的值基本都是上面枚举Action类的值。依然使用default定义了默认值。
2.ActiveAndroid类:
  偏向于一个Facade类,初始化和一些全局以及工具方法都在此定义:
  初始化可以具体的制定context等参数,也可以直接生成一个Configuration对象来
  封装初始化参数。
  初始化的主题其实就是Cache对象的初始化。
  还提供了清理cache/获取包装的SQLiteDatabase/直接执行某一句SQL语句的tool函数,
  注意的是也实现了SQL中transaction概念,当有大批量的DB操作时,建议使用,
  beginTransaction()/endTransaction().
3.Cache类:
  类本身隐藏了构造函数,提供了static的initialize函数,也是一个single.
  内部成员: 一个static 初始化时Context的引用,
           一个 static  ModelInfo<Model是activeandroid的一个重要基础类>,
         一个static的 DatabaseHelper
           一个LruCache<String, Model>(这就是为什么叫Cache)
           一个初始化Flag<全局唯一single,因此只需要init一次>
  (1)初始化操作都是例行的new和赋值, 并且会将SQLDataBase open,得到一个
  可写的SQLiteDatabase<具体操作由DatabaseHelper代劳>。而Cache提供的
  closeDatabase()方法close的就是此可写的SQLiteDatabase。
  (2)clear()操作在这里是轻量级的内容消除,将LRUCache清空。
  而dispose()则是重量级的环境消除,关闭SQLiteDB,将所有内部的引用成员
  全部null。此操作之后,如果想要再次使用,必须重新init.
  (3) getTableName, 传入的是 extends了某个Model的Class类对象,通过
   ModelInfo来得到此类对应于SQLDB中的Table的name.
   getTableInfo相似,而getTableInfos()则返回了当前ModelInfo中包含的
   所有的Table的Info.
  (4)addEntity/getEntity/removeEntity, value全部都是Model对象,操作的是
   内部的LRUCACHE,Key则是为Model生成的Identifier。
   Identifier的生成很简单: Model对应的table name + "@" + Model自己的id;
  (5)getParserForType: 针对输入的Class的类型,返回对应的TypeSerializer。
   用于在内存数据结构和硬盘SQL之间的序列化和反序列化<也可以是两个class之间>。
   个人认为Cache这个名字起的过于笼统,从目前的分析看,该Cache保存的信息都是
   Model的相关Info,因此完全可以叫做ModelInfoCache,当然了,自己内部用无所谓了.

4.Model:
  (1) 内部成员: 一个HASH_PRIME<用于Model自己的hashCode()>,一个mId,
  一份TableInfo<基本可以认为内存中的Model和硬盘中的DB的table一对一>, 一个idName,

  (2) Model的构造初始化很有意思: TableInfo是上面分析的Cache拿到的,参数自然就是自己的class type.
  看样子在编译/初始化时有一次对所有衍生Model类的遍历处理.
  idName也是得到的TableInfo的getIdName().

  (3) save()函数: 首先通过Cache取得了SQLDB<Cache的名字起得太不好了....>,
   new一个ContentValues来进行到ContentProvider的传输信息承载。
   这一步说白就是数据从内存序列化以后通过ContentProvider持久化到DB.
   从TableInfo就可以得到该Model对应的Table的每个Column对应到内存数据结构的Field<说的太绕了...>
   每个Filed对象对应这一个Column,还是在TableInfo里得到对应的fileName,以及Field的getType得到
   此Filed存储的对象的类型。
   因为是反射的操作,为了能读取Filed对象,Filed对象需要setAccessible(true).
   有了Field对象,就可以用get来获取field里存的对象了(注意try/catch),
   因为Field可能是任何对象,因此比如知道如何序列化此类型的对象,因此就需要结合Class type从Cache取得相应的
   TypeSerializer<可能是null>, TypeSerializer当然其实只是一个接口,其定义了serialize方法返回序列化以后的
   字符串<不是二进制流>,如果成功的序列化,那么还要在做一次TypeSerializer的Type和FieldType之间的匹配
   <作者还是挺谨慎的,不过这里不匹配只是log一下>,则对一个Field序列化以后,就开始填充到ContentValue了,
   保险起见,对于null的case也要调用contentValues.putNull(fieldName);
   在保存时,会按照不同类型对得到序列化字符串进行强转(Byte/Short/..../String/Byte[]),
   注意的是如果是Model,那么实际只保存该Model的Id, 对于枚举类极其衍生类(isSubclassOf),
   保存的则是该ENUM val的name.
   
   如果当前的mId是null,那么调用原生的insert将此ContentValue加入,并保存了mId
   如果mId不是null,那么原生的update来更新,filter是idName=mId。
   从这个过程可以看出,在使用Model对象.save()时,Model对象其实算是代表着
   Model对应的Table的一个row<这一点感觉设计的有点不和谐>。
   在修改DB以后,会调用ContentProvider的getContentResolver的notifyChange来通知Dataset的变化。
   这里有些细节需要到ContentProvider再说,这里notifyChange的url是由ActiveAndroid自己的contentprovider
   生成的。
   
  (4)Model在更高一级还是作为Table工具类的,因此直接提供了static的delete(Class<? extends Model> type, long id)
  来删除某个Table的某个row, 和 load函数来读取一个row封装到一个Model并返回。

  (5)hashCode()为了保证unique,采用了mId和TableName的hash拼接。

  (6)equals函数必然需要自己定制,常规的先判断地址以及类型(instanceof)然后是id和TableName。

  (7)delete()函数直接地通过Cache调用了原生的delete方法, filter取当前的mId值。
  在删除以后,会将此Model从Cache中remove<不太明白为何需要从cache remove>.
  然后同样的notifyChange
 
  (8)loadFromCursor(Cursor cursor),老实说,感觉这个函数有点勉强不对口,一个Cursor对应的不一定就是
  该Model对应的Table,有种强行读取的感觉,估计是作者在某些case下的权益之计。
  首先会尝试获取Cursor里封装的所有Column的name,然后遍历Model的TableInfo,
  通过indexof来获得Field在Cursor中的columnIndex(如果没有,直接下一个),
  得到了columnIndex还需要做一次cursor.isNull(columnIndex) check,为了保证类型匹配,
  从Cache获得TypeSerializer以后就可以得到此field指向的对象的类型,这样才能在后面的
  cursor.getXXX调用正确的函数<其实就是一次反序列化过程>,还是特殊类型特殊处理,
  比如对于Byte[],需要Cursor.getBlob(), Model的话其实代表的是另一个Table的一个row<如果row指定的话>,
  Enum则直接调用valueOf(enumType, val).对于复杂的类型,则会在有typeSerializer存在的情况下尝试反序列化。

  最后将value设置到TableInfo的field即可。最后将次Model加入到Cache中。

  不直接通过反射得到Model类的成员,而是通过TableInfo得到Field列表,一个原因应该是,在Model的衍生类中定义的

  新的变量,不一定也也不应该强制每个都是需要持久化到DB 和column一一对应的,这就体现了上面Column annoation的作用,

  以及TableInfo的作用, 一个是标示Model里真正对应Column的成员,一个则保存这些信息方便查询。

  不过看save貌似只支持String等基本类型<最多加上Model和Enum>,但是loadCursor却可以尝试用序列器反序列化其他类型。有点疑问,mark一下。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值