最近在学习Android开发,使用MVP设计模式写了一个登录的demo。代码写的比较简单且蹩脚,主要用于学习记录以及体现MVP设计模式。
MVP概述
MVP从MVC演变而来,通过表示器将视图与模型巧妙地分开。在该模式中,视图通常由表示器初始化,它呈现用户界面(UI)并接受用户所发出命令,但不对用户的输入作任何逻辑处理,而仅仅是将用户输入转发给表示器。通常每一个视图对应一个表示器,但是也可能一个拥有较复杂业务逻辑的视图会对应多个表示器,每个表示器完成该视图的一部分业务处理工作,降低了单个表示器的复杂程度,一个表示器也能被多个有着相同业务需求的视图复用,增加单个表示器的复用度。表示器包含大多数表示逻辑,用以处理视图,与模型交互以获取或更新数据等。模型描述了系统的处理逻辑,模型对于表示器和视图一无所知。
文件目录层级
如图,根目录下分为base(基类),business(业务类),constants(常量类),network(网络请求类),其中business目录下,根据业务再创建相应的子目录,如login(登录目录),login目录下,使用mvp设计模式,又分别创建LoginActivity(View层),LoginPresenter(Presenter层),LoginModel(Model层)。
基类
- BaseActivity
public abstract class BaseActivity <P extends BasePresenter> extends AppCompatActivity {
protected P mPresenter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(this.getLayoutId());
this.initView();
}
/**
* 设置布局
*
* @return
*/
public abstract int getLayoutId();
/**
* 初始化视图
*/
public abstract void initView();
@Override
protected void onPause() {
super.onPause();
}
@Override
protected void onResume() {
super.onResume();
}
@Override
protected void onDestroy() {
if (mPresenter != null) {
mPresenter.detachView();
}
super.onDestroy();
}
}
- BasePresenter
public class BasePresenter <V extends BaseActivity, M extends BaseModel> {
protected V mView;
protected M mModel;
/**
* 绑定View,一般在初始化中调用该方法
*
* @param view
*/
public void attachView(V view) {
this.mView = view;
}
/***
* 解绑View,一般在onDestroy中调用
*/
public void detachView() {
this.mView = null;
}
/**
* View是否绑定
*
* @return
*/
public boolean isAttachView() {
return this.mView != null;
}
}
- BaseModel
public class BaseModel <P extends BasePresenter> {
/**
* 请求类型枚举
*/
public enum RequestType {
GET,
POST;
}
/**
* 发起网络请求
* @param url 接口url
* @param type 请求类型
* @param params 请求参数
* @param callback 请求回调
*/
public void request(String url, RequestType type, JSONObject params, NetworkManager.NetworkListener callback) {
if (type == RequestType.GET) {
NetworkManager.getInstance().get(url, callback);
}
}
}
业务
- LoginActivity
public class LoginActivity extends BaseActivity <LoginPresenter> {
public ProgressBar loadingProgressBar;
@Override
public int getLayoutId() {
return R.layout.activity_login;
}
@Override
public void initView() {
mPresenter = new LoginPresenter();
mPresenter.attachView(this);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final EditText usernameEditText = findViewById(R.id.username);
final EditText passwordEditText = findViewById(R.id.password);
final Button loginButton = findViewById(R.id.login);
loadingProgressBar = findViewById(R.id.loading);
usernameEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// ignore
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mPresenter.name = s.toString();
Log.i("userName", mPresenter.name);
}
@Override
public void afterTextChanged(Editable s) {
}
});
passwordEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
// ignore
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
mPresenter.password = s.toString();
Log.i("password", mPresenter.name);
}
@Override
public void afterTextChanged(Editable s) {
}
});
loginButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
doLogin();
}
});
}
private void doLogin() {
mPresenter.startLogin(new LoginPresenter.LoginCallback() {
@Override
public void onLoginSuccess(String token) {
if (!token.isEmpty()) {
String msg = "登录成功";
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
}
}
@Override
public void onLoginFail(String msg) {
Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show();
}
});
}
}
- LoginPresenter
public class LoginPresenter extends BasePresenter <LoginActivity, LoginModel> {
public String name = "";
public String password = "";
public interface LoginCallback {
void onLoginSuccess(String token);
void onLoginFail(String msg);
}
public LoginPresenter() {
this.mModel = new LoginModel();
}
/**
* 登录
* @param loginCallback 登录回调
*/
public void startLogin(final LoginCallback loginCallback) {
if (name.isEmpty() == true) {
Toast.makeText(this.mView.getApplicationContext(), "用户名不能为空", Toast.LENGTH_LONG).show();
return;
}
if (password.isEmpty() == true) {
Toast.makeText(this.mView.getApplicationContext(), "用户名不能为空", Toast.LENGTH_LONG).show();
return;
}
mView.loadingProgressBar.setVisibility(View.VISIBLE);
mModel.requestLogin(new NetworkManager.NetworkListener() {
@Override
public void onSuccess(JSONObject jsonObject) {
mView.loadingProgressBar.setVisibility(View.GONE);
try {
JSONObject section = jsonObject.getJSONObject("section");
final String name = section.getString("name");//取得其名字的值,一般是字符串
loginCallback.onLoginSuccess(name);
} catch (JSONException e) {
e.printStackTrace();
loginCallback.onLoginFail("解析失败,Error:" + e.getLocalizedMessage());
Log.e("登录解析数据失败", " Error:" + e.getLocalizedMessage());
}
}
@Override
public void onFail(String msg) {
mView.loadingProgressBar.setVisibility(View.GONE);
loginCallback.onLoginFail("登录失败,Error:" + msg);
}
});
}
}
- LoginModel
public class LoginModel extends BaseModel <LoginPresenter> {
public void requestLogin(NetworkManager.NetworkListener networkListener) {
this.request(NetworkApi.Login, RequestType.GET, null, networkListener);
}
}
基于OkHttp的网络请求封装类
- NetworkManager
public class NetworkManager {
private static final int TIME_OUT = 60;// 超时时间
private volatile static NetworkManager networkManager = null;
private static OkHttpClient mOkHttpClient;
private static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
public static NetworkManager getInstance() {
if (null == networkManager) {
synchronized (NetworkManager.class) {
if (null == networkManager) {
networkManager = new NetworkManager();
}
}
}
return networkManager;
}
private NetworkManager() {
mOkHttpClient = new OkHttpClient.Builder()
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
.readTimeout(TIME_OUT, TimeUnit.SECONDS)
.writeTimeout(TIME_OUT, TimeUnit.SECONDS)
.followRedirects(true)
.hostnameVerifier(new HostnameVerifier() {
@Override
public boolean verify(String hostname, SSLSession session) {
return true;
}
})
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request()
.newBuilder()
.addHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
.addHeader("Connection", "keep-alive")
.addHeader("Accept", "*/*")
.addHeader("Cookie", "add cookies here")
.build();
return chain.proceed(request);
}
})
.build();
}
/**
* Get请求
*/
public void get(final String url, final NetworkListener networkListener) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Request request = new Request.Builder()
.url(url)
.get()
.build();//创建一个Request对象
Response response = mOkHttpClient.newCall(request).execute();
String responseData = response.body().string();
final JSONObject jsonObject = new JSONObject(responseData);//新建json对象实例
Log.i("get请求url", url);
Log.i("get请求返回数据", responseData);
new Handler(Looper.getMainLooper()).post(new Runnable() {// 此时还在非UI线程,因此要转发
@Override
public void run() {
networkListener.onSuccess(jsonObject);
}
});
} catch (final Exception e) {
e.printStackTrace();
Log.e("网络请求失败","请求url:"+ url + " Error:" + e.getLocalizedMessage());
new Handler(Looper.getMainLooper()).post(new Runnable() {// 此时还在非UI线程,因此要转发
@Override
public void run() {
networkListener.onFail("解析数据失败,Error:" + e.getLocalizedMessage());
}
});
}
}
}).start();
}
/**
* Post请求
*/
public void post(final String url, final JSONObject params, final NetworkListener networkListener) {
new Thread(new Runnable() {
@Override
public void run() {
JSONObject par = params == null ? new JSONObject() : params;
try {
RequestBody body = RequestBody.create(JSON, par.toString());
Request request = new Request.Builder()
.url(url)
.post(body)
.addHeader("Accept-Encoding", "identity")
.build();//创建一个Request对象
Response response = mOkHttpClient.newCall(request).execute();
String responseData = response.body().string();
Log.i("post请求url", url);
Log.i("post请求params", par.toString());
Log.i("post请求返回数据", responseData);
if (responseData.length() == 0) {
new Handler(Looper.getMainLooper()).post(new Runnable() {// 此时还在非UI线程,因此要转发
@Override
public void run() {
networkListener.onFail("解析数据失败,Error:返回数据为空");
}
});
Log.e("解析数据失败","请求url:"+ url + " Error:返回数据为空");
return;
}
final JSONObject jsonObject = new JSONObject(responseData);
new Handler(Looper.getMainLooper()).post(new Runnable() {// 此时还在非UI线程,因此要转发
@Override
public void run() {
networkListener.onSuccess(jsonObject);
}
});
} catch (final Exception e) {
e.printStackTrace();
Log.e("网络请求失败","请求url:"+ url + " Error:" + e.getLocalizedMessage());
new Handler(Looper.getMainLooper()).post(new Runnable() {// 此时还在非UI线程,因此要转发
@Override
public void run() {
networkListener.onFail("网络请求失败,Error:" + e.getLocalizedMessage());
}
});
}
}
}).start();
}
public interface NetworkListener {
void onSuccess(JSONObject jsonObject);
void onFail(String msg);
}
}
常量类
- NetworkApi
public class NetworkApi {
/**
* 域名
*/
public static final String BASE_URL = "https://news-at.zhihu.com/";
/**
* 接口
*/
// 登录
public static final String Login = BASE_URL + "api/4/news/9660723";
}