2024在项目中使用MVVM的正确打开方式,你没用过的船新版本,还不快学学!(1)

总结

Android架构学习进阶是一条漫长而艰苦的道路,不能靠一时激情,更不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

上面分享的字节跳动公司2021年的面试真题解析大全,笔者还把一线互联网企业主流面试技术要点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节。

【Android高级架构视频学习资源】

Android部分精讲视频领取学习后更加是如虎添翼!进军BATJ大厂等(备战)!现在都说互联网寒冬,其实无非就是你上错了车,且穿的少(技能),要是你上对车,自身技术能力够强,公司换掉的代价大,怎么可能会被裁掉,都是淘汰末端的业务Curd而已!现如今市场上初级程序员泛滥,这套教程针对Android开发工程师1-6年的人员、正处于瓶颈期,想要年后突破自己涨薪的,进阶Android中高级、架构师对你更是如鱼得水,赶快领取吧!

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

// 状态,成功或者错误的 code 以及 message
public final String code;
public final String message;
// 预留字段
public final Long udf1;
public final Double udf2;
public final Boolean udf3;
public final String udf4;
public final Object udf5;

// …
}

解释下这里的预留字段的作用:它们主要用来作为数据补充说明。比如进行分页的时候,如果 View 层不仅想获取真实的数据,还想得到当前的页码,那么你可以将页码信息塞到 udf1 字段上面。以上,我对各种不同类型的基础数据类型只提供了一个通用的选择,比如整型的只提供了 Long 类型,浮点型的只提供了 Double 类型。另外,我们还提供了一个无约束的类型 udf5.

除了数据交互格式的封装,Android-VMLib 还提供了交互格式的快捷操作方法。如下图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

那么,使用了 Resource 之后,代码会变成什么样呢?

// View 层代码
class MainActivity : CommonActivity<MainViewModel, ActivityMainBinding>() {

override fun getLayoutResId(): Int = R.layout.activity_main

override fun doCreateView(savedInstanceState: Bundle?) {
addSubscriptions()
vm.startLoad()
}

private fun addSubscriptions() {
vm.getObservable(String::class.java).observe(this, Observer {
when(it!!.status) {
Status.SUCCESS -> { ToastUtils.showShort(it.data) }
Status.FAILED -> { ToastUtils.showShort(it.message) }
Status.LOADING -> {/* temp do nothing / }
else -> {/
temp do nothing */ }
}
})
}
}

// ViewModel 层代码
class MainViewModel(application: Application) : BaseViewModel(application) {

fun startLoad() {
getObservable(String::class.java).value = Resources.loading()
ARouter.getInstance().navigation(MainDataService::class.java)
?.loadData(object : OnGetMainDataListener{
override fun onGetData() {
getObservable(String::class.java).value = Resources.loading()
}
})
}
}

这样对数据交互格式封装之后,代码是不是简洁多了呢?至于让你的代码更加简洁,Android-VMLib 还为你提供了其它的方法,请继续阅读。

2.4 进一步简化代码,优化无处不在的 LiveData

之前在使用 ViewModel+LiveData 的时候,为了进行数据交互,每个变量我都需要定义一个 LiveData,于是代码变成了下面这个样子。甚至我在有的同学那里看到过一个 ViewModel 中定义了 10+ 个 LiveData. 这让代码变得非常得难看,

public class ApartmentProjectViewModel extends ViewModel {

final private MutableLiveData data;
final private SingleLiveEvent toast;
final private SingleLiveEvent submit;
final private SingleLiveEvent loading;

public ApartmentProjectViewModel() {
data = new MutableLiveData<>();
toast = new SingleLiveEvent<>();
submit = new SingleLiveEvent<>();
loading = new SingleLiveEvent<>();
}

// …
}

后来我的一个同事建议我考虑下如何整理一下 LiveData,于是经过不断的推广和演化,如今这个解决方案已经比较完善——即通过 HashMap 统一管理单例的 LiveData。后来为了进一步简化 ViewModel 层的代码,我将这部分工作交给了一个 Holder 来完成。于是如下解决方案就基本成型了,

public class BaseViewModel extends AndroidViewModel {

private LiveDataHolder holder = new LiveDataHolder();

// 通过要传递的数据类型获取一个 LiveData 对象
public MutableLiveData<Resources> getObservable(Class dataType) {
return holder.getLiveData(dataType, false);
}
}

这里的 Holder 实现如下,

