一 开发架构 是什么?
我们先来理解开发架构的本质是什么,维基百科对软件架构的描述如下:
软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口来实现。
拆分开来就是三条:
- 针对的是一个完整系统,此系统可以实现某种功能。
- 系统包含多个模块,模块间有一些关系和连接。
- 架构是实现此系统的实施描述:模块责任、模块间的连接。
为啥要做开发架构设计呢?
- 模块化责任具体化,使得每个模块专注自己内部。
- 模块间的关联简单化,减少耦合。
- 易于使用、维护性好
- 提高开发效率
架构模式最终都是 服务于开发者。如果代码职责和逻辑混乱,维护成本就会相应地上升。
宏观上来说,开发架构是一种思想,每个领域都有一些成熟的架构模式,选择适合自己项目即可。
二 Android开发中的架构
具体到Android开发中,开发架构就是描述 视图层、逻辑层、数据层 三者之间的关系和实施:
- 视图层:用户界面,即界面的展示、以及交互事件的响应。
- 逻辑层:为了实现系统功能而进行的必要逻辑。
- 数据层:数据的获取和存储,含本地、server。
正常的开发流程中,开始写代码之前 都会有架构设计这一过程。这就需要你选择使用何种架构模式了。
我的Android开发之路完整地经过了 MVC、MVP、MVVM,相信很多开发者和我一样都是这样一个过程,先来回顾下三者。
2.1 MVC
MVC,Model-View-Controller,职责分类如下:
- Model,模型层,即数据模型,用于获取和存储数据。
- View,视图层,即xml布局
- Controller,控制层,负责业务逻辑。
View层 接收到用户操作事件,通知到 Controller 进行对应的逻辑处理,然后通知 Model去获取/更新数据,Model 再把新的数据 通知到 View 更新界面。这就是一个完整 MVC 的数据流向。
但在Android中,因为xml布局能力很弱,View的很多操作是在Activity/Fragment中的,而业务逻辑同样也是写在Activity/Fragment中。
所以,MVC 的问题点 如下:
- Activity/Fragment 责任不明,同时负责View、Controller,就会导致其中代码量大,不满足单一职责。
- Model耦合View,View 的修改会导致 Controller 和 Model 都进行改动,不满足最少知识原则。
2.2 MVP
MVP,Model-View-Presenter,职责分类如下:
- Model,模型层,即数据模型,用于获取和存储数据。
- View,视图层,即Activity/Fragment
- Presenter,控制层,负责业务逻辑。
MVP解决了MVC的问题:1.View责任明确,逻辑不再写在Activity中,而是在Presenter中;2.Model不再持有View。
View层 接收到用户操作事件,通知到Presenter,Presenter进行逻辑处理,然后通知Model更新数据,Model 把更新的数据给到Presenter,Presenter再通知到 View 更新界面。
MVP的实现思路:
- UI逻辑抽象成IView接口,由具体的Activity实现类来完成。且调用Presenter进行逻辑操作。
- 业务逻辑抽象成IPresenter接口,由具体的Presenter实现类来完成。逻辑操作完成后调用IView接口方法刷新UI。
MVP 本质是面向接口编程,实现了依赖倒置原则。MVP解决了View层责任不明的问题,但并没有解决代码耦合的问题,View和Presenter之间相互持有。
所以 MVP 有问题点 如下:
- 会引入大量的IView、IPresenter接口,增加实现的复杂度。
- View和Presenter相互持有,形成耦合。
2.3 MVVM
MVVM,Model-View-ViewModel,职责分类如下:
- Model,模型层,即数据模型,用于获取和存储数据。
- View,视图,即Activity/Fragment
- ViewModel,视图模型,负责业务逻辑。
注意,MVVM这里的ViewModel就是一个名称,可以理解为MVP中的Presenter。不等同于上一篇中的 ViewModel组件 ,Jetpack ViewModel组件是 对 MVVM的ViewModel 的具体实施方案。
MVVM 的本质是 数据驱动,把解耦做的更彻底,viewModel不持有view 。
View 产生事件,使用 ViewModel进行逻辑处理后,通知Model更新数据,Model把更新的数据给ViewModel,ViewModel自动通知View更新界面,而不是主动调用View的方法。
MVVM在Android开发中是如何实现的呢?接着看~
到这里你会发现,所谓的架构模式本质上理解很简单。比如MVP,甚至你都可以忽略这个名字,理解成 在更高的层面上 面向接口编程,实现了 依赖倒置 原则,就是这么简单。
三 MVVM 的实现 - Jetpack MVVM
前面提到,架构模式选择适合自己项目的即可。话虽如此,但Google官方推荐的架构模式 是适合大多数情况,是非常值得我们学习和实践的。
好了,下面我们就来详细介绍 Jetpack MVVM 架构。
3.1 Jetpack MVVM 理解
Jetpack MVVM 是 MVVM 模式在 Android 开发中的一个具体实现,是 Android中 Google 官方提供并推荐的 MVVM实现方式。
不仅通过数据驱动完成彻底解耦,还兼顾了 Android 页面开发中其他不可预期的错误,例如Lifecycle 能在妥善处理 页面生命周期 避免view空指针问题,ViewModel使得UI发生重建时 无需重新向后台请求数据,节省了开销,让视图重建时更快展示数据。
首先,请查看下图,该图显示了所有模块应如何彼此交互:
各模块对应MVVM架构:
- View层:Activity/Fragment
- ViewModel层:Jetpack ViewModel + Jetpack LivaData
- Model层:Repository仓库,包含 本地持久性数据 和 服务端数据
View层 包含了我们平时写的Activity/Fragment/布局文件等与界面相关的东西。
ViewModel层 用于持有和UI元素相关的数据,以保证这些数据在屏幕旋转时不会丢失,并且还要提供接口给View层调用以及和仓库层进行通信。
仓库层 要做的主要工作是判断调用方请求的数据应该是从本地数据源中获取还是从网络数据源中获取,并将获取到的数据返回给调用方。本地数据源可以使用数据库、SharedPreferences等持久化技术来实现,而网络数据源则通常使用Retrofit访问服务器提供的Webservice接口来实现。
另外,图中所有的箭头都是单向的,例如View层指向了ViewModel层,表示View层会持有ViewModel层的引用,但是反过来ViewModel层却不能持有View层的引用。除此之外,引用也不能跨层持有,比如View层不能持有仓库层的引用,谨记每一层的组件都只能与它相邻层的组件进行交互。
这种设计打造了一致且愉快的用户体验。无论用户上次使用应用是在几分钟前还是几天之前,现在回到应用时都会立即看到应用在本地保留的数据。如果此数据已过期,则应用的Repository将开始在后台更新数据。
3.2 实施
我们来举个完整的例子 - 在当用户更新笔记时, 笔记列表进行更新UI,来说明 Jetpack MVVM 的具体实施。
3.2.1 Model层
@Database(entities = {EntityNote.class}, version = 1, exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
private static final String DB_NAME = "note.db";
private static volatile AppDatabase instance;//创建单例
public static synchronized AppDatabase getInstance() {
if (instance == null) {
instance = create();
}
return instance;
}
/**
* 创建数据库
*/
private static AppDatabase create() {
return Room.databaseBuilder(MyApplication.getInstance(), AppDatabase.class, DB_NAME)
.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build();
}
public abstract NoteDao noteDao();
}
@Dao
public interface NoteDao {
@Query("select * from note")
LiveData<List<EntityNote>> getAll();
@Update
int update(EntityNote note);
@Delete
int delete(EntityNote note);
@Insert
void insert(EntityNote note);
}
@Entity(tableName = "note")
public class EntityNote {
@PrimaryKey(autoGenerate = true) // PrimaryKey 主键;autoGenerate自增长
public int id;
//下面定义表中包含的列(字段)
@ColumnInfo(name = "uuid") // ColumnInfo 列信息
public String uuid;
@ColumnInfo(name = "title")
public String title;
@ColumnInfo(name = "content")
public String content;
@ColumnInfo(name = "searchContent")
public String searchContent;
}
3.2.2 ViewModel 层
// 继承AndroidViewModel,带有Application环境
public class NoteViewModel extends AndroidViewModel {
private MediatorLiveData<List<EntityNote >> mMediatorLiveData;
public NoteViewModel(@NonNull Application application) {
super(application);
mMediatorLiveData = new MediatorLiveData<>();
LiveData<List<EntityNote>> EntityNoteLiveData = AppDatabase.getInstance().noteDao().getAll();
mMediatorLiveData.addSource(EntityNoteLiveData, new Observer<List<EntityNote>>() {
private List<EntityNote> mLastEntityNoteList;
@Override
public void onChanged(List<EntityNote> entityNotes) {
if (mLastEntityNoteList == null) {
mLastEntityNoteList = entityNotes;
return;
}
if (entityNotes == null) {
setValue(new ArrayList<>());
return;
}
int lastSize = mLastEntityNoteList.size();
int size = entityNotes.size();
if (lastSize != size) {
setValue(entityNotes);
return;
}
for (int i = 0; i < size; i++) {
EntityNote lastNote = mLastEntityNoteList.get(i);
EntityNote note = entityNotes.get(i);
if (!isSameNote(lastNote, note)) {
setValue(entityNotes);
break;
}
}
// 没有变化不setValue不触发onChanged
mLastEntityNoteList = entityNotes;
}
private void setValue(List<EntityNote> entityNotes) {
mMediatorLiveData.setValue(entityNotes);
mLastEntityNoteList = entityNotes;
}
private boolean isSameNote(EntityNote first, EntityNote second) {
if (first == null || second == null) {
return false;
}
return first.uuid.equals(second.uuid) && first.title.equals(second.title)
&& first.id == second.id && first.content.equals(second.content);
}
});
}
//查(所有)
public MediatorLiveData<List<EntityNote>> getNoteListLiveData() {
return mMediatorLiveData;
}
}
3.2.3 View层
public class NoteActivity extends AppCompatActivity {
private NoteViewModel mViewModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mViewModel = new ViewModelProvider.AndroidViewModelFactory(MyApplication.getInstance()).
create(NoteViewModel.class);
mViewModel.getNoteListLiveData().observe(this, new Observer<List<EntityNote>>() {
@Override
public void onChanged(List<EntityNote> entityNotes) {
// 更新UI
}
});
}
}
3.3 注意点
-
在应用的各个模块之间设定明确定义的职责界限。
-
ViewModel 不能持有 View层引用,包括Context也不能持有。
-
将一个数据源指定为单一可信来源。 每当需要访问数据时,都应一律源于此单一可信来源。 例如 UserRepository会将网络服务响应保存在数据库中。这样一来,对数据库的更改将触发对活跃 LiveData 对象的回调。数据库会充当单一可信来源。
-
保留尽可能多的相关数据和最新数据。 这样,即使用户的设备处于离线模式,他们也可以使用您应用的功能。请注意,并非所有用户都能享受到稳定的高速连接。
-
显示页面状态。 例如例子中的加载进度条,就是观察 ViewModel中的MutableLiveData loadingLiveData 进行操作的。
3.4 MVP改造MVVM
了解了Jetpack MVVM的实现,再来改造 MVP 是很简单的了。
步骤如下:
- 去除Presener 对View、context的引用。
- Presener 替换成ViewModel的实现,获取的数据以 LivaData呈现。
- 删除定义的IView等接口,Activity/Fragment中 获取ViewModel实例,调用其方法获取数据。
- Activity/Fragment 观察需要的 LivaData 然后刷新UI。
这样就已经成为了MVVM。当然也要检查下 原MVP的 Model层的实现,是否满足上面的要求。