前言:还是因为公司特别闲,把基础总结总结。如果对相关知识含糊不清,会导致你无法随意扩展你想要的框架和功能。但是觉得作为程序员这行业,只要踏进来了,不是在学习的路上就是在被淘汰的路上,加油!!
本文章将针对所有用户,大家根据自己的所需自行跳过一些章节。首先还是说说这个框架支持干一些什么事:
- 支持所有网络请求类型,get,post,put…(废话了!!Retrofit已经干了所有事情)
- 支持上传文件并监听上传进度
- 支持下载文件和断点下载并监听下载进度
- 有网络时,支持在线缓存(连接网络时的有效期)
- 断开网络,支持离线缓存(离线缓存有效期)
- 多次请求同一url,在网络还在请求时,是否只请求一次
- 支持网络请求失败,自动重连
- 支持取消网络请求
1、先看看一些简单效果(建议打开权限)
get请求 | post请求 | 上传文件 |
---|---|---|
![]() | ![]() | ![]() |
下载文件 | ||
![]() |
好了就这么多,几乎包括了你能用到的功能了。那么接下来我们分别来介绍RxJava是什么?Retrofit是什么?为什么使用MVP?
当然我这里是引导你怎么去学习RxJava和Retrofit,对这2方便本文章不会细将,只是把我封装的这些会讲的特别细,封装RxJava结合Retrofit的MVP的实战框架。正文开始了,初学者得一步一步来哦:
2、RxJava 是什么?
以观察者模式为主的一个响应式编程。响应式编程是一种基于异步数据流概念的编程模式。数据流就像一条河:它可以被观测,被过滤,被操作,或者为新的消费者与另外一条流合并为一条新的流。当然他不只是异步,也有同步。 它最大的有点是可以来回切换线程,不得不说很哇塞!!
直接上干货,组好都看一遍,花不了多长时间:
2.1、抛物线的RxJava讲解,适合初学者
2.2、nanchen简书热门文章,RxJava教程系列
2.3、RxJava所有操作符
个人建议在学习的时候,最好是手敲代码。虽然很多重复代码,但这会让你更加熟悉,甚至你都能拼出这些单词。操作符只建议记住前2篇的就行了,因为实在太多了你也记不住,第3篇适合当api,没事看看,有时候当api查查就好了
3、Retrofit 是什么?
这里我好想吐槽,好想吐槽!
Retrofit就是基于OKHttp封装的,只不过是原作者封装的,以注解的方式配置好就能请求网络了。说实话它或许真的不如一些优秀的OKHttp封装,但是谁让他支持RxJava呢。Retrofit结合RxJava使用简直就是一大利器。刚刚说到了RxJava可以切换线程,你可以把所有耗时操作放在 子线程,到更改ui回调的时候切换到主线程就好了。
下面是Retrofit的干货:
3.1、很详细的 Retrofit 2.0 使用教程,来自Carson_Ho
3.2、Retrofit结合RxJava简单使用
看完了这些相信你对Retrofit有了大致了解。个人建议,最好把get请求,post请求,文件上传都简单跑通。这里稍微说下@Url的用法,里面是一个完整的url地址,会忽略Retrofit设置的baseUrl。同时,Retrofit是封装好的。所以一些请求参数是不会打印的,当然可以通过拦截器打印。
当然在项目中要封装保持一个Retrofit,其中有个.client的方法,可以放一个OkhttpClient。这个时候可以自定义日志拦截器:
public class HttpLogInterceptor implements Interceptor {
//这里省略部分代码,具体看项目
//这里要注意去掉上传文件的请求头部打印,下载文件的返回参数不打印,因为文件太大处理不好会oom,在说是文件内容,不重要
private final Charset UTF8 = Charset.forName("UTF-8");
@Override
public Response intercept(Chain chain) throws IOException {
StringBuffer sbf = new StringBuffer();
Request request = chain.request();
RequestBody requestBody = request.body();
String body = null;
if (requestBody != null) {
Buffer buffer = new Buffer();
requestBody.writeTo(buffer);
Charset charset = UTF8;
MediaType contentType = requestBody.contentType();
if (contentType != null) {
charset = contentType.charset(UTF8);
}
body = buffer.clone().readString(charset);
if (!TextUtils.isEmpty(body)) {
//如果是图片上传调用URLDecoder会报错,即使tryCache都没用,what!!!
String netUrl = request.url().toString();
if (netUrl.contains(SystemConst.DIFFERT_URL)) {
body = "本次请求图片上传或下载,无法打印参数!";
} else {
body = URLDecoder.decode(body, "utf-8");
}
}
}
sbf.append(" \n请求方式:==> " + request.method())
.append("\nurl:" + request.url())
.append("\n请求头:" + request.headers())
.append("\n请求参数: " + body);
Response response = chain.proceed(request);
String rBody = "";
String netUrl = request.url().toString();
if (!netUrl.contains(SystemConst.QQ_APK)) {
ResponseBody responseBody = response.body();
BufferedSource source = responseBody.source();
source.request(Long.MAX_VALUE);
Buffer buffer = source.buffer();
Charset charset = UTF8;
MediaType contentType = responseBody.contentType();
if (contentType != null) {
try {
charset = contentType.charset(UTF8);
} catch (UnsupportedCharsetException e) {
e.printStackTrace();
}
}
rBody = buffer.clone().readString(charset);
if (!TextUtils.isEmpty(rBody)) {
rBody = decodeUnicode(rBody);
}
}
sbf.append("\n收到响应: code ==> " + response.code())
.append("\nResponse: " + rBody);
LogUtils.i("网络请求", sbf.toString());
return response;
}
}
看看打印出来的效果,很清晰哦:
看这一点,要打印请求参数,返回参数,head等信息还是要通过okhttp的拦截器去做。这是吐槽Retrofit封装的一点
4、MVP开发框架
首先是我个人观点。此框架适合大型一点项目,且此框架是代码逻辑简洁,清晰,分离出视图层和Model层逻辑处理,适合做单元测试,且使activity里的代码减少,而且不会因为有些线程,异步的处理,有activity的引用导致无法回收,引起一系列问题。还是要强调一下只是代码逻辑简洁,不是代码简洁。因为他要增加很多类。那么来说说怎么代码逻辑简洁(适合其他人接手项目,和团队开发):
4.1、先看看项目的分包
首先google官方demo,也是把功能页面模块化了。这样你看我的分包,结合我的图片展示一看就明白
- downfragemnt---->就是下载文件的页面
- getfragment----->get请求的页面
- main------------>mianActivity页面
- postfragment----->post请求页面
- uploadfragment---->上传文件页面
是不是一看就很清楚,比如getfragment的所有东西都在里面。当然你还可以在里面继续分包
4.2、在来看看契约类,个人觉得更加清晰,我们来看看get的器乐类GetContract
public interface GetContract {
//view只有2个更新ui的方法
interface View extends BaseView {
// 1、获取get数据,更新ui
void showGetData(String data);
// 2、获取get数据失败,更新ui
void showGetError(String msg);
}
//get的prensenter只有一个获取get数据的数据请求
interface Prensenter {
// 1、开启get网络请求
public void getGetData(String params);
}
}
比如你要看一个页面有什么更新UI的操作或者有什么网络请求,只要看契约类就对这个页面很了解了如:
1、view就是相当于MVP的V层,一般被activity或者Fragment实现,可以看到view里有2个方法:
- showGetData 获取get请求数据,更新UI
- showGetError get请求失败了,更新失败UI
- BaseView 放的都是通用的方法,这里放了2个:showLoading,hideLoading(当然有些网络请求不需要转loading,那么不调用就好了)
2、Prensenter就是MVP的P层需要使用到的方法,要搞清楚,其实还有一个实体类GetPresenter。实体类实现GetContract.Prensenter。可以看到里面就一个getGetData。的网络请求。为什么这么写,第一契约类可以减少类的数目,第二更重要的一点是你可以很快的清楚你开发模块的功能和操作
还有一点要注意,因为使用RxJava结合Retrofit,我把通用的方法全部封装了BasePresenter里。因为RxJava的链式编程,所以我直接省略掉了model层,当然你可以写一个接口,然后写个model层把数据回调给Presenter。这里要注意MVP,MVP只是开发框架,是我们借鉴的开发思路。可能100个人有100个不同的MVP框架,这里我是说说我的想法,大家可以借鉴和学习。
5、RxJava结合Retrofit的MVP
5.1、封装Retrofit,其实和OkHttp没有区别
public class RetrofitManager {
//省略部分代码,便于理解
private static RetrofitManager retrofitManager;
private OkHttpClient okHttpClient;
private Retrofit retrofit;
//这是我retrofit的网络请求配置
private RetrofitApiService retrofitApiService;
private RetrofitManager() {
//初始化Okhttp
initOkHttpClient();
//初始化retrofit
initRetrofit();
}
public static RetrofitManager getRetrofitManager() {
if (retrofitManager == null) {
synchronized (RetrofitManager.class) {
if (retrofitManager == null) {
retrofitManager = new RetrofitManager();
}
}
}
return retrofitManager;
}
private void initRetrofit() {
retrofit = new Retrofit.Builder()
.baseUrl(SystemConst.DEFAULT_SERVER)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.client(okHttpClient)
.build();
retrofitApiService = retrofit.create(RetrofitApiService.class);
}
public static RetrofitApiService getApiService() {
return retrofitManager.retrofitApiService;
}
private void initOkHttpClient() {
okHttpClient = new OkHttpClient.Builder()
//设置缓存文件路径,和文件大小
.cache(new Cache(new File(Environment.getExternalStorageDirectory() + "/okhttp_cache/"), 50 * 1024 * 1024))
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.writeTimeout(10, TimeUnit.SECONDS)
.addInterceptor(new HttpLogInterceptor())
//设置在线和离线缓存
.addInterceptor(OfflineCacheInterceptor.getInstance())
.addNetworkInterceptor(NetCacheInterceptor.getInstance())
.build();
}
}
5.2、比如我们来写GetFragment这块。首先写契约类
回想之前我们有个BaseView。里面有2个方法showLoading和hideLoading (只要记住大家通用的方法封装成base,有些页面不同就不调用就行了)
代码如下:
public interface BaseView {
//显示正在加载loading
void showLoading(String message);
// 关闭正在加载loading
void hideLoading();
//防止RxJava内存泄漏,可以暂且忽略
LifecycleTransformer bindLifecycle();
}
那么GetFragment契约类就是(还要说下这里的presenter接口只是GetPresenter所要用的方法,放在契约类里,一目了然):
public interface GetContract {
//view只有2个更新ui的方法
interface View extends BaseView {
// 1、获取get数据,更新ui
void showGetData(String data);
// 2、获取get数据失败,更新ui
void showGetError(String msg);
}
//get的prensenter只有一个获取get数据的数据请求
interface Prensenter {
// 1、开启get网络请求
public void getGetData(String params);
}
}
5.3、接下来是GetPresenter的实体类,当然这里有个BasePresenter(以下讲解移除部分代码更清晰)
我们想象有什么通用方法是在BasePresenter里的?当然你可以把一些复杂的写很多步骤的网络请求封装在这里,比如我们项目里的下载文件等,这里我们讲简单的通用方法
GetPresenter需要view的引用
所以有:1、setView加入引用 2、view = null置空引用,防止oom
//这个是为了退出页面,取消请求的
public CompositeDisposable compositeDisposable;
// 绑定的view
private V mvpView;
//绑定view,一般在初始化中调用该方法
public void attachView(V mvpView) {
this.mvpView = mvpView;
compositeDisposable = new CompositeDisposable();
}
//置空view,一般在onDestroy中调用
public void detachView() {
this.mvpView = null;
//退出页面的时候移除所有网络请求
removeDisposable();
}
//需要退出页面移除网络请求的加入进来
public void addDisposable(Disposable disposable) {
compositeDisposable.add(disposable);
}
//退出页面移除所有网络请求
public void removeDisposable() {
if (compositeDisposable != null) {
compositeDisposable.dispose();
}
}
使用一个方法快速获取RetrofitManager里的网络配置
public RetrofitApiService apiService() {
return RetrofitManager.getRetrofitManager().getApiService();
}
大部分的网络请求都有showLoading和hideLoading,还有线程切换,封装进来
//这里我多加了个是否显示loading的标识和loading上的文字,想偷懒可以用方法重载把这2个参数默认
public <T> Observable<T> observe(Observable<T> observable, final boolean showDialog, final String message) {
return observable.subscribeOn(Schedulers.io())
.doOnSubscribe(new Consumer<Disposable>() {
@Override
public void accept(Disposable disposable) throws Exception {
if (showDialog) {
mvpView.showLoading(message);
}
}
})
.subscribeOn(AndroidSchedulers.mainThread())
.doFinally(new Action() {
@Override
public void run() throws Exception {
if (showDialog) {
mvpView.hideLoading();
}
}
})
.observeOn(AndroidSchedulers.mainThread())
//防止RxJava内存泄漏
.compose(mvpView.bindLifecycle());
}
那么我们的BasePresenter(移除部分代码更清晰)
//这里加上泛型,第一在使用Presenter一眼就看出对应哪个View,其次确定我们V的类型
public abstract class BasePresenter<V extends BaseView> {
public CompositeDisposable compositeDisposable;
private V mvpView;
public void attachView(V mvpView) {
this.mvpView = mvpView;
compositeDisposable = new CompositeDisposable();
}
public void detachView() {
this.mvpView = null;
removeDisposable();
}
//检查是否有view的引用
public boolean isViewAttached() {
return mvpView != null;
}
//获取view的引用
public V getView() {
return mvpView;
}
public RetrofitApiService apiService() {
return RetrofitManager.getRetrofitManager().getApiService();
}
//需要退出页面移除网络请求的加入进来
public void addDisposable(Disposable disposable) { }
//退出页面移除所有网络请求
public void removeDisposable() {}
//省的写线程切换和showloading和hide的复杂操作
public <T> Observable<T> observe(Observable<T> observable, final boolean showDialog, final String message) {}
}
那么GetPresenter就是这样
//省略部分代码,便于理解。实体GetPresenter实现GetContract.Prensenter接口,里面就一个getGetData方法
public class GetPresenter extends BasePresenter<GetContract.View> implements GetContract.Prensenter {
@Override
public void getGetData(String params) {
if (!isViewAttached()) {
//如果没有View引用就不加载数据
return;
}
//BasePresenter里有了封装,切换线程和放置内存泄漏的 .compose(mvpView.bindLifecycle())都不用写了
//代码越能偷懒,说明框架越是封装的完美
observe(apiService().getGank(params))
.subscribe(new Consumer<GankFatherBean>() {
@Override
public void accept(GankFatherBean gankFatherBean) throws Exception {
getView().showGetData(gankFatherBean.getTitle());
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
getView().showGetError(throwable.toString());
}
});
}
}
5.4、其次是BaseFragment
因为使用RxJava,如果使用不当很容易造成RxJava内存泄漏,所以官方也出了方法继承RxFragment
因为fragment是作为view是要实现view接口的,同时每个fragment都要有presenter去调用方法
初步如下(省了部分代码,便于理解):
public abstract class BaseFragment<T extends BasePresenter> extends RxFragment implements BaseView {
public T mPresenter;
public abstract T cretaePresenter();
protected View mContentView;
private Unbinder mUnbinder;
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// 避免多次从xml中加载布局文件
mPresenter = cretaePresenter();
if (mPresenter != null) {
mPresenter.attachView(this);
}
return mContentView;
}
@Override
public void onDestroy() {
super.onDestroy();
if (mPresenter != null) {
mPresenter.detachView();
mPresenter = null;
}
}
@Override
public void showLoading(String message) {
LoadingDialog.getInstance().show(getActivity(), message);
}
@Override
public void hideLoading() {
LoadingDialog.getInstance().dismiss();
}
//防止Rx内存泄漏
@Override
public LifecycleTransformer bindLifecycle() {
LifecycleTransformer objectLifecycleTransformer = bindToLifecycle();
return objectLifecycleTransformer;
}
}
5.5、在GetFragment里使用就是这样(省略部分代码,便于理解)
public class GETFragment extends BaseFragment<GetPresenter> implements GetContract.View {
@BindView(R.id.txt_content) TextView txt_content;
@Override
public GetPresenter cretaePresenter() {
return new GetPresenter();
}
@Override
public int getContentViewId() {
return R.layout.fragment_get;
}
//处理逻辑
@Override
protected void processLogic(Bundle savedInstanceState) {
}
@OnClick(R.id.txt_get)
public void getClick() {
//请求网络
mPresenter.getGetData("Android");
}
@Override
public void showGetData(String data) {
txt_content.setText(data);
}
@Override
public void showGetError(String msg) {
ToastUtils.showToast(msg);
}
}
6、断网重连
因为使用了强大的RxJava,这里断网重连用的就是操作符retryWhen,封装在BasePresenter里的observeWithRetry。这里就不多讲了,可以先把操作符retryWhen了解清楚就看明白了
7、取消网络请求
这里的返回值就是一个disposable,如果主动取消就直接调 disposable.dispose();当然我这里也封装了离开页面取消请求你可以把它加到addDisposable(Disposable disposable)里。就不用管了
Disposable disposable = observe(apiService().getGank(params))
.subscribe(new Consumer<GankFatherBean>() {
@Override
public void accept(GankFatherBean gankFatherBean) throws Exception {
getView().showGetData(gankFatherBean.getTitle());
}
}, new Consumer<Throwable>() {
@Override
public void accept(Throwable throwable) throws Exception {
getView().showGetError(throwable.toString());
}
});
8、多次请求同一网络,只请求一次
这里是在RetrofitManage里放了一个private ArrayList oneNetList;请求网络的时候把,方法名当成tag加入进去,如果请求的时候判断下,oneNetList里如果已经有次tag,则直接return,在请求成功或者失败的时候移除tag
9、在线缓存和离线缓存
这里还是利用了okhttp的拦截器,所以这里再次吐槽下Retrofit。
- 离线缓存拦截器 OfflineCacheInterceptor
- 在线缓存拦截器 NetCacheInterceptor
这里不清楚的可以去看我之前的一篇文章EasyOk。讲的很详细,简直就是一模一样
本项目具体用法在,GetPresenter里有。
10、上传文件进度监听
因为用Restrofit上传文件是这样的。
@POST
@Multipart
Observable<ResponseBody> uploadPic(@Url String url, @Part MultipartBody.Part file);
参数是MultipartBody.Part。不监听进度参数就是这样写的
RequestBody requestFile = RequestBody.create(MediaType.parse("multipart/form-data"), file);
MultipartBody.Part body = MultipartBody.Part.createFormData("file", file.getName(), requestFile);
如果要监听进度,就要重写RequestBody,把文件写入进度返回出来,其实和EasyOk的思路是一模一样的,本项目上传文件相关类在:
retrofitwithrxjava.uploadutils的包里。用法在postfragment里。其中包括多张图片监听,不同key,不同图片。和同一key,不同图片
11、下载文件进度监听及断点下载
首先我们看下载文件请求
@GET
@Streaming //10以上用@streaming。不会造成oom,反正你用就是了
Observable<ResponseBody> downloadFile(@Url String url);
返回的是ResponseBody,是不是灵感来了,我们可以重写一个FileDownLoadObserver观察者,然后利用map操作符,对ResponseBody进行转换,转换成File的FileDownLoadObserver,当然有了ResponseBody,文件写入可以在FileDownLoadObserver里。有了文件写入,那么进度就来了。本项目下载文件相关类在:retrofitwithrxjava.downloadutils包里。用法在downloadfragment里。
断点下载
我们知道断点下载,其实就是head里加个RANG的字段,其他都是一模一样的:
@GET
@Streaming
Observable<ResponseBody> downloadFile(@Url String url, @Header("RANGE")String range);
这个字段是这样的:
String range = “bytes=” + currentLength + “-”;
currentLength是你下载文件里,剩下没下完的长度,你可以通过file.length获取。然后读写的时候fos = new FileOutputStream(file, true),输出流必须带第二个参数true,意思就是继续拼接的意思。全部功能都讲完了。没有讲的太细是因为我觉得大部分额外功能都是重写,而且都是利用okhttp,其实看过了我以前的EasyOk。其实就明白了。这也是我单独吐槽Retrofit的原因。我相信封装的很完美的okhttp,肯定勇气来好用,可惜人家支持Rxjava。不过通过这个RANGE,其实要下载一个文件的话可以分很多端,因为看他的字面意思就知道了"bytes=start-end",指的是一段范围,我上面写的是"bytes=start-"就是默认下载完,所以有些app开启多线程下载,包括迅雷开启vip就下的快,其实就是给你多开了几条线程下载了。!!