相关阅读:
Android发布官方的架构库你知道吗?聊聊Android Architecture Componets
吊炸天!74款APP完整源码!
Kotlin学习资料大全,附学习视频首发
简评:虽然说 Android 的架构选择一直都很自由,MVP、MVC、MVVM 各有拥趸。但 Google 最近还是推出了一份关于应用架构的实践指南,并给出了相当详尽的步骤和一些指导建议。希望大家都能看一看,学习一下,打造更加优秀易用的 APP,也为 Android 生态的改善做一点贡献。: )
最近,官方推出了一份关于应用架构的最佳实践指南。这里就给大家简要介绍一下:
首先,Android 开发者肯定都知道 Android 中有四大组件,这些组件都有各自的生命周期并且在一定程度上是不受你控制的。在任何时候,Android 操作系统都可能根据用户的行为或资源紧张等原因回收掉这些组件。
这也就引出了第一条准则:「不要在应用程序组件中保存任何应用数据或状态,并且组件间也不应该相互依赖」。
最常见的错误就是在 Activity 或 Fragment 中写了与 UI 和交互无关的代码。尽可能减少对它们的依赖,这能避免大量生命周期导致的问题,以提供更好的用户体验。
第二条准则:「通过 model 驱动应用 UI,并尽可能的持久化」。
这样做主要有两个原因:
-
如果系统回收了你的应用资源或其他什么意外情况,不会导致用户丢失数据。
-
Model 就应该是负责处理应用程序数据的组件。独立于视图和应用程序组件,保持了视图代码的简单,也让你的应用逻辑更容易管理。并且,将应用数据置于 model 类中,也更有利于测试。
官方推荐的 App 架构
在这里,官方演示了通过使用最新推出的 Architecture Components 来构建一个应用。
想象一下,您正在打算开发一个显示用户个人信息的界面,用户数据通过 REST API 从后端获取。
首先,我们需要创建三个文件:
为了简单起见,我们这里就省略掉布局文件。
public class UserProfileViewModel extends ViewModel { private String userId; private User user;
public class UserProfileFragment extends LifecycleFragment { private static final String UID_KEY = "uid"; private UserProfileViewModel viewModel; @Override
注意其中的 ViewModel 和 LifecycleFragment 都是 Android 新引入的,可以参考官方说明进行集成。
现在,我们完成了这三个模块,该如何将它们联系起来呢?也就是当 ViewModel 中的用户字段被设置时,我们需要一种方法来通知 UI。这就是 LiveData 的用武之地了。
LiveData 是一个可被观察的数据持有者(用到了观察者模式)。其能够允许 Activity, Fragment 等应用程序组件对其进行观察,并且不会在它们之间创建强依赖。LiveData 还能够自动响应各组件的声明周期事件,防止内存泄漏,从而使应用程序不会消耗更多的内存。
注意:LiveData 和 RxJava 或 Agera 的区别主要在于 LiveData 自动帮助处理了生命周期事件,避免了内存泄漏。
所以,现在我们来修改一下 UserProfileViewModel:
public class UserProfileViewModel extends ViewModel {
再在 UserProfileFragment 中对其进行观察并更新我们的 UI:
@Overridepublic void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); viewModel.getUser().observe(this, user -> { // update UI
获取数据
现在,我们联系了 ViewModel 和 Fragment,但 ViewModel 又怎么来获取到数据呢?
在这个示例中,我们假定后端提供了 REST API,因此我们选用 Retrofit 来访问我们的后端。
首先,定义一个 Webservice:
public interface Webservice { /**
不要通过 ViewModel 直接来获取数据,这里我们将工作转交给一个新的 Repository 模块。
Repository 模块负责数据处理,为应用的其他部分提供干净可靠的 API。你可以将其考虑为不同数据源(Web,缓存或数据库)与应用之间的中间层。
public class UserRepository { private Webservice webservice; // ...
管理组件间的依赖关系
根据上面的代码,我们可以看到 UserRepository 中有一个 Webservice 的实例,不要直接在 UserRepository 中 new 一个 Webservice。这很容易导致代码的重复与复杂化,比如 UserRepository 很可能不是唯一用到 Webservice 的类,如果每个用到的类都新建一个 Webservice,这显示会导致资源的浪费。
这里,我们推荐使用 Dagger 2 来管理这些依赖关系。
现在,让我们来把 ViewModel 和 Repository 连接起来吧:
public class UserProfileViewModel extends ViewModel { private LiveData user; private UserRepository userRepo; @Inject // UserRepository parameter is provided by Dagger 2
缓存数据
在实际项目中,Repository 往往不会只有一个数据源。因此,我们这里在其中再加入缓存:
@Singleton // informs Dagger that this class should be constructed oncepublic class UserRepository { private Webservice webservice; // simple in memory cache, details omitted for brevity
持久化数据
现在当用户旋转屏幕或暂时离开应用再回来时,数据是直接可见的,因为是直接从缓存中获取的数据。但要是用户长时间关闭应用,并且 Android 还彻底杀死了进程呢?
我们目前的实现中,会再次从网络中获取数据。这可不是一个好的用户体验。这时就需要数据持久化了。继续引入一个新组件 Room。
Room 能帮助我们方便的实现本地数据持久化,抽象出了很多常用的数据库操作,并且在编译时会验证每个查询,从而损坏的 SQL 查询只会导致编译时错误,而不是运行时崩溃。还能和上面介绍的 LiveData 完美合作,并帮开发者处理了很多线程问题。
现在,让我们来看看怎么使用 Room 吧。: )
首先,在 User 类上面加上 @Entity,将 User 声明为你数据库中的一张表。
@Entityclass User {
再创建数据库类并继承 RoomDatabase:
@Database(entities = {User.class}, version = 1)
注意 MyDatabase 是一个抽象类,Room 会自动添加实现的。
现在我们需要一种方法来将用户数据插入到数据库:
@Daopublic interface UserDao { @Insert(onConflict = REPLACE) void save(User user); @Query("SELECT * FROM user WHERE id = :userId") LiveData load(String userId);
再在数据库类中加入 DAO:
@Database(entities = {User.class}, version = 1)
注意上面的 load 方法返回的是 LiveData
,Room 会知道什么时候数据库发生了变化并自动通知所有的观察者。这也就是 LiveData 和 Room 搭配的妙用。
现在继续修改 UserRepository:
@Singletonpublic class UserRepository { private final Webservice webservice; private final UserDao userDao; private final Executor executor; @Inject
可以看到,即使我们更改了 UserRepository 中的数据源,我们也完全不需要修改 ViewModel 和 Fragment,这就是抽象的好处。同时还非常适合测试,我们可以在测试 UserProfileViewModel 时提供测试用的 UserRepository。
下面部分的内容在原文中是作为附录,但我个人觉得也很重要,所以擅自挪上来,一起为大家介绍了。: )
在上面的例子中,有心的大家可能发现了我们没有处理网络错误和正在加载状态。但在实际开发中其实是很重要的。这里,我们就实现一个工具类来根据不同的网络状况选择不同的数据源。
首先,实现一个 Resource 类:
//a generic class that describes a data with a statuspublic class Resource { @NonNull public final Status status; @Nullable public final T data; @Nullable public final String message; private Resource(@NonNull Status status, @Nullable T data, @Nullable String message) { this.status = status; this.data = data; this.message = message;
因为,从网络加载数据和从磁盘加载是很相似的,所以再新建一个 NetworkBoundResource 类,方便多处复用。下面是 NetworkBoundResource 的决策树:
API 设计: