关于使用 Android MVVM + LiveData 模式的一些建议

View 和 ViewModels

理论情况下,ViewModel 不需要知道任何关于 Android 的东西。这提供了可测试性,防止内存泄漏和模块化的好处。一条基本规制是确保在你的 ViewModels 类中没有任何 android.* 的类导入(android.arch.* 例外)。这点和 Presenter 一样。

别让 ViewModels(和 Presenters)知道任何关于 Android 框架的类。

条件语句,循环和一般决策应该在 app 的 ViewModels 或其他层次中完成,而不是在 Activity 或 Fragment 中。View 通常不是单元测试的(除非你使用 Robolectric),所以越少代码越好。View 仅仅应该知道如何展示数据以及发送用户的事件给 ViewModel(或 Presenter)。这就叫被动视图模式。

在 Activities 和 Fragments 中保持最小化的逻辑

在 ViewModels 中引用 View

ViewModels 和 Activities 或 Fragments 相比有着不同的范围。当一个 ViewModel 存活并运行时,一个 Activity 可以处于它生命周期的任何一个状态。在 ViewModel 不知道的情况下,Activities 和 Fragments 可以被再次摧毁和创建。

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

ViewModels 在配置改变时仍然存在

将 View 的引用(Activity 或 Fragment)传递到 ViewModel 是一个严重的风险。假设 ViewModel 中请求了网络数据,数据在之后的一段时间返回。此时,View 的引用有可能被回收或者旧的 Activity 不再可见,这样就产生了内存泄漏,甚至崩溃。

避免在 ViewModels 中引用 Views

在 ViewModels 和 Views 中沟通推荐的方式是观察者模式,利用 LiveData 或者其他库提供的可观察的方式。

观察者模式

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

通过观察者模式,在 Android 展示层中让 View(Activity 或 Fragment)观察(订阅改变) ViewModel 显得非常方便。因为 ViewModel 不需要知道 Android 内容,它也不知道 Android 是如何频繁地杀死 View 的。好处在于:

  1. ViewModels 在配置改变时依然存在,因此当旋转屏幕时不需要再次查询内部数据(数据库或网络)。
  2. 当一个长时间的操作结束时,在 ViewModel 中可观察的部分更新了。无论数据是否被观察了,当尝试更新不存在的 View 时没有空指针异常的发生。
  3. ViewModels 不引用 View,所以减少了内存泄漏的风险。

private void subscribeToModel() {
// Observe product data
viewModel.getObservableProduct().observe(this, new Observer() {
@Override
public void onChanged(@Nullable Product product) {
mTitle.setText(product.title);
}
});
}

在 UI 中注册数据推送,让 UI 观察它的改变。

臃肿的 ViewModels

如果你的 ViewModel 中放了太多代码或者太多的职责,可以考虑:

  • 将一些逻辑移动到 presenter,它和 ViewModel 有相同的范围。它可以和你 app 中的其他部分沟通并更新 ViewModel 中 LiveData 的持有者。
  • 添加一个领域层并采取简洁的架构,这样有利于测试和维护,也同样促进了快速脱离主线程。在 架构蓝图中有一个简洁架构的样例。

必要时分散职责,增加领域层。

使用数据仓库

在 App 架构指南中,可以看到大多数应用都有多种数据来源,比如:

  1. 远程:网络或云
  2. 本地:数据库或文件
  3. 内存缓存

在你的应用中拥有数据层是一个好主意,展示层完全注意不到。保留缓存、同步数据库和网络的算法都是无关紧要的。拥有一个完全隔离的仓库类作为一个单一的入口来处理这些复杂的事情是非常推荐的。

如果你有多种并且不同的数据模型,考虑添加多种仓库。

添加一个数据仓库作为访问数据的单一入口。

处理数据状态

考虑这个场景:你正在观察 ViewModel 中暴露的 LiveData,它包含一个展示项目的列表。那么在数据加载,网络错误或者空列表时,视图该如何呈现这些变化呢?

  • 你可以在 ViewModel 中暴露一个 LiveData。例如,MyDataState 应该包含那些数据是否正在加载,或者加载成功或加载失败的信息。

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

你可以把数据包裹在一个保存了状态或其他元数据(例如一条错误消息)的类中。看看我们样例中的 Resource 类。

使用一些包裹类或另一个 LiveData 来暴露你数据的信息。

保存 Activity 状态

