首語
- Android項目開發中,尤其大型的項目中,模塊內部的高聚合和模塊間的低耦合顯得很是重要。爲此我們需要選擇一種框架模式,Android通常使用到的有MVC,MVP和MVVM。通過框架模式設計的項目能夠極大提高開發效率,提高項目的可維護性和可擴展性,同時在模塊測試和Bug處理上也有很大便利。
需求(查詢用戶賬號信息)
- 用戶輸入賬號,點擊按鈕可進行查詢賬號信息,如果查詢數據成功,則將數據展示在頁面上;如果查詢失敗,則在頁面上提示獲取數據失敗。
不使用框架
- Activity主要實現的功能有獲取用戶輸入的信息、展示獲取信息成功頁面、展示獲取信息失敗頁面、查詢用戶數據和業務邏輯。
private String getUserInput(){//獲取用戶輸入的信息
return etinput.getText().toString().trim();
}
private void showSuccessPage(Account account){//展示獲取信息成功頁面
tvinformation.setText("用戶賬號:"+account.getName()+"用戶等級:"+account.getLevel());
}
private void showFailedPage(){//展示獲取信息失敗頁面
tvinformation.setText("獲取數據失敗");
}
private void getAccountData(String accountName, MCallBack callBack){//查詢用戶數據
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callBack.onSuccess(account);
}else {
callBack.onFailed();
}
}
@Override
public void onClick(View view) {
String userInput = getUserInput();
getAccountData(userInput, new MCallBack() {
@Override
public void onSuccess(Account account) {
showSuccessPage(account);
}
@Override
public void onFailed() {
showFailedPage();
}
});
}
public interface MCallBack {
void onSuccess(Account account);
void onFailed();
}
//Account Bean類
private String name;//姓名
private int level;//等級
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
}
MVC
- MVC的全名是Model-View-Controller,是模型(Model)-視圖(View)-控制器(Controller)的縮寫。Model層負責數據處理(網絡請求,SQL等),View層負責Layout、View控件。Controller層負責Activity,Fragment。
需求分析
- Controller層:業務邏輯處理,獲取用戶輸入,展示成功頁面,展示失敗頁面。
- Model層:查詢賬號數據。
- View層:layout佈局。
實現
- 1.將數據的獲取與界面的展示分離 。
- 2.解決各層之間通信問題(Activity通知Model獲取數據,Model通知Activity更新頁面)。
//Model層
public void getAccountData(String accountName, MCallBack callBack){
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callBack.onSuccess(account);
}else {
callBack.onFailed();
}
}
//Controller層
private String getUserInput(){
return etinput.getText().toString().trim();
}
private void showSuccessPage(Account account){
tvinformation.setText("用戶賬號:"+account.getName()+"用戶等級:"+account.getLevel());
}
private void showFailedPage(){
tvinformation.setText("獲取數據失敗");
}
@Override
public void onClick(View view) {
mMvcModel.getAccountData(getUserInput(), new MCallBack() {
@Override
public void onSuccess(Account account) {
showSuccessPage(account);
}
@Override
public void onFailed() {
showFailedPage();
}
});
}
MVC優缺點
- 優點:一定程度上實現了Model與View的分離,降低了代碼的耦合性。
- 缺點:Controller與View難以完全解耦,並且隨着項目複雜度的提升,Controller將越來越臃腫。
MVP
- MVP的全名是Model-View-Presenter,是模型(Model)-視圖(View)-中間層(Presenter)的縮寫。Model層負責數據處理(網絡請求、SQL等) ,View層負責用戶交互(Activity,Fragment),Presenter層作爲View層與Model層中間的紐帶,負責處理與用戶交互的主要業務邏輯。
需求分析
- Model層:查詢賬號數據。
- View層:獲取用戶輸入、展示成功界面、展示失敗界面。
- Presenter層:業務邏輯處理。
實現
- 1.Activity負責提供View層面的功能(採用實現接口的方式)
- 2.Model層負責提供數據方面的功能。
- Model層與View層不再直接通信,通過Presenter來實現。
//Model層負責提供數據方面的功能
public void getAccountData(String accountName, MCallBack callBack){
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callBack.onSuccess(account);
}else {
callBack.onFailed();
}
}
//採用實現接口的方式實現View層面的功能
public interface MVPView {
String getUserInput();
void showSuccessPage(Account account);
void showFailedPage();
}
//中間層Presenter
private MVPView mMVPView;
private MVPModel mMVPModel;
public MVPPresenter(MVPView mMVPView) {
this.mMVPView = mMVPView;
mMVPModel = new MVPModel();
}
public void getData(String accountName){
mMVPModel.getAccountData(accountName, new MCallBack() {
@Override
public void onSuccess(Account account) {
mMVPView.showSuccessPage(account);
}
@Override
public void onFailed() {
mMVPView.showFailedPage();
}
});
}
//Activity中Model與View不再直接通信,通過Presenter來實現。繼承View接口層
@Override
public void onClick(View view) {
mMVPPresenter.getData(getUserInput());
}
@Override
public String getUserInput() {
return etinput.getText().toString().trim();
}
@Override
public void showSuccessPage(Account account) {
tvinformation.setText("用戶賬號:"+account.getName()+"用戶等級:"+account.getLevel());
}
@Override
public void showFailedPage() {
tvinformation.setText("獲取數據失敗");
}
MVP優缺點
- 優點:解決了MVC中Controller與View過度耦合的的缺點,職責劃分明顯,更加易於維護。
- 缺點:接口數量多,項目複雜度提高,隨着項目複雜度的提高,Presenter將越來越臃腫。
建議
-
1.接口規範化(封裝父類接口以減少接口的使用量)
-
2.使用MVP插件自動生成MVP的代碼
-
3.對於一些簡單的頁面,可以選擇不使用框架。
-
4.根據項目複雜度,部分模塊可以選擇不使用接口。
MVC與MVP的區別
- 1.Model與View不再直接進行通信,而是通過中間層Presenter來實現。
- 2.Activity的功能被簡化,不再充當控制器,主要負責View層面的工作。
PS(常見MVP插件)
- MVPHelper
GitHub地址:https://github.com/githubwing/MVPHelper - MVPPlugin
Github地址:https://github.com/yugai/MVPPlugin
MVVM
- MVVM的全名是Model-View-ViewModel,是模型(Model)-視圖(View)-數據視圖管理器(ViewModel)的縮寫。Model層負責數據處理,View層負責Activity,Fragment。VIewModel層負責調用Model,拿到數據更新自身,而View與ViewModel雙向綁定,所以View會自動更新。MVVM在MVP的基礎上實現了數據視圖的綁定(DataBinding),當數據變化時,視圖會自動更新;反之,視圖發生變化時,數據也會自動更新。
DataBinding
- DataBinding是Google官方發佈的一個實現數據綁定的框架(實現數據與視圖的雙向綁定),它會使我們告別繁瑣的findViewById,同時也減少了接口數量。DataBinding可以幫助我們在安卓中更好的實現MVVM模式。
- 官方文檔:https://developer.android.google.cn/jetpack/androidx/releases/databinding?hl=zh_cn
使用步驟
1.啓用DataBinding
//編輯app目錄下的build.gradle文件,添加下面的內容。
android {
...
dataBinding {
enabled = true
}
}
2.修改佈局文件爲DataBinding佈局
- 選中佈局文件根佈局(如LinearLayout),按alt+enter,點擊Convert to data binding layout,
佈局文件轉化爲DataBinding佈局。它最外層是layout,裏面包含data和我們的原始佈局。 - 在data中我們可以聲明對象,然後我們就可以在佈局文件中使用對象。@={}表示雙向綁定(具體見代碼)
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="account"
type="com.yhj.framedemo.bean.Account" />
<variable
name="activity"
type="android.app.Activity" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".databinding.DemoActivity">
<TextView
android:id="@+id/tv_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="80dp"
android:text="@{account.name+'|'+account.level}" />
<Button
android:id="@+id/bt_level"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="80dp"
android:onClick="@{activity.onClick()}"
android:text="等級+1" />
</LinearLayout>
</layout>
- 生成DataBinding佈局以後,setContentView(view)也要發生改變,使用DataBindingUtil.setContentView,同時使用binding省略了findViewById。
ActivityMvvmBinding binding =
DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
binding.etInput.setText("省略findViewById()");
3.數據綁定
- 讓Bean類Account繼承BaseObservable,爲了使數據發生變化視圖自動更新,在getLevel()上加註解Binable,在setLevel()中notifyPropertyChanged(BR.level)。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_demo);
account = new Account();
account.setName("Tom");
account.setLevel(100);
binding.setAccount(account);
binding.setActivity(this);
}
public void onClick(View view){
Toast.makeText(this, "點擊了!", Toast.LENGTH_SHORT).show();
int level = account.getLevel();
account.setLevel(level+1);
//binding.setAccount(account);
}
public class Account extends BaseObservable {
private String name;//姓名
private int level;//等級
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Bindable
public int getLevel() {
return level;
}
public void setLevel(int level) {
this.level = level;
notifyPropertyChanged(BR.level);
}
}
需求分析
- Model層:查詢賬號數據。
- View層:獲取用戶輸入,展示成功界面,展示失敗界面。
- ViewModel層:業務邏輯處理,數據更新。
實現
- 提供View,ViewModel以及Model三層。
- 將佈局修改爲DataBinding佈局
- View與ViewModel之間通過DataBinding進行通信。
- 獲取數據展示在界面上。
//首先是DataBinding佈局
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.yhj.framedemo.mvvm.MVVMViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".mvvm.MVVMActivity">
<EditText
android:hint="請輸入要查詢的賬號"
android:text="@={viewModel.userInput}"
android:layout_marginTop="20dp"
android:id="@+id/et_input"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:layout_gravity="center"
android:layout_marginTop="80dp"
android:id="@+id/bt_get"
android:onClick="@{viewModel.getData}"
android:text="獲取賬號信息"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:layout_gravity="center"
android:layout_marginTop="80dp"
android:id="@+id/tv_information"
android:text="@{viewModel.result}"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</layout>
//Model層數據處理
public void getAccountData(String accountName, MCallBack callBack){
Random random = new Random();
boolean isSuccess = random.nextBoolean();
if (isSuccess){
Account account = new Account();
account.setName(accountName);
account.setLevel(100);
callBack.onSuccess(account);
}else {
callBack.onFailed();
}
}
//ViewModel層
public class MVVMViewModel extends BaseObservable {
private String result;
private String userInput;
@Bindable
public String getUserInput() {
return userInput;
}
public void setUserInput(String userInput) {
this.userInput = userInput;
notifyPropertyChanged(BR.userInput);
}
private final MVVMModel mvvmModel;
private MVVMModel mvvmModel1;
private ActivityMvvmBinding binding;
@Bindable
public String getResult() {
return result;
}
public void setResult(String result) {
this.result = result;
notifyPropertyChanged(BR.result);
}
//一般需要傳入Application對象,方便在ViewModel中使用Application
//比如SharedPreference
public MVVMViewModel(Application application) {
mvvmModel = new MVVMModel();
}
public void getData(View view) {
mvvmModel.getAccountData(userInput, new MCallBack() {
@Override
public void onSuccess(Account account) {
String info = account.getName() + "|" + account.getLevel();
setResult(info);
}
@Override
public void onFailed() {
setResult("獲取失敗!!!");
}
});
}
}
//View層Activity
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMvvmBinding binding =
DataBindingUtil.setContentView(this, R.layout.activity_mvvm);
MVVMViewModel mvvmViewModel = new MVVMViewModel(getApplication());
binding.setViewModel(mvvmViewModel);
}
- View可以向ViewModel傳遞信息,ViewModel改變數據通知View自動更新,但是有些功能我們要在Activity中實現(比如點擊按鈕獲取應用權限),ViewModel如何通知View呢?要實現這個功能,我們可以讓ViewModel執有Activity的引用,或者借用EventBus。對於MVVM模式來說,我們可以使用LiveData+ViewModel來實現。
LiveData
- LiveData是一個可以被觀察的數據持有者,它可以通過添加觀察者的方式來讓其他組件觀察他的變更。LiveData遵從應用程序的生命週期(如果LiveData的觀察者已經是銷燬狀態,LiveData就不會通知該觀察者)。
- 官方文檔:https://developer.android.google.cn/reference/android/arch/lifecycle/LiveData?hl=zh-cn
MVVM優缺點
- 優點:實現了數據與視圖的雙向綁定,極大的簡化代碼。
- 缺點:bug難以調試,並且DataBinding目前還存在一些編譯問題。