前言
这个网络请求框架使用的是MVP模式,用习惯之后你会发现这个框架写网络请求很方便,因为它封装的很好,而且这个框架也是目前android开发比较主流的网络请求框架,所以写一篇文章记录一下使用这个框架的大致流程。
正文
1.添加项目依赖库:
//components
implementation 'com.android.support:design:28.0.0'
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.android.support:recyclerview-v7:28.0.0'
implementation 'com.blankj:utilcode:1.23.7'
//rx
implementation 'io.reactivex.rxjava2:rxjava:2.2.2'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.0'
//network
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
implementation 'com.squareup.okhttp3:okhttp:3.11.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.11.0'
implementation 'com.github.bumptech.glide:glide:4.8.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0'
implementation('com.github.ihsanbal:LoggingInterceptor:3.0.0') {
exclude group: 'org.json', module: 'json'
}
//di
implementation 'com.google.dagger:dagger:2.17'
annotationProcessor 'com.google.dagger:dagger-compiler:2.17'
implementation 'com.jakewharton:butterknife:8.8.1'
annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
implementation 'com.android.support:multidex:1.0.3'
implementation 'me.yokeyword:fragmentation:1.3.3'
implementation 'com.orhanobut:logger:2.1.1'
工程目录下build目录修改如下:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
maven { url "https://dl.bintray.com/thelasterstar/maven/" }
jcenter()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0'
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
maven{ url 'http://maven.aliyun.com/nexus/content/groups/public/'}
mavenCentral()
google()
jcenter()
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
2.初始化okhttp设置:
@Module
public class HttpModule {
@Singleton
@Provides
Retrofit.Builder provideRetrofitBuilder() {
return new Retrofit.Builder();
}
@Singleton
@Provides
OkHttpClient.Builder provideOkHttpBuilder() {
return new OkHttpClient.Builder();
}
@Singleton
@Provides
@MainUrl
Retrofit provideZhihuRetrofit(Retrofit.Builder builder, OkHttpClient client) {
return createRetrofit(builder, client, Constant.BASE_HOST);
}
@Singleton
@Provides
OkHttpClient provideClient(OkHttpClient.Builder builder) {
if (BuildConfig.DEBUG) {
LoggingInterceptor httpLoggingInterceptor = new LoggingInterceptor.Builder()
.loggable(BuildConfig.DEBUG)
.setLevel(Level.BASIC)
.log(Platform.INFO)
.request("Request")
.response("Response")
.build();
// HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
// loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BASIC);
builder.addInterceptor(httpLoggingInterceptor);
}
File cacheFile = new File(Constant.PATH_CACHE);
Cache cache = new Cache(cacheFile, 1024 * 1024 * 50);
Interceptor cacheInterceptor = new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request();
if (!SystemUtil.isNetworkConnected()) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build();
}
Response response = chain.proceed(request);
if (SystemUtil.isNetworkConnected()) {
int maxAge = 0;
// 有网络时, 不缓存, 最大保存时长为0
response.newBuilder()
.header("Cache-Control", "public, max-age=" + maxAge)
.removeHeader("Pragma")
.build();
} else {
// 无网络时,设置超时为4周
int maxStale = 60 * 60 * 24 * 28;
response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale)
.removeHeader("Pragma")
.build();
}
return response;
}
};
// Interceptor apikey = new Interceptor() {
// @Override
// public Response intercept(Chain chain) throws IOException {
// Request request = chain.request();
// request = request.newBuilder()
// .addHeader("apikey",Constants.KEY_API)
// .build();
// return chain.proceed(request);
// }
// }
// 设置统一的请求头部参数
// builder.addInterceptor(apikey);
//设置缓存
builder.addNetworkInterceptor(cacheInterceptor);
builder.addInterceptor(cacheInterceptor);
builder.cache(cache);
//设置超时
builder.connectTimeout(10, TimeUnit.SECONDS);
builder.readTimeout(20, TimeUnit.SECONDS);
builder.writeTimeout(20, TimeUnit.SECONDS);
//错误重连
builder.retryOnConnectionFailure(true);
return builder.build();
}
@Singleton
@Provides
MainApi provideMainService(@MainUrl Retrofit retrofit) {
return retrofit.create(MainApi.class);
}
private Retrofit createRetrofit(Retrofit.Builder builder, OkHttpClient client, String url) {
return builder
.baseUrl(url)
.client(client)
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.build();
}
}
细节解析:
1.该类中使用到的一些标签不要遗漏,尤其是类名上的@Module标签;
2.Constant.BASE_HOST--使用你的接口DoMain,比如你的接口请求完整地址是https://www.baidu.com/v2/draft/add,那么这个参数应该填入https://www.baidu.com/;
3.按照上面的写法,请求和返回的相关数据都会打印到日志里,需要自定义打印日志信息的话自行修改provideClient方法中的httpLoggingInterceptor;
4.provideMainService中的MainApi.class是你的接口声明类,下一步会提到。
2.创建接口声明类MainApi
public interface MainApi {
@POST("courses/index/")
Flowable<EditFrameInfo> getUserInfo();
@Multipart
@POST("v2/draft/add")
Flowable<PictureBean> saveCoursewareInfo(@PartMap Map<String, RequestBody> map);
@GET("attach/materials")
Flowable<ResourcesBean> getResources(@Query("type") String type);
}
这里我选择的代码把常用的几种请求方式都涵盖了,第一种是不需要参数的post请求,第二种是需要参数的post请求,第三种是带参数的get请求,根据你的请求类型自行选用即可。这里如果你是post的带参数请求,可以统一把参数格式都写成@PartMap Map<String, RequestBody> map这样。
细节解析:
1.@POST("v2/draft/add")--括号里的是你想要调用的地址domain后面的部分。
3.实现MainApi中的接口
1.定义HttpHelper接口:
public interface HttpHelper {
Flowable<PictureBean> saveCoursewareInfo(@PartMap Map<String, RequestBody> map);
}
这里直接把MainApi里定义的接口除了标签部分复制进来就可以了。
2.实现HttpHelper接口:
public class RetrofitHelper implements HttpHelper {
private MainApi mMainApiService;
@Inject
public RetrofitHelper(MainApi mMainApiService) {
this.mMainApiService = mMainApiService;
}
@Override
public Flowable<PictureBean> saveCoursewareInfo(Map<String, RequestBody> map) {
return mMainApiService.saveCoursewareInfo(map);
}
}
注意这里的@Inject别忘记加。
3.创建DataManager类:
该类用于管理所有的数据请求,就是封装了一层方法调用。
public class DataManager implements HttpHelper {
HttpHelper mHttpHelper;
public DataManager(HttpHelper httpHelper) {
mHttpHelper = httpHelper;
}
@Override
public Flowable<PictureBean> saveCoursewareInfo(Map<String, RequestBody> map) {
return mHttpHelper.saveCoursewareInfo(map);
}
}
4.定义Contract接口:
public interface MainContract {
interface View extends BaseView {
void responseCoursewareId(int id);
}
interface Presenter extends BasePresenter<View> {
void saveCoursewareLib(Map<String, RequestBody> map);
}
}
Presenter接口定义请求方法,View接口定义回调方法。简单来说就是你想调用MainApi里定义的接口就用Presenter里的方法去请求,请求之后的会回调View里对应的方法。
5.定义Presenter:
public class MainPresenter extends RxPresenter<MainContract.View> implements MainContract.Presenter {
private static final String TAG = MainPresenter.class.getSimpleName();
DataManager dataManager;
@Inject
public MainPresenter(DataManager dataManager) {
this.dataManager = dataManager;
}
@Override
public void saveCoursewareLib(Map<String, RequestBody> map) {
addSubscribe(dataManager.saveCoursewareInfo(map)
.compose(RxUtil.rxSchedulerHelper())
.subscribeWith(new CommonSubscriber<PictureBean>(mView) {
@Override
public void onNext(PictureBean pictureBean) {
if (pictureBean.getData() != null) {
if (pictureBean.getData().getCode() == Constant.NET_REQUEST_OK && mView != null) {
mView.responseCoursewareId(pictureBean.getData().getCourseId());
ToastUtils.showShort(pictureBean.getMsg());
} else {
LogUtil.e(TAG + " request fail or mView is null");
}
} else {
ToastUtils.showShort(pictureBean.getMsg());
}
}
}));
}
}
Presenter实现Contract里的接口,记住不要遗漏构造函数上方的@Injeft标签。
除了onNext方法以外,还有
@Override
public void onError(Throwable e) {
super.onError(e);
}
@Override
protected void onStart() {
super.onStart();
}
@Override
public void onComplete() {
super.onComplete();
}
根据自己需求自行添加。
细节解析:
1.onNext返回的参数PictureBean是用来解析这个接口返回的数据写的实体类,框架会自动将接口返回的数据按照这个实体类去解析,该实体类根据自己的接口返回数据去写,如果你的AS安装了GsonFormat插件,使用插件直接生成一个实体类就行了。
附上代码中用到的实体类代码作为参考:
public class PictureBean {
/**
* errorCode : 0
* msg : success
* data : {"id":1,"url":"asadsa"}
*/
private int errorCode;
private String msg;
private DataBean data;
public int getErrorCode() {
return errorCode;
}
public void setErrorCode(int errorCode) {
this.errorCode = errorCode;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public DataBean getData() {
return data;
}
public void setData(DataBean data) {
this.data = data;
}
public static class DataBean {
/**
* id : 1
* url : asadsa
*/
private int imgId;
private String url;
private int code;
private int chunk;
private int courseId;
private int actualNum;
private int lastChunk;
public int getImgId() {
return imgId;
}
public void setImgId(int imgId) {
this.imgId = imgId;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public int getChunk() {
return chunk;
}
public void setChunk(int chunk) {
this.chunk = chunk;
}
public int getCourseId() {
return courseId;
}
public void setCourseId(int courseId) {
this.courseId = courseId;
}
public int getActualNum() {
return actualNum;
}
public void setActualNum(int actualNum) {
this.actualNum = actualNum;
}
public int getLastChunk() {
return lastChunk;
}
public void setLastChunk(int lastChunk) {
this.lastChunk = lastChunk;
}
@Override
public String toString() {
return "DataBean{" +
"imgId=" + imgId +
", url='" + url + '\'' +
", code=" + code +
", chunk=" + chunk +
", courseId=" + courseId +
", actualNum=" + actualNum +
", lastChunk=" + lastChunk +
'}';
}
}
@Override
public String toString() {
return "PictureBean{" +
"errorCode=" + errorCode +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
6.在代码中使用
这里需要提到几个基类,Activity基类和Fragment基类:
1.SimpleActivity
/**
* Created by codeest on 16/8/11.
* 无MVP的activity基类
*/
public abstract class SimpleActivity extends SupportActivity {
protected Activity mContext;
private Unbinder mUnBinder;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getLayout());
mUnBinder = ButterKnife.bind(this);
mContext = this;
if(Utils.checkLogin(mContext)){
onViewCreated();
App.getInstance().addActivity(this);
initEventAndData();
}
}
protected void setToolBar(Toolbar toolbar, String title) {
toolbar.setTitle(title);
setSupportActionBar(toolbar);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
onBackPressedSupport();
}
});
}
protected void onViewCreated() {
}
@Override
protected void onDestroy() {
super.onDestroy();
App.getInstance().removeActivity(this);
mUnBinder.unbind();
System.gc();
}
protected abstract int getLayout();
protected abstract void initEventAndData();
}
项目中所有Activity的基类,如果不涉及到网络请求,就继承这个,如果涉及到网络请求则继承下面提到的这个。
2.BaseActivity
/**
* Created by codeest on 2016/8/2.
* MVP activity基类
*/
public abstract class BaseActivity<T extends BasePresenter> extends SimpleActivity implements BaseView {
@Inject
protected T mPresenter;
protected LoadingUtil mLoading;
protected ActivityComponent getActivityComponent() {
return DaggerActivityComponent.builder()
.appComponent(App.getAppComponent())
.activityModule(getActivityModule())
.build();
}
protected ActivityModule getActivityModule() {
return new ActivityModule(this);
}
@Override
protected void onViewCreated() {
super.onViewCreated();
initInject();
mLoading = LoadingUtil.getInstance(new ProgressDialogHandler(this, new ProgressCancelListener() {
@Override
public void onCancelProgress() {
//等待框显示时 取消监听
}
@Override
public void onCancelDispose() {
//等待框显示时 返回键监听
}
}, false));
if (mPresenter != null)
mPresenter.attachView(this);
}
@Override
protected void onDestroy() {
if (mPresenter != null)
mPresenter.detachView();
super.onDestroy();
}
@Override
public void showErrorMsg(String msg) {
SnackbarUtil.show(((ViewGroup) findViewById(android.R.id.content)).getChildAt(0), msg);
}
// @Override
// public void useNightMode(boolean isNight) {
// if (isNight) {
// AppCompatDelegate.setDefaultNightMode(
// AppCompatDelegate.MODE_NIGHT_YES);
// } else {
// AppCompatDelegate.setDefaultNightMode(
// AppCompatDelegate.MODE_NIGHT_NO);
// }
// recreate();
// }
@Override
public void stateError() {
}
@Override
public void stateEmpty() {
}
@Override
public void stateLoading() {
}
@Override
public void stateMain() {
}
protected abstract void initInject();
}
3.SimpleFragment
/**
* Created by codeest on 16/8/11.
* 无MVP的Fragment基类
*/
public abstract class SimpleFragment extends SupportFragment {
protected View mView;
protected Activity mActivity;
protected Context mContext;
private Unbinder mUnBinder;
protected boolean isInited = false;
@Override
public void onAttach(Context context) {
mActivity = (Activity) context;
mContext = context;
super.onAttach(context);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
mView = inflater.inflate(getLayoutId(), null);
return mView;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mUnBinder = ButterKnife.bind(this, view);
}
@Override
public void onLazyInitView(@Nullable Bundle savedInstanceState) {
super.onLazyInitView(savedInstanceState);
isInited = true;
initEventAndData();
}
@Override
public void onDestroyView() {
super.onDestroyView();
mUnBinder.unbind();
}
protected abstract int getLayoutId();
protected abstract void initEventAndData();
}
跟Activity一样。
4.BaseFragment
/**
* Created by codeest on 2016/8/2.
* MVP Fragment基类
*/
public abstract class BaseFragment<T extends BasePresenter> extends SimpleFragment implements BaseView {
@Inject
protected T mPresenter;
protected FragmentComponent getFragmentComponent(){
return DaggerFragmentComponent.builder()
.appComponent(App.getAppComponent())
.fragmentModule(getFragmentModule())
.build();
}
protected FragmentModule getFragmentModule(){
return new FragmentModule(this);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
initInject();
mPresenter.attachView(this);
super.onViewCreated(view, savedInstanceState);
}
@Override
public void onDestroyView() {
if (mPresenter != null) mPresenter.detachView();
super.onDestroyView();
}
@Override
public void showErrorMsg(String msg) {
SnackbarUtil.show(((ViewGroup) getActivity().findViewById(android.R.id.content)).getChildAt(0), msg);
}
// @Override
// public void useNightMode(boolean isNight) {
//
// }
@Override
public void stateError() {
}
@Override
public void stateEmpty() {
}
@Override
public void stateLoading() {
}
@Override
public void stateMain() {
}
protected abstract void initInject();
}
5.在Activity中进行网络请求:
public class MainActivity extends BaseActivity<MainPresenter> implements MainContract.View{
@Override
protected void initInject() {
getActivityComponent().inject(this);
}
@Override
protected int getLayout() {
return R.layout.activity_main;
}
@Override
protected void initEventAndData() {
int mills = Utils.getCurrentTimeMills();
Map<String, RequestBody> map = new HashMap<>();
if (course_id != 0) {
map.put("course_id", MultipartBody.create(MultipartBody.FORM, String.valueOf(course_id)));
}
map.put("user_id", MultipartBody.create(MultipartBody.FORM, String.valueOf(SharedPreferencesUtil.getIntDate(Constant.SPKEY_USERID))));
map.put("title", MultipartBody.create(MultipartBody.FORM, ""));
map.put("content", MultipartBody.create(MultipartBody.FORM, new Gson().toJson(mCoursewareInfo)));
map.put("timestamp", MultipartBody.create(MultipartBody.FORM, mills + ""));
map.put("token", MultipartBody.create(MultipartBody.FORM, MD5Utils.md5(String.valueOf(SharedPreferencesUtil.getIntDate(Constant.SPKEY_USERID)) + mills + Constant.KEY)));
mPresenter.saveCoursewareLib(map);
}
@Override
public void responseCoursewareId(int id) {
//saveCoursewareLib的请求回调
}
}
这里用请求saveCoursewareLib方法举例,就是我们上面有过声明的一个请求方法。这里有个带有标签@Inject的方法,里面有一句getActivityComponent().inject(this); 这个方法的调用就需要我们配置Activity组件了,另外会附上Fragment组件的配置代码。
6.Activity组件配置:
@ActivityScope
@Component(dependencies = AppComponent.class, modules = ActivityModule.class)
public interface ActivityComponent {
Activity getActivity();
void inject(MainActivity mainActivity);
}
//移到项目中请另外创建一个类,文章中偷懒写在一起了,并不是上面的内部类
@Module
public class ActivityModule {
private Activity mActivity;
public ActivityModule(Activity activity) {
this.mActivity = activity;
}
@Provides
@ActivityScope
public Activity provideActivity() {
return mActivity;
}
}
7.Fragment组件配置:
@FragmentScope
@Component(dependencies = AppComponent.class, modules = FragmentModule.class)
public interface FragmentComponent {
Activity getActivity();
void inject(CoursewareDraftFragment coursewareDraftFragment);
}
//跟Activity组件一样,偷懒写在一起了
@Module
public class FragmentModule {
private Fragment fragment;
public FragmentModule(Fragment fragment) {
this.fragment = fragment;
}
@Provides
@FragmentScope
public Activity provideActivity() {
return fragment.getActivity();
}
}
//使用:
@Override
protected void initInject() {
getFragmentComponent().inject(this);
}
8.附上全局接口回调定义类,兴许你能用得着
public abstract class CommonSubscriber<T> extends ResourceSubscriber<T> {
private BaseView mView;
private String mErrorMsg;
private boolean isShowErrorState = true;
protected CommonSubscriber(BaseView view) {
this.mView = view;
}
protected CommonSubscriber(BaseView view, String errorMsg) {
this.mView = view;
this.mErrorMsg = errorMsg;
}
protected CommonSubscriber(BaseView view, boolean isShowErrorState) {
this.mView = view;
this.isShowErrorState = isShowErrorState;
}
protected CommonSubscriber(BaseView view, String errorMsg, boolean isShowErrorState) {
this.mView = view;
this.mErrorMsg = errorMsg;
this.isShowErrorState = isShowErrorState;
}
@Override
public void onComplete() {
}
@Override
public void onError(Throwable e) {
if (mView == null) {
return;
}
if (mErrorMsg != null && !TextUtils.isEmpty(mErrorMsg)) {
mView.showErrorMsg(mErrorMsg);
} else if (e instanceof ApiException) {
mView.showErrorMsg(e.getMessage());
} else if (e instanceof HttpException) {
if (NetworkUtils.isConnected())
mView.showErrorMsg("数据请求异常ヽ(≧Д≦)ノ");
else
mView.showErrorMsg("网络异常,请检查网络是否连接ヽ(≧Д≦)ノ");
} else if (e instanceof SocketTimeoutException) {
mView.showErrorMsg("网络不佳,请重新加载!ヽ(≧Д≦)ノ");
} else if (e instanceof JsonParseException) {
mView.showErrorMsg("数据解析错误ヽ(≧Д≦)ノ");
} else {
mView.showErrorMsg("未知错误ヽ(≧Д≦)ノ");
LogUtil.d(e.toString());
}
if (isShowErrorState) {
mView.stateError();
}
e.printStackTrace();
}
}
结语
一个流程走下来,涉及到的东西挺多的,所以刚接触的话可能会有点摸不着北,其实每个接口的定义和使用都是这么一套东西,可以试着自己走一两个接口的全流程,走几遍之后流程熟悉了就好了。熟悉之后你会发现这个框架用起来还是很方便的。
如果这篇文章对你有帮助,点一下免费的赞和关注吧,这些是我更博的动力~
如果遇到问题,欢迎下方留言。
最后推荐给一些想进大厂或者还没有拿到心仪offer的攻城狮们一本书,由大厂java面试官胡书敏编写,满满的干货,助你进到想去的公司。