Activity 状态是一些当 Activity 消失时(意味着被回收或进程被杀死)你需要重新恢复屏幕的信息。旋转屏幕是一个最显著的案例,还好我们有 ViewModel。状态保存在 ViewModel 中是安全的。

然而在某些场景中,当 ViewModel 也消失时,你可能也需要恢复状态。比如当操作系统的资源紧缺时,可能会杀死你的进程。

为了高效地保存和恢复 UI 状态,需要结合持续性,onSaveInstanceState() 以及 ViewModels。

看看例子:ViewModels:持续,onSaveInstanceState(),恢复 UI 状态和装载器

事件

一个事件指发送一次的动作。ViewModels 暴露了数据,但什么是事件呢?例如,导航事件或者展示 Snackbar 消息都是应该只执行一次的动作。

事件的概念并不能很好的展示 LiveData 是如何存储和恢复数据的。来看一下下面的 ViewModel:

LiveData snackbarMessage = new MutableLiveData<>();

一个 Activity 开始观察这个数据,并且 ViewModel 完成了一个操作之后它需要更新这条消息:

snackbarMessage.setValue(“Item saved!”);

Activity 收到这条消息,并展示在 Snackbar 中。这显然没毛病。

然而,如果用户旋转手机,创建了新的 Activity 并开始观察。当 LiveData 观察发生后,Activity 立即收到了旧的值,这时消息再次展示了!

我们扩展了 LiveData,并创建了一个类叫 SingleLiveEvent,作为刚刚问题的解决方案。它仅仅发送订阅之后出现的更新。注意它只支持一个观察者。

为像导航或 Snackbar 消息等事件使用可观察的行为如 SingleLiveEvent

泄露 ViewModels

反应式范例在 Android 中工作得很好,因为它允许在 UI 和应用的其他层次建立方便的连接。LiveData 是这个结构中的关键组件,因此通常情况下你的 Activities 和 Fragments 都会观察一个 LiveData 实例。

ViewModels 和其他组件是如何沟通的取决于你,但要注意泄露和边界情况。下图中展示层使用了观察者模式,数据层使用了回调:

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

如果用户退出了应用,View 将会消失,因此 ViewModel 不再被观察。如果仓库是一个单例,或者应用范围的,那么仓库将不会回收直到进程被杀死。这只会在操作系统需要资源或者用户手动杀死应用时才会发生。如果仓库保留了 ViewModel 中回调的引用,那么 ViewModel 就会暂时泄露。

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

如果 ViewModel 存活或者被分配的操作很快就完成了,那么这个泄露没什么。然而,不是所有的时候都这样。理想情况下,ViewModels 应该在没有任何 Views 观察它们时回收:

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

你可以采取以下选项来达到这个目的:

  • 使用 ViewModel.onCleared() 你可以告诉仓库扔掉 ViewModel 的回调。
  • 在仓库中你可以使用弱引用或者 EventBus(这两者都容易滥用甚至有害)

考虑边界情况,泄露以及长时间的操作如何影响你架构中的实例。

不要把保存清除状态或者相关的关键逻辑放在 ViewModel 中。任何你从 ViewModel 中的调用都可能是最后一次。

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)

文末

初级工程师拿到需求会直接开始做,然后做着做着发现有问题了,要么技术实现不了,要么逻辑有问题。

而高级工程师拿到需求会考虑很多,技术的可行性?对现有业务有没有帮助?对现有技术架构的影响?扩展性如何?等等…之后才会再进行设计编码阶段。

而现在随着跨平台开发,混合式开发,前端开发之类的热门,Android开发者需要学习和掌握的技术也在不断的增加。

通过和一些行业里的朋友交流讨论,以及参考现在大厂面试的要求。我们花了差不多一个月时间整理出了这份Android高级工程师需要掌握的所有知识体系。你可以看下掌握了多少。

混合式开发,微信小程序。都是得学会并且熟练的

这些是Android相关技术的内核,还有Java进阶

高级进阶必备的一些技术。像移动开发架构项目实战等

Android前沿技术;包括了组件化,热升级和热修复,以及各种架构跟框架的详细技术体系

以上即是我们整理的Android高级工程师需要掌握的技术体系了。可能很多朋友觉得很多技术自己都会了,只是一些新的技术不清楚而已。应该没什么太大的问题。

而这恰恰是问题所在!为什么别人高级工程师能年限突破30万,而你只有十几万呢?

就因为你只需补充你自己认为需要的,但并不知道企业需要的。这个就特别容易造成差距。因为你的技术体系并不系统,是零碎的,散乱的。那么你凭什么突破30万年薪呢?