public class LiveDataHolder {

private Map<Class, SingleLiveEvent> map = new HashMap<>();

public MutableLiveData<Resources> getLiveData(Class dataType, boolean single) {
SingleLiveEvent<Resources> liveData = map.get(dataType);
if (liveData == null) {
liveData = new SingleLiveEvent<>(single);
map.put(dataType, liveData);
}
return liveData;
}
}

原理很简单吧。使用了这套方案之后你的代码将会变得如下面这般简洁优雅,

// ViewModel 层
class EyepetizerViewModel(application: Application) : BaseViewModel(application) {

private var eyepetizerService: EyepetizerService = ARouter.getInstance().navigation(EyepetizerService::class.java)

private var nextPageUrl: String? = null

fun requestFirstPage() {
getObservable(HomeBean::class.java).value = Resources.loading()
eyepetizerService.getFirstHomePage(null, object : OnGetHomeBeansListener {
override fun onError(errorCode: String, errorMsg: String) {
getObservable(HomeBean::class.java).value = Resources.failed(errorCode, errorMsg)
}

override fun onGetHomeBean(homeBean: HomeBean) {
nextPageUrl = homeBean.nextPageUrl
getObservable(HomeBean::class.java).value = Resources.success(homeBean)
// 再请求一页
requestNextPage()
}
})
}

fun requestNextPage() {
eyepetizerService.getMoreHomePage(nextPageUrl, object : OnGetHomeBeansListener {
override fun onError(errorCode: String, errorMsg: String) {
getObservable(HomeBean::class.java).value = Resources.failed(errorCode, errorMsg)
}

override fun onGetHomeBean(homeBean: HomeBean) {
nextPageUrl = homeBean.nextPageUrl
getObservable(HomeBean::class.java).value = Resources.success(homeBean)
}
})
}
}

