架构设计的主要目的是为了解决软件系统复杂度带来的问题,通过设计使程序模块化,做到模块内部的高聚合和模块之间的低耦合。
常见的架构设计有MVC、MVP和MVVM,当前MVP和MVVM的使用相对比较广泛,下面将逐个介绍这几种架构设计。
MVC(Model View Controller):将控制器、模型和视图分离,降低耦合,但并未完全解耦
MVP(Model View Presenter):从MVC框架演变过来的,将模型和视图完全分离,提高了扩展性
MVVM(Model View ViewModel):跟MVP相似,将模型和视图完全分离,通过DataBinding将数据的变量值与视图绑定,当数据发生变化时,View会自动更新
DataBinding 是 Google 在 JetPack(Google推出的一些库的集合) 中推出的一款数据绑定的支持库,MVVM 是一种程序架构设计模式,主要目的是分离视图(View)和模型(Model),在MVVM模式中ViewModel和View是用绑定关系来实现的,View层不需要findViewById,也不需要拿到具体的View去设置数据,这些都可以通过DataBinding完成,它同时也增加了布局文件的复杂度。
这里以“天气预报”的需求为案例:用户手动输入城市,点击刷新按钮,进行网络请求,返回数据之后再更新用户界面。同一个需求,同一套布局,同样的功能,不同的架构设计,通过MVC/MVP和MVVM模式去分别实现。
天气实体类:
public class Weather {
private String city;
private String temp;
private String weather;
private String wind;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getTemp() {
return temp;
}
public void setTemp(String temp) {
this.temp = temp;
}
public String getWeather() {
return weather;
}
public void setWeather(String weather) {
this.weather = weather;
}
public String getWind() {
return wind;
}
public void setWind(String wind) {
this.wind = wind;
}
}
Model层,收到数据更新请求后,进行业务逻辑处理,并通知View层去更新界面显示
public class WeatherModel {
private WeatherView weatherView;
private final static Handler handler = new Handler(Looper.getMainLooper());
public WeatherModel(WeatherView weatherView) {
this.weatherView = weatherView;
}
// 切换城市
public void changeCity(final String city) {
try {
// 开启一个子线程进行网络请求获取天气预报信息(网络请求,通常会引用第三方开源库或自己封装成工具类)
new Thread(new Runnable() {
@Override
public void run() {
StringBuilder builder = new StringBuilder();
try {
// http://wthrcdn.etouch.cn/weather_mini?city=
URL url = new URL("http://api.help.bj.cn/apis/weather2d/?id=" + city);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(2000);
conn.setReadTimeout(2000);
int code = conn.getResponseCode();
if (code == 200) {
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
}
} catch (Exception e) {
}
String json = builder.toString();
Log.e("TAG", "json:" + json);
if (TextUtils.isEmpty(json)) {
// 如果网络请求不到数据,则模拟一份本地数据进行演示
json = "{\"city\":\"" + city + "\"," +
"\"temp\":\"20摄氏度\"," +
"\"wind\":\"西北风\"," +
"\"weather\":\"晴朗多云\"}";
}
try {
JSONObject jsonObject = new JSONObject(json);
String city = getJSONValue(jsonObject, "city");
String temp = getJSONValue(jsonObject, "temp");
String wind = getJSONValue(jsonObject, "wind");
String _weather = getJSONValue(jsonObject, "weather");
final Weather weather = new Weather();
weather.setCity(city);
weather.setTemp(temp);
weather.setWind(wind);
weather.setWeather(_weather);
Runnable callbackRunnable = new Runnable() {
@Override
public void run() {
if (null != weatherView) {
weatherView.updateView(weather);
}
}
};
handler.post(callbackRunnable);
} catch (Exception ex) {
}
}
}).start();
} catch (Exception ex) {
}
}
// json解析,通常会引用第三方开源库或自己封装成工具类
private String getJSONValue(JSONObject jsonObject, String key) {
try {
if (null != jsonObject && !TextUtils.isEmpty(key)) {
return jsonObject.isNull(key) ? "" : jsonObject.getString(key);
}
} catch (Exception ex) {
}
return "";
}
}
View层,一般采用xml文件进行界面的描述,当接收到用户的操作时,会将用户操作的事件传递给Controller
public class WeatherView {
private TextView tvCity;
private TextView tvTemp;
private TextView tvWind;
private TextView tvWeather;
public WeatherView(WeatherActivity activity) {
tvCity = activity.findViewById(R.id.tvCity);
tvTemp = activity.findViewById(R.id.tvTemp);
tvWeather = activity.findViewById(R.id.tvWeather);
tvWind = activity.findViewById(R.id.tvWind);
EditText etCity = activity.findViewById(R.id.etCity);
// 用户输入框监听 (这里将事件传递给Controller层)
etCity.addTextChangedListener(activity);
Button btnRefresh = activity.findViewById(R.id.btnRefresh);
// 用户按钮点击监听 (这里将事件传递给Controller层)
btnRefresh.setOnClickListener(activity);
}
public void updateView(Weather weather) {
// 更新视图 (Model层实体数据发生变化,通知View层更新视图)
tvCity.setText(weather.getCity());
tvTemp.setText(weather.getTemp());
tvWind.setText(weather.getWind());
tvWeather.setText(weather.getWeather());
}
}
Controller层,收到请求后立即响应,进行业务逻辑处理,并通知Model层处理数据
public class WeatherActivity extends AppCompatActivity implements View.OnClickListener, TextWatcher {
private String mCity;
// 视图对象
private WeatherView mView;
// 模型对象
private WeatherModel mModel;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
mView = new WeatherView(this);
mModel = new WeatherModel(mView);
}
// 用户点击按钮,互动操作(View层传递给事件给Controller层)
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnRefresh:
if (!TextUtils.isEmpty(mCity)) {
// 当切换城市时,调用该方法进行更新
mModel.changeCity(mCity);
} else {
Toast.makeText(this, "城市不能为空,请先输入城市", Toast.LENGTH_SHORT).show();
}
break;
}
}
// 用户输入框监听,互动操作(View层传递给事件给Controller层)
@Override
public void afterTextChanged(Editable s) {
mCity = s.toString();
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}
Contract是一个契约,将Model、View、Presenter 进行约束管理,方便后期类的查找和维护
public interface WeatherContract {
interface Model {
void updateModel(String city, Presenter presenter);
}
interface View {
void updateView(Weather weather);
}
interface Presenter {
void updateWeather(Weather weather);
}
}
天气实体类:
public class Weather {
private String city;
private String temp;
private String weather;
private String wind;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getTemp() {
return temp;
}
public void setTemp(String temp) {
this.temp = temp;
}
public String getWeather() {
return weather;
}
public void setWeather(String weather) {
this.weather = weather;
}
public String getWind() {
return wind;
}
public void setWind(String wind) {
this.wind = wind;
}
}
Model层,负责处理数据业务逻辑
public class WeatherModel implements WeatherContract.Model {
@Override
public void updateModel(final String city, final WeatherContract.Presenter presenter) {
// 开启一个子线程进行网络请求获取天气预报信息(网络请求,通常会引用第三方开源库或自己封装成工具类)
new Thread(new Runnable() {
@Override
public void run() {
StringBuilder builder = new StringBuilder();
try {
// http://wthrcdn.etouch.cn/weather_mini?city=
URL url = new URL("http://api.help.bj.cn/apis/weather2d/?id=" + city);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(2000);
conn.setReadTimeout(2000);
int code = conn.getResponseCode();
if (code == 200) {
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
}
} catch (Exception e) {
}
String json = builder.toString();
if (TextUtils.isEmpty(json)) {
// 如果网络请求不到数据,则模拟一份本地数据进行演示
json = "{\"city\":\"" + city + "\"," +
"\"temp\":\"20摄氏度\"," +
"\"wind\":\"西北风\"," +
"\"weather\":\"晴朗多云\"}";
}
try {
// 解析网络请求返回的天气预报数据
JSONObject jsonObject = new JSONObject(json);
String city = getJSONValue(jsonObject, "city");
String temp = getJSONValue(jsonObject, "temp");
String wind = getJSONValue(jsonObject, "wind");
String _weather = getJSONValue(jsonObject, "weather");
Weather weather = new Weather();
weather.setCity(city);
weather.setTemp(temp);
weather.setWind(wind);
weather.setWeather(_weather);
if (null != presenter) {
// model层完成业务逻辑后,presenter层更新实体对象
presenter.updateWeather(weather);
}
} catch (Exception ex) {
}
}
}).start();
}
// json解析,通常会引用第三方开源库或自己封装成工具类
private String getJSONValue(JSONObject jsonObject, String key) {
try {
if (null != jsonObject && !TextUtils.isEmpty(key)) {
return jsonObject.isNull(key) ? "" : jsonObject.getString(key);
}
} catch (Exception ex) {
}
return "";
}
}
View层,负责根据数据进行界面展示,并向Presenter层报告用户行为
public class WeatherActivity extends AppCompatActivity implements View.OnClickListener, WeatherContract.View {
// 各个控件
private EditText etCity;
private TextView tvCity;
private TextView tvTemp;
private TextView tvWind;
private TextView tvWeather;
// Presenter对象
private WeatherPresenter mPresenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_weather);
tvCity = findViewById(R.id.tvCity);
tvTemp = findViewById(R.id.tvTemp);
tvWeather = findViewById(R.id.tvWeather);
tvWind = findViewById(R.id.tvWind);
etCity = findViewById(R.id.etCity);
Button btnRefresh = findViewById(R.id.btnRefresh);
btnRefresh.setOnClickListener(this);
// 初始化Presenter对象
mPresenter = new WeatherPresenter(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btnRefresh:
// 请求数据
final String city = etCity.getText().toString();
if (!TextUtils.isEmpty(city)) {
// 当切换城市时,调用该方法进行更新
mPresenter.changeCity(city);
} else {
Toast.makeText(this, "城市不能为空,请先输入城市", Toast.LENGTH_SHORT).show();
}
break;
}
}
@Override
public void updateView(final Weather weather) {
runOnUiThread(new Runnable() {
@Override
public void run() {
// 当“天气实体对象”发生改变时,需要更新视图
tvCity.setText(weather.getCity());
tvTemp.setText(weather.getTemp());
tvWind.setText(weather.getWind());
tvWeather.setText(weather.getWeather());
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// presenter会持有view对象,为防止异步请求耗时内存泄漏,需执行销毁方法
mPresenter.onDestroy();
}
}
Presenter层,负责从Model层拿数据,并交给View层,是Model和View之间的桥梁
public class WeatherPresenter implements WeatherContract.Presenter {
// presenter层持有view对象
private WeatherContract.View view;
// presenter层持有model对象
private WeatherContract.Model model;
public WeatherPresenter(WeatherContract.View view) {
this.view = view;
this.model = new WeatherModel();
}
public void changeCity(String city) {
// presenter层操纵model层,发起更新操作
model.updateModel(city, this);
}
@Override
public void updateWeather(Weather weather) {
if (null != view) {
// presenter层操纵view层,发起更新操作
view.updateView(weather);
}
}
public void onDestroy() {
// 解绑view
view = null;
}
}
Model更新和数据加载监听接口
public interface IWeatherMode {
void updateModel(String city, OnDataLoadListener listener);
}
public interface OnDataLoadListener {
void start();
void onFinish(Weather weather);
}
天气实体类:
public class Weather {
private String city;
private String temp;
private String weather;
private String wind;
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public String getTemp() {
return temp;
}
public void setTemp(String temp) {
this.temp = temp;
}
public String getWeather() {
return weather;
}
public void setWeather(String weather) {
this.weather = weather;
}
public String getWind() {
return wind;
}
public void setWind(String wind) {
this.wind = wind;
}
}
Model层,负责处理数据模型
public class WeatherModel implements IWeatherMode {
private final static Handler handler = new Handler(Looper.getMainLooper());
public WeatherModel() {
}
@Override
public void updateModel(final String city, final OnDataLoadListener listener) {
try {
// 开启一个子线程进行网络请求获取天气预报信息(网络请求,通常会引用第三方开源库或自己封装成工具类)
new Thread(new Runnable() {
@Override
public void run() {
if (null != listener) {
listener.start();
}
StringBuilder builder = new StringBuilder();
try {
// http://wthrcdn.etouch.cn/weather_mini?city=
URL url = new URL("http://api.help.bj.cn/apis/weather2d/?id=" + city);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(2000);
conn.setReadTimeout(2000);
int code = conn.getResponseCode();
if (code == 200) {
InputStream inputStream = conn.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, "UTF-8");
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
}
} catch (Exception e) {
}
String json = builder.toString();
Log.e("TAG", "json:" + json);
if (TextUtils.isEmpty(json)) {
// 如果网络请求不到数据,则模拟一份本地数据进行演示
json = "{\"city\":\"" + city + "\"," +
"\"temp\":\"20摄氏度\"," +
"\"wind\":\"西北风\"," +
"\"weather\":\"晴朗多云\"}";
}
final String lastJson = json;
Runnable callbackRunnable = new Runnable() {
@Override
public void run() {
Weather weather = new Weather();
try {
JSONObject jsonObject = new JSONObject(lastJson);
String city = getJSONValue(jsonObject, "city");
String temp = getJSONValue(jsonObject, "temp");
String wind = getJSONValue(jsonObject, "wind");
String _weather = getJSONValue(jsonObject, "weather");
weather.setCity(city);
weather.setTemp(temp);
weather.setWind(wind);
weather.setWeather(_weather);
} catch (Exception ex) {
} finally {
if (null != listener) {
listener.onFinish(weather);
}
}
}
};
handler.post(callbackRunnable);
}
}).start();
} catch (Exception ex) {
}
}
// json解析,通常会引用第三方开源库或自己封装成工具类
private String getJSONValue(JSONObject jsonObject, String key) {
try {
if (null != jsonObject && !TextUtils.isEmpty(key)) {
return jsonObject.isNull(key) ? "" : jsonObject.getString(key);
}
} catch (Exception ex) {
}
return "";
}
}
View层,对应Activity和xml,负责View的绘制以及用户交互
public class WeatherActivity extends AppCompatActivity {
// ViewModel对象
private WeatherViewModel viewModel;
// 这个是根据布局文件自动生成的类,布局文件名(首字母大写)+Binding
private ActivityWeatherBinding dataBinding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
dataBinding = DataBindingUtil.setContentView(this, R.layout.activity_weather);
// 实例化ViewModel对象
viewModel = new WeatherViewModel();
// 绑定ViewModel对象
dataBinding.setViewModel(viewModel);
// 点击刷新按钮
dataBinding.btnRefresh.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 获取用户输入的“城市”名称
String city = dataBinding.etCity.getText().toString();
if (!TextUtils.isEmpty(city)) {
// 当切换城市时,调用该方法进行更新
viewModel.changeCity(city);
} else {
Toast.makeText(getApplication(), "城市不能为空,请先输入城市", Toast.LENGTH_SHORT).show();
}
}
});
}
}
ViewModel层,负责完成View于Model间的交互
public class WeatherViewModel {
private IWeatherMode model;
public final ObservableField<String> cityField = new ObservableField<>();
public final ObservableField<String> tempField = new ObservableField<>();
public final ObservableField<String> weatherField = new ObservableField<>();
public final ObservableField<String> windField = new ObservableField<>();
public WeatherViewModel() {
model = new WeatherModel();
}
public void changeCity(String city) {
model.updateModel(city, new OnDataLoadListener() {
@Override
public void start() {
}
@Override
public void onFinish(Weather weather) {
cityField.set(weather.getCity());
tempField.set(weather.getTemp());
weatherField.set(weather.getWeather());
windField.set(weather.getWind());
}
});
}
}
布局文件
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<!-- 引用 -->
<import type="android.view.View" />
<!-- 数据源 -->
<variable
name="ViewModel"
type="com.wyq.design.mvvm.viewmodel.WeatherViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#f5f5f5"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="56dp"
android:layout_gravity="center_vertical"
android:background="@color/colorPrimary"
android:gravity="center"
android:text="天气预报"
android:textColor="#ffffff"
android:textSize="20sp" />
<EditText
android:id="@+id/etCity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:hint="请输入城市"
android:singleLine="true"
android:textColor="#000000"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="城市:"
android:textColor="#4B0082"
android:textSize="18sp" />
<TextView
android:id="@+id/tvCity"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="@{ViewModel.cityField}"
android:textColor="#000000"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="温度:"
android:textColor="#4B0082"
android:textSize="18sp" />
<TextView
android:id="@+id/tvTemp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="@{ViewModel.tempField}"
android:textColor="#000000"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="气候:"
android:textColor="#4B0082"
android:textSize="18sp" />
<TextView
android:id="@+id/tvWeather"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="@{ViewModel.weatherField}"
android:textColor="#000000"
android:textSize="18sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginRight="16dp"
android:orientation="horizontal">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="风向:"
android:textColor="#4B0082"
android:textSize="18sp" />
<TextView
android:id="@+id/tvWind"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:text="@{ViewModel.windField}"
android:textColor="#000000"
android:textSize="18sp" />
</LinearLayout>
<Button
android:id="@+id/btnRefresh"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginTop="48dp"
android:layout_marginRight="16dp"
android:text="刷新" />
</LinearLayout>
</layout>
通过java语言编写的一个Android应用程序,项目中围绕着MVC/MVP和MVVM架构设计,功能完整,注释齐全,同一个需求,同一套布局,同样的功能,不同的架构设计。下载地址:Android架构设计(MVC/MVP/MVVM)