1、MVP架构
直接上图:
mvp- Model层
- Model:负责存储、检索、操纵数据(有时也实现一个Model interface用来降低耦合)
- View层
- View:负责绘制UI元素、与用户进行交互(在Android中体现为Activity)
- Presenter层
- Presenter:作为View与Model交互的中间纽带,处理与用户交互的负责逻辑
Android SDK
当我们简要分析Android SDK之后,尤其是layout和activity之间的关系,能很强烈的感觉到最适合Android的设计模式是MVC。但是随着项目复杂性的增加,MVC对于一些功能点的分离支持的不是特别好,尤其是单元测试。
现在,Android SDK开始逐渐允许我们使用其他类型的架构模式,甚至是没有任何架构模式,反模式。
MVC、MVP的区别
MVC定义:
Model–view–controller (MVC)主要是(但不限于)用于在计算机上实现用户界面的软件架构设计模式。它把一个既定的软件应用程序分为三个相互连接的部分,以便于把显示内容或者从用户获取信息的这两种内在的数据表现动作分离出来。
MVP定义
mvp与mvc的区别Model–view–presenter (MVP)是MVC设计模式的推导,并主要用于构建用户界面。在MVP中Presenter承担了“中间人”的功能,并且所有的表示逻辑都交给了Presenter。主要作用是功能点分离。
两种模式的主要区别:
-
(最主要区别)View与Model并不直接交互,而是通过与Presenter交互来与Model间接交互。而在MVC中View可以与Model直接交互
-
通常View与Presenter是一对一的,但复杂的View可能绑定多个Presenter来处理逻辑。而Controller是基于行为的,并且可以被多个View共享,Controller可以负责决定显示哪个View
-
Presenter与View的交互是通过接口来进行的,更有利于添加单元测试。
代码上的区别:
MVC -- View&Controller
public class MainActivity extends ActionBarActivity implements OnWeatherListener, View.OnClickListener {
private WeatherModel weatherModel;
private EditText cityNOInput;
private TextView city;
...
//activity启动时调用的
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//加载xml文件,view
setContentView(R.layout.activity_main);
initView();
//获取weatherModel实例,model
weatherModel = new WeatherModelImpl();
}
//初始化View
private void initView() {
cityNOInput = findView(R.id.et_city_no);
city = findView(R.id.tv_city);
...
findView(R.id.btn_go).setOnClickListener(this);
}
//显示结果
public void displayResult(Weather weather) {
WeatherInfo weatherInfo = weather.getWeatherinfo();
city.setText(weatherInfo.getCity());
...
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_go:
weatherModel.getWeather(cityNOInput.getText().toString().trim(), this);
break;
}
}
@Override
public void onSuccess(Weather weather) {
displayResult(weather);
}
@Override
public void onError() {
Toast.makeText(this, 获取天气信息失败, Toast.LENGTH_SHORT).show();
}
private T findView(int id) {
return (T) findViewById(id);
}
}
MVC -- Model
//接口
public interface WeatherModel {
void getWeather(String cityNumber, OnWeatherListener listener);
}
-----
public class WeatherModelImpl implements WeatherModel{
@Override
public void getWeather(String cityNumber, final OnWeatherListener listener) {
/*数据层操作*/
VolleyRequest.newInstance().newGsonRequest(http://www.weather.com.cn/data/sk/ + cityNumber + .html,
Weather.class, new Response.Listener<weather>() {
@Override
public void onResponse(Weather weather) {
if (weather != null) {
listener.onSuccess(weather);
} else {
listener.onError();
}
}
}, new Response.ErrorListener() {
@Override
public void onErrorResponse(VolleyError error) {
listener.onError();
}
});
}
}
缺点:
在Android开发中,Activity并不是一个标准的MVC模式中的Controller,它的首要职责是加载应用的布局和初始化用户 界面,并接受并处理来自用户的操作请求,进而作出响应。随着界面及其逻辑的复杂度不断提升,Activity类的职责不断增加,以致变得庞大臃肿。
MVP -- Model
//bean类
public class UserBean {
private String mFirstName;
private String mLastName;
public UserBean(String firstName, String lastName) {
this. mFirstName = firstName;
this. mLastName = lastName;
}
public String getFirstName() {
return mFirstName;
}
public String getLastName() {
return mLastName;
}
}
----
//Model的接口
public interface IUserModel {
void setID(int id);
void setFirstName(String firstName);
void setLastName(String lastName);
int getID();
UserBean load(int id);// 通过id读取user信息,返回一个UserBean
}
MVP -- View
public interface IUserView {
int getID();
String getFristName();
String getLastName();
void setFirstName(String firstName);
void setLastName(String lastName);
}
-----
public class MainActivity extends Activity implements OnClickListener,IUserView {
UserPresenter presenter;
EditText id,first,last;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout. activity_main);
findViewById(R.id. save).setOnClickListener( this);
findViewById(R.id. load).setOnClickListener( this);
id = (EditText) findViewById(R.id. id);
first = (EditText) findViewById(R.id. first);
last = (EditText) findViewById(R.id. last);
presenter = new UserPresenter( this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id. save:
presenter.saveUser(getID(), getFristName(), getLastName());
break;
case R.id. load:
presenter.loadUser(getID());
break;
default:
break;
}
}
@Override
public int getID() {
return new Integer( id.getText().toString());
}
@Override
public String getFristName() {
return first.getText().toString();
}
@Override
public String getLastName() {
return last.getText().toString();
}
@Override
public void setFirstName(String firstName) {
first.setText(firstName);
}
@Override
public void setLastName(String lastName) {
last.setText(lastName);
}
}
MVP的优势:
- Activity的代码不臃肿;
- Model的改动不会影响Activity(View),两者也互不干涉,而在MVC中会;
- IUserView这个接口可以实现方便地对Presenter的测试;
- UserPresenter可以用于多个视图,但是在MVC中的Activity就不行。
MVP的缺点和一些解决方案:
- 转移逻辑操作之后可能部分较为复杂的Activity内代码量还是不少。
1、具体做法是在Activity内部分层。其中最顶层为BaseActivity,不做具体显示,而是提供一些基础样式,Dialog,ActionBar在内的内容,展现给用户的Activity继承BaseActivity,重写BaseActivity预留的方法。
2、使用fragment分层。能用fragment独立区分的布局,尽量用fragment分开。
- 模型层(Model)中的整体代码量是最大的,一般由大量的Package组成。
针对这部分需要做的就是在程序设计的过程中,做好模块的划分,进行接口隔离,在内部进行分层。
- 强化Presenter的作用,将所有逻辑操作都放在Presenter内也容易造成代码量过大。
终极mvp在UI层和Presenter之间设置中介者Mediator,将例如数据校验、组装在内的轻量级逻辑操作放在Mediator中;在Presenter和Model之间使用代理Proxy;通过上述两者分担一部分Presenter的逻辑操作,但整体框架的控制权还是在Presenter手中。Mediator和Proxy不是必须的,只在Presenter负担过大时才建议使用。
2、MVVM架构
什么是MVVM
MVVM是不是和MVP很像,只是把Presenter改成了ViewModel。
但是!!!
这里的“Model”指的是View的Model,跟MVP中的一个Model不是一回事。所谓View的Model就是包含View的一些数据属性和操作的这么一个东西,这种模式的关键技术就是数据绑定(data binding),View的变化会直接影响ViewModel,ViewModel的变化或者内容也会直接体现在View上。这种模式实际上是框架替应用开发者做了一些工作,开发者只需要较少的代码就能实现比较复杂的交互。
在我理解的MVVM是MVP的进一步升级。Data Binding框架将会接管Presenter的主要职责(作用于model和view上),Presenter的其他剩余职责(从仓库中获取数据并进行格式化处理)则由ViewModel(一个增强版的Model)接管。ViewModel是一个独立的Java类,它的唯一职责是表示一个View后面的数据。它可以合并来自多个数据源(Models)的数据,并将这些数据加工后用于展示。
代码展示
MVP - VIEW - XML
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivityFragment">
<TextView
android:text="..."
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:id="@+id/loggedInUserCount"/>
<TextView
android:text="# logged in users:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="false"
android:layout_toLeftOf="@+id/loggedInUserCount"/>
<RadioGroup
android:layout_marginTop="40dp"
android:id="@+id/existingOrNewUser"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:orientation="horizontal">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Returning user"
android:id="@+id/returningUserRb"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New user"
android:id="@+id/newUserRb"
/>
</RadioGroup>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/username_block"
android:layout_below="@+id/existingOrNewUser">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Username:"
android:id="@+id/textView"
android:minWidth="100dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/username"
android:minWidth="200dp"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="false"
android:id="@+id/password_block"
android:layout_below="@+id/username_block">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Password:"
android:minWidth="100dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:ems="10"
android:id="@+id/password"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/password_block"
android:id="@+id/email_block">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Email:"
android:minWidth="100dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:ems="10"
android:id="@+id/email"/>
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Log in"
android:id="@+id/loginOrCreateButton"
android:layout_below="@+id/email_block"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
MVP - VIEW - JAVA
public class MainActivityFragment extends MvpFragment implements MvpView {
@InjectView(R.id.username)
TextView mUsername;
@InjectView(R.id.password)
TextView mPassword;
@InjectView(R.id.newUserRb)
RadioButton mNewUserRb;
@InjectView(R.id.returningUserRb)
RadioButton mReturningUserRb;
@InjectView(R.id.loginOrCreateButton)
Button mLoginOrCreateButton;
@InjectView(R.id.email_block)
ViewGroup mEmailBlock;
@InjectView(R.id.loggedInUserCount)
TextView mLoggedInUserCount;
public MainActivityFragment() {
}
@Override
public MainPresenter createPresenter() {
return new MainPresenter();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_main, container, false);
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
attachEventListeners();
}
private void attachEventListeners() {
mNewUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
updateDependentViews();
}
});
mReturningUserRb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
updateDependentViews();
}
});
}
/** Prepares the initial state of the view upon startup */
public void setInitialState() {
mReturningUserRb.setChecked(true);
updateDependentViews();
}
/** Shows/hides email field and sets correct text of login button depending on state of radio buttons */
public void updateDependentViews() {
if (mReturningUserRb.isChecked()) {
mEmailBlock.setVisibility(View.GONE);
mLoginOrCreateButton.setText(R.string.log_in);
}
else {
mEmailBlock.setVisibility(View.VISIBLE);
mLoginOrCreateButton.setText(R.string.create_user);
}
}
public void setNumberOfLoggedIn(int numberOfLoggedIn) {
mLoggedInUserCount.setText("" + numberOfLoggedIn);
}
@OnClick(R.id.loginOrCreateButton)
public void loginOrCreate() {
if (mNewUserRb.isChecked()) {
Toast.makeText(getActivity(), "Please enter a valid email address", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getActivity(), "Invalid username or password", Toast.LENGTH_SHORT).show();
}
}
}
``
MVP – PRESENTER
```java
public class MainPresenter implements MvpPresenter {
MainModel mModel;
private MainActivityFragment mView;
public MainPresenter() {
mModel = new MainModel();
}
@Override
public void attachView(MainActivityFragment view) {
mView = view;
view.setInitialState();
updateViewFromModel();
ensureModelDataIsLoaded();
}
@Override
public void detachView(boolean retainInstance) {
mView = null;
}
private void ensureModelDataIsLoaded() {
if (!mModel.isLoaded()) {
mModel.loadAsync(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
updateViewFromModel();
return true;
}
});
}
}
/** Notifies the views of the current value of "numberOfUsersLoggedIn", if any */
private void updateViewFromModel() {
if (mView != null && mModel.isLoaded()) {
mView.setNumberOfLoggedIn(mModel.numberOfUsersLoggedIn);
}
}
}
MVP – MODEL
public class MainModel {
public Integer numberOfUsersLoggedIn;
private boolean mIsLoaded;
public boolean isLoaded() {
return mIsLoaded;
}
public void loadAsync(final Handler.Callback onDoneCallback) {
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
// Simulating some asynchronous task fetching data from a remote server
try {Thread.sleep(2000);} catch (Exception ex) {};
numberOfUsersLoggedIn = new Random().nextInt(1000);
mIsLoaded = true;
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
onDoneCallback.handleMessage(null);
}
}.execute((Void) null);
}
}
MVVM – VIEW – XML
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable name="data" type="com.nilzor.presenterexample.MainModel"/>
</data>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".MainActivityFragment">
<TextView
android:text="@{data.numberOfUsersLoggedIn}"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:id="@+id/loggedInUserCount"/>
<TextView
android:text="# logged in users:"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="false"
android:layout_toLeftOf="@+id/loggedInUserCount"/>
<RadioGroup
android:layout_marginTop="40dp"
android:id="@+id/existingOrNewUser"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:orientation="horizontal">
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Returning user"
android:checked="@{data.isExistingUserChecked}"
android:id="@+id/returningUserRb"/>
<RadioButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="New user"
android:id="@+id/newUserRb"
/>
</RadioGroup>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/username_block"
android:layout_below="@+id/existingOrNewUser">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Username:"
android:id="@+id/textView"
android:minWidth="100dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/username"
android:minWidth="200dp"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentStart="false"
android:id="@+id/password_block"
android:layout_below="@+id/username_block">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Password:"
android:minWidth="100dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:ems="10"
android:id="@+id/password"/>
</LinearLayout>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/password_block"
android:id="@+id/email_block"
android:visibility="@{data.emailBlockVisibility}">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Email:"
android:minWidth="100dp"/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
android:ems="10"
android:id="@+id/email"/>
</LinearLayout>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{data.loginOrCreateButtonText}"
android:id="@+id/loginOrCreateButton"
android:layout_below="@+id/email_block"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
</layout>
MVVM – VIEW – JAVA
public class MainActivityFragment extends Fragment {
private FragmentMainBinding mBinding;
private MainModel mViewModel;
public MainActivityFragment() {
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
mBinding = FragmentMainBinding.bind(view);
mViewModel = new MainModel(this, getResources());
mBinding.setData(mViewModel);
attachButtonListener();
return view;
}
private void attachButtonListener() {
mBinding.loginOrCreateButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mViewModel.logInClicked();
}
});
}
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
ensureModelDataIsLodaded();
}
private void ensureModelDataIsLodaded() {
if (!mViewModel.isLoaded()) {
mViewModel.loadAsync();
}
}
public void showShortToast(String text) {
Toast.makeText(getActivity(), text, Toast.LENGTH_SHORT).show();
}
}
public class MainModel {
public ObservableField numberOfUsersLoggedIn = new ObservableField();
public ObservableField isExistingUserChecked = new ObservableField();
public ObservableField emailBlockVisibility = new ObservableField();
public ObservableField loginOrCreateButtonText = new ObservableField();
private boolean mIsLoaded;
private MainActivityFragment mView;
private Resources mResources;
public MainModel(MainActivityFragment view, Resources resources) {
mView = view;
mResources = resources; // You might want to abstract this for testability
setInitialState();
updateDependentViews();
hookUpDependencies();
}
public boolean isLoaded() {
return mIsLoaded;
}
private void setInitialState() {
numberOfUsersLoggedIn.set("...");
isExistingUserChecked.set(true);
}
private void hookUpDependencies() {
isExistingUserChecked.addOnPropertyChangedCallback(new android.databinding.Observable.OnPropertyChangedCallback() {
@Override
public void onPropertyChanged(android.databinding.Observable sender, int propertyId) {
updateDependentViews();
}
});
}
public void updateDependentViews() {
if (isExistingUserChecked.get()) {
emailBlockVisibility.set(View.GONE);
loginOrCreateButtonText.set(mResources.getString(R.string.log_in));
}
else {
emailBlockVisibility.set(View.VISIBLE);
loginOrCreateButtonText.set(mResources.getString(R.string.create_user));
}
}
public void loadAsync() {
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
// Simulating some asynchronous task fetching data from a remote server
try {Thread.sleep(2000);} catch (Exception ex) {};
numberOfUsersLoggedIn.set("" + new Random().nextInt(1000));
mIsLoaded = true;
return null;
}
}.execute((Void) null);
}
public void logInClicked() {
// Illustrating the need for calling back to the view though testable interfaces.
if (isExistingUserChecked.get()) {
mView.showShortToast("Invalid username or password");
}
else {
mView.showShortToast("Please enter a valid email address");
}
}
}
MVVM的优势:
- 与MVP相比,减少了代码量。能更注重业务逻辑处理。
可参考的项目:
总结:
先实现,再重构!!!
网友评论