Android框架现在常见的有MVC模式、MVP模式、MVVM模式。我们首先先明确一个概念:模式是指组织代码的结构方式,模式并不能提高代码的执行效率。模式是为了后续功能的扩展方便和代码的结构清晰而使用的。
刚开始做Android开发时我们把代码都写在Activity里,这样代码的扩展性和结构清晰并不好。由此演化出Android的MVC模式,本篇文章也主要描述怎么将原来的写法抽取成MVC模式。
MVC模式即将软件分为三个基本部分:模型(Model)、视图(View)和控制器(Controller)。
模型(Model)- 用来产生数据(访问网络、数据库等)
视图(View)- 视图层相关(界面布局控件等相关的代码)
控制器(Controller)- 逻辑控制层(业务逻辑相关的代码)
一、Activity是万能的
首先我们以登录功能为例看一下原来把代码都写到Activity的情况:
布局文件activity_login.xml:
Activity代码LoginActivity:
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "LoginActivity";
private EditText etPhoneNumber;
private EditText etPassword;
private Button btnLogin;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
etPhoneNumber = (EditText) findViewById(R.id.et_phone_number);
etPassword = (EditText) findViewById(R.id.et_password);
btnLogin = (Button) findViewById(R.id.btn_login);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
btnLogin.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login: //登录
String phoneNumber = etPhoneNumber.getText().toString().trim();
String password = etPassword.getText().toString().trim();
gotoLogin(phoneNumber, password);
break;
default:
break;
}
}
private void gotoLogin(String phoneNumber, String password) {
//本地对输入情况做校验
boolean validateOk = validateInput(phoneNumber, password);
if (validateOk) {
progressBar.setVisibility(View.VISIBLE);
String md5Password = MD5Utils.getMd5(password);
OkHttpUtils
.post()
.url(Constants.URL_LOGIN)
.addParams("phoneNumber", phoneNumber)
.addParams("password", md5Password)
.build()
.execute(new StringCallback() {
@Override
public void onError(okhttp3.Call call, Exception e, int id) {
progressBar.setVisibility(View.GONE);
Log.i(TAG, "onError: ---登录访问异常---" + e.getMessage());
e.printStackTrace();
Toast.makeText(LoginActivity.this, "网络访问出现异常", Toast.LENGTH_SHORT).show();
}
@Override
public void onResponse(String response, int id) {
progressBar.setVisibility(View.GONE);
Log.i(TAG, "onResponse: 登录成功 response = " + response + " ---");
Gson gson = new Gson();
LoginData loginData = gson.fromJson(response, LoginData.class);
switch (loginData.status) {
case 200: //用户名未注册
case 201: //密码有误
case 203: //登录失败
Toast.makeText(LoginActivity.this, loginData.message, Toast.LENGTH_SHORT).show();
Log.i(TAG, "onResponse: = " + loginData.message);
break;
case 202: //登录成功
Toast.makeText(LoginActivity.this, loginData.message, Toast.LENGTH_SHORT).show();
Log.i(TAG, "onResponse: = " + loginData.message);
//本地保存必要的用户信息
//......
Intent intent = new Intent(LoginActivity.this, LoginSuccessActivity.class);
startActivity(intent);
//登录页面直接消失
finish();
break;
default:
Toast.makeText(LoginActivity.this, "登录出现未知异常", Toast.LENGTH_SHORT).show();
break;
}
}
});
}
}
private boolean validateInput(String phoneNumber, String password) {
if (TextUtils.isEmpty(phoneNumber)) {
Toast.makeText(this, "手机号不能为空", Toast.LENGTH_SHORT).show();
return false;
}
if (TextUtils.isEmpty(password)) {
Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show();
return false;
}
if (!phoneNumber.matches(Constants.STR_PHONE_REGEX2)) { //匹配正则表达式
Toast.makeText(this, "请输入正确的手机号", Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
}
二、实现MVC模式
要使用MVC模式同样实现上面的登录功能,需要我们将数据相关的代码抽取出来,成为单独的Model层,最终Model层能直接提供登录的用户数据LoginData。
在项目中先创建一个model包,然后创建LoginModel.java类,即登录的Model层:
//Model层只用来产生数据
public class LoginModel {
private static final String TAG = "LoginModel";
public void gotoLogin(String phoneNumber, String md5Password, final OnNetResponseListener listener) {
OkHttpUtils
.post()
.url(Constants.URL_LOGIN)
.addParams("phoneNumber", phoneNumber)
.addParams("password", md5Password)
.build()
.execute(new StringCallback() {
@Override
public void onError(okhttp3.Call call, Exception e, int id) {
Log.i(TAG, "onError: ---网络访问出现异常---" + e.getMessage());
e.printStackTrace();
listener.onNetResposeError("网络访问出现异常");
}
@Override
public void onResponse(String response, int id) {
Log.i(TAG, "onResponse: 登录成功 response = " + response + " ---");
Gson gson = new Gson();
LoginData loginData = gson.fromJson(response, LoginData.class);
listener.onNetReponseSuccess(loginData);
}
});
}
public interface OnNetResponseListener {
void onNetResposeError(String msg);
void onNetReponseSuccess(LoginData loginData);
}
}
其中,由于访问网络是异步操作,所以获取到的数据需要使用回调(即观察者设计模式)的方式返回回去,抽取LoginModel之后原LoginActivity访问网络的代码做相应调整,如下:
public class LoginActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "LoginActivity";
private EditText etPhoneNumber;
private EditText etPassword;
private Button btnLogin;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
etPhoneNumber = (EditText) findViewById(R.id.et_phone_number);
etPassword = (EditText) findViewById(R.id.et_password);
btnLogin = (Button) findViewById(R.id.btn_login);
progressBar = (ProgressBar) findViewById(R.id.progressBar);
btnLogin.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login: //登录
String phoneNumber = etPhoneNumber.getText().toString().trim();
String password = etPassword.getText().toString().trim();
login(phoneNumber, password);
break;
default:
break;
}
}
private void login(String phoneNumber, String password) {
//本地对输入情况做校验
boolean validateOk = validateInput(phoneNumber, password);
if (validateOk) {
progressBar.setVisibility(View.VISIBLE);
String md5Password = MD5Utils.getMd5(password);
LoginModel loginModel = new LoginModel();
loginModel.gotoLogin(phoneNumber, md5Password, new LoginModel.OnNetResponseListener() {
@Override
public void onNetResposeError(String msg) {
progressBar.setVisibility(View.GONE);
Toast.makeText(LoginActivity.this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void onNetReponseSuccess(LoginData loginData) {
progressBar.setVisibility(View.GONE);
switch (loginData.status) {
case 200: //用户名未注册
case 201: //密码有误
case 203: //登录失败
Toast.makeText(LoginActivity.this, loginData.message, Toast.LENGTH_SHORT).show();
Log.i(TAG, "onResponse: = " + loginData.message);
break;
case 202: //登录成功
Toast.makeText(LoginActivity.this, loginData.message, Toast.LENGTH_SHORT).show();
Log.i(TAG, "onResponse: = " + loginData.message);
//本地保存必要的用户信息
//......
Intent intent = new Intent(LoginActivity.this, LoginSuccessActivity.class);
startActivity(intent);
//登录页面直接消失
finish();
break;
default:
Toast.makeText(LoginActivity.this, "登录出现未知异常", Toast.LENGTH_SHORT).show();
break;
}
}
});
}
}
private boolean validateInput(String phoneNumber, String password) {
if (TextUtils.isEmpty(phoneNumber)) {
Toast.makeText(this, "手机号不能为空", Toast.LENGTH_SHORT).show();
return false;
}
if (TextUtils.isEmpty(password)) {
Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show();
return false;
}
if (!phoneNumber.matches(Constants.STR_PHONE_REGEX2)) { //匹配正则表达式
Toast.makeText(this, "请输入正确的手机号", Toast.LENGTH_SHORT).show();
return false;
}
return true;
}
}
这样我们就使用MVC模式同样实现上面的登录功能
三、总结
我的理解:所谓的MVC模式就是把我们最初代码的数据获取功能的那部分代码抽取成model层。其实在各个端的开发中都有MVC模式,Android作为移动端来说MVC模式和后端并不完全一致。在Android中我更愿意这样来划分:M层是独立出来的,V层和C层的角色都由Activity来承担。(有一些人会认为:将XML布局视为V层,将Activity视为控制器,这样Activity读取V视图层的数据。如果这样理解的话,MVP模式中V又是Activity,个人感觉有点混乱)
通过以上的代码,或许我们还是感觉Activity中的代码有些繁杂,因为V层和C层的角色都由Activity来承担,要想更进一步的话,就需要我们再进一步了解MVP模式。