Android 架构组件的最新进展 (下篇)

根据我们曾经做的调查,开发者们希望 Android 官方可以维护一些实用的组件库和架构实践,以降低中大型应用的开发门槛,这样开发团队就可以集中更多精力在实际业务的优化和改进上。

Jetpack 项目正是为了解决这些问题而诞生的,Jetpack 是一系列助力您更容易打造优秀 Android 应用的工具和组件,这些组件能帮助您遵循最佳实践、免除编写繁复的样板代码并简化复杂任务,从而使您可以专注于最核心的代码逻辑。其中 androidx.* 库与 Framework API 解耦,这能够提供向后兼容的同时,也能更频繁地更新。

Android Jetpack 中的架构组件可帮助您设计稳健、可测试且易维护的应用。从最初发布的管理 Activity 和 Fragment 生命周期的 Lifecycle 库和访问 SQLite 数据库的 Room 库,后来推出了分页 (Paging)导航 (Navigation) 和管理后台任务的 WorkManager 库。根据 2019 年最新的开发者调查中,70% 以上的专业开发者用过这五个库当中的至少一个库进行应用开发。

这里我们分上下两篇介绍架构组件的最新更新,如果您还没有阅读本文的上篇,请点击这里查看《Android 架构组件的最新进展 (上篇) 》。本篇将会继续为大家介绍分页库、Room 持久性库和 WorkManager。希望大家能在其中发现对自己的应用有帮助的全新功能以及改进:

分页库

Paging (分页) 使得开发者可以逐步、高效地加载大量数据,从而节省用户的电池和流量。而且它和架构组件中的其他部分或者其他技术都能配合使用,比如 Room, Realm, Retrofit 等等。 为了让分页的使用更加便捷,在不久未来的版本里我们将提供:

  • 内置的网络支持,而且提供错误处理机制
  • Header 和 Footer 支持
  • 更好的 RxJava 支持以及协程的集成

Room 持久性库

Room 是一个在 SQLite 上提供抽象层的持久存储库,您可以回顾我们之前的介绍文章了解更多 Room 的详细信息。

协程处理

在 Room 2.1 中,开发者可以通过 Kotlin 语言的 suspend 关键字让 Room 生成正确的协程代码,包括使用后台 dispatcher,这大大降低了开发者处理协程的工作量:

// Room 2.1

@Query("SELECT * FROM song WHERE songId = :songId")
suspend fun getSong(songId: String): Song

@Insert
suspend fun insertSong(song: Song)

@Transaction
suspend fun deleteShortSongs(): List<Song> {
    val songs = getSongsWithElapsedTimeLessThan(1000)
    deleteSongsWithIds(songs.map { it.songId })
    return songs
}

另外,在 Room 2.1 中也提供了扩展函数让开发者方便地启动事务。它还会提供一个协程上下文 (CoroutineContext),这样开发者可以更方便地执行多个数据库操作:

database.withTransaction {
    val songs = getSongsWithElapsedTimeLessThan(1000)
    deleteSongsWithIds(songs.map { it.songId })
    return songs
}

全文搜索
全文搜索功能是对 SQLite 的一个扩展,让其创建一个数据表从而更高效地检索数据。

在 Room 2.0 中,一个 Dao 的检索方法看起来可能是这样:

// Room 2.0

@Dao
interface SongDao {
    @Query("""
        SELECT *
        FROM Song
        WHERE songName LIKE ‘%’ || :query || ‘%’
        OR albumName LIKE ‘%’ || :query || ‘%’
        OR artistName LIKE ‘%’ || :query || ‘%
        """)
    fun searchSongs(query: String): List<Song>
}

△ 注意 WHERE 和 OR 语句的长度

而在 2.1 中,只需要加入一个 @Fts4 注解,就可以通过 MATCH 语句让一切都轻松很多:

// Room 2.1

@Entity
@Fts4
data class Song(
    @PrimaryKey
    @ColumnInfo(name = "rowid")
    val id: Long,
    val url: String,
    val songName: String,
    val albumName: String,
    val artistName: String
)

@Dao
interface SongDao {
    @Query("""
        SELECT *
        FROM Song
        WHERE Song MATCH :query
        """)
    fun searchSongs(query: String): List<Song>
}

数据库视图
很像数据表,但又不完全一样。基本上,您可以像检索数据表一样检索数据库视图,但不能在其中插入数据。

在 2.1 中,您可以用 @DatabaseView 注解您的数据类,但这时您不需要创建一个数据表,而是直接将 BigQuery 放在注解部分,让其成为一个能快速检索的视图:

