前言
Android的ORM框架有很多,比如Realm,greenDAO,LitePal,DBFlow,afinal,SugarORM,ORMLite和LiteORM,还有Anko的ManagedSqliteOpenHelper。其中Realm和GreenDAO在2017年百大框架排行榜里面排名最高,27名和28名。下面就来简单的说说这些框架并且说说Realm的使用和封装。
ORM框架
如果使用Android SQLite创建一个数据库需要实现下面的步骤:
- 创建一个
DBHelper类实现SqliteOpenHelper,传入context,数据库名称和初始版本,并实现OnCreate和OnUpgrade方法 - 实现
Dao层,通过getReadableDatabase和getWritableDatabase结合ContentValue和Cursor实现增删修改
原生操作复杂,写SQL语句容易出错。各种ORM的出现,使它的变得操作更加简单。
Realm
- 数据库大小(152kb左右)
- 改用
C++实现的数据库存储引擎 - 支持
JSON,流式API,数据变更通知,自动数据同步,访问控制,事件处理,简单的数据库加密 - 操作比原生
Android ORM快 - 不支持多库和
SQL语句执行 - 跨平台,支持
Java,OC,Swift,RN,JS等等
Github路径Realm-Java,下面进一步讲解具体使用
GreenDAO
- 轻量级数据库(<150kb)
- 快,可能是Android最快的
ORM - 可通过“生成器工程”生成
DaoMaster,DaoSession,对应的数据表和Dao层 - 通过
QueryBuilder操作,查询得到需要的表数据 - 支持跨实体查询
- 支持数据库加密
使用教程和源码参考greenDAO-Github
LitePal
- 轻量级数据库(大小176kb左右)
-
xml形式配置数据库名,数据库版本号和数据库表 - 1.4.0版本以上支持多库
-
API操作简单,支持原生SQL语句执行 - 不支持打开自建数据库的,需使用用
litepal创建
更多功能和使用文档查看郭霖大神的博客以及LitePal-Github
DBFlow
- 轻量级数据库(大小70kb左右)
- 性能高,不是基于反射
- 操作速度快,基于
annotationProcessor - 使用
apt技术,在编译过程中生成操作类 - 支持多个数据库
- 可基于
ModelContainer类解析像JSON这样的数据
使用文档参考DBFlow和DBFlow-Github
afinal
- 轻量级数据库(大小153kb左右)
- 具有
xml的id绑定和事件绑定功能 - 网络图片的显示功能(里面包含了强大的缓存框架)
- 数据库sqlite的操作功能,通过
FinalDb一行代码即可搞定 - http数据的读取功能(支持ajax方式读取)
使用文档参考afinal和afinal-Github
SugarORM
- 轻量级数据库(大小93kb左右)
- 集成简单,
API使用简单 - 通过反射自动创建表和列命名
- 支持表的一对多
使用文档参考SugarORM-Github
ORMLite
- 轻量级数据库(大小388kb左右)
- 继承
OrmLiteOpenHelper实现 - 需要通过
TableUtils手动创建数据库表和处理数据库升级 - 通过注解方式映射数据库表
- 获取
Dao对象进行增删修改
使用文档参考鸿洋大神介绍介绍ORMLite博客和ORMLite-Android-Github
LiteORM
- 轻量级的数据库(大小122kb左右)
- 比原生数据库快1倍,
Github上有测试数据 - 无须额外配置,自动检测升级数据库版本和
Model的变化 - 支持多库
-
API操作简单,支持save(replace), insert, update, delete, query, mapping等等操作 - 查询支持
in, columns, where, order, limit, having group - 不支持原生
SQL数据库的执行,貌似最近没有维护了
更多功能和使用文档查看LiteORM-Github
Anko-SQLite
通过kotlin+anko简化了创建原生Android数据库表操作,详情使用文档参考Anko-SQLite
从Github的Star来说,则Realm和Green占优势,同时这两个的功能十分强大。
从ORM库大小来说,则GreenDao,LitePal和LiteORM等轻量级的占优势。
从ORM的使用配置简单程度来说,则LitePal,afinal和LiteORM占优势。
综上所述,从稳定性,安全性,功能的强大性选Realm,GreenDao,ORMLite似乎更好,从轻量程度性,配置简单化来说选LitePal,afinal,LiteORM,SugarORM和DBFlow似乎更好。当然,如果想不依赖框架,使用Anko-SQLite来实现就再好不过了。
Realm基础
集成
project里面的build.gradle加入
classpath "io.realm:realm-gradle-plugin:4.1.1"
然后在app的build.gradle加入
apply plugin: 'realm-android'
同时在defaultConfig里面加入
ndk{ abiFilters "armeabi"}
可减小Realm库的大小
数据库表
下面简单定义一个User表
public class User extends RealmObject {
@PrimaryKey
private int id;
private String name;
private int age;
@Ignore
private int sessionId;
public boolean IsEmptyName(){
return name.isEmpty();
}
//----------下面是Set和Get方法,此处省略-----------
}
RealmObject是一个抽象类,如果想使用接口形式,使用RealmModel和注解@RealmClass也是同样的效果。属性添加@PrimaryKey注解即表示表的主键,使用@Ignore即表示该属性不添加到库里面,同时也可以在User表里面添加Public和Protected方法。如上面的IsEmptyName方法,所以几乎可以把User表当PoJo来使用
初始化Realm
在Application里面的onCreate方法里面执行
Realm.init(this)
增删修改操作
结合上篇MVP的封装以及上面User表,实现下图效果。

