先扯两句
前几篇博客把之前的BaseActivity做了拆解,分成了BaseActivity与BaseLayoutActivity,而BaseNetActivity确实也在之前做了一定的去耦合处理,虽然没有发对应的博客,但是如果有关注我github的朋友或许会发现,那就是封装了一个NetUtils类,将原本Header的封装,Retrofit的调用封装全都拆解了出来,只对应GET、POST请求暴露了两个方法。不过当前的封装实际上,还是针对的mvc框架。毕竟网络请求还是没有脱离BaseNetActivity与BaseNetFragment,无法达到进一步的去耦合,彻底摆脱Activity与Fragment的限制。于是查了一下MVP框架与MVVM框架。当然,我这种懒汉性格,不会去看什么分析文档了,那么正式的纯文字,看两段我就睡给他看。所以就找了些demo类的:
- JesseBraveMan的Android MVP架构搭建
- 江南一点雨的 玩转Android之MVVM开发模式实战,炫酷的DataBinding!
- SoloHo的Android,DataBinding的官方双向绑定
大家按照上面的博客直接敲代码就能实现最基本的操作,余下的部分就是适配自己的底层环境以及业务逻辑,这些内容我也会后续跟进学习,只是当前《一个Android工程的从零开始》还是比较基础的部分,甚至大学生之间拿来简单搭建个毕业设计应该都可以,个人感觉说明的还算详细了(除了博客更新慢点。。。)。所以关于MVP和MVVM框架暂时就不集成进来了,当前如果大家使用过程中,真的遇到有需要从adapter或者是自定义View中进行网络请求的,可以参考BaseNetActivity和BaseNetFragment自行封装一个BaseNetAdapter或者BaseNetView。再简单点,参考BaseNetActivity和BaseNetFragment直接给NetUtils传递参数即可(其实个人感觉MVP的BasePresenter与我的NetUtils也差不多)。
好了,闲言少叙,老规矩还是先上我的Git,然后开始正文吧。 MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)
正文
其实这部分,对于之前看过我博客的朋友来说,其实没有什么新意,就像上面扯的一样,只是把大多数公共部分提出来而已,先说明一下我这里的网络框架是使用的Retrofit2,大家如果使用其他网络框架的话,就辛苦自行替换一下喽。首先把在初级阶段不那么常用的部分拿到前面,那就是header:
private Map<String, String> headerParams;
/**
* 初始化请求头,具体情况根据需求设置
*/
public void initHeader() {
headerParams = new HashMap<>();
// 传参方式为json
headerParams.put("Content-Type", "application/json");
}
这就是传说中的header了,是不是超简单!!!好吧,我承认还需要调用,不过就是传个参数喽,关于Retrofit Service的封装,大家可以看一下我之前的博客,
《一个Android工程的从零开始》-8、base(七) Retrofit的封装,如果不想看,直接去Git找源码就好,链接你知道的,就在上面。
如果后台需要其他的header参数,对应添加就好,都是键值对的方式,至于有没有更复杂的header方式,我猜是有,不过反正至今没遇到,那就管他去死。
当然,这里呢当前这条header的意思是要传的数据类型是json(只针对POST请求,GET不受影响),需要说明一点的是,其实这里设置json,服务器收到的请求还不是json而是key—value形式(忘记了默认格式是不是formdata了),具体如何传json,大家可以看一下我的这篇博客——《一个Android工程的从零开始》阶段总结与修改2-Retrofit 上传JSON及尾址特殊字符转译问题,当然,也有过一个朋友,使用我的框架,去调取了“聚合数据”的接口,结果POST请求无法正常获取数据,最后查证,是因为“聚合数据”的那个接口POST请求不支持JSON格式,只能还原到原本的key-value格式,所以大家在使用框架的时候一定要具体问题具体分析,尤其是请求方式,最好能与后台约定好,不然麻烦也算不上,但至少要多很多操作。
好了跑题够远,该收回来了,上面的部分就是header的设置,对我而言,一般都会跟后台约束好header字段,然后统一传参,对方用不上可以不取。不过万一遇到一些严谨的公司,就要有好几套不同的header,也禁止传无用的数据,那就需要提供一个自定义header的入口,也比较好实现,相信能进入这种严谨公司的人完成这部分调整也不在话下,我就不班门弄斧,直接进行下一部分了。
/**
* 初始化数据
*
* @param action 当前请求的尾址
*/
private Retrofit initBaseData(final String action) {
// 监听请求条件
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(5, TimeUnit.SECONDS);
builder.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Logger.i("zzz", "request====" + action);
Logger.i("zzz", "request====" + request.headers().toString());
Logger.i("zzz", "request====" + request.toString());
okhttp3.Response proceed = chain.proceed(request);
Logger.i("zzz", "proceed====" + proceed.headers().toString());
return proceed;
}
});
Retrofit.Builder builder1 = new Retrofit.Builder()
.client(builder.build()) // 配置监听请求
.addConverterFactory(GsonConverterFactory.create()) // 请求结果转换(当前为GSON)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()); // 请求接受工具(当前为RxJava2)
builder1.baseUrl(BuildConfig.BASE_URL + action.substring(0, action.lastIndexOf("/") + 1));
return builder1.build();
}
这里主要是Retrofit的一些配置,通过注释,大家也能了解到具体都是做什么功能的,还是不加以赘述了。只是说一点,那就是OkHttpClient的部分,注释是“监听请求条件”,也就是将我们向平台发送的数据做了一下回显,这样开发过程中,一旦出现什么错误,也方便查找。说的猥琐点,至少出问题了能做个判断,自己没错的时候也方便甩锅不是!
但是项目正式上线的时候,一定要把这个部分干掉,我是因为release版和debug版通过自己封装的Logger做了处理,大家如果使用系统的Log,一定要想着这部分,不然打开Android Studio运行一下APP,我们请求的数据就都暴露在对方眼前了。再加上后面返回数据的输出,那我们的项目就完完全全成透明的了。虽然说即便如此,他人使用代理一样可以抓包,但是毕竟相对于前者来说,会玩代理的还是少数,老板来问,我们也只能说非战之过了。
/**
* Get请求
*
* @param action 请求接口的尾址
* @param params 索要传递的参数
* @param observer 求情观察者
*/
public void get(final String action, Map<String, String> params, Observer<ResponseBody> observer) {
RetrofitGetService getService = initBaseData(action).create(RetrofitGetService.class);
if (params == null) {
params = new HashMap<>();
}
if (null == headerParams){
headerParams = new HashMap<>();
}
Logger.i("zzz", "request====" + new JSONObject(params));
getService.getResult(action.substring(action.lastIndexOf("/") + 1), headerParams, params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
/**
* Post请求
*
* @param action 请求接口的尾址
* @param observer 求情观察者
*/
public void post(final String action, String json, Observer<ResponseBody> observer) {
RetrofitPostJsonService jsonService = initBaseData(action).create(RetrofitPostJsonService.class);
RequestBody requestBody =
RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
json);
Logger.i("zzz", "request====" + json);
jsonService.postResult(action.substring(action.lastIndexOf("/") + 1), requestBody)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
上面两个方法分别是对GET的封装以及对POST(参数格式JSON)的封装,毕竟只是个demo,所以这部分我GET中使用的是传header的形式,而POST中使用的是不需要传header的形式,大家在使用的时候,针对应业务需求统一一下即可。以上也就是NetUtils中的全部内容,完整代码如下:
public class NetUtils {
private Map<String, String> headerParams;
/**
* 初始化请求头,具体情况根据需求设置
*/
public void initHeader() {
headerParams = new HashMap<>();
// 传参方式为json
headerParams.put("Content-Type", "application/json");
}
/**
* 初始化数据
*
* @param action 当前请求的尾址
*/
private Retrofit initBaseData(final String action) {
// 监听请求条件
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.connectTimeout(5, TimeUnit.SECONDS);
builder.addInterceptor(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
Request request = chain.request();
Logger.i("zzz", "request====" + action);
Logger.i("zzz", "request====" + request.headers().toString());
Logger.i("zzz", "request====" + request.toString());
okhttp3.Response proceed = chain.proceed(request);
Logger.i("zzz", "proceed====" + proceed.headers().toString());
return proceed;
}
});
Retrofit.Builder builder1 = new Retrofit.Builder()
.client(builder.build()) // 配置监听请求
.addConverterFactory(GsonConverterFactory.create()) // 请求结果转换(当前为GSON)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create()); // 请求接受工具(当前为RxJava2)
builder1.baseUrl(BuildConfig.BASE_URL + action.substring(0, action.lastIndexOf("/") + 1));
return builder1.build();
}
/**
* Get请求
*
* @param action 请求接口的尾址
* @param params 索要传递的参数
* @param observer 求情观察者
*/
public void get(final String action, Map<String, String> params, Observer<ResponseBody> observer) {
RetrofitGetService getService = initBaseData(action).create(RetrofitGetService.class);
if (params == null) {
params = new HashMap<>();
}
if (null == headerParams){
headerParams = new HashMap<>();
}
Logger.i("zzz", "request====" + new JSONObject(params));
getService.getResult(action.substring(action.lastIndexOf("/") + 1), headerParams, params)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
/**
* Post请求
*
* @param action 请求接口的尾址
* @param observer 求情观察者
*/
public void post(final String action, String json, Observer<ResponseBody> observer) {
RetrofitPostJsonService jsonService = initBaseData(action).create(RetrofitPostJsonService.class);
RequestBody requestBody =
RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
json);
Logger.i("zzz", "request====" + json);
jsonService.postResult(action.substring(action.lastIndexOf("/") + 1), requestBody)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
}
而这部分搞定之后,下面就要回到我们的BaseNetActivity中了,其实BaseNetActivity也是相对简单的,因为我们已经有了NetUtils中的get方法以及post方法,其他的只需要你对应业务做一下处理即可。所以第一步是什么呢?恭喜你,答对了,就是在BaseNetActivity中创建NetUtils对象!
/**
* 加载提示框
*/
private CustomProgressDialog customProgressDialog;
private NetUtils netUtils;
protected Map<String, String> params;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
customProgressDialog = new CustomProgressDialog(activity, R.style.progress_dialog_loading, "玩命加载中。。。");
netUtils = new NetUtils();
netUtils.initHeader();
}
这里调用了initHeader方法,不是必须的,还是一切看业务逻辑,还记得小时候考试那个抄别人卷子,最后连对方名字都抄上的人吗?没错,那就是我。除此之外,这里还创建了一个进度加载提示框,相关内容大家还是到源码中寻找答案吧。至于这里使用的params,我使用的是Map<String, String>,而大多数情况下使用的还是Map<String, Object>,大家也可以替换一下。
创建后当然就要开始使用了:
/**
* Get请求
*
* @param action 请求接口的尾址
* @param clazz 要转换的Bean类型(需继承BaseBean)
* @param showDialog 显示加载进度条
*/
protected <T extends BaseBean> void get(final String action, Class<T> clazz, boolean showDialog) {
if (!isNetworkAvailable()) {
toast("网络异常,请检查网络是否连接");
error(action, new Exception("网络异常,请检查网络是否连接"));
return;
}
if (showDialog) {
showLoadDialog();
}
if (params == null) {
params = new HashMap<>();
}
netUtils.get(action, params, new MyObserver<>(action, clazz));
params = null;
}
private class MyObserver<T extends BaseBean> implements Observer<ResponseBody> {
private Class<T> clazz;
private String action;
MyObserver(String action, Class<T> clazz) {
this.clazz = clazz;
this.action = action;
}
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ResponseBody responseBody) {
hideLoadDialog();
try {
String responseString = responseBody.string();
Logger.i("responseString", action + "********** responseString get " + responseString);
success(action, (T) new Gson().fromJson(responseString, clazz));
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError(@NonNull Throwable e) {
Logger.i("responseString", "responseString get " + e.toString());
error(action, e);
}
@Override
public void onComplete() {
params = null;
}
}
这里是以封装get请求与为例,传入的参数分别为网络请求尾址、参数接收类、以及是否显示提示框。这三类个人认为是最基础的部分了,大家使用过程中可以根据业务调整参数,当然,这部分也是在封装接口,所以调整的时候,最好尽可能兼容多个请求,而不要一个接口对应一个方法,那样的就不如直接请求NetUtilsL ,BaseNetActivity这一层的封装就没有用了。
而下面封装的MyObserver完全是为了结果统一获取,而不需要每封装一个方法,就定义一次结果取值的部分,那样过于繁琐。当然,这样自然不可能就完全兼顾所有请求,所以特殊情况,我们可以提供一个标志位,去做对应处理:
/**
* Get请求
*
* @param action 请求接口的尾址
* @param showDialog 显示加载进度条
*/
protected <T extends BaseBean> void getImage(final String action, boolean showDialog) {
if (!isNetworkAvailable()) {
toast("网络异常,请检查网络是否连接");
error(action, new Exception("网络异常,请检查网络是否连接"));
return;
}
if (showDialog) {
showLoadDialog();
}
if (params == null) {
params = new HashMap<>();
}
netUtils.get(action, params, new MyObserver<>(action, 1));
params = null;
}
private class MyObserver<T extends BaseBean> implements Observer<ResponseBody> {
private Class<T> clazz;
private String action;
/**
* 返回结果状态:0、正常Bean;1、Bitmap
*/
private int resultStatus = 0;
MyObserver(String action, Class<T> clazz) {
this.clazz = clazz;
this.action = action;
}
MyObserver(String action, int resultStatus) {
this.action = action;
this.resultStatus = resultStatus;
}
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ResponseBody responseBody) {
hideLoadDialog();
try {
switch (resultStatus) {
case 0:
String responseString = responseBody.string();
Logger.i("responseString", action + "********** responseString get " + responseString);
success(action, (T) new Gson().fromJson(responseString, clazz));
break;
case 1:
success(action, BitmapFactory.decodeStream(responseBody.byteStream()));
Logger.i("responseString", action + "********** 图片获取成功 ");
break;
default:
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError(@NonNull Throwable e) {
Logger.i("responseString", "responseString get " + e.toString());
error(action, e);
}
@Override
public void onComplete() {
params = null;
}
}
都说需求(规则)是死的,人是活的,可有的时候活人改活规则,真容易把其他活人逼死。。。之所以有这个感慨完全是因为上面这个封装,如今开发APP,图片加载的部分肯定是绕不过去的,什么头像啊、产品样例啊、朋友圈晒娃晒狗晒美食的查看啊,我写过最奇葩的是一个自定义日记本封面尺寸是 350dp*100dp,WTF(这个感叹来自于截图)!!!不过一般而言都是获取的图片Url,然后使用各种图片加载控价加载即可。可一直这样人怎么能进步啊,这不就需要获取图片字节流了吗?
设计之初,想过直接取responseBody.string(),没有值再去取字节流,就在为自己的机智鼓掌时,发现这根本行不通,因为Observer的onNext中,responseBody.string()连续调用连词,第二次都无法取得数据。无奈之下,就只有创建一个标志位resultStatus,判断究竟是获取的String还是Bitmap。当然,如果同样适用这个框架的情况下,遇到其他奇葩需求,也可以继续扩展。
需要说明的是,一般而言,我们的请求还是获取文本数据居多,所以这里success的抽象方法还是返回的“String action, BaseBean baseBean”,而返回bitmap的没有做抽象处理,需要使用的时候重写一下就好。BaseNetAcrivity完整代码如下,:
public abstract class BaseNetActivity extends BaseLayoutActivity {
/**
* 加载提示框
*/
private CustomProgressDialog customProgressDialog;
private NetUtils netUtils;
protected Map<String, String> params;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
customProgressDialog = new CustomProgressDialog(activity, R.style.progress_dialog_loading, "玩命加载中。。。");
netUtils = new NetUtils();
netUtils.initHeader();
}
protected void refreshHeader() {
netUtils.initHeader();
}
/**
* Get请求
*
* @param action 请求接口的尾址
* @param clazz 要转换的Bean类型(需继承BaseBean)
* @param showDialog 显示加载进度条
*/
protected <T extends BaseBean> void get(final String action, Class<T> clazz, boolean showDialog) {
if (!isNetworkAvailable()) {
toast("网络异常,请检查网络是否连接");
error(action, new Exception("网络异常,请检查网络是否连接"));
return;
}
if (showDialog) {
showLoadDialog();
}
if (params == null) {
params = new HashMap<>();
}
netUtils.get(action, params, new MyObserver<>(action, clazz));
params = null;
}
/**
* Get请求
*
* @param action 请求接口的尾址
* @param showDialog 显示加载进度条
*/
protected <T extends BaseBean> void getImage(final String action, boolean showDialog) {
if (!isNetworkAvailable()) {
toast("网络异常,请检查网络是否连接");
error(action, new Exception("网络异常,请检查网络是否连接"));
return;
}
if (showDialog) {
showLoadDialog();
}
if (params == null) {
params = new HashMap<>();
}
netUtils.get(action, params, new MyObserver<>(action, 1));
params = null;
}
/**
* Post请求
*
* @param action 请求接口的尾址
* @param clazz 要转换的Bean类型(需继承BaseBean)
* @param showDialog 显示加载进度条
*/
protected <T extends BaseBean> void post(final String action, Class<T> clazz, boolean showDialog) {
if (!isNetworkAvailable()) {
toast("网络异常,请检查网络是否连接");
error(action, new Exception("网络异常,请检查网络是否连接"));
return;
}
if (showDialog) {
showLoadDialog();
}
if (params == null) {
params = new HashMap<>();
}
netUtils.post(action, String.valueOf(new JSONObject(params)), new MyObserver<>(action, clazz));
params = null;
}
/**
* Post请求
*
* @param action 请求接口的尾址
* @param clazz 要转换的Bean类型(需继承BaseBean)
* @param showDialog 显示加载进度条
*/
protected <T extends BaseBean> void post(final String action, String json, final Class<T> clazz, boolean showDialog) {
if (!isNetworkAvailable()) {
toast("网络异常,请检查网络是否连接");
error(action, new Exception("网络异常,请检查网络是否连接"));
return;
}
if (showDialog) {
showLoadDialog();
}
netUtils.post(action, json, new MyObserver<>(action, clazz));
}
/**
* 访问成功回调抽象方法
*
* @param action 网络访问尾址
* @param baseBean 返回的数据Bean
*/
protected abstract void success(String action, BaseBean baseBean);
/**
* 访问成功回调方法
*
* @param action 网络访问尾址
* @param bitmap 获取的Bitmap
*/
protected void success(String action, Bitmap bitmap) {
}
protected abstract void error(String action, Throwable e);
/**
* 显示加载提示框
*/
private void showLoadDialog() {
runOnUiThread(new Runnable() {
@Override
public void run() {
customProgressDialog.show();
}
});
}
/**
* 隐藏加载提示框
*/
private void hideLoadDialog() {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (customProgressDialog != null && customProgressDialog.isShowing()) {
customProgressDialog.dismiss();
}
}
});
}
private class MyObserver<T extends BaseBean> implements Observer<ResponseBody> {
private Class<T> clazz;
private String action;
/**
* 返回结果状态:0、正常Bean;1、Bitmap
*/
private int resultStatus = 0;
MyObserver(String action, Class<T> clazz) {
this.clazz = clazz;
this.action = action;
}
MyObserver(String action, int resultStatus) {
this.action = action;
this.resultStatus = resultStatus;
}
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ResponseBody responseBody) {
hideLoadDialog();
try {
switch (resultStatus) {
case 0:
String responseString = responseBody.string();
Logger.i("responseString", action + "********** responseString get " + responseString);
success(action, (T) new Gson().fromJson(responseString, clazz));
break;
case 1:
success(action, BitmapFactory.decodeStream(responseBody.byteStream()));
Logger.i("responseString", action + "********** 图片获取成功 ");
break;
default:
break;
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onError(@NonNull Throwable e) {
Logger.i("responseString", "responseString get " + e.toString());
error(action, e);
}
@Override
public void onComplete() {
params = null;
}
}
}