前言
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
表,实现下图效果。
- 同步增加
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
到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
表查询结果
注意创建一个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
文档。
最后给出一张各个ORM
的性能图
[图片上传失败...(image-3a9bcc-1510804091717)]
.png](http://upload-images.jianshu.io/upload_images/2148217-03cbb53711e69a07.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
可以参考参考。