增删修改效果图.png
- 同步增加
mvpView.mRealm.beginTransaction()
mvpView.mRealm.copyToRealmOrUpdate(createUser())
mvpView.mRealm.commitTransaction()
或者
mvpView.mRealm.executeTransaction {
realm ->
realm.copyToRealmOrUpdate(createUser())
}
其中mvpView.mRealm是在BaseActivity/BaseFragment实例化的一个Realm即
val mRealm = Realm.getDefaultInstance()
- 异步增加
mvpView.mRealm.executeTransactionAsync({
it.copyToRealmOrUpdate(createUser())
},{},{}).bindTo(mvpView.realmAsyncList)
executeTransactionAsync分别对应execute,OnSuccess和OnError,其中OnSuccess和OnError也可不回调。
- 异步查询
val results = mvpView.mRealm.where(User::class.java).equalTo("name","Android").findAllAsync()
mvpView.UpdateUI(results)
这种写法类似Java的Future,查询将会在后台线程中被执行,当其完成时,之前返回的 RealmResults 实例会被更新。
- 删除
val results = findAllUser()
mvpView.mRealm.executeTransaction {
_ ->
results.deleteFirstFromRealm()
}
- 删除全部
results.deleteAllFromRealm()
最后的Presenter就如下
class RealmPresenter:BasePresenter<RealmFragment>() {
private val names = arrayOf("Android","Java","Kotlin","JS","PHP")
private val idCount:AtomicInteger = AtomicInteger(0)
//同步增加或者修改
fun syncAddOrUpdateItem(){
//第一种方式,自己手动管理事务
mvpView.mRealm.beginTransaction()
mvpView.mRealm.copyToRealmOrUpdate(createUser())
mvpView.mRealm.commitTransaction()
mvpView.UpdateUI(findAllUser())
/*
//第二种方式,Realm自动管理事务
mvpView.mRealm.executeTransaction {
realm ->
realm.copyToRealmOrUpdate(createUser())
mvpView.UpdateUI(findAllUser())
}*/
}
//异步增加或者修改
fun asyncAddOrUpdateItem(){
mvpView.mRealm.executeTransactionAsync({
it.copyToRealmOrUpdate(createUser())
},{
mvpView.UpdateUI(findAllUser())
},{
}).bindTo(mvpView.realmAsyncList)
}
//异步查询
fun asyncQueryItem(){
val results = mvpView.mRealm.where(User::class.java).equalTo("name","Android").findAllAsync()
mvpView.UpdateUI(results)
}
//删除
fun removeItem(){
val results = findAllUser()
mvpView.mRealm.executeTransaction {
_ ->
results.deleteFirstFromRealm()
if(idCount.get()>1) idCount.decrementAndGet()
mvpView.UpdateUI(results)
}
}
//清除
fun removeAll(){
val results = findAllUser()
mvpView.mRealm.executeTransaction {
_ ->
results.deleteAllFromRealm()
idCount.set(0)
mvpView.UpdateUI(results)
}
}
private fun createUser():User{
val user = User()
user.id = idCount.get()
user.name = names[(Math.random()*4).toInt()]
user.age = (Math.random()*10).toInt()
idCount.incrementAndGet()
return user
}
//同步查询
private fun findAllUser():RealmResults<User>{
return mvpView.mRealm.where(User::class.java).findAll()
}
}
JSON
Realm是支持json数据的,可以通过String,InputStream,JsonObject直接传入保存到对应的表里面
三种方式如下:

