Android-架构新组件---让天下没有难做的-App

打印 上一主题 下一主题

主题 523|帖子 523|积分 1569

如果我们的组件需要强绑定声明周期,那么只需要借助 Lifecycle 去监听生命周期的状态和事件即可,再也不用覆写各种回调方法了。下面将要讲到的 LiveData 和 ViewModel 都是 Lifecycle-Aware Components,它们都用到了 Lifecycle。
Android 生命周期管理不妥带来的最大题目就是内存泄漏,举一个我们经常遇到的场景:一个异步使命(好比网络哀求)持有了 UI 元素的引用,只要使命没有实行完,所有与这个 UI 元素有强引用关系的元素都没法被 GC,如果如许的场景多发生几次,很可能会引起 OOM。
为了异步对象引用的题目,最早我们使用 AsyncTask,使命实行在 worker thread,实行结果在主线程上发起回调。AsyncTask 的致命缺点是不支持流式数据(stream),而且回调嵌套太深(callback hell),与软件质量权衡指标之一的 maintainable 背道而驰,不好用自然就会逐步被淘汰。
后来我们开始使用 RxJava,响应式编程,声明式写法,再借助 retrolambda 这种 backport,即使当年 Android 只支持到 JDK7,我们依然可以使用各种 operator 写出非常简洁的代码,“filter map 我闭~着眼”。RxJava 不但美满解决了线程调度的题目,还为我们提供了 OO 之外的抽象——作用在流上的 lambda,基于函数的抽象。但是,即便美满如斯,生命周期的题目依然无法回避,由于 Java 天生的局限性,一个 lambda 无论伪造地再像高阶函数,它本质上还是一个匿名内部类,这个匿名内部类依然持有对 outer class 实例的引用。于是我们必须通过 CompositeDisposable 来管理订阅关系,发起异步操作时记录订阅,离开页面时取消订阅,仍然需要覆写 onDestory 或者 onPause 。
如果我们以 Repository 层为界把架构蓝图分为上下两部门的话,上面的部门是数据展示,下面的部门是数据获取,数据获取部门由于要哀求 Remote 数据,必然会依赖到线程调度,而数据展示必然运行在 UI 线程,与生命周期强相关,这个时候就需要 LiveData 登场了。
LiveData

LiveData 也是一个观察者模子,但是它是一个与 Lifecycle 绑定了的 Subject,也就是说,只有当 UI 组件处于 ACTIVE 状态时,它的 Observer 才能收到消息,否则会主动切断订阅关系,不用再像 RxJava 那样通过 CompositeDisposable 来手动处理。
LiveData 的数据类似 EventBus 的 sticky event,不会被消耗掉,只要有数据,它的 observer 就会收到关照。如果我们要把 LiveData 用作事件总线,还需要做一些定制,Github 上搜 SingleLiveEvent 可以找到源码实现。
我们没法直接修改 LiveData 的 value,由于它是不可变的(immutable),可变(mutable)版本是 MutableLiveData,通过调用 setValue(主线程)或 postValue(非主线程)可以修改它的 value。如果我们对外袒露一个 LiveData,但是不希望外部可以改变它的值,可以用如下技巧实现:
private val _waveCode = MutableLiveData()
val waveCode: LiveData = _waveCode
   内部用 MutableLiveData ,可以修改值,对外袒露成 LiveData 类型,只能获取值,不能修改值。
  LiveData 有一个实现了中介者模式的子类 —— MediatorL

iveData,它可以把多个 LiveData 整合成一个,只要任何一个 LiveData 有数据变革,它的观察者就会收到消息:
val liveData1 = …
val liveData2 = …
val liveDataMerger = MediatorLiveData<>();
liveDataMerger.addSource(liveData1) { value -> liveDataMerger.setValue(value))
liveDataMerger.addSource(liveData2) { value -> liveDataMerger.setValue(value))
综上,我们汇总一下 LiveData 的使用场景:


  • LiveData - immutable 版本
  • MutableLiveData - mutable 版本
  • MediatorLiveData - 可汇总多个数据源
  • SingleLiveEvent - 事件总线
LiveData 只存储最新的数据,虽然用法类似 RxJava2 的 Flowable,但是它不支持背压(backpressure),所以不是一个流(stream),使用 LiveDataReactiveStreams 我们可以实现 Flowable 和 LiveData 的互换。
如果把异步获取到的数据封装成 Flowable,通过 toLiveData 方法转换成 LiveData,既使用了 RxJava 的线程模子,还消除了 Flowable 与 UI Controller 生命周期的耦合关系,借助 Data Binding 再把 LiveData 绑定到 xml UI 元素上,数据驱动 UI,妥妥的响应式。于是一幅如下模样的数据流向图就被勾勒了出来:

图中右上角的 Local Data 是 AAC 提供的另一个强大武器 —— ORM 框架 Room。
Room

