前言
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
表,实现下图效果。
![](https://img.haomeiwen.com/i25298483/12010c453ee1a533.png)
增删修改效果图.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
直接传入保存到对应的表里面
三种方式如下:
![](https://img.haomeiwen.com/i25298483/d06805b93eb10995.png)
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
表查询结果
![](https://img.haomeiwen.com/i25298483/5808bb93251a6b70.png)
查询结果.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
文档。
网友评论