最近使用了NoHttp+MVP写了一个项目,NoHttp是一个网络框架,个人觉得是我用过最好用的网络请求框架,没有之一,嗯,对!(NoHttp开源地址) 严大神之作。MVP,大家都再熟悉不过了,优点就是高度解耦和能有效避免内存泄漏。
一、NoHttp网络框架:要详细的可以到GitHub里面看,首先把NoHttp封装成单例模式:
/**
* @author: Administrator
* @description: 网络请求
* @date: 2017-11-22 09:23
*/
public class CallServer {
private static CallServer instance;
/**
* 请求队列。
*/
private RequestQueue requestQueue;
/**
* 下载队列
*/
private DownloadQueue downloadQueue;
private CallServer() {
requestQueue = NoHttp.newRequestQueue(5);
downloadQueue = NoHttp.newDownloadQueue();
}
/**
* 请求队列。
*/
public static CallServer getInstance() {
if (instance == null)
synchronized (CallServer.class) {
if (instance == null)
instance = new CallServer();
}
return instance;
}
/**
* 添加一个请求到请求队列。
*
* @param what 用来标志请求, 当多个请求使用同一个Listener时, 在回调方法中会返回这个what。
* @param request 请求对象。
* @param listener 结果回调对象。
*/
public <T> void add(int what, Request<T> request, OnResponseListener listener) {
requestQueue.add(what, request, listener);
}
/**
* 文件下载
* @param what 用来标志请求, 当多个请求使用同一个Listener时, 在回调方法中会返回这个what。
* @param request 请求对象。
* @param listener 结果回调对象。
*/
public void loadAdd(int what, DownloadRequest request, DownloadListener listener){
downloadQueue.add(what,request,listener);
}
public void loadStop(){
downloadQueue.stop();
}
/**
* 取消这个sign标记的所有请求。
* @param sign 请求的取消标志。
*/
public void cancelBySign(Object sign) {
requestQueue.cancelBySign(sign);
}
/**
* 取消队列中所有请求。
*/
public void cancelAll() {
requestQueue.cancelAll();
}
}
然后就是网络请求异常处理:
/**
* @author: Administrator
* @description: 请求异常
* @date: 2017-08-24 16:00
*/
public class HttpException {
public static String doException(Throwable exception) {
if (exception instanceof NetworkError) {// 网络不好
return MyApplication.getMyApplication().getString(R.string.error_please_check_network);
} else if (exception instanceof TimeoutError) {// 请求超时
return MyApplication.getMyApplication().getString(R.string.error_timeout);
} else if (exception instanceof UnKnownHostError) {// 找不到服务器
return MyApplication.getMyApplication().getString(R.string.error_not_found_server);
} else if (exception instanceof URLError) {// URL是错的
return MyApplication.getMyApplication().getString(R.string.error_url_error);
} else if (exception instanceof NotFoundCacheError) {
// 这个异常只会在仅仅查找缓存时没有找到缓存时返回
return MyApplication.getMyApplication().getString(R.string.error_not_found_cache);
} else {
return MyApplication.getMyApplication().getString(R.string.error_unknow);
}
}
}
这些东西都可以在开源项目上找到。
二、MVP架构封装:
1、M层(model),model层是用来处理数据来源的,请求网络数据或者加载本地数据库数据等,这项目中,抽取了一个model层的基类,因为model层是用来网络请求的,所以在model层抽取了网络请求的添加到请求队列和取消请求的方法,这里取消请求这个方法比较重要,比如,在网络不好的情况下,你在Activity发出请求,但是后台10s后再给你做出了响应,此时,你结束了这个Activity,跳到下一个Activity,这个情况下有可能会造成空指针异常的请情况,或者,在下一个Activity中会弹出上一个Activity的Toast内容,这个就非常不好了,所以需要一个取消请求的方法,与Activity的什么周期绑定,在onDestroy中去取消网络请求,代码如下:
public class BaseModel {
//用给每个网络请求添加一个标志,用于取消请求
private Object cancelObject;
public BaseModel() {
this.cancelObject = new Object();
}
protected <T> void doRequest(int what, Request<T> request, OnResponseListener<T> listener) {
// 这里设置一个sign给这个请求。
request.setCancelSign(cancelObject);
CallServer.getInstance().add(what, request, listener);
}
public void cancelRequest() {
//取消请求
CallServer.getInstance().cancelBySign(cancelObject);
}
}
2、V层(View),view层是一个接口形式,将所有有关于view的方法写成接口,在接口里面处理相关逻辑
当然view层也可以抽出基类,比如,网络加载时的加载框的显示与隐藏,当然还以个是toast,在这个例子中没有用到这个基类:
public interface BaseView {
void showLoading();
void dismissLoading();
}
3、P层(Presenter) P就是连接model层与View层的中间桥梁,这样就达到了View与model层的解耦的方式,为了防止内存泄漏,p层也需要抽取出基类,将View绑定到Presenter上,同时,当view销毁时,解除绑定,同时View使用了弱引用:
public abstract class BasePresenter<T> {
public WeakReference<T> refView;
// public T mView;
public void attach(T mView){
//this.mView = mView;
refView = new WeakReference<T>(mView);
}
public void detach(){
//mView = null;
if (refView != null) {
refView.clear();
refView = null;
}
}
public boolean isViewAttached() {
return refView != null && refView.get() != null;
}
//当对象被销毁时,可以用次方法获取
protected T getRefView() {
return refView.get();
}
//用于取消网络请求
public abstract void destroy();
}
4、再看Activity的基类:Activity需要将View和model绑定在一起,然后在子类Activity中实现相关view的接口,在Activity中引入了Persenter的泛型作为基类,同时听歌一个抽象方法去实例化该对象,然后在onResume中绑定View,在onDestroy中解除绑定:
public abstract class BaseActivity<V, T extends BasePresenter<V>> extends AppCompatActivity {
public T presenter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
presenter = initPresent();
}
/**
初始化view抽象方法
*/
protected abstract void initView();
@Override
protected void onResume() {
super.onResume();
presenter.attach((V) this);
}
@Override
protected void onDestroy() {
presenter.detach();
super.onDestroy();
}
public abstract T initPresent();
}
三,用一个登录的例子来使用这个框架:
1、网络请求:LoginModel,写网络请求,然后将网络请求的结果用接口回调出去:
public class LoginModel extends BaseModel{
public void loginRequest(final String userName, final String psw, final OnLoginListener loginListener) {
Request<JSONObject> request = NoHttp.createJsonObjectRequest(Constants.URL + "/applogin", RequestMethod.POST);
request.add("mobile", userName);
request.add("userPwd", Base64.encode(psw.getBytes()));
request.add("app", "android");
doRequest(0, request, new OnResponseListener<JSONObject>() {
@Override
public void onStart(int what) {
loginListener.onStart();
}
@Override
public void onSucceed(int what, Response<JSONObject> response) {
if (response.responseCode() == 200) {
Log.i("tag","登录:" + response.get());
JSONObject object = response.get();
if (object.optInt("code") == 1) {
String data = object.optString("data");
if (data != null) {
LoginInfo info = JSON.parseObject(data, LoginInfo.class);
loginListener.onSuccess(info);
}
} else {
loginListener.onFailed(object.optString("msg"));
}
}
}
@Override
public void onFailed(int what, Response<JSONObject> response) {
loginListener.onFailed(HttpException.doException(response.getException()));
}
@Override
public void onFinish(int what) {
loginListener.onFinish();
}
});
}
//取消网络请求
@Override
public void cancelRequest() {
super.cancelRequest();
}
//接口回调
public interface OnLoginListener {
void onStart();
void onSuccess(LoginInfo info);
void onFailed(String msg);
void onFinish();
}
}
看网络请求回调,有四个方法,onStart,onSucceed,onFailed,onFinish,一般在onStart,onFinish找个来处理网络请求的加载框的显示和隐藏,onFinish方法不管请求成功或者失败,都会回调这个方法,在每个方法中都会有what这个参数,这个是用来区分请求队里中的请求,比如你在这个界面中有三个请求,同时将这个三个请求添加到请求队列中,然后用同一个回调(OnResponseListener)来回调数据,此时这个what就是用来区分是哪个请求的。
2、ILoginView 的接口,结合上面的界面和网络请求的参数,我们可以知道,在view中,我们需要获取,账号,密码,更新view,显示Toast,显示加载框,隐藏加载框这些方法:
public interface ILoginView {
String getLoginName();
String getLoginPsw();
void updateView(LoginInfo info);
void showError(String msg);
void showLoading();
void hiddenLoading();
}
3、LoginPresenter:处理LoginModel和ILoginView之间的关系,也就是数据和view之间的处理,有些写法也会将这里面的方法用接口的形式来处理,但是我个人觉得这里面直接自己写方法方便一点:
public class LoginPresenter extends BasePresenter<ILoginView>{
private ILoginView iLoginView;
private LoginModel loginModel;
public LoginPresenter(ILoginView iLoginView) {
this.iLoginView = iLoginView;
loginModel = new LoginModel();
}
public void login(){
loginModel.loginRequest(iLoginView.getLoginName(), iLoginView.getLoginPsw(), new LoginModel.OnLoginListener() {
@Override
public void onStart() {
iLoginView.showLoading();
}
@Override
public void onSuccess(LoginInfo info) {
iLoginView.updateView(info);
}
@Override
public void onFailed(String msg) {
iLoginView.showError(msg);
}
@Override
public void onFinish() {
iLoginView.hiddenLoading();
}
});
}
@Override
public void destroy() {
loginModel.cancelRequest();
}
}
4、LoginActivity:登录界面实现ILoginView接口用来处理相关逻辑,同时这里要重要说明的就是onDestroy方法,这个方法调用presenter中的destroy方法来取消网络请求:
public class MainActivity extends BaseActivity<ILoginView, LoginPresenter> implements ILoginView {
@InjectView(R.id.et_account)
EditText etAccount;
@InjectView(R.id.et_psw)
EditText etPsw;
@InjectView(R.id.commit)
Button commit;
@InjectView(R.id.tv_result)
TextView tvResult;
@InjectView(R.id.loading)
ProgressBar loading;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
initView();
}
@Override
protected void initView() {
}
@Override
public LoginPresenter initPresent() {
return new LoginPresenter(this);
}
@Override
public String getLoginName() {
return etAccount.getText().toString();
}
@Override
public String getLoginPsw() {
return etPsw.getText().toString();
}
@Override
public void updateView(LoginInfo info) {
tvResult.setText(info.toString());
}
@Override
public void showError(String msg) {
tvResult.setText(msg);
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
@Override
public void showLoading() {
loading.setVisibility(View.VISIBLE);
}
@Override
public void hiddenLoading() {
loading.setVisibility(View.GONE);
}
@OnClick(R.id.commit)
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.commit:
presenter.login();
break;
}
}
@Override
protected void onDestroy() {
super.onDestroy();
presenter.destroy();
}
}
到此,这个架构的例子已经全部完成,相信直接看代码就可以很明了了,同时如果有什么错误的地方还请多多指正!