数据库作为数据持久层,其重要性不问可知,当设备处于离线状态时,数据库可用于缓存数据;当多个 App 需要共享数据时,数据库可以作为数据源,但是基于原生 API 徒手写 CRUD 着实是痛楚,虽然 Github 上出现了不少 ORM 框架,但是它们的易用性也不敢让人恭维,直到 Room 出来之后,Android 程序员终于可以像 mybatis 那样轻松地操纵数据库了。
Room 是 SQLite 之上的应用抽象层,而 SQLite 是一个位于 Android Framework 层的内存型数据库。虽然 Realm 也是一个良好的数据库,但是它并没有内置于 Android 体系,所会增大 apk 的体积,使用 Room 则没有这方面烦恼。
Room 的结构抽象得非常简单,数据对象(表名 + 字段)用 @Entity 注解来定义,数据访问用 @Dao 来注解,db 自己则用 @Database 来定义,如果要支持复杂类型,可以定义 @TypeConverters,然后在编译阶段,apt 会根据这些注解生成代码。Room 与 App 其他部门的交互如下图所示:

Entity 是一个数据实体,表示一条记录,它的用法如下:
@Entity(tableName = “actors”)
data class Actor(
@PrimaryKey @ColumnInfo(name = “id”)
val actorId: String,
val name: String,
val birthday: Date?,
val pictureUrl: String
)
Actor 是一个用 @Entity 注解的 data class,它会生成一个名字是 actors 的表,留意到有一个字段是 @Date? ,但是 SQLite 自己不支持这种复杂类型(complex type),所以我们还需要写一个可以转换成基础类型的转换器:
class Converters {
@TypeConverter
fun timestampToDate(value: Long?) = value?.let { Date(it) }
@TypeConverter
fun dateToTimestamp(date: Date?) = date?.time
}
转换器通过 @TypeConverters 可作用于 class、field、method、parameter,分别代表差别的作用域。好比作用在 @Database 类的上,那么它的作用域就是 db 中出现的所有 @Dao 和 @Entity。
@Database(entities = [Actor::class], version = 1, exportSchema = false)
@TypeConverters(Converters::class)
abstract class AppDatabase : RoomDatabase() {
abstract fun actorDao(): ActorDao
}
代码出现的 ActorDao 定义了 CRUD 操作。用 @Dao 来注解,它既可以是一个接口,也可以是抽象类,用法如下:
@Dao
interface ActorDao {
@Query(“SELECT * FROM actors WHERE id = :actorId”)
fun getActor(actorId: String): LiveData
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(actors: List)
}
@Query 中的 SQL 语句可以直接引用方法参数,而且它的返回值可以是 LiveData 类型,也支持 Flowable 类型,也就是说,Room 原生支持响应式,这是对数据驱动最有利的支持,也是 Room 区别于其他 ORM 框架的显著特征。
至此,我们可以确定,无论数据来自 Remote 还是来自本地 DB,架构蓝图中的 Repository 对 ViewModel 提供的数据可以永远是 LiveData 类型,接下来我们看一下 ViewModel 的妙用。
ViewModel

ViewModel 是一个多面手,由于它的生命周期比力长,可以跨越由于配置变更(configuration changed,好比屏幕翻转)引起的 Activity 重修,因此 ViewModel 不能持有对 Activity / Fragment 的引用。

如果 ViewModel 中要用到 context 怎么办呢?不要紧,框架提供了一个 ViewModel 的子类 AndroidViewModel ,它在构造时需要传入 Application 实例。
既然 ViewModel 与 UI Controller 无关,固然可以用作 MVP 的 Presenter 层提供 LiveData 给 View 层,由于 LiveData 绑定了 Lifecycle,所以不存在内存泄漏的题目。除此之外,ViewModel 也可以用做 MVVM 模式的 VM 层,使用 Data Binding 直接把 ViewModel 的 LiveData 属性绑定到 xml 元素上,xml 中声明式的写法避免了很多样板代码,数据驱动 UI 的最后一步,我们只需要关注数据的变革即可,UI 的状态会主动发生变革。
ViewModel 配合 Data Binding 的用法与 React 非常相似,ViewModel 实例相称于 state,xml 文件就好比 render 函数,只要 state 数据发生变革,render 就会重新渲染 UI,但是 data binding 另有更强大的一点,它支持双向绑定。举个例子,UI 需要展示一个评论框,答应展示评论,也答应用户修改,那么我们可以直接把 EditText 双向绑定到一个 LiveData 之上,只要用户有输入,我们就可以收到关照,完全不需要通过 Kotlin/Java 来操控 UI:
android:text=“@={viewModel.commentText}” />
留意,如果要在 xml 中使用 LiveData,需要把 lifecycle owner 赋给 binding:
val binding: MainBinding = DataBindingUtil.setContentView(this, R.layout.main)
// Specify the current activity as the lifecycle owner.
binding.setLifecycleOwner(this)
由于 ViewModel 拿到的数据是 Repository 给的,可能不适用于 UI 元素,所以 ViewModel 还承担了数据适配的工作,偶然候我们需要汇总 repository 的多个返回值一次性给到 UI,那么就可以使用 LiveData 的“操作符” Transformations.switchMap,用法可以以为等同于 Rx 的 flatMap;如果只想对 LiveData 的 value 做一些映射,可以使用 Transformations.map,目前 Transformations 只有这两个操作符,由于不管 Kotlin 还是 Java8,都提供了很多声明式的操作符,对流的支持都比力友好,而 LiveData 自己不是一个流,所以这两个操作符足矣。
除了数据适配之外,ViewModel 另有一个强大的用法 —— Fragment 之间共享数据,如许 ViewModel 又饰演了 FLUX 模式中的 store 这一角色,是多个页面(fragment)之间唯一的数据出口。