json方式.png
举个例子,存储一个全国城市列表的json到City表里面
首先准备一个city.json文件放在raw目录下面,json格式如下
[
{
"area": "010",
"code": "110000",
"level": "1",
"name": "北京市",
"prefix": "市"
},
{
"area": "010",
"code": "110101",
"level": "2",
"name": "东城区",
"prefix": "区"
},
{
"area": "010",
"code": "110102",
"level": "2",
"name": "西城区",
"prefix": "区"
},
//省略.....
]
然后我们定义一个City表
public class City extends RealmObject {
@PrimaryKey
private String code;
private String area;
private String level;
private String name;
private String prefix;
//省略Get和Set方法
}
这里封装一个创建Realm对象的帮助类,支持多Realm
首先抽象出变化需要配置的RealmConfiguration的参数,定义一个IRealmMigrate接口,
interface IRealmMigrate {
fun src():InputStream?
fun realmName():String
fun schemaVersion():Int
fun migration(): RealmMigration
}
其中
src()指的是需要本地资源迁移的时候传入的InputStream,
realmName()指的是realm自定义的realm后缀的文件,默认存储在data/data/<packagename>/file/路径下,
schemaVersion()默认的数据库版本,
migration() 实现RealmMigration对升级数据库的一些操作
实现一个RealmHelper帮助类
object RealmHelper {
private val mMigrationMap:SparseArrayCompat<Realm> = SparseArrayCompat()
fun getRealmInstance(migration:IRealmMigrate):Realm{
val key = migration.realmName().hashCode()
var mRealm:Realm? = mMigrationMap.get(key)
if(mRealm==null||mRealm.isClosed||mMigrationMap.indexOfKey(key)<0){
val migrationConfig = RealmConfiguration.Builder()
.name(migration.realmName())
.schemaVersion(migration.schemaVersion().toLong())
.migration(migration.migration())
.build()
if(migration.src()!=null){
val file = File(migrationConfig!!.path)
if (!file.exists()||file.length() == 0L) {
file.delete()
file.createNewFile()
file.outputStream().use { out -> migration.src()!!.use { it.copyTo(out) } }
}
}
mRealm = Realm.getInstance(migrationConfig)
mMigrationMap.put(key,mRealm)
}
return mRealm!!
}
fun clear(){
mMigrationMap.clear()
}
}
外部传入IRealmMigrate,然后对RealmConfiguration进行设置,同时通过SparseArrayCompat进行保存,另外当退出app的时候调用clear()方法。
最后在application里面调用initCity()方法
private fun initCity():App{
val inStream = this.resources.openRawResource(R.raw.city)
val mRealm = RealmHelper.getRealmInstance(AppRealmMigrateImpl())
mRealm.use { it ->
it.executeTransaction {
realm ->
realm.createOrUpdateAllFromJson(City::class.java,inStream)
}
}
return this
}
这样通过调用realm.createAllFromJson(xx)对应的City表就会有city.json里面的数据了。在使用的时候通过查表就可以查到对应的城市数据。City表查询结果

查询结果.png
注意创建一个Realm,对应就要close一次。所以上文的BaseActivity/BaseFragment的Realm可以改成
val mRealm = RealmHelper.getRealmInstance(AppRealmMigrateImpl())
然后在OnDestroy()里面进行close就行了。
迁移/升级
当数据库表发生变化的时候,如果不进行处理,Realm会抛出类似下面这样的错误
io.realm.exceptions.RealmMigrationNeededException: Field count is less than expected - expected 4 but was 3
所以要对数据进行迁移,也就是数据库升级
这里对上面的User表增加一个字段
public class User extends RealmObject {
//同上...
@Required
private Integer sex;
//省略Get和Set方法
}
其中@Required是指sex不能为null,然后定义一个方法实现RealmMigration
@Suppress("INACCESSIBLE_TYPE")
class AppMigration: RealmMigration {
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
val schema = realm.schema as RealmSchema
if(oldVersion==0L&&newVersion==1L){
val userSchema = schema.get(User::class.java.simpleName)
if(userSchema!=null&&!userSchema.hasField("sex")){
userSchema.addField("sex",Int::class.java,FieldAttribute.REQUIRED)
}
oldVersion.plus(1)
}
}
}
同时对User表增加数据的时候,需要设置sex的值。这样就ok了。
不过数据迁移的时候只能读取Realm后缀的文件,例如db文件貌似不支持。
坑
- 结合
RxJava使用的时候,Realm只能在创建Realm的线程使用,不能切换线程进行使用 - 异常
Configurations cannot be different if used to open the same file. The most likely cause is that equals() and hashCode() are not overridden in the migration class: com.data.lib.impl.AppMigration
RealmConfiguration相同的情況下,Realm.getInstance(migrationConfig)不能获取两次
@Required annotation is unnecessary for primitive field "xxx".
只有Boolean, Byte, Short, Integer, Long, Float, Double, String, byte[], Date 这些数据类型才支持@Required
-
Realm.getDefaultConfiguration()在模拟器上面会报错,真机不会
总结
关于Realm的加密功能,异步线程监听功能,集合通知的一些注意事项,结合Gson使用等等之类的功能都可以参考Realm文档
Realm是一批好马,操作查询之类的确实是相当的快, 但是需要驯服得熟读Realm文档。