安卓框架有哪些(讲解安卓主流app开发框架)

大家在 Android 上做数据持久化经常会用到数据库。除了借助 SQLiteHelper 以外,业界也有不少成熟的三方库供大家使用。本文就这些三方库做一个横向对比,供大家在技术选型时做个参考。

RoomRelamGreenDAOObjectBoxSQLDelight

以 Article 类型的数据存储为例,我们如下设计数据库表:

https://developer.android.com/training/data-storage/room

工程依赖implementation”androidx.room:room-runtime:$latest_version”implementation”androidx.room:room-ktx:$latest_version”kapt”androidx.room:room-compiler:$latest_version”//注解处理器Entity 定义表结构

Room 使用 data class 定义 Entity 代表 db 的表结构, @PrimaryKey 标识主键, @ColumnInfo 定义属性在 db 中的字段名

@EntitydataclassArticle(@PrimaryKeyvalid:Long,valauthor:String,valtitle:String,valdesc:String,valurl:String,vallikes:Int,@ColumnInfo(name=”updateDate”)@TypeConverters(DateTypeConverter::class)valdate:Date,)

Room 底层基于 SQLite 所以只能存储基本型数据,任何对象类型必须通过 TypeConvert 转化为基本型:

classConverters{@TypeConverterfunfromString(value:String?):Date?{returnformat.parse(value)}@TypeConverterfundateToString(date:Date?):String?{returnSimpleDateFormat(“yyyy-MM-dd”,Locale.US).format(date)}}DAO

Room 的最主要特点是基于注解生成 CURD 代码,减少手写代码的工作量。

首先通过 @Dao 创建 DAO

@DaointerfaceArticleDao{@Insert(onConflict=OnConflictStrategy.REPLACE)suspendfunsaveArticls(varargarticles:Article)@Query(“SELECT*FROMArticle”)fungetArticles():Flow<List<Article>>}

然后通过 @Insert, @Update, @Delete 等定义相关方法用来更新数据;定义 @Query 方法从数据库读取信息,SELECT 的 SQL 语句作为其注解的参数。

@Query 方法支持 RxJava 或者 Coroutine Flow 类型的返回值,KAPT 会根据返回值类型生成相应代码。当 db 的数据更新造成 query 的 Observable 或者 Flow 结果发生变化时,订阅方会自动收到新的数据。

注意:虽然 Room 也支持 LiveData 类型的返回值,LiveData 是一个 Androd 平台对象。一个比较理想的 MVVM 架构,其数据层最好是 Android 无关的,所以不推荐使用 LiveData 作为返回值类型

AppDatabase 实例

最后,通过创建个 Database 实例来获取 DAO

@Database(entities=[Article::class],version=1)//定义当前db的版本以及数据库表(数组可定义多张表)@TypeConverters(value=[DateTypeConverter::class])//定义使用到的typeconvertersabstractclassAppDatabase:RoomDatabase(){abstractfunarticleDao():ArticleDaocompanionobject{@Volatileprivatevarinstance:AppDatabase?=nullfungetInstance(context:Context):AppDatabase=instance?:synchronized(this){instance?:buildDatabase(context).also{instance=it}}privatefunbuildDatabase(context:Context):AppDatabase=Room.databaseBuilder(context,AppDatabase::class.java,”ArticleDb”).fallbackToDestructiveMigration()//数据库升级策略.build()}}2. Realm

Realm 是一个专门针对移动端设计的数据库,不同于 Room 等其他 ORM 框架,Realm 底层并不依赖 SQLite,有自己的一套基于零拷贝的存储引擎,在速度上明显优于其他 ORM 框架。

https://docs.mongodb.com/realm/sdk/android/

工程依赖//rootbuild.gradledependencies{…classpath”io.realm:realm-gradle-plugin:$realmVersion”…}//modulebuild.gradleapplyplugin:’com.android.application’applyplugin:’realm-android’Entity

Realm 要求 Entity 必须要有一个空构造函数,所以不能使用 data class 定义。Entity 必须继承自 RealmObject

openclassRealmArticle:RealmObject(){@PrimaryKeyvalid:Long=0L,valauthor:String=””,valtitle:String=””,valdesc:String=””,valurl:String=””,vallikes:Int=0,valupdateDate:Date=Date(),}

