还在一个模块打天下嘛?你知道引入Jetpack架构后,你的App会发生哪些奇妙的变化吗?
前言
上篇文章我给大家分享了我对Android架构的理解,从思想层面去讲述架构的演进过程。很多小伙伴读完后拍手叫好,表示还想听我讲一下对Jetpack 架构
的看法,本着帮人帮到底的精神,今天我将再次动笔 尽量从本质上讲清楚Jetpack 架构
存在的意义,以及解决的问题。
知识储备:需要对Lifcycle、LiveData、ViewModel、DataBinding
有基本了解
作者
目录
1.1 为什么要做生命周期绑定?
1.2 Lifecycle解决了哪些问题?
2.1 观察者模式的优点有哪些?
2.2 LiveData基于观察者模式又做了哪些扩展?
2.3 LiveData + Lifecycle 实现 1 + 1 > 2
3.1 如何优雅的实现Fragment之间通讯?
3.2 由ViewModel担任 VM/Presenter 的好处有哪些?
4.1 使用DataBinding的好处有哪些?
4.2 为什么很多人说DataBinding很难调试?
5.1 什么是MVVM
5.2 Jetpack只是让MVVM更简单、更安全
1. 有了Lifecycle,再也不用担心生命周期同步问题
1.1 为什么要做生命周期绑定?
关于Activity/Fragment
其最重要的概念就是生命周期管理,我们开发者需要在不同生命周期回调中做不同事情。比如onCreate
做一些初始化操作,onResume
做一些恢复操作等等等等,以上这些操作都比较单一直接去写也没有多大问题。
但有一些组件需要强依赖于Activity/Fragment
生命周期,常规写法一旦疏忽便会引发安全问题,比如下面这个案例:
现有一个视频播放界面,我们需要做到当跳到另一个界面就暂停播放,返回后再继续播放,退出后重置播放,常规思路:
#class PlayerActivity
onCreate(){
player.init()
}
onResume(){
player.resume()
}
onPause(){
player.pause()
}
onDestroy(){
player.release()
}
读过我上篇文章的小伙伴可能一眼就能看出来这违背了控制反转
,人不是机器很容易写错或者忘写,特别是player.release()
如果忘写便会引发内存泄漏 此时我们可以基于控制反转思想(将player生命周期控制权交给不会出错的框架)
进行改造: 第一步:
interface ObserverLifecycle{
onCreate()
...
onDestroy()
}
首先定义一个观察者接口,包含Activity/Fragment
主要生命周期方法
第二步:
class BaseActivity{
val observers = mutableList<ObserverLifecycle>()
onCreate(){
observers.forEach{
observer.onCreate()
}
}
...
onDestroy(){
observers.forEach{
observer.onDestroy()
}
}
}
在BaseActivity
中观察生命周期并逐一通知到observers
的观察者
第三步:
class VideoPlayer : PlayerObserver{
onCreate(){
init()
}
...
onDestroy(){
release()
}
}
class PlayerActivity : BaseActivity{
observers.add(videoPlayer)
}
播放器实现PlayerObserver
接口,并在每个时机调用相应方法。PlayerActivity
只需将videoPlayer
注册到observers
即可实现生命周期同步。
其实不光videoPlayer
,任何需要依赖Activity
生命周期的组件 只需实现PlayerObserver
接口最后注册到Activity
的observers
即可实现生命周期自动化管理,进而可以规避误操作带来的风险
1.2 Lifecycle解决了哪些问题?
既然生命周期的同步如此重要,Google肯定不会视而不见,虽然自定义ObserverLifecycle
可以解决这种问题,但并不是每个人都能想到。所以Google就制定了一个标准化的生命周期管理工具Lifecycle
,让开发者碰到生命周期问题自然而然的想到Lifecycle
,就如同想在Android
手机上新建一个界面就会想到Activity
一样。
同时Activity
和Fragment
内部均内置了Lifecycle
,使用非常简单,以1.1 案例通过Lifecycle
改造后如下:
class VideoPlayer : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate(){
init()
}
..
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(){
release()
}
}
class PlayerActivity : BaseActivity{
lifecycle.addObserver(videoPlayer)
}
两步操作即可,不用我们自己向观察者(videoPlayer)
做生命周期分发处理。
2. LiveData并不是只运用观察者模式
2.1 观察者模式的优点有哪些?
观察者是一种常见并且非常实用的一种行为型模式,具有扩展性强、耦合性低的特性。
本文1.1
中 生命周期同步设计就是一个标准的观察者模式,ObserverLifecycle
可作为观察者,PlayerActivity
作为被观察者,当被观察者(PlayerActivity)
生命周期发生改变时会主动通知到观察者(VideoPlayer)
同时观察者在不改变代码结构的情况随意扩展,比如PlayerActivity
属于一个MVP
架构,此时可以将Presenter
实现ObserverLifecycle
作为观察者 随后 注册到被观察者(PlayerActivity)
中, 这样Presenter
也可以监测到Activity
生命周期,并且代码结构没有任何改变,符合开闭原则(对扩展开发 修改关闭)
2.2 LiveData基于观察者模式又做了哪些扩展?
LiveData
符合标准的观察者模式,所以它具备扩展性强、耦合性低的特性,同样它还是一个存储数据的容器,当容器数据改变时会触发观察者,即数据驱动。
数据驱动是前端开发领域非常重要的一个概念,说数据驱动之前我们先思考一个问题,为什么要改变数据? 答案显而易见,无非是想让数据使用者感知到而已,而LiveData
可以优雅的实现这一流程,将 改变、通知
两步操作合并为一步 即省事也提高了安全性.
根据LiveData的特性决定它非常适合去做数据驱动UI,下面举个例子简单描述下:
# 需求:改变textView内容以及对应的数据,用LiveData实现方式如下
val liveData = MutableLiveData<String>()
liveData?.observe(this, Observer { value->
textView.text = value
})
//这一步会改变liveData值并且会触发textView重新渲染
liveData.value = "android"
看起来平平无奇甚至理所当然,但它确实解决了我们前端开发的痛点,在此之前数据和UI都需要我们开发者单独修改,当面对十几个View时很难做到不漏不忘。 引入liveData
后改变数据会自动触发UI渲染,将两步操作合并为一步,大大降低出错的概率 关于数据驱动UI
上篇文章我已经做了详细描述,感兴趣的可以翻回去查看。
2.3 LiveData + Lifecycle 实现 1 + 1 > 2
LiveData
在Lifecycle
的加持下可以实现只在可见状态接收通知,说的通俗一点Activity
执行了onStop()
后内部的LiveData
就无法收到通知,这样设计有什么好处? 举个例子: ActivityA
和ActivityB
共享同一个LiveData
,伪代码如下
class ActivityA{
liveData?.observe(this, Observer { value->
textView.text = value
})
}
class ActivityB{
liveData?.observe(this, Observer { value->
textView.text = value
})
}
当ActivityA
启动ActivityB
后多次改变liveData
值,等回到ActivityA
时 你肯定不希望Observer
收到多次通知而引发textView多次
重绘。
引入Lifecycle
后这个问题便可迎刃而解,liveData
绑定Lifecycle(例子中的this)
后,当回到ActivityA
时只会取liveData
最新的值然后做通知,从而避免多余的操作引发的性能问题
3. ViewModel与LiveData真乃天作之合
3.1 Jetpack ViewModel 并不等价于 MVVM ViewModel
经常有小伙伴将Jetpack ViewModel
和 MVVM ViewModel
,其实这二者根本没有在同一个层次,MVVM ViewModel
是MVVM
架构中的一个角色,看不见摸不着只是一种思想。 而Jetpack ViewModel
是一个实实在在的框架用于做状态托管,有对应的作用域可跟随Activity/Fragment生命周期,但这种特性恰好可以充当MVVM ViewModel
的角色,分隔数据层和视图层并做数据托管。
所以结论是Jetpack ViewModel
可以充当MVVM ViewModel
但二者并不等价
3.2 如何优雅的实现Fragment之间通讯?
ViewModel
官方定义是一个带作用域的状态托管框架,为了将其状态托管发挥到极致,Google甚至单独为ViewModel
开了个后门,Activity
横竖屏切换时不会销毁对应的ViewModel
,为的就是横竖屏能共用同一个ViewModel
,从而保证数据的一致性。
既然是状态托管框架那ViewModel
的第一要务 就要时时刻刻保证最新状态分发到视图层,这让我不禁想到了LiveData,数据的承载以及分发交给Livedata
,而ViewModel
专注于托管LiveData
保证不丢失,二者搭配简直是天作之合。
有了ViewModel
加LiveData
,Fragment
之间可以更优雅的通讯。比如我的开源项目中的音乐播放器(属于单Activity多Fragment
架构下),播放页和首页悬浮都包含音乐基本自信,如下图所示:
想要使两个Fragment中播放信息实时同步,最优雅的方式是将播放状态托管在Activity
作用域下ViewModel
的LiveData
中,然后各自做状态监听,这样只有要有一方改变就能立即通知到另一方,简单又安全,具体细节可至我的开源项目中查看。
3.3 由ViewModel担任 VM/Presenter 的好处有哪些?
传统MVVM
和MVP
遇到最多的的问题无非就是多线程下的内存泄露,ViewModel
可以完全规避这个问题,内部的viewModelScope
是一个协程的扩展函数,viewModelScope
生命周期跟随ViewModel
对应的Lifecycle(Activity/Fragment
),当页面销毁时会一并结束viewModelScope
协程作用域,所以将耗时操作直接放在viewModelScope
即刻
另外在界面销毁时会调用ViewModel
的onClear
方法,可以在该方法做一些释放资源的操作,进一步降低内存泄露的风险
4. 解除你对DataBinding的误解
4.1 使用DataBinding的作用有哪些?
DataBinding
最大的优点跟唯一的作用就是数据 UI双向绑定
,UI和数据
修改任何一方另外一方都会自动同步,这样的好处其实跟LiveData
的类似,都是做数据跟UI同步操作,用来保证数据和UI一致性。其实写到这可以发现,不管是LiveData
、DataBinding
还是DiffUtil
都是用来解决数据和UI一致性问题,可见Google对这方面有多么重视,所以我们一定要紧跟官方步伐
小知识点:
DataBinding包中的
ObservebleFile
作用跟LiveData
基本一致,但ObservebleFile
有一个去重的效果,
4.2 为什么很多人说DataBinding很难调试?
经常听一些小伙伴提DataBinding
不好用,原因是要在xml中写业务逻辑不好调试,对于这个观点我是持否定态度的。并不是我同意xml中写业务逻辑这一观点,我觉得碰到问题就得去解决问题,如果解决问题的路上有障碍就尽量扫清障碍,而不是一味的逃避。
如{vm.isShow ? View.VISIBLE : View.GONE}
之类的业务逻辑不写在xml放在哪好呢?关于这个问题我在上篇文章Data Mapper
章节中描述的很清楚,拿到后端数据转换成本地模型(此过程会编写所有数据相关逻辑
),本地模型与设计图一一对应,不但可以将视图与后段隔离,而且可以解决xml中编写业务逻辑的问题。
5. Jetpack和MVVM有什么关系?
5.1 什么是MVVM
MVVM
其实是前端领域一个专注于界面开发的架构模式,总共分为View
、ViewModel
、Repository
三个模块 (需严格按照单一设计原则划分)
View(视图层):
专门做视图渲染以及UI逻辑的处理Repository(远程):
代表远程仓库,从Repository取需要的数据ViewModel:
Repository取出的数据需暂存到ViewModel,同时将数据映射到视图层
分层固然重要,但MVVM
最核心点是通过ViewModel
做数据驱动UI以及双向绑定的操作用来解决数据/UI
的一致性问题。MVVM
就这么些东西,千万不要把它理解的特别复杂
其实我上篇文章也简单说过,好的架构不应该局限到某一种模式(MVC/MVP/MVVM)
上,需要根据自己项目的实际情况不断添砖加瓦。如果你们的后端比较善变我建议引入Data Mapper
的概念~如果你经常和同事开发同一个界面,可以试图将每一条业务逻辑封装到use case
中,这样大概率可以解决Git冲突
的问题..等等等等,总之只要能实实在在 提高 开发效率以及项目稳定性的架构就是好架构.
5.2 Jetpack只是让MVVM更简单、更安全
Jetpack
是Android官方为确立标准化开发而提供的一套框架,Lifecycle
可以让开发者不用过多考虑 生命周期引发的一系列问题 ~ 有了DataBinding的支持让数据UI双向绑定成为了可能 ~ LiveData
的存在解除ViewModel
跟Activity
双向依赖的问题....
归根到底Jetpack
就是一套开发框架,MVVM
在这套框架的加持之下变得更加简单、安全。
Tips:作者公司项目引入Jetpack后,项目稳定性有着肉眼可见的提升。
综上所述
- Lifecycle 解决了生命周期 同步问题
- LiveData 实现了真正的状态驱动
- ViewModel 可以让 Fragment 通讯变得更优雅
- DataBinding 让双向绑定成为了可能
- Jetpack 只是让 MVVM 更简单、更安全
文末
最近火热的Jetpack Compose是谷歌在2019Google i/o大会上发布的新的库,是用于构建原生Android UI的现代工具包。他有强大的工具和直观的Kotlin API,简化并加速了Android上的UI开发。可以帮助开发者用更少更直观的代码创建View,还有更强大的功能,以及还能提高开发速度。
客观地讲,Compose 确实是一套比较难学的东西,因为它毕竟太新也太大了,它是一个完整的、全新的框架,确实让很多人感觉「学不动」,这也是个事实。
如果你是因为缺少学习资料,而我正好薅到这本谷歌内部大佬根据实战编写的《Jetpack Compose最全上手指南》,从入门到精通,教程通俗易懂,实例丰富,既有基础知识,也有进阶技能,能够帮助读者快速入门,是你学习Jetpack Compose的葵花宝典,快收藏起来!!!
由于篇幅原因,如有需要以下完整学习笔记PDF,可以点击我的GitHub免费下载获取!
第一章 初识 Jetpack Compose
1. 为什么我们需要一个新的UI 工具?
2. Jetpack Compose的着重点
- 加速开发
- 强大的UI工具
- 直观的Kotlin API
3. API 设计
4. Compose API 的原则
- 一切都是函数
- 顶层函数(Top-level function)
- 组合优于继承
- 信任单一来源
5. 深入了解Compose
- Core
- Foundation
- Material
- 插槽API
第二章 Jetpack Compose构建Android UI
1. Android Jetpack Compose 最全上手指南
- Jetpack Compose 环境准备和Hello World
- 布局
- 使用Material design 设计
- Compose 布局实时预览
……
2. 深入详解 Jetpack Compose | 优化 UI 构建
- Compose 所解决的问题
- Composable 函数剖析
- 声明式 UI
- 组合 vs 继承
- 封装
- 重组
……
3. 深入详解 Jetpack Compose | 实现原理
- @Composable 注解意味着什么?
- 执行模式
- Positional Memoization (位置记忆化)
- 存储参数
- 重组
……
第三章 Jetpack Compose 项目实战演练(附Demo)
1. Jetpack Compose应用1
- 开始前的准备
- 创建DEMO
- 遇到的问题
2. Jetpack Compose应用2
3. Jetpack Compose应用做一个倒计时器
- 数据结构
- 倒计时功能
- 状态模式
- Compose 布局
- 绘制时钟
4. 用Jetpack Compose写一个玩安卓App
- 准备工作
- 引入依赖
- 新建 Activity
- 创建 Compose
- PlayTheme
- 画页面
- 底部导航栏
- 管理状态
- 添加页面
5. 用Compose Android 写一个天气应用
- 开篇
- 画页面
- 画背景
- 画内容
……
6. 用Compose快速打造一个“电影App”
- 成品
- 实现方案
- 实战
- 不足
……
由于篇幅原因,如有需要以上完整学习笔记PDF,可以点击我的GitHub免费下载获取!
还在一个模块打天下嘛?你知道引入Jetpack架构后,你的App会发生哪些奇妙的变化吗?的更多相关文章
- 编写一个函数来找出所有不带歧义的函数名,也就是 那些只在一个模块里出现过的函数名(erlang)
erlang程序设计第八章练习题第二题: code:all_loaded()命令会返回一个由{Mod,File}对构成的列表,内含所有Erlang系统 载入的模块.使用内置函数Mod:module_i ...
- 用pip下载的python模块怎么在PyCharm中引入报错
在IDE中导入下载的模块,比如:numpy模块 你会发现虽然你安装了numpy模块,在CMD中python可以import numpy,但是你在PyCharm引不进去,为什么呢?你要是有注意的话,安装 ...
- Netflix:我们为什么要将 GraphQL 引入前端架构? (转)
在刚开始时,Monet 的 React UI 层需要访问由 Tomcat 服务器提供的传统 REST API.随着时间的推移,随着应用程序的发展,我们的用例变得越来越复杂,即使是一个简单页面也需要从各 ...
- Netflix:我们为什么要将GraphQL引入前端架构?
作者|Artem Shtatnov译者|无明 在这篇文章中,我们将分享 Netflix 在这些应用程序的前端架构中引入 GraphQL 所积累的经验. 在内部,我们把用于管理广告创建和组装的主要应用程 ...
- thinkPHP 空模块和空操作、前置操作和后置操作 详细介绍(十四)
原文:thinkPHP 空模块和空操作.前置操作和后置操作 详细介绍(十四) 本章节:介绍 TP 空模块和空操作.前置操作和后置操作 详细介绍 一.空模块和空操作 1.空操作 function _em ...
- Eclipse中的工程引入jar包后没有整合到一个文件夹而是全部在根目录下显示
Eclipse中的工程引入jar包后没有整合到一个文件夹而是全部在根目录下显示 解决方案: 1,在Eclipse中,点击window-->Preferences-->Java-->B ...
- thinkPHP 空模块和空操作、前置操作和后置操作 具体介绍(十四)
本章节:介绍 TP 空模块和空操作.前置操作和后置操作 具体介绍 一.空模块和空操作 1.空操作 function _empty($name){ $this->show("$name ...
- Vue工程化之引入element-ui框架后图标失效
场景: vue-cli搭建的工程化项目,引入element框架后发现图标无效,变为方块 解决方案: 在index.html引入样式文件CDN链接即可 <!-- 引入样式 --> <l ...
- react项目中引入了redux后js控制路由跳转方案
如果你的项目中并没有用到redux,那本文你可以忽略 问题引入 纯粹的单页面react应用中,通过this.props.history.push('/list')就可以进行路由跳转,但是加上了redu ...
随机推荐
- Spring AOP的源码流程
一.AOP完成日志输出 1,导入AOP模块 <dependency> <groupId>org.springframework</groupId> <arti ...
- 常见 git 需求整理(持续更新中)
首发于 语雀文档 突然感觉自己对 git 还是挺熟悉的,因为团队里新来的七八号应届生来问我 git 问题,基本没有答不上的情况,但为了能更好地对知识进行整理,还是记录一下为好. (希望能)持续更新.. ...
- 关于主机不能访问虚拟机的web服务解决
centos7默认并没有开启80端口,我们只有开启就行 [root@localhost sysconfig]# firewall-cmd --permanent --add-port=3032/tcp ...
- 如何快速的插入 100W数据到数据库,使用PreparedStatement 最快实现!
有时候,我们使用数据库的时候,如何快速的添加测试数据到数据库中,做测试呢,添加100W 数据,如果使用工具的话可能很慢,这里我推荐大家使用 PreparedStatement 预编译 去进行操作:单线 ...
- Mysql被黑客攻击了?一定要注意一点,不要关闭mysql服务
因为mysql没有关闭的情况,可以从缓存里面获取到数据,如果关闭了只能从文件里面去获取数据了,会大大加大恢复难度
- MySQL入门(4)——操作数据表
MySQL入门(4)--操作数据表 创建数据库 CREATE [TEMPORARY] TABLE [IF NOT EXISTS] 数据库名 [(create_definition,...)] [tab ...
- Python的web开发
一.Web开发 Tcp udp Cs即客户端.服务器端编程,客户端和服务器端之间需要使用socket,约定协议.版本(协议使用的是tcp或者udp).Tcp协议和udp协议,指定地址和 ...
- 使用Webpack构建多页面程序
使用webpack搭建单页面程序十分常见,但在实际开发中我们可能还会有开发多页面程序的需求,因此我研究了一下如何使用webpack搭建多页面程序. 原理 将每个页面所在的文件夹都看作是一个单独的单页面 ...
- c++ 反汇编 继承
单继承,父类中没有虚函数 单继承,父类存在虚函数,子类重写虚函数 单继承,父类存在虚函数,子类不新定义虚函数 单继承,父类存在虚函数,子类新定义虚函数 单继承,父类不存在虚函数,子类定义虚函数 多继承 ...
- (三)SpringBoot启动过程的分析-创建应用程序上下文
-- 以下内容均基于2.1.8.RELEASE版本 紧接着上一篇(二)SpringBoot启动过程的分析-环境信息准备,本文将分析环境准备完毕之后的下一步操作:ApplicationContext的创 ...