// View 层
class EyepetizerActivity : CommonActivity<EyepetizerViewModel, ActivityEyepetizerBinding>() {

private var loading : Boolean = false
private lateinit var adapter: HomeAdapter

override fun getLayoutResId() = R.layout.activity_eyepetizer

override fun doCreateView(savedInstanceState: Bundle?) {
addSubscriptions()
vm.requestFirstPage()
}

private fun addSubscriptions() {
vm.getObservable(HomeBean::class.java).observe(this, Observer { resources ->
loading = false
when (resources!!.status) {
Status.SUCCESS -> {
L.d(resources.data)
val list = mutableListOf()
resources.data.issueList.forEach {
it.itemList.forEach { item ->
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 */ }
}
})
}

// …
}

这里我们通过 getObservable(HomeBean::class.java) 获取一个 ViewModel 和 View 层之间交互的 LiveData<HomeBean>,然后通过它进行数据传递。这种处理方式的好处是,你不需要在自己代码中到处定义 LiveData,将 Holder 当作一个 LiveData 池,需要数据交互的时候直接从 Holder 中获取即可。

有的同学可能会疑问,将 Class 作为从“池”中获取 LiveData 的唯一标记,会不会应用场景有限呢?Android-VMLib 已经考虑到了这个问题,下文踩坑部分会为你讲解。

2.5 共享 ViewModel,配置可以更简单

如果多个 ViewModel 在同一 Activity 的 Fragment 之间进行共享,那么该如何获取呢?

如果是不实用 Android-VMLib,你只需要在 Fragment 中通过 Activity 获取 ViewModel 即可,

ViewModelProviders.of(getActivity()).get(vmClass)

使用了 Android-VMLib 之后这个过程可以变得更加简洁——直接在 Fragment 上声明一个注解即可。比如,

@FragmentConfiguration(shareViewModel = true)
class SecondFragment : BaseFragment() {

override fun getLayoutResId(): Int = R.layout.fragment_second

override fun doCreateView(savedInstanceState: Bundle?) {
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 的注解并获取 shareViewModel 字段的值,并决定使用 Activity 还是 Fragment 获取 ViewModel,以此来实现 ViewModel 的共享。是不是更加简洁了呢?

2.6 Android-VMLib 另一优势,强大的工具类支持

我看过很多框架,它们通常会将一些常用的工具类与框架打包到一起提供给用户使用。Android-VMLib 与之相反,我们将工具类作为独立项目进行支持。这样的目的是,1). 希望工具类本身可以摆脱对框架的依赖,独立应用到各个项目当中;2). 作为单独的模块,单独进行优化,使功能不断完善。

截至目前,工具类库 Android-Utils 已经提供了 22 个独立的工具类,涉及从 IO、资源读取、图像处理、动画到运行时权限获取等各种功能,对于该库我会在以后的文章里进行说明。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

需要说明的是,该库在开发的过程中参考了很多其它的类库,当然我们也开发了自己的特色工具类,比如运行时权限获取、主题中属性获取、字符串拼接等等。

3、Jetpack MVVM 踩坑实录以及 Android-VMLib 的解决方案

3.1 反复通知,不该来的来了

这部分涉及到 ViewModel 的实现原理,如果没有了解过其原理,可参考 《揭开 ViewModel 的生命周期控制的神秘面纱》 一文进行了解。

以我在该项目中的示例代码为例,MainFragment 和 SecondFragment 之间共享了 SharedViewModel,在 MainFragment 当中,我们往 LiveData 中塞了一个值。然后我们跳转到 SecondFragment,从 SecondFragment 中回来的时候再次收到了这个值的通知。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

很多时候我们只希望在调用 LiveData#setValue() 的时候通知一次数据变化。此时,我们可以通过 SingleLiveEvent 解决这个问题。这个类的原理并不难,只是通过 AtomicBoolean 来管理通知,当前仅当调用 setValue() 的时候进行通知。这解决了许多从后台回来之后页面的通知问题。

在 Andoird-VMLib 当中,当你通过 getObservable() 从“池”中获取 LiveData 的时候,你可以通过带 single 参数的方法来获取这种类型的事件,

// 这里通过指定 single 为 true 来使用 SingleLiveEvent
vm.getObservable(String::class.java, FLAG_1, true).observe(this, Observer {
toast(“#1.1: " + it!!.data)
L.d(”#1.1: " + it.data)
})

在使用 SingleLiveEvent 中的问题,

  1. 从“池”中获取 LiveData 的时候只会根据第一次获取时的参数决定这个 LiveData 是不是 SingleLiveEvent 类型时。也就是说,当你第一次使用 vm.getObservable(String::class.java, FLAG_1, true) 获取 LiveData 之后,再通过 vm.getObservable(String::class.java, FLAG_1) 获取到的是同一个 LiveData.

  2. SingleLiveEvent 本身存在一个问题:当存在多个观察者的时候,它只能通知给其中的一个,并且你无法确定被通知的是哪一个。这和 SingleLiveEvent 的设计原理相关,因为它通过原子的 Boolean 标记通知状态,通知给一个观察者之后状态就被修改掉了。另外,注册的观察者会被放进 Map 里,然后使用迭代器遍历进行通知,因此无法确定通知的先后顺序(哈希之后的坑位先后顺序无法确定)。

3.2 LiveData 的本质和本职

LiveData 本质上等同于数据本身,本职是数据缓存。

参考 《揭开 LiveData 的通知机制的神秘面纱》 一文,其实现原理就是在 LiveData 内部定义了一个 Object 类型的、名为 data 的对象,我们调用 setValue() 的时候就是为这个对象赋值。LiveData 巧妙的地方在于利用了 LifecycleOwner 的生命周期回调,在生命周期发生变化的时候通知结果给观察者。如果不熟悉 LiveData 的这些特性,编码的时候就容易出现一些问题。

以文章为例,其包含两个主要部分:标题和内容,并且两者都是 String 类型的。这就导致我们通过 getObservable(Class<T> dataType) 进行监听的时候,无法判断发生变化的是文章的内容还是文章的标题。因此,除了 getObservable(Class<T> dataType),在 BaseViewModel 中,我们还加入了 getObservable(Class<T> dataType, int flag) 方法来获取 LiveData。你可以这样理解——指定不同的 Flag 的时候,我们会从不同的“池”中获取 LiveData,因此,得到的是不同 LiveData 对象。

文末

那么对于想坚持程序员这行的真的就一点希望都没有吗?
其实不然,在互联网的大浪淘沙之下,留下的永远是最优秀的,我们考虑的不是哪个行业差哪个行业难,就逃避掉这些,无论哪个行业,都会有他的问题,但是无论哪个行业都会有站在最顶端的那群人。我们要做的就是努力提升自己,让自己站在最顶端,学历不够那就去读,知识不够那就去学。人之所以为人,不就是有解决问题的能力吗?挡住自己的由于只有自己。
Android希望=技能+面试

  • 技能
  • 面试技巧+面试题

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

tmx5rrP-1715574789811)]

  • 面试技巧+面试题
    [外链图片转存中…(img-zz9BSoA2-1715574789811)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 30
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值