@DatabaseView("""
    SELECT
        Album.*,
        count(song_id) AS num_of_songs,
        sum(song_elapsed_time) AS total_time
    FROM Album
    JOIN AlbumSongRef ON (album_id = ref_album_id)
    JOIN Song ON (ref_song_id = song_id)
    GROUP BY album_id
    """)
data class AlbumItem(
    @Embedded
    val album: Album,
    @ColumnName("num_of_songs")
    val numOfSongs: Int,
    @ColumnName("total_time")
    val totalTime: Long
)

而这个视图 (如上面的 AlbumItem) 可以像其他数据表一样使用:

@Query("""
        SELECT * FROM AlbumItem
        ORDER BY num_of_songs DESC
        """)
    fun getAlbumItemsByNumOfSongs(): List<AlbumItem>

扩展的 Rx 支持
在 Room 2.1 中,您使用的 insert, update, delete 方法能返回 Completable, Maybe 和 Single。而且在 Query 注解的方法里可以使用 Rx 作为返回类型,并处理 update, insert 或者 delete 这样的写入操作:

@Insert
    fun addSong(song: Song): Completable

    @Update
    fun updateSong(song: Song): Single<Int>

    @Query("""
        UPDATE Song SET
        lastPlayedTime = :time
        WHERE id = :id
        """)
    fun updateSongPlayTime(Long: time, String: id): Completable

Room 接下来会实现增量注解处理,提高构建速度,另外也会进一步提升关系数据的处理效率,并提升数据迁移的便利性。在协程方面,则会加入 Channel 和 Flow 的支持。

WorkManager

WorkManager 是一个后台进程库,用于处理那些不需要即时处理的任务,而且可以在应用甚至设备重启后依然确保任务正确触发。另外,WorkManager 也支持按条件启动,比如根据网络连接状况的变化启动特定的任务。

性能和兼容性

按需配置

以往 WorkManager 需要在应用启动时就初始化,而按需配置 (On-demand Configuration) 可以让开发者仅在需要时才启动 WorkManager。在 WorkManager 2.1 中,您可以通过重载 Configuration.Provider 中的方法来获得一个 WorkManager 的配置对象。

// WorkManager 2.1.0

class MyApp : Application(), Configuration.Provider {

    override fun getWorkManagerConfiguration() : Configuration {
        return Configuration.Builder()
            // set your options here
            .build()
    }
}

在目前 WorkManager 2.0 中 WorkManager.getInstance() 方法并不需要开发者提供参数,而在 2.1 中开发者传入 context 参数后,WorkManager 如果没有初始化,它会基于参数访问 application 对象并获取到配置:

WorkManager.getInstance().enqueue(...)

Google Play services 集成
此功能即将到来,而且提升了在 Marshmallow 之前版本的设备上的运行性能。而且这个集成是可选的,开发者可以根据需要自行选择是否集成。

兼容性改进
兼容性方面,我们主要是在做 "幕后工作"。比如和 OEM 沟通,确保不同的设备能拥有一致的应用退出操作。

测试

第一点,也是开发者们一直有提到的: Robolectric 支持。Robolectric 是一个高效可靠的 Android 单元测试框架,现在已被全面支持。

第二点,Worker 已提供单元测试的支持。

您可以使用 TestWorkerBuilder:

// WorkManager 2.1.0

// Create a test worker
val request = OneTimeWorkRequestBuilder<MyWorker>.build()
val worker = TestWorkerBuilder
    .from(context, request, executor)
    .build()

// Test its behavior
val result = worker.doWork()
assertThat(result, `is`(Result.success()))
assertThat(...)

也可以使用 TestListenableWorkerBuilder:

// WorkManager 2.1.0

// Or create a listenable worker
val request = OneTimeWorkRequestBuilder<MyWorker>.build()
val listenableWorker = TestListenableWorkerBuilder
    .from(context, request)
    .build()

// Test its behavior
val result = listenableWorker.startWork().get()
assertThat(result, `is`(Result.success()))
assertThat(...)

WorkManager 的下一步

我们正在努力实现前台服务的支持,让您可以在前台也能使用 WorkManager API。

感谢大家对本次连载的关注,希望在了解完架构组件的最新进展后,大家能在其中找到适合自己应用的功能。您也可以观看 ☟下面的视频☟ 重温我们对架构组件进展的介绍。
腾讯视频链接:
https://v.qq.com/x/page/g30084zstx6.html
Bilibili 视频链接:
https://www.bilibili.com/video/av71179658/

如果对架构组件有疑问或者建议,欢迎在评论区和我们分享。

点击这里进一步了解 Android Jetpack

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容