ViewModel 的用法也非常简单,通过 ViewModelProviders.of 可以获取 ViewModel 实例:
.get(ActorViewModel::class.java)
一通操作猛如虎之后,UI controller 层变得薄如蝉翼,它只做了一件事变,把数据从左手(ViewModel)倒给了右手(使用了 Data Binding 的 xml)。
如果把 ViewModel 作为 SSOC(唯一原形源),多个 Fragment 之间共享数据,再使用 SingleLiveEvent 做总线,一个 Activity 配多个 Fragment 的写法就避免了 Activity 之间通过 Intent 传递数据的繁琐。但是 Fragment 的堆栈管理一直是一个让人头疼的题目,AAC 的 Navigation 不但美满解决了这个题目,而且还提供可视化的路由,只需拖拽一下就能生成类型安全的跳转逻辑。
Navigation

Navigation 用一个图(graph)来表示页面间的路由关系,图的节点(node)表示页面,边(edge)表示跳转关系。比方下图 8 个页面的跳转关系,一目了然:

页面与页面之间的连线叫 action,它可以配置进离场动画(Animations),也可以配置出栈行为(Pop Behavior),还支持 Single Top 的启动选项(Launch Options)。进离场动画和启动选项很好理解,出栈行为是一个比力强大的功能,action 箭头所指的方向表示目标页面入栈,箭头的反方向则表示目标页面出栈,而出栈的行为在 Navigation 编辑器中完全可控,我们可以指定要出栈到哪个页面,以致可以指定目标页面是否也需要出栈:

针对页面节点,还可以定义它要接收的参数(arguments),支持默认值,从此 Fragment 之间的参数传递变得非常直观,非常安全。
看一下具体用法,首先在跳转发起页面,通过 apt 生成的跳转函数传入参数:
val direction = ActorListFragmentDirections.showDetail(actorId)
findNavController().navigate(direction)
然后使用目标页面生成的 *Args 获取参数:
private val args: ActorDetailFragmentArgs by navArgs()
   这里的 navArgs 是一个扩展函数,使用了 Kotlin 的 ReadWriteProperty。
  几行代码就搞定了页面之间的跳转,而且还是可视化!从没有想过 Android 的页面跳转竟会变得如何简单,但是 Navigation 的方案并不是原创,iOS 的 Storyboard 很早就支持拖拽生成路由。当年 Android 推出 ConstraintLayout 之时,我们都以为是参考了 Storyboard 的页面拖拽,现在再配上 Navigation,从页面到跳转,一个完备的拖拽链路就形成了。平台虽然有差别化,但是使用场景一致的条件下,解决方案也就殊途同归了。
了解完了与生命周期有关的组件,接下来我们来看细节。
Paging

学习宝典

对我们开发者来说,肯定要打好基础,随时准备战斗。不论隆冬是否到来,都要把自己的技术做精做深。虽然目前移动端的招聘量确实变少了,但中高端的职位还是很多的,这阐明行业只是变得成熟规范起来了。竞争越激烈,产物质量与留存就变得更加重要,我们进入了技术赋能业务的期间。
   不论遇到什么困难,都不应该成为我们放弃的理由!
  很多人在刚接触这个行业的时候或者是在遇到瓶颈期的时候,总会遇到一些题目,好比学了一段时间感觉没有方向感,不知道该从那里入手去学习,对此我针对Android程序员,我这边给大家整理了一套学习宝典!包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节流大家在网上搜索资料的时间来学习,也可以分享动态给身边挚友一起学习!
【Android焦点高级技术PDF文档,BAT大厂口试真题解析】

【算法合集】

【延伸Android必备知识点】

【Android部门高级架构视频学习资源】
高级技术PDF文档,BAT大厂口试真题解析】**
[外链图片转存中…(img-vHEZmHLd-1716051540781)]
【算法合集】
[外链图片转存中…(img-njUjPxgx-1716051540782)]
【延伸Android必备知识点】
[外链图片转存中…(img-IxHRGS1U-1716051540782)]
【Android部门高级架构视频学习资源】

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

tsx81428

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表