我这些话比较直接,可能会戳到一些人的玻璃心,但是我知道肯定会对一些人起到点醒的效果的。而但凡只要有人因为我的这份高级系统大纲以及这些话找到了方向,并且付出行动去提升自我,为了成功变得更加努力。那么我做的这些就都有了意义。

喜欢的话请帮忙转发点赞一下能让更多有需要的人看到吧。谢谢!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

级系统大纲以及这些话找到了方向,并且付出行动去提升自我,为了成功变得更加努力。那么我做的这些就都有了意义。

喜欢的话请帮忙转发点赞一下能让更多有需要的人看到吧。谢谢!

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 27
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是一个简单的示例: 首先,在build.gradle文件中添加以下依赖项: ```groovy // ViewModel and LiveData implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' // Data Binding implementation 'androidx.databinding:databinding-runtime:4.0.1' ``` 接下来,创建一个名为MainActivity的Activity,并在其布局文件中添加两个Fragment的占位符: activity_main.xml: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <FrameLayout android:id="@+id/container" android:layout_width="match_parent" android:layout_height="match_parent"/> </layout> ``` MainActivity.java: ```java public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); // 加载第一个Fragment getSupportFragmentManager().beginTransaction() .replace(R.id.container, new FirstFragment()) .commit(); } } ``` 接下来,创建一个名为FirstFragment的Fragment,并在其布局文件中使用DataBinding绑定数据: first_fragment.xml: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.text}" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Next" android:onClick="@{viewModel::onNextClicked}" /> </LinearLayout> </layout> ``` FirstFragment.java: ```java public class FirstFragment extends Fragment { private FirstViewModel viewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // 使用DataBinding绑定布局文件 FirstFragmentBinding binding = DataBindingUtil.inflate(inflater, R.layout.first_fragment, container, false); // 创建ViewModel实例 viewModel = ViewModelProviders.of(this).get(FirstViewModel.class); // 将ViewModel与布局文件中的变量绑定 binding.setViewModel(viewModel); // 设置LifecycleOwner,以便LiveData知道何时更新UI binding.setLifecycleOwner(this); return binding.getRoot(); } } ``` 下面是FirstViewModel.java: ```java public class FirstViewModel extends ViewModel { private MutableLiveData<String> textLiveData = new MutableLiveData<>(); public FirstViewModel() { // 初始化LiveData的默认值 textLiveData.setValue("Hello, World!"); } public LiveData<String> getText() { return textLiveData; } public void onNextClicked() { // 更新LiveData的值 textLiveData.setValue("Next Clicked!"); } } ``` 最后,创建另一个名为SecondFragment的Fragment,并在MainActivity中添加一个方法来加载它: second_fragment.xml: ```xml <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{viewModel.text}" /> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Previous" android:onClick="@{viewModel::onPreviousClicked}" /> </LinearLayout> </layout> ``` SecondFragment.java: ```java public class SecondFragment extends Fragment { private SecondViewModel viewModel; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // 使用DataBinding绑定布局文件 SecondFragmentBinding binding = DataBindingUtil.inflate(inflater, R.layout.second_fragment, container, false); // 创建ViewModel实例 viewModel = ViewModelProviders.of(this).get(SecondViewModel.class); // 将ViewModel与布局文件中的变量绑定 binding.setViewModel(viewModel); // 设置LifecycleOwner,以便LiveData知道何时更新UI binding.setLifecycleOwner(this); return binding.getRoot(); } } ``` 下面是SecondViewModel.java: ```java public class SecondViewModel extends ViewModel { private MutableLiveData<String> textLiveData = new MutableLiveData<>(); public SecondViewModel() { // 初始化LiveData的默认值 textLiveData.setValue("Goodbye, World!"); } public LiveData<String> getText() { return textLiveData; } public void onPreviousClicked() { // 更新LiveData的值 textLiveData.setValue("Previous Clicked!"); } } ``` 最后,在MainActivity中添加一个方法来加载SecondFragment: ```java private void loadSecondFragment() { getSupportFragmentManager().beginTransaction() .replace(R.id.container, new SecondFragment()) .commit(); } ``` 现在,你的MVVM Android项目就完成了!你可以使用LiveData和ViewModel来管理数据,并使用DataBinding将数据绑定到UI上。Lifecycle组件可确保UI在活动和片段之间正确地进行管理。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值