开发的项目也不少了,网络请求这一块是必不可少的,使用过的网络请求方式也很多了,也不想每次搭建一个项目,都要重复造轮子,决定封装一个网络请求框架供自己以后使用,同时如果同行的小伙伴觉得不错的话也可以使用使用!(如果有不好的地方多多提提意见,将继续完善)
刚好最近公司有一个新的项目要启动,正好可以使用我封装的框架,还可以在测试中完善。
首先先介绍下封装的框架的类的作用:
HttpMethods:http网络请求助手,在此类中设置请求的基地址,拦截器,请求时间,请求参数等
BasePresenter:接口,用于绑定VIew和分离View
ApiException:自定义异常类
ApiCallback:自定义返回结果回调
RxBaseView:空接口,主要是在BasePresenter中绑定View中需要使用到,而且MVP模式中的总回调方法也可以继承此类,进行方法的拓展。具体使用方法下面再详细介绍。
RxBus:事件处理,与eventBus差不多。
RxPresenter:此类实现BasePresenter类,View的绑定,分离,Rxjava的绑定和解绑操作都在此类。
SubscriberCallBack:自定义解析类,实现Observer类,与ApiCallback的回调方法一起使用,错误信息通过转换,转换成用户能理解的通俗语言表达。
这里只是网络请求的封装,使用方法也很简单,下面继续详细介绍主要的类和方法。
1、考虑到不同的人,不同的公司对于网络请求时间的控制是不一样的,所以开放了请求连接超时时间、读取超时时间、写入超时时间的设置,如果不设置则使用默认时间10s,具体方法:
/**
* 设置连接超时时间(秒)
*
* @param connectTimeOut 连接时间
*/
public static void setConnectTimeOut(int connectTimeOut) {
HttpMethods.connectTimeOut = connectTimeOut;
}
/**
* 设置读取超时时间(秒)
*
* @param readTimeOut 读取时间
*/
public static void setReadTimeOut(int readTimeOut) {
HttpMethods.readTimeOut = readTimeOut;
}
/**
* 设置写入超时时间(秒)
*
* @param writeTimeOut 写入时间
*/
public static void setWriteTimeOut(int writeTimeOut) {
HttpMethods.writeTimeOut = writeTimeOut;
}
2、解析库,默认使用的是Gson的解析库,但是可能因为公司返回数据的特殊,所以提供了一个可以自定义的解析库,然后解析逻辑可在自定义的解析方法中解析。
/**
* 设置解析库
* 可自定义解析逻辑
*
* @param factory 解析库
*/
public static void setFactory(Converter.Factory factory) {
mFactory = factory;
}
3、设置拦截器
/**
* 设置拦截器
*
* @param interceptor
*/
public static void setInterceptor(Interceptor interceptor) {
mInterceptor = interceptor;
}
/**
* 设置日志打印logger拦截器
*
* @param logger HttpLoggingInterceptor.Logger
*/
public static void setLogger(HttpLoggingInterceptor.Logger logger) {
mLogger = logger;
}
4、设置日志打印级别,可根据自己下个查看的日志信息设置不同的打印级别
/**
* 设置日志打印级别
*
* @param level
*/
public static void setLevel(HttpLoggingInterceptor.Level level) {
Level = level;
}
1、rxjava订阅,订阅目前只有Observer的回调,没Consumer的方式(待完善)
/**
* RxJava绑定
* 添加订阅
*
* @param observable
* @param observer
* @param
*/
public void addSubscription(Observable observable, Observer observer) {
observable.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(observer);
}
在application中进行HttpMethods的初始化
HttpMethods.setBaseUrl(Constants.BASE_URL);//必不可少,请求的基地址
HttpMethods.setInterceptor(new CommonParametersInterceptor());//设置自定义的公共参数拦截器
HttpMethods.setLevel(HttpMethods.BODY);//设置日志打印基本
HttpMethods.setLogger(new HttpLogger());//设置日志打印拦截器
HttpMethods
.getInstanceBuilder()
.setBaseUrl(Constants.BASE_URL)//设置域名
.setLogLevel(LogLevel.ERROR)//设置日志打印级别,使用默认的日志打印才需要设置这个
.setLogName("123456")//设置默认日志打印名字
.setIsOpenLog(true)//设置是否开启框架默认的日志打印
.setCookieJar(new CookieJarImpl())//设置自定义的cookiejar
// .setLogger(new HttpLogger())//设置自定义logger,此设置是打印网络请求的数据(如果设置了自定义的,则框架默认的则不需要设置)
// .setLevel(LoggerLevel.BODY)//设置日志打印级别(自定义logger可设置,框架默认的是BODY级别,如果上架需要关闭日志打印,则设置setIsOpenLog(false)即可)
.setReadTimeOut(60)
.setConnectTimeOut(60)
.setWriteTimeOut(60)
// .setInterceptor(new CommonParametersInterceptor())//设置拦截器
// .setNetworkInterceptor(new CommonParametersInterceptor())//设置拦截器
// .setFactory(CustomConverterFactory.create())//设置自定义解析器
.setInterceptors(new CommonParametersInterceptor(), new CommonParametersInterceptorHead());//设置多个拦截器
以下是自定义的logger类:
/**
* 添加公共参数
*
* @author Administrator
* @date 2019/3/11
*/
public class CommonParametersInterceptor implements Interceptor {
private String TAG = "CommonParametersInterceptor";
@Override
public Response intercept(Chain chain) throws IOException {
Request oldRequest = chain.request();
Response response = null;
// 新的请求,添加参数
Request newRequest = addParam(oldRequest);
response = chain.proceed(newRequest);
return response;
}
/**
* 添加公共参数
*
* @param oldRequest
* @return
*/
private Request addParam(Request oldRequest) {
HttpUrl.Builder builder = oldRequest.url()
.newBuilder()
.setEncodedQueryParameter("lversion", Constants.BASE_URL)
.setEncodedQueryParameter("token", Constants.BASE_URL);
Request newRequest = oldRequest.newBuilder()
.method(oldRequest.method(), oldRequest.body())
.url(builder.build())
.build();
return newRequest;
}
}
public class HttpLogger implements HttpLoggingInterceptor.Logger {
private StringBuilder mMessage = new StringBuilder();
@Override
public void log(String message) {
// 请求或者响应开始
if (message.startsWith("--> POST")) {
mMessage.setLength(0);
}
// 以{}或者[]形式的说明是响应结果的json数据,需要进行格式化
if ((message.startsWith("{") && message.endsWith("}"))
|| (message.startsWith("[") && message.endsWith("]"))) {
message = JsonUtil.formatJson(message);
}
mMessage.append(message.concat("\n"));
// 请求或者响应结束,打印整条日志
if (message.startsWith("<-- END HTTP")) {
LogUtil.d(mMessage.toString());
LogUtil.e(mMessage.toString());
}
}
}
此日志打印使用的logger打印框架,具体使用方法可到github查看文档:
https://github.com/orhanobut/logger
日志打印拦截器使用到的日志打印类LogUtil和JsonUtil分别如下:
public class LogUtil {
/**
* 初始化log工具,在app入口处调用
*
* @param logName 打印日志名字
* @param isLog 是否打印log
*/
public static void init(String logName, final boolean isLog) {
FormatStrategy mFormatStrategy = PrettyFormatStrategy.newBuilder()
.showThreadInfo(false) // (可选)是否显示线程信息。默认值true
// .methodCount(5) // (可选)要显示的方法行数。默认值2
// .methodOffset(7) // (可选)隐藏内部方法调用到偏移量。默认值5
// .logStrategy() // (可选)更改要打印的日志策略。默认LogCat
.tag(logName) // (可选)每个日志的全局标记。默认PRETTY_LOGGER .build
.build();
//log日志打印框架Logger
com.orhanobut.logger.Logger.addLogAdapter(new AndroidLogAdapter(mFormatStrategy){
@Override
public boolean isLoggable(int priority, @Nullable String tag) {
return isLog;
}
});
}
public static void d(String message) {
Logger.d(message);
}
public static void e(String message) {
Logger.e(message);
}
public static void i(String message) {
Logger.i(message);
}
public static void w(String message, Throwable e) {
String info = e != null ? e.toString() : "null";
Logger.w(message + ":" + info);
}
public static void e(String message, Throwable e) {
Logger.e(e, message);
}
public static void json(String json) {
Logger.json(json);
}
}
public class JsonUtil {
/**
* 格式化json字符串
*
* @param jsonStr 需要格式化的json串
* @return 格式化后的json串
*/
public static String formatJson(String jsonStr) {
if (null == jsonStr || "".equals(jsonStr)) {
return "";
}
StringBuilder sb = new StringBuilder();
char last = '\0';
char current = '\0';
int indent = 0;
for (int i = 0; i < jsonStr.length(); i++) {
last = current;
current = jsonStr.charAt(i);
//遇到{ [换行,且下一行缩进
switch (current) {
case '{':
case '[':
sb.append(current);
sb.append('\n');
indent++;
addIndentBlank(sb, indent);
break;
//遇到} ]换行,当前行缩进
case '}':
case ']':
sb.append('\n');
indent--;
addIndentBlank(sb, indent);
sb.append(current);
break;
//遇到,换行
case ',':
sb.append(current);
if (last != '\\') {
sb.append('\n');
addIndentBlank(sb, indent);
}
break;
default:
sb.append(current);
}
}
return sb.toString();
}
/**
* 添加space
*
* @param sb
* @param indent
*/
private static void addIndentBlank(StringBuilder sb, int indent) {
for (int i = 0; i < indent; i++) {
sb.append('\t');
}
}
}
public interface BaseView extends RxBaseView {
void showResult(String result);
}
public interface MainContract {
interface View extends BaseView {
void onSuccess(LoginBean loginBean);
void onSuccess(HttpResult loginBean);
void onError(String msg);
}
interface Presenter extends BasePresenter {
void doLogin1(String userName, String pwd);
void doLogin2(String userName, String pwd);
void loadLoginStatusEntity();
void doLogin(String phone, String password);
}
}
public class MainPresenter extends RxPresenter implements MainContract.Presenter {
ApiServer apiServer = HttpMethods.getInstance().create(ApiServer.class);
@Override
public void doLogin1(String userName, String pwd) {
Observable observable = apiServer.login1(userName, pwd);
addSubscription(observable, new SubscriberCallBack<>(new ApiCallback() {
@Override
public void onSuccess(HttpResult model) {
mView.onSuccess(model);
Logger.d(model);
}
@Override
public void onFailure(String msg) {
mView.onError(msg);
Logger.d(msg);
}
}));
}
@Override
public void doLogin2(String userName, String pwd) {
Observable observable = apiServer.login2(userName, pwd).map(new HttpResultFunc());
addSubscription(observable, new SubscriberCallBack<>(new ApiCallback() {
@Override
public void onSuccess(LoginBean model) {
Logger.d(model);
mView.onSuccess(model);
}
@Override
public void onFailure(String msg) {
mView.onError(msg);
Logger.d(msg);
}
}));
}
@Override
public void loadLoginStatusEntity() {
Observable observable = apiServer.loadLoginStatus();
addSubscription(observable, new SubscriberCallBack<>(new ApiCallback() {
@Override
public void onSuccess(LoginStatusEntity model) {
LogUtil.e(model.toString());
}
@Override
public void onFailure(String msg) {
LogUtil.e(msg);
}
}));
}
@Override
public void doLogin(String phone, String password) {
Observable observable = apiServer.login(phone, password);
addSubscription(observable, new SubscriberCallBack<>(new ApiCallback() {
@Override
public void onSuccess(LoginEntity model) {
LogUtil.e(model.toString());
}
@Override
public void onFailure(String msg) {
ToastUtil.shortShow(msg);
LogUtil.e(msg);
}
}));
}
}
注意:presenter类是contract类中Presenter接口的实现类,view层不直接与presenter层交互, 而是通过contract层作为桥梁与presenter层交互,此presenter实现类是要进行网络请求,所有 还需要继承RxPresenter,RxPresenter使用泛型,传入了view对象。
public abstract class BaseActivity extends AppCompatActivity implements RxBaseView {
protected T mPresenter;
protected Activity mActivity;
private View netErrorView;
protected abstract int getLayout();
protected abstract void initEventAndData();
protected abstract T createPresenter();
@SuppressLint("RestrictedApi")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
/**
* 创建presenter对象
*/
mPresenter = createPresenter();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
super.onCreate(savedInstanceState);
setContentView(getLayout());
mActivity = this;
//活动控制器
App.getInstance().addActivity(this);
if (mPresenter != null) {
mPresenter.attachView(this);
}
initEventAndData();
}
@Override
protected void onDestroy() {
super.onDestroy();
/**
* presenter 解除view订阅
*/
if (mPresenter != null) {
mPresenter.detachView();
}
App.getInstance().removeActivity(this);
}
/**
* 是否需要注册网络变化的Observer,如果不需要监听网络变化,则返回false;否则返回true
*/
protected boolean needRegisterNetworkChangeObserver() {
return true;
}
//设置title
public void setTitleTx(String title_tx) {
try {
TextView title = findViewById(R.id.title);
title.setText(title_tx);
} catch (Exception e) {
}
}
/**
* 打开一个Activity 默认 不关闭当前activity
*/
public void gotoActivity(Class> clz) {
gotoActivity(clz, false, null);
}
public void gotoActivity(Class> clz, boolean isCloseCurrentActivity) {
gotoActivity(clz, isCloseCurrentActivity, null);
}
public void gotoActivity(Class> clz, boolean isCloseCurrentActivity, Bundle ex) {
Intent intent = new Intent(this, clz);
if (ex != null) intent.putExtras(ex);
startActivity(intent);
if (isCloseCurrentActivity) {
finish();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
super.onBackPressed();//返回
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}
activity示例:
public class MainActivity extends BaseActivity implements MainContract.View, RxView.OnRxViewClickListener {
private final static String TAG = "MainActivity";
private EditText username, pwd;
private TextView tvResult;
private Disposable mSubscribe;
private Button rx_view;
@Override
protected int getLayout() {
return R.layout.activity_main;
}
@Override
protected void initEventAndData() {
username = findViewById(R.id.username);
rx_view = findViewById(R.id.rx_view);
pwd = findViewById(R.id.pwd);
tvResult = findViewById(R.id.result);
mSubscribe = RxBus.getDefault().tObservable(RxEvent.class).subscribe(new Consumer() {
@Override
public void accept(RxEvent rxEvent) throws Exception {
if (rxEvent.getCode() == 1000) {
username.setText(rxEvent.getUserName());
pwd.setText(rxEvent.getPassWord());
}
}
});
RxView.setIntervalTime(2000);
RxView.setOnClickListeners(this, rx_view);
}
@Override
protected MainPresenter createPresenter() {
return new MainPresenter();
}
@Override
public void onSuccess(LoginBean loginBean) {
Logger.e("onSuccess");
Logger.d(loginBean);
tvResult.setText(loginBean.toString());
}
@Override
public void onSuccess(HttpResult loginBean) {
tvResult.setText(loginBean.toString());
}
@Override
public void onError(String msg) {
Log.e(TAG, msg);
tvResult.setText(msg);
}
public void update(View view) {
}
public void login(View view) {
mPresenter.doLogin1(username.getText().toString().trim(), pwd.getText().toString().trim());
}
@Override
public void showResult(String result) {
tvResult.setText(result);
}
public void rxBusOnclick(View view) {
RxBusActivity.startAction(this);
// jsonTest();
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mSubscribe != null) {
if (mSubscribe.isDisposed()) {
mSubscribe.dispose();
}
}
}
public void login2(View view) {
mPresenter.doLogin2(username.getText().toString().trim(), pwd.getText().toString().trim());
}
public void cookieLogin(View view) {
mPresenter.doLogin("13790994100", "caishouhui0524");
}
public void cookieLoginStatus(View view) {
mPresenter.loadLoginStatusEntity();
}
public void downOnclick(View view) {
DownActivity.startAction(this);
}
public void systemDownOnclick(View view) {
SystemDownloadActivity.startAction(this);
}
@Override
public void onRxViewClick(View view) {
switch (view.getId()) {
case R.id.rx_view:
LogUtil.e("点击了");
break;
default:
break;
}
}
public void downTask(View view) {
DownTaskListActivity.startAction(this);
}
}
在activity中进行接口的请求,一句代码:
mPresenter.doLogin1(username.getText().toString().trim(), pwd.getText().toString().trim());
ApiServer apiServer = HttpMethods.getInstance().create(ApiServer.class);
@Override
public void doLogin1(String userName, String pwd) {
Observable observable = apiServer.login1(userName, pwd);
addSubscription(observable, new SubscriberCallBack<>(new ApiCallback() {
@Override
public void onSuccess(HttpResult model) {
mView.onSuccess(model);
Logger.d(model);
}
@Override
public void onFailure(String msg) {
mView.onError(msg);
Logger.d(msg);
}
}));
}
@Override
public void doLogin2(String userName, String pwd) {
Observable observable = apiServer.login2(userName, pwd).map(new HttpResultFunc());
addSubscription(observable, new SubscriberCallBack<>(new ApiCallback() {
@Override
public void onSuccess(LoginBean model) {
Logger.d(model);
mView.onSuccess(model);
}
@Override
public void onFailure(String msg) {
mView.onError(msg);
Logger.d(msg);
}
}));
}
HttpDownMethods mHttpDownMethods;
String wechatUrl = "http://dldir1.qq.com/weixin/android/weixin703android1400.apk";
String qqUrl = "https://qd.myapp.com/myapp/qqteam/AndroidQQ/mobileqq_android.apk";
private HttpDownInfo mDownInfo;
private HttpDownInfo mDownInfo2;
private void initDownloads() {
mHttpDownMethods = HttpDownMethods.getInstance();
mHttpDownMethods.setDeleteFile(true);
mDownInfo = new HttpDownInfo();
mDownInfo.setUrl(qqUrl);
mDownInfo.setSavePath(Environment.getExternalStorageDirectory().getAbsolutePath() + "/text/qq.apk");
mDownInfo2 = new HttpDownInfo();
mDownInfo2.setUrl(wechatUrl);
}
mHttpDownMethods.downStart(mDownInfo, new HttpDownListener() {
@Override
public void downStart() {
btn_download1.setText("暂停");
}
@Override
public void downPause(HttpDownInfo httpDownInfo,long progress) {
LogUtil.e("暂停了");
mDownInfo.setReadLength(progress);
btn_download1.setText("下载");
}
@Override
public void downStop(HttpDownInfo httpDownInfo) {
LogUtil.e("停止了");
pb_progress1.setProgress(0);
tv_progress1.setText(0 + "%");
mDownInfo.setReadLength(0);
mDownInfo.setCountLength(0);
btn_download1.setText("下载");
}
@Override
public void downFinish(HttpDownInfo httpDownInfo) {
LogUtil.e("下载完成");
mDownInfo = httpDownInfo;
btn_download1.setText("下载完成");
}
@Override
public void downError(HttpDownInfo httpDownInfo, String msg) {
LogUtil.e("出错了");
mDownInfo = httpDownInfo;
btn_download1.setText("下载出错了");
}
@Override
public void downProgress(long readLength, long countLength) {
int pro = 0;
try {
pro = (int) (readLength * 100 / countLength);
} catch (Exception e) {
e.printStackTrace();
}
pb_progress1.setProgress(pro);
tv_progress1.setText(pro + "%");
}
});
RxView.setIntervalTime(2000);//设置间隔时间
RxView.setOnClickListeners(this, rx_view,tvResult);//设置需要控制重复点击的按钮,可设置多个
如果RxView使用觉得不方便,可以查看我的博文:
Android处理按钮重复点击事件
网络请求框架代码传送门
如果遇上Android9.0系统无法请求接口的问题,可以查看博文:
Android9.0 http网络请求失败问题分析与解决方案