最近看到了几篇与 Jetpack MVVM 有关到文章,使我不由也想淌一下这场混水。我是在 2017 年下半年触摸的 Jetpack 的那套开发东西,并且后来一直将其作为开发的首要结构。在这段时刻的运用过程中,我踩过一些坑,也积累了一些经历,为[ [ A 5 n了将其推行到其它到项目中又专门封t J 3 s / Y % m装出了一个库。当然,JetpaY H H {ck 所供给的组件现已比较完善,我的作业只能算是锦. H l N 2 k ~上添花。下面我就介绍下,现在我是如何在项目中运用 Jetpack MVVM 的。
1、后起之秀和相形见绌的 MVP
MVP 十分强壮,也是或许曾经是许多大n 4 B N f * = + N公司首选的开发结构。但是相比于现在的 MVVM,曾经的 MVP 形式在运用起来存在许多的不方便,H [ ) E u ~ +不免显得相形见绌。
首当其冲的是,在运用 MVP 编写客户端代码的时分你要写许多的接口和类。一般的 MVP 形式中,咱们需求先经过接口界说 Model、Viewb G t ] * 7 1 j % 和 Presenter 要完结的办法;然后,需求再编写三个类来完结以上三个接口的逻辑。就是说,一般的,为了完结一个界面的逻辑,你需求编写至少六个) – 1 @ L d P类文件。
其次,Presenter 比较臃# 6 s : ( v e K `肿。PresenV g a q c w k &ter 负责 V$ i P 2 LieH k u d c 4w 和 Model 层之间的l G C B交互,恳求的成果及其与 View 层的交互逻辑都在 Presenter 中完结。许多结构中,( & * u为了处理 View 和 Model 层之间的强引用联系,在 Presenter 界说 Handler 来经过音讯的方式传递信息。这就使得代码F ] V 7 ; W [的过于模版化,增加了许多与详细事务逻辑无关的代; o i码。此外,假设运用S O [ / } L i C Handler 作为音讯传递的桥梁,因为经过更新 UI 又发生在主线程里面,就可能会导致主线程音讯堆积a ( x d u 9 ] ) 6过多。
那 MVVM 是如何做的呢?下面的这张图大致地e Q s X n H z N 0诠释了 Jetpack MVVM。实际上,Jetpack 所供给的 MVVM 中心的功用只要 ViewModel 和 LiveData 两个。ViewModel 首要用来经过 Model 恳求数据信息,然后经过 LiveDae ] 5ta 作为信息交互的桥梁,将信息传递给 View 层。从类的数量的视点上来看,运用 MVVM 完结一个页面,你只需求写三个类文件。
而作为 ViewModel 和 View 层交互的中介的 LiveData 呢?你只需求将它看作一个一般的变量就能够了` ! ] z。它内部缓存了咱们的数据。它会在你修正这个数据的时分告诉一切的观察者数据的变更。所以,LiveDE K | $ ^ Cata 不存在 Handler 那样是音讯拥堵的问题。
此外,运用 LiveData 还| p | F能够处理页面频频改写以及改写的机遇的问题。比方,一个处x ? 3 ` 8 l u w r于后台的页面经过 EventBus 监听数据变化8 $ ; W A之后会先被保留到 LiveData,然后当页面回到前台的时分再一次性改写 UI。这样就防止了处于后台的页面多次改写 UI 又操控了改写的机遇。
假设你不熟D c 1 4 =悉 MVP、MVVM 等架构形式,或许你想知道 LiveData 和 ViewModel 的完结原理,能够参阅我之前的相关的文章(获取更多技能文章,能够重视我的公众号【Code Brick】):
- 《一文a ] l T F * 4 ]说透 Android 运用架构 MVC、MVP、MVVM 和 组件化》
- 《揭开 ViewModel 的生命周期操控的奥秘面纱》
- 《揭开 LiveData 的告诉机制的奥秘面纱》
2、Jetpack MVVM 的项目实践
Jetpack 供给的 MVVM 现已满足强壮,许多人会认为没有必要在其根底之上做进一步封装。正因如此,使* ? | c # i R得 MV4 g c ` +VM 在实际运用过程中呈现出了许多千奇百怪的姿态。比方,MVVM 和 MVP 杂糅5 H a j R在一起,ViewModel 和 View 层混乱的数据交互格局,ViewModel 中罗列出一堆的 LiveData 等等。实际上,经过简单地封装,咱们能够更好地在项目中推行和运用 MVVM。我开7 s 0 e t P y p发 Android-VMLib 这个结构的意图也正在于此。
2.1 拒绝大杂烩,一个 Clean 的 MVVM 结构
首要,作为一个 MVVM 的结构,Android-VMLib 所做的东西并不多,我并没有将其与各种网络结构等整合在一起,能够说得上是十分干净的一个结构P . ^ ~ 6 p X v e。到现在它的依靠联系如下,
也就! – f Q z Z是说,除了我的编写的图片压缩库 ComprY z c r d j J i Iessor 以及一个东西类库 Android-utils 之外,引进它并不会为你的项目引进更多的库。关于 Evf ; / w N ] b kentBus,除了咱们在项目中供给的一个封装之外,也不会为你y s u O引进任何类库。至于友盟,咱们只不过是为你在顶层的 View 层里注入了一些事情追踪办法,也不会逼迫你在项目中增加友盟依靠。也就` L W v是说,除了那两个必选的e z 3 = 4库之外,其它都是可选的。该结构的u { D v首要意图是赋能,是不是要在项目中运用彻底取决于用户。
好了,接下来就详细介绍下这个类库以及正确的运用N H } . x s u J MVVM 的姿势。
2.2 MVVM 和 Databinding 的暧昧联系
提到 MVVM,总是绕不开 Databinding. 我之前也了解过许多 MVVM 结构,许多都将p Y ] Datag u | A K Z O Xb^ # H j % Z [inding 作为了项目必需的计划。事实上,MVVM 和 Databinding 之间没有半毛钱的联系。
Databinding 供给了1 Y X { ~ R )双向的绑定功用,因而许多结构直接将 ViewModel 注入到了 xml 里面。我个人是比较反感这种做法的。或许是早起 Databinding 不成熟的时分常常带来莫名其妙的编译过错 xxx.BR not found
以及 can'ta - i @ a E O ~ ] find variable
等,在运用 Databinding 的时分,我更多地经过它来获取控件并在代码中完结赋值。此外,xmlY , | , i * @ B 中的代码缺少编译时的查看机制,比方当你在 xml 中将 int 类型赋值给 S; i # 6tring 类型的时分,它不会在编译期间报错。别的,在 xml 中能完结的事务逻辑有限,B % } K ?一个三目 1 B x p J运算符 ?:
就现已超出了代码的宽度限制。许x O g s l多时分你不得不将一部分事务放在 Java 代码里,一部分代码放在 xml 里。呈现问题的时分,这就增加了排查问题的难度。记得之前在项目中运用 Da% Z qtabinding 的时分还呈现过 UI 没有及时改写的问题,由于年代久远,详细原因我现已记不大清。最终,运用 Databinding 还可能会拖慢项目编译的速度,假设项目比较Y H d f N %小的话或许问题不大,但关于一个模块过百的大型项目而言,这无疑是雪上加e S Q d 8 z 3霜。
就一个结构O u 1 – @ e M而言,Android-VMLib 在 Databinding 上所做的作业是赋能。咱们为你供给了运用 Dataq V bbinding 的能力,同时也为 2 4你W 7 ) 6 8 T 1供给了彻底扫除 Databinding 的计划。以 Activit; h y _ X { my 为例,咱们供给了 BaseActivity 和 CommonActivity 两个抽象类。假设你想在项目中运用 Databinding,仿制下面的类这样传入布局的 DataBinding 类,以及 ViewModel,然后经过 binding 获取控件并运用即可:
c1 0 R U ;lass MainActivity : CommonActivity<MainViewModel,k 4 c F ` 8 ~ V Activity_ * J A % p e !MainBinding>() {
override fun getLayoutResId(): Int = R.layout.activity_ma2 h 7 M ~ in
override fun doCreateView(savedInstanceState: Bundle?) {
// 经过 binding 获取控件
setSuppt N Z * | n 8 r jortActionBar(binding.toolbar)
}
}
假设你不想在项目中运用 Databinding,那么你能够像下面的类这样继承 BaseActivity,然后经过传统的 findViewById 来获取控件并运用:
class ContainerActivit. W q ;y : BaseActivity/ R e B n 7 } j<EmptyViewModel> {
override fun getLayoutResId(): Int = R.layout.vmlib_activity_contain. 9 [ E K # ,er
override fun doCreateView(savedInstanceState: Bundle?) {
// 经过 findViewById 来获取3 # @ 7 ~ h p控件
// 或许,引进 kotlin-android-extensions 之后直接经过 id 运用控件
}
}
也许你看到了,我在运用 Databinding 的时分更多地将当作 ButterKinfe 来运用。我专门供给了不包括 Databinding 的能力,也是处于另一个考虑——运用 kotlin-android-extensions
之后5 5 &,能够直接在代码中经过o 6 _ N控件的 id 来运用它。假设仅仅想经过 Databinding t s j q | P i F 来获取控件的话,那么就# O 1 ~ g没有必要运用 D6 W } / l n M Patabindini – m – R i Kg 的必要了。而关于确实喜爱 Databinding 的数据绑定功用的同学能够在 Android-VMLib 之上个性化封装一层。当然了,我并不是排斥 Databinding。Databinding 是一个很好的设E ` 1计理念,仅仅关于? v E ~ P K 1 { ,将其大范围运用到项目中,我仍是持张望态度的。
2.3 统一数据交互格局
有过后端开发经历的同学可能会知道,在后端代码中,咱们一般会将代码按照层次分为 DAO、Service 和j : $ 4 k Controler 层,各个层之间进行数据交互的时分就需求对数据交互格局做统一的封装。后端和前端之间的交互的时分也要对H # m数据格局进行封装。咱们将其推行到 MVVM 中,显然,ViewModel 层和 View 层之间进行交互的时分也应该进行一层数据包装。下面是我看到的一段代码,
final private SingleLiveEvp s p ^ 7 _ ? jent<String> toast;
finalg ) q . U ) private SingleLiveEvent<Boolean> loading;
public ApartmentProjectViewModel() {
toast = new SingleLiveEvent<_ U ? Y v;&g3 ^ 7 7t;(v t Z 0 & p -);
loadin] . & ^ s | 3 - g =! ) P 4 c * new SinR r 2gleLiveEvent<>();
}
public SingleLiveEvent<String: B s T> getToast() {
return toast;
}
public SingleLiveEvent<Boolean> getLoading() {
return loading;
}
public void requestDat: d Oa() {
loading.setValue(true);
ApartmentProjectRepo* z n N [ K n 4sitory.getInstance().requestDetail(projectId, new Business.ResuE Y q W :ltListener<ProjectDetailBean>() {
@Override
public void onFailure(Busc # 3 } 7 M x ninessRespons_ g =e businessResponse, Prot B r OjectD ^ ` . 8etailBean projectDetailBean, S. ! x f C X v :tring s) {
to_ G Y E E v / 1ast.setValue(s);
loading.setValue(false);
}
@Override
public void onSuccess(BusinessResponse businessResponse, ProjectDetailBean pr R 7 _ nrojectDetailBean, String s) {
data.postValue(d 4 ^ j - f KealProjectBean(projectDetailBean));
loading.setValue(false);
}
});
}
这儿为了告诉 View 层数据的加载状况界说了一个 Boolean 类型的 LiveData 进行交互。这样8 N O z [ u 8 3 Y你需求多保护一个变量,显得代码不够简练。实际上,经过对数据交互格局的标准,咱们能够更高雅地完结这个使命。
在 Android-VMLib 傍边,咱们经过自界说枚举来表示数据的状, o a况,
public enum St0 U W X 6 # ` T oatus {
// 成功
SUCCESS(0),
// 失败
FAILED(1),
// 加载中
LOADING(2);
public final int id;
Status(int id) {
this.id = id;
}
}
然后,将过错信息、数据成果、数据状况以及预留字段包装成一个 Resource 目标,来作为固定的数据交互格局,
puG ] H 5 q { 1 1 Mblic final class Resources<T> {
// 状况
public final Status status;
// 数据
puJ ? g x : N 0blic final T data;
// 状况,成功或许G 7 ~ + P ^ d R过错的 code 以及 meO b 6 yssage
public final String code;
public final String message;
// 预留字段
public final Long udf1;
public final Double udf2;
public final Boolean udf3;
public final StrinC , Q ` 4 6g udf4;
public final Object udf5;
// ...
}
解说! : k下这儿的预留字段的作用:它们首要用来作为数据弥补阐明。比方进行分页的时分,假设 View 层不只想获取实在的数据,还想得到当时的页码,那么你能够将页码信息塞到 udf1 字段上面。l ( S H ] C以上b g [ { R f . /,我对各种不同类型的根底数据类型只供给了J ? E ) 6 s 5 O一个( L [ [通用的挑[ U S s选,比方整_ j e $型的只供给了 Long 类型,浮点L ] / U型的只供给了 Double 类型。别的,咱们还供给了一个无约束的Q o G类型 udf5.
除了数据交互格局的封装,Android-VMLib 还供给了交互格局的方便操作办法? V o t U a = [。如下图所示,
那么N : f p G : } ^,运用了 ResourcO B e /e 之后,代码会变成什么样呢?
// View 层代码
class MainActivity : CommonActivity<MainViewModel, ActivityMainBinding>() {
override fun getLayoutResId():) y M j a Int = R.layout.activity_main
override fun doCreateView(savedInstanu D 1 @ ~ /ceState: Bundle?) {
addSubscriptions()
vm& ) a g 3 W m K.startLoad()
}
private fun addSubscriptions() {
vm.getObservable(String::class.java).observe(this, Observer {
when(it!!.stat, L BusP i J 9 t h p v) {
Status.SUCCESS -> { ToastUtils.showShoc 0 f r X |rt(it.data) }
Sb 3 }tatus.FAILEDK T / 0 L ^ ^ -> { ToastUtils.showShort(it.message) }
Status.LOADING -> {/* temp do nothing */ }
else -> {/* temp do nothing */ }
}
})
}
}
// ViewModel 层代码a t u X y W l { *
class MainViewModel(application: Application) : BaseViewW x v O ~ q EModel(apf h # r U : yplication) {
fun startLoad() {
getObserva^ p i E ^ble(String::class.java).value = Resources.loading()
ARouter.getInstance().navigation(MainDataService::classC a p Q 3 ^ ~ } K.java)
?.loadData(object : OnGb n Y GetMainDataLiZ 1 Z X / M lstener{
override fun onGetData() {
getObse@ x J x I ;rvable(String::class.java).value = Resources.loading()
}
})
}
}
这样对数据交互C S # X I格局封装之后,代码是不是简练多了呢?至于让你的代码愈加简练,An% g ] Tdroid-VMLib 还为你供给了其它的办法,请持续阅j P } h :览。
2.4 进一c v (步简化代码,优化无处不在的 LiveData
之前在运用 ViewMog T ~ 2 R Rdel+LiveData 的时分,为了进行数据交互,C b ! ! | * e每个? S t S变量我都需求界说一个 LR N biveData,所以代码变成了下面这个样子。乃至我在有的同学那里看到过一个 ViewModel 中界说了 10+ 个 LiveData. 这J t Q Y $ e K S让代码变得十分得难看,
public class ApartmentProjectViewModel extends ViewModel {
final private MutableLiveData<ProjectDetailBean>F T ; data;
final private SingleLiveEvent<String> toast;
final private SingleLiveEvep @ 0 D 5 8 2 ` Ynt<Booleanm 0 X # r * - ?> submit;
final private Singlez c 0 J -LiveEvent<Booleany 6 {> loading;
public ApartmentProjectViewModel() {
data = new0 M P W ^ L W h x MutableLiveData<>();
tA q X M d !oast = new SingleLiveEvent<>();
submit_ h P 8 ] g = new SingleLiveEvent^ g j W + *<>();
loading = new SK J 0 ` : / wingleLiveEvent<>();
}
// ...
}
后来我的一个同事建Q ( Z 2 a B H z 3议我考虑下如何收拾一下 LiveData– 5 ) n,h A 9所以经过不断的推行和演化,现在这个处理计划现已比较完善——N { H A ` w : j即经过 HashMap 统一办理单例的 LiveData。后来为了进一步简化 ViewModel 层的代码,我将这部分作业交g $ , ~ $ X z .给了一个 Holder 来完结。所以如下处理计划就基本成5 V T型了,
public class BaseViewModel et Q A F Z / }xtends A4 e = 8ndroidViewModel {
private LiveDataHolder holder = new LiveDataHolder();
// 经过要传递的数据类型获取一个 LiveData 目标
public <T> MutableLiveData<Resos - k ] V $ 8 ] -urces<Tp U q a M&g{ , E Ft;> getObservable(Class<T> dataType) {
return holder.getLiv3 P S xeDatb s 2 ? I _ ? .a(dataType, false);
}
}
这儿的 Holder 完结如下,
public class LiveDataHolder&ld G 7 st;T2 T = x k U Q B> {
private Map<@ =Class, SingleLiveEvent> map = ns y ) }ew HashMap<>} B ] [ I + U C();
public MutableLiX # : pveData<Resourcel / q ? / Ts<T>> getLiveData(Class<T> dataType, boolean single) {
SingleLiveEvent<Resources<T>> liveData = map.get(dataT[ B r / s hype);
if (liveData == null) {
liveData = new SingleLiveEvent<>(single);
map.put(dataType, liveDa/ 9 % 0 5ta);
}
return liveDatas ^ P w;
}
}
原理很简单吧。运用了这套计划之后你的代码将会变得如下面这般简练高雅,
// Vq k NiewModel 层
class Eyepe? v m L ~tizerViewModel(appla n 9 # 1 o Iication: Application) :r m K B BaseViewModel(application) {
private var eyepetizerService: EyepetizerService = ARouter.getInstaf 4 nce().navigation(EyepetizerService::class.$ t 2 ` @ *java)
private var nextPageUrl: Stringw { k 6 4 R ?? = null
fun requestFirstPage() {
getObservable(HomeBean::class.java# X M l u).value = Resources.loadinV r r tg()
eyepetizeF ` F = !rService.getFirstHomePage(null, object : OnGetHomeBeansListener {
override fun onErron F Hr(errorCode: String, erro; V t @ I srMsg: String) {
getObservable(H[ # ! - bomeBean::class.java).value = Resources.failed(e V 9 ~ w %errorCode, error+ n ~ f 0Msg)
}
override fun onGetHomeBean(homeBean: HomeBean` , R * ` @ 1 w) {
nextPageUrl = hom& ; w 7 z ` T OeBean.nextPageUrl
getObservable(HomeBean::class.java).value = Resources.success(homeBean)
// 再恳求一页
requestNextPage()
}
}t K 4 } M ! J)
}
fun requestNextPage() {
eyepetizerService.* Y z z V 2 H _getMoreHomePagh ` s z j w .e(nextPageUrl,[ | t H 3 object : OnGetHomeBeansListener {
override fun onError(: Z [ q z _ n XerrorCode: String, errorMsg: Str} d G _ h WiH V | # F kng) {
getObseT 4 S 1rvable(HomeBean::class.java).value = Resources.failed(eV 6 j S h @rrorCodeD u B v ~ , errorMsg)
}
override fun om H vnGetHomeBean(homeBeA t d / 3 _an: HomeBean) {
nextPageUrl = homeBean.nextPageUrl
getObservable(HomeBean::clR C U [ ) f 7 Lass.java).value = Resources.success(homeBean)
}
})
}
}
// VieB L ? Sw 层
class EyepetizerActO ^ i ivity : CommonActivity<EyepetizerViewModel, ActivityEyepetizerBinding>() {
pr # M v _ 5 Arivate lateinit var adapter: HomeAdapter
private var loading : Boolean = false
overr+ i x !ide fun getLayoutResId() = R.layout.activity_eyepetizer
override fun doCreateView(savedInstanceState: Bundle?) {
addSubscriptions()
vm.requestFirstPagC w w ^ r s 8 ] &e()
}
privZ F D V (ate fun addSubscriptiQ i [ W q @ Q ,ons() {
vm.getObservable(HomeBean::class.java).observe(this, Obs/ 4 gerver { resources ->
loading = false
when (resources!!.status) {
Status.SUCCESS -> {
L.d(resourceS 8 z M A O ss.data)
val list = mutableListOf<Item>()
resources.data.iss@ w ( y ^ueList.forEach {
it.itemList.forEach { item -> ; q 1 :;
if (item.data.cover != null
&& item.data.author != null
) list.add(item)
}
}
adapter.addData(list)
}
Status.FAILED -> {/* temp do nothing */ }
Status.LOADING -> {/* temp do nothing */ }
else -> {/* temp do nothing */ }
}
})
}
// ..x 6 1 & S P I.
}
这儿咱们经过 getObservable(HomeBean::claK [ }ss.java)
获取一个 ViewModel 和 View 层之间交互的 LiveData<HomeBean>
,然后经过R d 7 h o h # !它` @ ^ w I d n进行数据传递。这种处理方式的优点是,你不需求在自己代码中处处界说 LiveData,将 Holder 当作一个 LiveData 池,需求数据交互的时分直接从 Holder 中获取即可。
有的同学可能会疑问,将 Class 作为从“池”中获取 LiveData 的唯一符号,会不会运用场景有限呢?K 8 }Android-VMLib 现已考虑到了这个问题,下文踩坑部分会为你讲解。
2.5 同享 ViewModel,配置能够更简单
假设多个 ViewModel{ / H x I t 2 在同一 Activity 的 Fragment 之间进行同享,那么该如何获取呢?
假设是不运用 Android-VMLib,你只需求在 Fragme} j G n 7nt 中经过 Activity 获取 ViewModel 即% O P S t可,
ViewModelProviders.z A P % o ,of(getActivity()).get(vmClass)
运用了 Android-VMLib 之后这个过程能够变得愈加简练——直接在 Fragment 上声明一个注解即可D z $ y E . g ( ^。比方,
@FragmentConfiguration(shareViewModel = true)
class SecondFra~ ? ) p 2 9 Ogment : BaseFragment<ShareO F 2 - w 3 R JdViewModel>() {
override fun getLayoutResId(): Int = R.layout.fragment_second
override fun doCreateView(savedInstanceState: Bundl$ E k 1 x :e?) {
L.d(vm)
// Get and display shared value from MainFragment
tv.text = vm.shareValue
btn_post.setOnClickListener {
Bus.get().post(SimpleEvent("MSG#00001"))
}
}
}
Android-VMLib 会读取你的 Fragment 的注解并获取 s( o z T mhareViewModel 字段的值,并决议运用 Activity 仍是 Fragment 获取 ViewModel,以此来完结 ViewModel 的同享。是不a 5 ^是愈加简练了呢?
2.6 Androd Y b h _ !id-VMLib 另一优势,强壮的东西类X B ! ) , [ S q支撑
我看过许多结构,它们一般会将= I i N 1 K ) 0一些常用的东西类与结构打包到一起供给给用户运用。An| R @droD C | &id-VMLib 与之相反,咱们将东西类作为独立项目进行支撑。这样的意图是,1). 希望东西类自身能够脱节对结构的依靠,独立运用到各个项目傍边;2). 作为独自的模块,独自进行优化,使功用不断完善。
到现在,东西类库 Android-Utils 现已供给了 22 个独立的东西类,涉及从 IO、资源读取、图像处理、动画到运行时权限获取等各种功用,关于该库我会在今后的文章里进行阐明。
需求阐明的是,该库在开发的过程中参阅了许多其它的类库,当然咱们也开发了自己的特征东西类,比方运行时权限获取、主题中特点获取、字符串拼接等等。
3、J* f N }etpack MVVM 踩坑实录以及 Android-VMLib 的处理计划
3.1 重复告诉s S 3 Z 7 A m 3,不该来的来了
这部分涉及到 ViewModel 的完结原理,假设没有了解过其原理,可参阅 《揭开 ViewModelP Z g C { w 的生命周期操控的奥秘面纱》 一文进行了解。
以我在该项目中的示例代码为例,MainFragment 和 SecondFragment 之间] H s同享了 SharedViewModel,在 MainFragq B wment 傍边,咱们往 LiveData 中塞了一个值9 o R G ] j。然后咱们跳转到 SecondFragment,从 SecondFragment 中回来的时分再次收到了这个值的告诉。
许多时分咱们只希T S |望在调用 LiveData#setValue()
的时分告W y V 4 {诉一次数据变化。此刻,咱们能] c p m M够经过 SingleLiveEvent 处理这个问题。这个类的原理并不难,仅仅经过 AtomicBoolean 来办理告L j ! ` 7 p w 诉,当时仅当调用 setValue()
的时分进行告诉。这处理了许多从后台回来之后页面的告诉问题。
在 Andoird-VMLib 傍边,当你经过 getObservable()
从“池”中获取 LiveDq 0 3 ` ) T tata 的时分,你能够经过带 single
参L 3 e F C *数的办法来获取这种类型的事情,i L Y } – G
// 这儿经过指定 sv / $ : K ]ingle 为 true 来运用D k F # p M ~ # E SingleLiveEvent
vm.getObservable(String::class.java, FLAG_1, true).observe(this, Observer {
toast("#1.1: " + it!!.data)
L.d("#1.1: " + it.data)
})
在运用 SingleLiveEvent 中的问题,
-
从“池D P K”中获取 LiveData 的时分只会根据第一次获取时的参数决议9 y (这个 Liv= C v ieDa5 t V $ D Xta 是不是 SingleLiveEvent 类型时。也就是说,当你第一次运用
vm.getObservable(String::class.java, FLAG_1, true)@ j L t s
获取 Ls 5 t TiveData 之后,M s w ^ l $ | G r再经过vm.getY # x _ ; p W f *Observable(String::class.java, FLAG_1)
获取到的是同一个 LiveData. -
SingleLiveEveC i @ 6 5 3 tnt 自身存在一a 8 l E % L个问题:当存在多个观察者的时分,它只能告诉给其间的一个,并且你无法确认被告诉的是哪一个。这和 SingleLiveEvent 的设计原理相关,因为它经过原子的 Boolean 符号告诉状况,告诉给一个观察者之后状况就被修正掉了。别的,注册的观察者会被放进 Map 里,然后运用迭代器遍历进行告诉,因而无法确认告诉的先后顺序(哈希之后的坑位先后顺序无法确认)。
3.2 LiveData 的实质和本职
Lb 6 E 3 a # # – /iveData 实质上等同于数据自身,本职是数据缓存。
参阅 《揭开 LiveData 的告诉机制的奥秘面纱》 一文,其完结原理就是在 LiveData 内部界说了一个 Object 类型的、名为 data 的目标,咱们调用 setValue()
的时分就是为这个目标赋值。LiveData 巧妙的当地在于利用了 LifecycleOwner 的2 S k h a生命周期回调,在生命周期发生变化的时分告诉成果给观察者。假设不熟悉 LiveData 的这m N j ~ . # T e些特性,编码的时分就容易呈现一些问题。
以文章为例,其包括两个首要部分:标题和内容,并且两者都是 String 类型的。这就导致咱4 I ! J们经过 getObservable(Class<T> dataType)
进行监听的时分,无法判断发生R 9 _ P M : O –变化的是文章的内容仍是文章的a { 1 #标题。因而,除了 getObservable(Class<T> dataType e Q je)
,在 BaseViewModel 中,咱们还加入K e G I s Q r了 getObservable(Class<TG f 7> dataType, int flaU ( e 0 p | j $ Jg)
办法来获取 LiveData。你能够这样了解——指m k ~定不同的 Flag 的时分,咱们会从不同的“池”中获取 LiveData,因而,得到的是不同 LiveData 目标。
public <T> MutableLiveData<Resources<T>? & $ I V K p } -;> getO- K . C gbservable(Class<T> dataType) {
return holder.getLiveData(dataType, false);
}
public <T> MutableLiveData<Resources<T>> getObservable(R & } y g sClass<T> dK ~ P EataType, int flag) {
return holder.getLi7 9 ` O [ .veData(d& # n O I N ] UataType, flag/ b w, false);
}
有的同学可能会想到运用之前封装的 Resource 目标的预留字段来指定发生变化的是文章的标题仍是内容。我强烈建议你不要这么做! 因为,正如咱们上面说的那样,LiveData 和 Data 自身应该是一对一的。这样处理的成果是文章的标题和内容6 4 )被设置到x i s % T n 4 T了同一个目标上面,内存之中只保护了一份缓存。其结果是,当页面出于后台的e ] [ ; ^ C时分,假设你先更新了文章的标题,后更新了文章的内容,那么此刻缓V m * S Q h –存之# l d 0 }中只会保= 7 # Y留文章的数据。当你的页面从后台回来的时分,标题就无法更新D 6 k Y % H =到 UI 上。还应该注意,假设一个数据分为前半部分和后半部分,你不能在修正后半部分的时分覆盖了前半部分修正的内容I | | # ] },这会导致前半部分修正成果无法更新到 UI。这不! ~ _ + z是 Android-VMLib 的错,许多时分不了解 LiveData 的实质和V . n |本职就容易~ M . N a H i p u陷到这个坑里去F 6 N E _。
3.3 数据恢复问题,ViewMo{ b # 9 +del 的版别差异
在 《揭开 ViewModel 的生命周期操控的奥秘0 R N F $面纱》 一文中,我剖析了无法从 ViewModelt _ g x q K y A n 中获取缓存的成果的问题。这是因为前期的 ViewModel 是经过空的 FragmeD I B Cnt 完W – y O结生命周期操控的。所以,当页面在后台被杀死的时分,Fragment 被销毁,然后导致再次获取到的 ViewModel 与之前的 ViewModel 不是同一目标。在后来的版别中,ViewModel 的状况恢复问题采用了另一种处理计划。下面是两个版别类库的差异(第一张是前期版别,第二张是近期的版别),
近期的版别抛弃了之前的 Fragment 的处理计划,改为了经过 savedstate 保存 ViewModel 的数据。这儿再次提及这个问题是提示你在开发的时分R f a i注意挑选的库的版别以及前期版别中存在的问题以提早避坑。
总结
这篇文章中介绍了 Android-V} ! M ) a r `MLib 以及运用 MVVM 的过程中遇到过的一些问题。假设在运用过t K i T l s p程中你还遇到了其它的问题能够与笔者进行沟通。
关于这个库,其地址是 github.com/Shouheng88/…. 当初开发这个库首要是为了提高个人开发的功率,防止每次发动新项意图时分都要 Copy 一份代码。除了 MVVM,该项目中还加入了组件化x E 5 ] e、服务化架构的示例,感兴趣的能够参阅源码。
从上一5 % Y A %年到现在,我首要在保护三个库,除了上面的东西类库 AndroidUtils 之外,还有一个 UI 库 AndroidUIX,只不过现在还不够成熟。除了为我个人开发提高功率,我将其开放,也是为了帮助更多个人开发者提高开发功率。# r O e 9 Z毕竟现在嘛,996、裁员、35 岁……程序员现已. u * P } y [ i p被“十面埋伏”。我开发这些也是想要为自己和为其它开发者打通一{ + L } S k 6 r W条新的谋生之路。这是我的初衷,也是我的意图。B T E 1 ? x y假设你感兴趣的话,也能够加入咱们 :)
除了 Android 相关的技能文章,近期我还在收拾 Java 后端以及服p V 5 5 v j :务器运5 O :维. f h相关的文章,感兴趣的/ & w E 4 T r G能够直接重视我的公众号「Code Brick」,别的感兴趣的能够加入技能 QQ 沟通群:10182n y 0 I U W x T `35573.
以上,感谢阅览~