网站开发销售员,自己制作简易网页,宁波seo智能优化,成都网站建设网站制作写在前面#xff1a;现在随便出去面试Android APP相关的工作#xff0c;面试官基本上都会提问APP架构相关的问题#xff0c;用Java、kotlin写APP的话#xff0c;其实就三种架构MVC、MVP、MVVM#xff0c;MVC和MVP高度相似#xff0c;区别不大#xff0c;MVVM则不同… 写在前面现在随便出去面试Android APP相关的工作面试官基本上都会提问APP架构相关的问题用Java、kotlin写APP的话其实就三种架构MVC、MVP、MVVMMVC和MVP高度相似区别不大MVVM则不同引入了新的JetPack工具ViewModel、LiveData、DataBinding导入了“View和数据双向绑定的概念”。搞Android APP的必须把这三种架构搞清楚、搞透彻。 正式开始码字前我们要先清楚两个词高内聚、低耦合。相信计算机和相关专业的同学肯定都在老师的嘴里听说过这两个词我们写Android APP为什么要用架构其实就是为了实现这两个词代表的含义。 高内聚Java、kotlin都是面向对象编程的语言高内聚就是要求每个类的功能精简类里面的每个方法精简。最好就是每个类、每个方法相对独立对外的依赖少功能明确。 低耦合追求高内聚的结果必然就是低耦合低耦合说的就是代码的不同功能模块之间没有绝对的依赖关系不是谁离开谁就运行不下去那种。不能出现搭积木拆了一块剩下的全塌了这种情况。 高内聚、低耦合的目的最终就是为了项目代码方便维护后续方便功能扩展。代码架构就是为了更方便的实现高内聚、低耦合目标的代码组成方式使用了之后你的代码就像用收纳盒规整过一样。当然高内聚、低耦合只是一个指导思想在实际开发中我们不可能处处都能完美做到但必须作为一个追求的目标。 下面依次介绍MVC、MVP、MVVM搞清楚它们之间的联系和区别以及为什么会演变出新的架构。
一、MVC MVC主要分为三个部分Model数据层、View视图层、Controller控制层Model和View不直接联系它们之间以Controller作为纽带。下面举一个简单的例子进行说明。
1.1 Model负责数据处理 用Android Studio创建一个普通的项目。创建一个UserModel类用来模拟处理数据当然写得很简单。
// Model - 保存用户数据
public class UserModel {private String name;// 设置用户名字public void setName(String name) {this.name name;}// 获取用户名字public String getName() {return this.name;}
}
1.2 View负责显示UI、更新UI和接收用户输入事件 View视图层简单来说就是Activity、Fragment、Dialog这些负责承载视图的组件方便理解就直接把它看成Activity吧。MainActivity中我们这样写代码都比较简单可以直接看懂。
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private EditText nameInput; // 输入框private Button submitButton; // 提交按钮private TextView welcomeMessage; // 显示欢迎消息private UserController controller; // ControllerOverrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 初始化界面组件nameInput findViewById(R.id.nameInput);submitButton findViewById(R.id.submitButton);welcomeMessage findViewById(R.id.welcomeMessage);// 初始化 Controllercontroller new UserController(this);// 按钮点击事件submitButton.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View v) {// 获取用户输入并通过 Controller 处理controller.onSubmitButtonClicked();}});}// 显示欢迎消息public void showWelcomeMessage(String message) {welcomeMessage.setText(message);}// 获取用户输入的名字public String getUserInput() {return nameInput.getText().toString();}
}1.3 Controller负责将用户输入的数据传递给 Model并通过 View 更新界面 创建一个Controller类
// Controller - 处理业务逻辑
public class UserController {private MainActivity view; // Viewprivate UserModel model; // Modelpublic UserController(MainActivity view) {this.view view;this.model new UserModel();}// 处理提交按钮点击事件public void onSubmitButtonClicked() {// 从 View 获取用户输入String name view.getUserInput();// 将输入保存到 Model 中model.setName(name);// 创建欢迎消息并通过 View 显示String welcomeMessage Hello, model.getName() !;view.showWelcomeMessage(welcomeMessage);}
}
1.4 总结 上述的代码我们实现了一个最简单的MVC架构MVC的核心思想就是让View视图层和Model数据处理层完全解耦分离中间通过Controller控制层链接视图层只负责更新数据不像以前把很多逻辑都塞到Activity中导致Activity文件臃肿。可以看到Controller同时持有了Model、View的实例对象。MVC架构可以用下面这张图做一个形象的表示 点击下载 Android MVC架构示例Demo https://github.com/xuhao120833/MVC
二、MVP MVP包含三个部分Model、View、Presenter。和MVC相比用Presenter替代了Controller在MVC中Controller同时持有了Model、View的实例对象起到中间人的作用但是这种形式的缺点在于换一个View就得新建一个ControllerController无法复用大大增加了代码量于是MVP更进一步Presenter持有的不再是Model、View的实例对象而是一个接口引用这样一来Presenter就可以得到复用进一步解耦了Model和Presenter的关系、View和Presenter的关系。 我们举一个模拟登录界面的例子来说明MVP架构。
2.1 创建接口 Android Studio新建一个名称为MVP的项目之后新建如下三个Java接口ILoginModel、LoginCallback、LoginView。 ILoginModel接口用于不同的Model实现。
package com.htc.mvp;public interface ILoginModel {// 定义登录方法void login(String username, String password, LoginCallback callback);
}LoginCallback接口在调用ILoginModel.login方法时实现的回调。
package com.htc.mvp;// 回调接口用于通知登录结果
public interface LoginCallback {void onSuccess(String user);void onFailure(String error);
}LoginView接口用于不同的View去实现。
package com.htc.mvp;public interface LoginView {// 显示加载动画void showLoading();// 隐藏加载动画void hideLoading();// 登录成功时显示消息void showLoginSuccess(String message);// 登录失败时显示错误void showLoginError(String error);
}2.2 LoginModel实现ILoginModel接口
package com.htc.mvp;import android.os.Handler;
import android.os.Looper;public class LoginModel implements ILoginModel {Overridepublic void login(String username, String password, LoginCallback callback) {// 模拟网络延迟2秒new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {Overridepublic void run() {if (admin.equals(username) 123456.equals(password)) {callback.onSuccess(欢迎您, username !);} else {callback.onFailure(用户名或密码错误);}}}, 2000);}
}2.3 MainActivity实现LoginView接口
package com.htc.mvp;import android.annotation.SuppressLint;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity implements LoginView {private LoginPresenter presenter;private EditText etUsername;private EditText etPassword;private Button btnLogin;private ProgressBar progressBar;SuppressLint(MissingInflatedId)Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);etUsername findViewById(R.id.etUsername);etPassword findViewById(R.id.etPassword);btnLogin findViewById(R.id.btnLogin);progressBar findViewById(R.id.progressBar);// 使用构造函数注入 LoginModel 实例presenter new LoginPresenter(this, new LoginModel());btnLogin.setOnClickListener(new View.OnClickListener() {Overridepublic void onClick(View view) {String username etUsername.getText().toString().trim();String password etPassword.getText().toString().trim();presenter.performLogin(username, password);}});}Overridepublic void showLoading() {progressBar.setVisibility(View.VISIBLE);}Overridepublic void hideLoading() {progressBar.setVisibility(View.GONE);}Overridepublic void showLoginSuccess(String message) {Toast.makeText(this, message, Toast.LENGTH_LONG).show();}Overridepublic void showLoginError(String error) {Toast.makeText(this, error, Toast.LENGTH_LONG).show();}
}2.4 实现LoginPresenter
package com.htc.mvp;public class LoginPresenter {private LoginView view;private ILoginModel model;// 通过构造方法传入 ILoginModel 的实例可以在外部进行依赖注入public LoginPresenter(LoginView view, ILoginModel model) {this.view view;this.model model;}// 执行登录操作public void performLogin(String username, String password) {if (username null || username.isEmpty() || password null || password.isEmpty()) {view.showLoginError(用户名和密码不能为空);return;}view.showLoading();model.login(username, password, new LoginCallback() {Overridepublic void onSuccess(String user) {view.hideLoading();view.showLoginSuccess(user);}Overridepublic void onFailure(String error) {view.hideLoading();view.showLoginError(error);}});}
}2.5 总结 可以看到MVP模式中Model、View中用到的方法都被抽象到接口中了而Presenter只持有了Model、View的接口引用保证了Presenter可以得到复用。MVP架构可以用下面这张图做一个形象的表示创建Presenter的时候传的是View、Model的实例但是Presenter保存的却是View、Model的接口引用这就保证了Presenter可以调用不同的View和Model保证了Presenter的复用这是MVP相较于MVC进步最大的地方。还有一个不常提的优势是由于Presenter持有的是接口引用就很方便进行单元测试不用创建真的View、Model可以使用Mockito或类似的库模拟传入Presenter进行测试。 注图中的虚线表示通过接口进行方法调用。 点击下载Android MVP架构示例Demohttps://github.com/xuhao120833/MVP
三、MVVM MVVM架构有三大要素Model、View、ViewModel。相较于MVC、MVPMVVM迎来了很大的变化ViewModel不直接持有View的引用或者实例对象而是通过DataBinding或者LiveData.observe来更新UI。MVVM的学习难度更高引入了三个新的技术ViewModel、DataBinding、LiveData。在正式举例介绍MVVM之前我们必须对这三个新伙伴做一个介绍。 3.1 ViewModel ViewModel是JetPack androidx.lifecycle仓库中的组件存在的意义主要是帮助开发者有效保存UI界面数据看它所属于的仓库路径包含lifecycle就知道它和“生命周期”强相关。上述解释很抽象必须举个例子理解一下。 Android开发者都知道当系统语言、屏幕方向(横屏)、主题等发生变化时会触发当前Activity重建重新执行Activity如果你把和UI相关的数据放在Activity中数据就会被重新赋值导致数据丢失。倘若你的UI界面复杂数据很多那么数据丢失带来的结果将是毁灭性的。Android传统的开发也给我们提供了保存数据的方法但是很简陋、不实用只适合保存简单键值对如下所示以下示例为伪代码。 Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (savedInstanceState ! null) {// 恢复数据front savedInstanceState.getInt(front, -1);rear savedInstanceState.getInt(rear, -1);}}Overrideprotected void onSaveInstanceState(Bundle outState) {//onSaveInstanceState() → onPause() → onStop() → onDestroy()super.onSaveInstanceState(outState);//保存数据到 Bundle,避免系统设置如语言改变时首页midlleApp显示的位置复位。if (circularQueue ! null) {outState.putInt(front, circularQueue.front); // 保存头指针outState.putInt(rear, circularQueue.rear); // 保存尾指针Log.d(TAG,onSaveInstanceState 保存头尾指针 front circularQueue.front rearcircularQueue.rear);}}注Bundle savedInstanceState就是Android原生提供的保存数据的方法。用起来也很简单就是当系统属性发生变化时Activity被重建前会先回调到onSaveInstanceState方法如果有需要保存的数据就用Bundle outState进行保存Activity重新执行到onCreate时又通过Bundle savedInstanceState把数据取出来使用达到防止数据丢失的作用。 但是通过Bundle来保存数据也有致命的缺陷1、它只能保存下面的类型简单的基本类型键值对List、ArrayList实例对象T必须实现Parcelable接口也就是必须是可序列化、反序列化的类。2、Bundle最大只能保存1MB的数据很小超过限制会崩溃。就是因为有这些缺点我们必须引入ViewModel它可以保存大量数据、几乎所有类型数据都能保存、生命周期和Activity/Fragment无关。 Bundle 和ViewModel区别如下图 总结为什么要用ViewModel ——》数据放在ViewModel中ViewModel有独立的生命周期Activity/Fragment意外被销毁时和它没有关系它可以保存大量数据囊括几乎所有类型完美解决了Bundle savedInstanceState的致命缺陷。
3.2 LiveData LiveData也是JetPack androidx.lifecycle仓库下提供的一个组件和生命周期这个概念也是强相关当然这次不是它自己的什么周期而是观察者的生命周期。这话听起来很拗口我们接下来慢慢说。 LiveData是用来保存数据的它最核心的设计思想是“观察者模式”也就是它可以被Activty/Fragment观察当它保存的数据发生变化时自动通知所有的观察者更新UI。说到这里有同学就问了“那这和普通的观察者模式也没区别呀只不过代替码农封装了通知观察者的过程把这个过程隐藏了而已。”LiveData不仅如此它最牛的地方来了可以多个Activity/Fragment同时观察一个LiveData数据LiveData数据发生变化时会根据传进来的LifecycleOwner提前获悉所有观察者的生命周期状态只有处在 “活跃期”的它才会通知已经销毁的还会自动解绑。 完全不用程序员自己操心避免了Activity/Fragment已经被销毁但是依然是观察者数据改变依然需要通知的内存泄漏。下面我总结一下LiveData的特点 1 观察者生命周期感知上面说了它只会通知“活跃期”的观察者更新UI那么怎么定义活跃期很简单就是用户能用眼睛看到的就是处在活跃期的观察者。 2自动处理线程处理耗时任务传统的写法是开一个线程后台处理处理完之后如果数据需要用来更新UI那么还得手动切换到主线程比如runOnUiThread。LiveData就不需要这么麻烦它提供了两个更新数据的方法一个setValue用于在主线程直接更新数据立马通知活跃观察者一个postValue用于在其它线程更新数据postValue的工作逻辑是把数据更新的任务自动放到主线程的工作队列中主线程执行到了这个任务再去更新数据——》通知活跃的观察者。 3避免内存泄LiveData 是弱引用的这意味着当 Activity 或 Fragment 被销毁时它的观察者会自动被解除不会导致内存泄漏。你不需要手动移除观察者。 4常见类型常见的有LiveData和MutableLiveData两种LiveData中的数据只读MutableLiveData的数据可读可写。 5支持保存几乎所有数据类型 说了这么多接下来用kotlin看看一个最简单的Livedata如何使用
注以下代码用伪代码展示//ViewModel类中使用MutableLiveData保存数据
class UserViewModel : ViewModel() {val user MutableLiveDataUser()val userName MutableLiveDataString()fun update_user() {user.value User(id 1, name userName.value.toString(), age 30)//kotlin中的.value 就是setValue在主线程更新LiveData数据的意思}. . . . . .
}//MainActivity 中观察userName的变化
class MainActivity : AppCompatActivity() {companion object {private const val TAG MainActivity}private val userViewModel: UserViewModel by viewModels() // ✅ 移到类内部private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)// 观察 LiveData 数据变化userViewModel.userName.observe(this) { userName -// 这里可以处理一些更新逻辑Log.d(TAG,userViewModel.user.observe binding.livedata.setTextuserName)userViewModel.update_user()}}. . . . . .
}总结综合看下来LiveData更像是一个托管工具人它够强大也很好用专门用来更新UI自动管理线程避免内存泄漏是一个半自动化工具。
3.3 DataBinding 如果说LiveData是一个半自动化工具的话那么DataBinding就是全自动化工具。强如LiveData最后也需要通过binding.livedata.setText(userName Livedata使用)这种形式来更新UIDataBinding直接把userViewModel.userName.observe(this)这种观察到变化再更新的模式直接抛弃了它直接到layout布局文件给UI组件绑定数据。 其它特点和LiveData高度相似也是只会更新活跃期的Activity/Fragment。我们需要注意的是DataBinding的绑定方式分为两种 1 双向绑定数据变化会导致UI变化UI变化也会导致数据变化最典型的例子就是EditText。 2 单向绑定只有数据变化才会导致UI变化反过来却不行。下面举个kotlin例子介绍如何使用 想要使用DataBinding第一步需要在build.gradl.kts中打开databinding开关
android {. . . . . .buildFeatures {dataBinding true}
}第二步去layout布局文件中结合ViewModel(用一般的数据类也行这里只是用ViewModel结合举例)绑定UI和数据。{ 双向绑定{ 单向绑定
layout xmlns:androidhttp://schemas.android.com/apk/res/androiddatavariablenameviewModeltypecom.htc.mvvm_kotlin.UserViewModel //dataLinearLayoutandroid:layout_widthmatch_parentandroid:layout_heightmatch_parentandroid:orientationvertical!-- 双向绑定EditText 与 ViewModel 中的 userName 绑定 --EditTextandroid:idid/editTextNameandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:text{viewModel.userName} /!-- 单向绑定TextView 显示 userName --EditTextandroid:layout_widthmatch_parentandroid:layout_heightwrap_contentandroid:layout_marginTop20dpandroid:text{viewModel.userName} /!-- 显示其他信息 --TextViewandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginTop20dpandroid:text{User Name: viewModel.user.name} /TextViewandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginTop20dpandroid:text{User ID: viewModel.user.id} /TextViewandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginTop20dpandroid:text{Age: viewModel.user.age} /TextViewandroid:idid/livedataandroid:layout_widthwrap_contentandroid:layout_heightwrap_contentandroid:layout_marginTop20dpandroid:textLiveData //LinearLayout
/layout 第三步Activty/Fragment中加载使用。
class MainActivity : AppCompatActivity() {companion object {private const val TAG MainActivity}private val userViewModel: UserViewModel by viewModels() // ✅ 移到类内部private lateinit var binding: ActivityMainBindingoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)binding DataBindingUtil.setContentView(this, R.layout.activity_main)// 绑定 ViewModelbinding.viewModel userViewModelbinding.lifecycleOwner this// 观察 LiveData 数据变化userViewModel.userName.observe(this) { userName -// 这里可以处理一些更新逻辑Log.d(TAG,userViewModel.user.observe binding.livedata.setTextuserName)binding.livedata.setText(userName Livedata使用)userViewModel.update_user()}}
}总结DataBinding是一个绑定UI、自动更新UI的工具很省事中间过程几乎都省略了但是我个人是不推荐使用DataBinding双向绑定的因为如果一旦UI更新出错那么将是致命的报错信息很少几乎无法排错。使用起来也容易出错导致编译不过。
3.4 总结 MVVM引入了LiveData、DataBinding、ViewModel这些强大的工具旨在进一步解耦代码ViewModel不直接持有View层的实例对象或者引用而是通过DataBinding绑定、LiveData.oberverve这些方式来更新UI。MVVM相较于MVC、MVP来说带来了数据持久化、更加解耦、防止内存泄漏等诸多进步可以用下面的图片来简单表示这种结构ViewModel和Model之间即有双箭头实线又有双箭头虚线意思是ViewModel即可以持有Model的引用(虚线)也可以持有Model的实例对象(实线)。 可以看到同一个ViewModel的LiveData可以被多个Activity/Fragment observe或者DataBinding。 3.5 Demo APP下载 MVVM架构推荐使用Kotlin编写更简洁和JetPack组件结合得更好。这里Koltin、Java的实现都一起给出。
3.5.1 Kotlin Demo APP下载 点击下载Kotlin Demo APP ,GitHub链接https://github.com/xuhao120833/MVVM_Kotlin
3.5.2 Java Demo APP下载 点击下载Java Demo APP ,GitHub链接https://github.com/xuhao120833/MVVM_Java
四、总结 总的看起来MVVM比MVC、MVP先进得多但是也较为复杂容易出错。如果你写的APP对性能、内存这些要求没那么高完全可以不用MVVM。最后还是一句话没有最好的架构只有最适合的架构。熟练掌握一种就能工作了。但是对所有架构的持续学习是必须要做的。 注还没来得及校对如果有错别字和表达不清的地方还请见谅后续会陆续改正的。