除了整形、字符串等基本型,Realm 也支持存储例如 Date 这类的常见的对象类型,Realm 内部会做兼容处理。你也可以在 Entity 中使用自定义类型,但需要保证这个类也是 RealmObject 的派生类。

初始化

要使用 Realm 需要传入 Application 进行初始化

Realm.init(context)DAO

定义 DAO 的关键是获取一个 Realm 实例,然后通过 executeTransactionAwait 开启事务,在内部完成 CURD 操作。

classRealmDao(){privatevalrealm:Realm=Realm.getDefaultInstance()suspendfunsave(articles:List<Article>){realm.executeTransactionAwait{r->//openarealmtransactionfor(articleinarticles){if(r.where(RealmArticle::class.java).equalTo(“id”,article.id).findFirst()!=null){continue}valrealmArticle=r.createObject(Article::class.java,article.id)//createobject(table)//savedatarealmArticle.author=article.authorrealmArticle.desc=article.descrealmArticle.title=article.titlerealmArticle.url=article.urlrealmArticle.likes=article.likesrealmArticle.updateDate=article.updateDate}}}fungetArticles():Flow<List<Article>>=callbackFlow{//wrapresultincallbackflow“realm.executeTransactionAwait{r->valarticles=r.where(RealmArticle::class.java).findAll()articles.forEach{offer(it)}}awaitClose{println(“EndRealm”)}}}

除了获取默认配置的 Realm ,还可以基于自定义配置获取实例

valconfig=RealmConfiguration.Builder().name(“default-realm”).allowQueriesOnUiThread(true).allowWritesOnUiThread(true).compactOnLaunch().inMemory().build()//setthisconfigasthedefaultrealmRealm.setDefaultConfiguration(config)3. GreenDAO

greenDao 是 Android 平台上的开源框架,跟 Room 一样也是一套基于 SQLite 的轻量级 ORM 解决方案。greenDAO 针对 Android 平台进行了优化,运行时的内存开销非常小。

https://github.com/greenrobot/greenDAO

工程依赖//rootbuild.gradlebuildscript{repositories{jcenter()mavenCentral()//addrepository}dependencies{…classpath’org.greenrobot:greendao-gradle-plugin:3.3.0’//greenDao插件…}}//modulebuild.gradle//添加GreenDao插件applyplugin:’org.greenrobot.greendao’dependencies{//GreenDao依赖添加implementation’org.greenrobot:greendao:latest_version’}greendao{//数据库版本号schemaVersion1//生成数据库文件的目录targetGenDir’src/main/java’//生成的数据库相关文件的包名daoPackage’com.sample.greendao.gen’}Entity

greenDAO 的 Entity 定义和 Room 类似,@Property 用来定义属性在 db 中的名字

@EntitydataclassArticle(@Id(assignable=true)valid:Long,valauthor:String,valtitle:String,valdesc:String,valurl:String,vallikes:Int,@Property(nameInDb=”updateDate”)@Convert(converter=DateConvert::class.java,columnType=String.class)valdate:Date,)

greenDAO 只支持基本型数据,复杂类型通过 PropertyConverter 进行类型转换

classDateConverter:PropertyConverter<Date,String>{@OverridefunconvertToEntityProperty(value:Integer):Date{returnformat.parse(value)}@OverridefunconvertToDatabaseValue(date:Date):String{returnSimpleDateFormat(“yyyy-MM-dd”,Locale.US).format(date)}}生成 DAO 相关文件

定义 Entity 后,编译工程会在我们配置的 com.sample.greendao.ge 目录下生成 DAO 相关的三个文件:DaoMaster,DaoSessiion,ArticleDao ,

DaoMaster:管理数据库连接,内部持有着数据库对象 SQLiteDatabase,DaoSession:每个数据库连接可以开放多个 session,而 session 的开销很小,无需反复创建 connectionXXDao:通过 DaoSessioin 获取访问具体 XX 实体的 DAO

初始化 DaoSession 的过程如下:

funinitDao(){valhelper=DaoMaster.DevOpenHelper(this,”test”)//创建的数据库名valdb=helper.writableDbdaoSession=DaoMaster(db).newSession()//创建DaoMaster和DaoSession}数据读写//插入一条数据,数据类型为Article实体类funinsertArticle(article:Article){daoSession.articleDao.insertOrReplace(article)}//返回全部文章fungetArticles():List<Article>{returndaoSession.articleDao.queryBuilder().list()}//按名字查找一条数据,并返回ListfungetArticle(name:String):List<Article>{returndaoSession.articleDao.queryBuilder().where(ArticleDao.Properties.Title.eq(name)).list()}

通过 daoSession 获取 ArticleDao,而后可以通过 QueryBuilder 添加条件进行调价查询。

4.ObjectBox

ObjectBox 是专为小型物联网和移动设备打造的 NoSQL 数据库,它是一个键值存储数据库,非列式存储,在非关系型数据的存储场景中性能上更具优势。ObjectBox 和 GreenDAO 使用一个团队。

https://docs.objectbox.io/kotlin-support

工程依赖//rootbuild.gradledependencies{…classpath”io.objectbox:objectbox-gradle-plugin:$latest_version”…}//modulebuild.gradleapplyplugin:’com.android.application’applyplugin:’io.objectbox’…dependencies{…implementation”io.objectbox:objectbox-kotlin:$latest_version”…}Entity@EntitydataclassArticle(@Id(assignable=true)valid:Long,valauthor:String,valtitle:String,valdesc:String,valurl:String,vallikes:Int,@NameInDb(“updateDate”)valdate:Date,)

ObjectBox 的 Entity 和自家的 greenDAO 很像,只是个别注解的名字不同,例如使用 @NameInDb 替代 @Property 等

BoxStore

需要为 ObjectBox 创建一个 BoxStore来管理数据

objectObjectBox{lateinitvarboxStore:BoxStoreprivatesetfuninit(context:Context){boxStore=MyObjectBox.builder().androidContext(context.applicationContext).build()}}

BoxStore 的创建需要使用 Application 实例

ObjectBox.init(context)DAO

ObjectBox 为实体类提供 Box 对象, 通过 Box 对象实现数据读写

classObjectBoxDao():DbRepository{//基于Article创建Box实例privatevalarticlesBox:Box<Article>=ObjectBox.boxStore.boxFor(Article::class.java)overridesuspendfunsave(articles:List<Article>){articlesBox.put(articles)}overridefungetArticles():Flow<List<Article>>=callbackFlow{//将query结果转换为Flowvalsubscription=articlesBox.query().build().subscribe().observer{offer(it)}awaitClose{subscription.cancel()}}}

ObjectBox 的 query 可以返回 RxJava 的结果, 如果要使用 Flow 等其他形式,需要自己做一个转换。

5. SQLDelight

SQLDelight 是 Square 家的开源库,可以基于 SQL 语句生成类型安全的 Kotlin 以及其他平台语言的 API。

https://cashapp.github.io/sqldelight/android_sqlite/

工程依赖//rootbuild.gradledependencies{…classpath”com.squareup.sqldelight:gradle-plugin:$latest_version”…}//modulebuild.gradleapplyplugin:’com.android.application’applyplugin:’com.squareup.sqldelight’…dependencies{…implementation”com.squareup.sqldelight:android-driver:$latest_version”implementation”com.squareup.sqldelight:coroutines-extensions-jvm:$delightVersion”…}.sq 文件

DqlDelight 的工程结构与其他框架有所不同,需要在 src/main/java 的同级创建 src/main/sqldelight 目录,并按照包名建立子目录,添加 .sq 文件

#Article.sqimportjava.util.Date;CREATETABLEArticle(idINTEGERPRIMARYKEY,authorTEXT,titleTEXT,descTEXT,urlTEXT,likesINTEGER,updateDateTEXTasDate);selectAll:#label:selectAllSELECT*FROMArticle;insert:#label:insertINSERTORIGNOREINTOArticle(id,author,title,desc,url,likes,updateDate)VALUES?;

Article.sq 中对 SQL 语句添加 label 会生成对应的 .kt 文件 ArticleQueries.kt。我们创建的 DAO 也是通过 ArticleQueries 完成 SQL 的 CURD

DAO

首先需要创建一个 SqlDriver 用来进行 SQL 数据库的连接、事务等管理,Android平台需要传入 Context, 基于 SqlDriver 获取 ArticleQueries 实例

classSqlDelightDao(){//创建SQL驱动privatevaldriver:SqlDriver=AndroidSqliteDriver(Database.Schema,context,”test.db”)//基于驱动创建db实例privatevaldatabase=Database(driver,Article.Adapter(DateAdapter()))//获取ArticleQueries实例privatevalqueries=database.articleQueriesoverridesuspendfunsave(artilces:List<Article>){artilces.forEach{article->queries.insert(article)//insert是Article.sq中的定义的label}}overridefungetArticles():Flow<List<Article>>=queries.selectAll()//selectAll是Article.sq中的定义的label.asFlow()//converttoCoroutinesFlow.map{query->query.executeAsList().map{article->Article(id=article.id,author=article.authordesc=article.desctitle=article.titleurl=article.urllikes=article.likesupdateDate=article.updateDate)}}}

类似于 Room 的 TypeConverter,SQLDelight 提供了 ColumnAdapter 用来进行数据类型的转换:

classDateAdapter:ColumnAdapter<Date,String>{companionobject{privatevalformat=SimpleDateFormat(“yyyy-MM-dd”,Locale.US)}overridefundecode(databaseValue:String):Date=format.parse(databaseValue)?:Date()overridefunencode(value:Date):String=format.format(value)}6. 总结

前文走马观花地介绍了各种数据库的基本使用,更详细的内容还请移步官网。各框架在 Entity 定义以及 DAO 的生成上各具特色,但是设计目的殊途同归:减少对 SQL 的直接操作,更加类型安全的读写数据库。

最后,通过一张表格总结一下各种框架的特点:

出身存储引擎RxJavaCoroutine附件文件数据类型RoomGoogle亲生SQLite支持支持编译期代码生成基本型 TypeConverterRealm三方C Core支持部分支持无支持复杂类型GreenDAO三方SQLite不支持不支持编译期代码生成基本型 PropertyConverterObjectBox三方Json支持不支持无支持复杂类型SQLDelight三方SQLite支持支持手写.sq基本型 ColumnAdapter

关于性能方面的比较可以参考下图,横坐标是读写的数据量,纵坐标是耗时:

安卓框架有哪些(讲解安卓主流app开发框架)

从实验结果可知 Room 和 GreenDAO 底层都是基于 SQLite,性能接近,在查询速度上 GreenDAO 表现更好一些;Realm 自有引擎的数据拷贝效率高,复杂对象也无需做映射,在性能表现上优势明显;ObjectBox 作为一个 KV 数据库,性能由于 SQL 也是预期中的。图片缺少 SQLDelight 的曲线,实际性能与 GreeDAO 相近,在查询速度上优于 Room。

空间性能方面可参考上图( 50K 条记录的内存占用情况)。Realm 需要加载 so 同时为了提高性能缓存数据较多,运行时内存占用最大,SQLite 系的数据库依托平台服务,内存开销较小,其中 GreenDAO 在运行时内存的优化是最好的。ObjectBox 介于 SQLite 与 Realm 之间。

选型建议

上述个框架目前都在维护中,都存在不少用户,大家在选型上可以遵循以下原则:

Room 虽然在性能上不具优势,但是作为 Google 的亲儿子,与 Jetpack 全家桶兼容最好,而且天然支持协程,如果你的项目只用在 Android 平台上且对性能不敏感,首推 Room ;如果你的项目是一个 KMM 或其他跨平台应用,那么建议选择 SQLDelight ;如果你对性能有比较高的需求,那么 Realm 无疑是更好的选择 ;如果对查询条件没有过多要求,那么可以考虑 KV 型数据库的 ObjectBox,如果只用在 Android 平台,那么前不久 stable 的 DataStore 也是不错的选择。

— End —

推荐阅读

D-KMP 架构:声明式UI Kotlin跨平台

知乎移动端动态化探索与实践

打造 ViewPager 滑动文字渐变效果

加好友进交流群,技术干货聊不停

发表评论

登录后才能评论