老生常谈

  • 什么是 Retrofit
  • Retrofit 早已不是什么新技术了,想必看到这篇博客的大家都早已熟知,这里就不啰嗦了,简单介绍下:

  • Retrofit 是一个针对 Java 和 Android 的设计的 REST 客户机。它通过基于 REST 的 web 服务检索和上传 JSON (或其他结构化数据)变得相对容易。在使用中,您可以配置用于数据序列化的转换器。对于 JSON ,通常使用Gson ,但是可以添加自定义转换器来处理 XML 或其他协议。Retrofit 对 HTTP 请求使用 OkHttp 库。

A type-safe HTTP client for Android and Java

  • 好了介绍结束,想必大家的大刀都饥渴难耐了,那么我们直接开始吧

本文流程

依赖注入

  • so Easy 不用说了吧
  • 在 app module 下的 build.gradle 中添加以下依赖:
// OkHttp3
api 'com.squareup.okhttp3:okhttp:3.10.0'
api 'com.squareup.okio:okio:1.8.0'
// Retrofit
api 'com.squareup.retrofit2:retrofit:2.7.0'
// Gson 服务器数据交互
api 'com.google.code.gson:gson:2.8.6'

依赖注入很简单, Retrofit 一直是结合 OkHttp 和 Gson(无所谓什么 JSON 解析器都行,这里就用 Gson 了)

我这里专门找了最新的版本库,so~ 大家直接用即可

  • 别急,前面也说了 Retrofit 是结合 OkHttp 做网络请求用的,所以悉心提醒记得开下网络权限:
<uses-permission android:name="android.permission.INTERNET" />

全面进击

  • 网上关于 Retrofit 的教程可谓琳瑯满目,但是总给人一种云里雾里的感觉
  • 所以本文的亮点就在于,我会通过我自己实际项目的代码来给大家介绍 Retrofit 到底牛在哪

Retrofit 开始之前

  • 这里我将以我的一个开源项目 FIWKeepApp 的登录模块举例
  • Retrofit 出现之前,原始社会的我们一般是这样进行网络请求的:
    public void login2() {
OkHttpClient okHttpClient = new OkHttpClient();
//Form表单格式的参数传递
FormBody formBody = new FormBody
.Builder()
//设置参数名称和参数值
.add("username",mAccountEdit.getText().toString())
.add("password",mPasswordEdit.getText().toString())
.build();
Request request = new Request
.Builder()
//Post请求的参数传递
.post(formBody)
.url("http://hyh.hljdx.net:8080/SitUpWebServer/login")
.build();
okHttpClient.newCall(request).enqueue(new Callback() {
@Override
public void onFailure(okhttp3.Call call, IOException e) {
Log.d("my_Test", e.getMessage());
} @Override
public void onResponse(okhttp3.Call call, Response response) throws IOException {
String result = response.body().toString();
UserBean userBean = JSON.parseObject(result, UserBean.class);
Log.d("my_Test",userBean.getUser_head_img());
response.body().close();
}
});
}
  • 有没有一种云里雾里的感觉?
  • 首先你得先将要发送的表单信息封装为 Post 请求的 Body 对象,那么有的同学会问什么是 POST ,什么是 Body?这个问题建议大家 Google 下,这里我建议大家学一些后端或者计网的知识,很简单也很有必要
  • 接着你需要再封装一个 Request 对象,也就是我们的请求体,在这里设置信息要提交到哪去
  • 最后调用 okHttpClient 的相应方法,将前面实现的东西组合发送,并在回调里接收
  • 所以,这一步步,又是封装 FormBody 又是封装 Request ,搞了半天还要用 okHttpClient 发送,一套下来头晕眼花,那么如何解决呢?
  • 那么 Retrofit 救世主就出现了

Retrofit 实现

  • 还是我项目中的登录模块,我将其改为 Retrofit 的形式
  • 同样完成上面的功能,如果用 Retrofit 实现只需要:
    // baseUrl() 设置路由地址
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl(ApiUtils.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build(); // 设置参数
Call<UserBean> call = retrofit.create(UserMgrService.class)
.login( mAccountEdit.getText().toString(),
mPasswordEdit.getText().toString()); // 回调
call.enqueue(new Callback<UserBean>() {
@Override
public void onResponse(Call<UserBean> call, Response<UserBean> response) {
Log.d("123123", "msg--" + response.body().getUser_head_img());
} @Override
public void onFailure(Call<UserBean> call, Throwable t) {
// 失败时做处理
}
});
  • 如上就实现了和纯 okHttp 代码一样的功能
  • 大家可能会觉得,这也没简单多少啊 ?但细心观察发现,第一步 Retrofit 的实例化过程,只要服务器不换代码几乎是不变的,所以我们完全可以将它封装

  • 而且大家有没有发现,如果单单使用 OkHttp 我们的返回值是一个 Response 对象,我们还需要在其中提取相应 JSON 对象,进行类型转换,而在 Retrofit 中,由于使用了数据解析器,所以这一大块代码都省略了
  • 还有很多优点,这里就不唠叨了,我们直接开始学习使用之路吧!

实现流程

  • 那么现在就给大家解释下使用的每个步骤

创建接口

  • 首先我们要创建 UserMgrService 接口
/**
* @author fishinwater-1999
* @version 2019-12-21
*/
public interface UserMgrService { /**
* GET 用 Query
*/
@GET("login")
Call<UserBean> login(@Query("username") String username, @Query("password") String password); }
  • @GET() 注解就可以猜到,这将会是一个 Get 请求
  • 我们在看方法体,返回值会是一个封装了 UserBeanCall<> 对象
  • 参数有两个,分别是 String usernameString password
  • 与平常方法不同的是,这两个参数各自带上了 @Query("...") 注解
  • 通过 @Query("...") 里的参数我们发现,这与 okHttp 创建 FormBody 时,add 的参数不谋而合

看到这里想必大家都明白了,如果大家还不明白什么是 Get 请求,以及 @Query("...") 里的 username 和 password 是怎么的话,我这里简单说下

比如说我们现在随便打开一个网页,就拿百度图片里搜索 Github 页面为例:

  • 后端写服务器的同学会通过这些参数,像 HashMap get(“key”) 方法取值一样拿出来

POST

  • 这样解释,想必大家就明白了
  • 除了 GET 方法之外 还有一种 POST 方法,相比于使用 GET ,使用 POST 有很多其他的优点,这里就不多说了
  • 他使用和 GET 的思路一样,如果用 POST 那么我们的代码将会是这样的:
public interface UserMgrService {

    /**
* POST 用 Field
*/
@POST("login")
@FormUrlEncoded
Call<UserBean> login(@Field("username") String username, @Field("password") String password); }
  • 就是把注解换了套名字,然后在 @POST("...") 下再加上一个 @FormUrlEncoded 注解
  • 这里就不多说了,我们直接进入下一步

生成 Retrofit 对象

  • 我们先看下怎么创建和设置的:
// baseUrl() 设置路由地址
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl(ApiUtils.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
  • 这里主要是两步,设置 baseUrl 、设置数据解析器
  • 老样子什么是 baseUrl ?就拿我之前用 OkHttp 设置的那个 url 为例
http://hyh.hljdx.net:8080/SitUpWebServer/login
  • 大家可以这么理解:上面的这个 url = baseurl + @GET("...") 注解里传入的字符串
  • 如果我们前面设置的是 @GET("login") 那这里 baseurl 就是:http://hyh.hljdx.net:8080/SitUpWebServer/ 是不是一下子就明白了,但是其他博客不照顾新人,从没说清楚
  • 然后就是数据解析器,大家应该还记得刚开始的时候我们导入了一个三方库:
// Gson 服务器数据交互
api 'com.google.code.gson:gson:2.8.6'
  • 我们和服务器的数据,都是以 JSON 的形式交互的,比如 Bing 每日壁纸接口

  • 设置了这个数据解析器,就可以把返回的信息自动封装为相应的对象,明白了吧

具体这个对象怎么获得,大家可以联系后端,或者百度搜下 JsonFormat 插件使用或者 JSON 对象生成器,门路很多这里都告诉你们啦

生成接口对象

  • 老样子,先看看代码
UserMgrService service = retrofit.create(UserMgrService.class);
  • 过于简单,调用前面 retrofit 对象的 create() 方法传入接口的 class 文件即可

获得 Call 对象

  • 由刚开始的代码我们知道
  • 我们向服务器发送请求需要调用 call 对象的 enqueue() 方法
  • 那么 Call 对象怎么获得呢?其实很简单:
Call<UserBean> call = service.login( mAccountEdit.getText().toString(), mPasswordEdit.getText().toString());
  • 说白了就是,直接调用接口的相应方法,他返回的直接就是一个 Call 对象

发送请求

  • 请求分两种 同步的和异步的

  • 由于请求是耗时的,假设我们发送同步请求 ,在请求就过返回之前,应用界面会进去阻塞状态
  • 说白了就是会卡,甚至卡死。。。所以说这种请求很少用到
  • 虽然不用,但负责的我还是也给大家代码:
Response<UserBean> response = call.execute();
Log.d("123123", "msg--" + response.body().getUser_head_img());
  • 具体就不说了,就是调用 callexecute() 会返回一个值
  • 这个值就是请求结果,大家直接用就是( 但是在这个只没返回,比如网速慢时,手机会卡在那动不了甚至 ANR
  • 这里我介绍下异步请求:
// 回调
call.enqueue(new Callback<UserBean>() {
@Override
public void onResponse(Call<UserBean> call, Response<UserBean> response) {
Log.d("123123", "msg--" + response.body().getUser_head_img());
} @Override
public void onFailure(Call<UserBean> call, Throwable t) {
// 失败时做处理
}
});
  • 这就是异步方法,直接调用 callenqueue 方法,传入一个 Callback 接口即可
  • 调用后系统自动释放资源,不会阻塞,等到请求结果返回时
  • 就会自动调用 onResponse 方法,方法 里的 response 就是处理好的结果
  • 本文代码运行后结果 Demo Example 是不是特别简单!

登录功能实战

  • 到这里想必大家都已经学会了 Retrofit 的使用
  • 那么现在我就拿登录功能举例,看看如何在项目中引用 Retrofit
  • 实战部分先置条件是 MVP + ButterKnife,大家很容易在网上找到资料,这就不赘述了

搭建 Model 层

  • 创建接口 ILoginModel
  • 接口对外暴露 username password 和 一个监听回调接口 (接口通过泛型传入)
/**
* @author fishinwater-1999
* @version 2019-11-12
*/
public interface IBaseLog<L> { /**
* 登录 Api
* @param userAccount
* @param mPassword
* @param loginCallback
*/
void login(String userAccount, String mPassword, L loginCallback); }
  • 实现回调接口
  • 观察者模式,当请求信息返回后动态通知 P 层
/**
* @author fishinwater-1999
* @version 2019-12-23
*/
public interface IBaseRetCallback<T> { void onSucceed(Response<T> response); void onFailed(Throwable t); }
  • 创建 LoginModel 实现 ILoginModel 接口
  • 实现 login 方法,请求成功后回调 IBaseRetCallback 监听
/**
* @author fishinwater-1999
* @version 2019-11-12
*/
public class LogViewModel implements IBaseLog<IBaseRetCallback<UserBean>> { private final String TAG = "LogViewModel"; @Override
public void login(String userAccount, String userPassword, final IBaseRetCallback<UserBean> retCallback) {
// baseUrl() 设置路由地址
Retrofit retrofit = new Retrofit
.Builder()
.baseUrl(ApiUtils.BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
// 设置参数
UserMgrService service = retrofit.create(UserMgrService.class);
retrofit2.Call<UserBean> call = service.login( userAccount, userPassword);
// 回调
call.enqueue(new Callback<UserBean>() {
@Override
public void onResponse(retrofit2.Call<UserBean> call, Response<UserBean> response) {
retCallback.onSucceed(response);
} @Override
public void onFailure(retrofit2.Call<UserBean> call, Throwable t) {
// 失败时做处理
retCallback.onFailed(t);
}
});
} }

搭建 Presenter 层

  • 首先实现 Presenter 层基类
  • 同样的,要搭建 Presenter 层基类,首先要实现器接口
/**
* @author fishinwater-1999
* @version 2019-11-12
*/
public interface IBasePresenter<V> { /**
* 绑定
* @param mLogView
*/
void attachView(V mLogView); /**
* 解绑
*/
void detachView(); /**
* 登录
* @param userName
* @param userPassword
* @param resultListener
*/
void login(String userName, String userPassword, V resultListener); }
  • 编写抽象类 BasePresenter 实现 IBasePresenter 接口
/**
* @author fishinwater-1999
* @version 2019-11-12
*/
public abstract class BasePresenter<V> implements IBasePresenter<V> { private V view; @Override
public void attachView(V mLogView) {
this.view = mLogView;
} @Override
public void detachView() {
this.view = null;
} @Override
public V getLoginVew() {
return this.view;
} }
  • 然后就到了我们具体的 LogPresenter 类的实现
  • LogPresenter 类需要持有 View 层和 Model 层接口
/**
* @author fishinwater-1999
* @version 2019-11-12
*/
public class LogPresenter extends BasePresenter<ILoginView> { private IBaseLog logViewModel; public LogPresenter(IBaseLog logViewModel) {
this.logViewModel = logViewModel;
} @Override
public void login(String userName, String userPassword, final ILoginView iLoginView) {
logViewModel.login(userName, userPassword, new IBaseRetCallback<UserBean>() {
@Override
public void onSucceed(Response<UserBean> response) {
UserBean userBean = response.body();
if (userBean != null) {
String user_id = userBean.getUser_id();
iLoginView.showLoginSuccess(user_id);
}
} @Override
public void onFailed(Throwable t) {
iLoginView.showLoginFailed(ILoginView.ErrCode.WRONG_NET_WORK);
}
}); }
}
  • 上面的代码中,构造方法 LogPresenter 持有了 Model 层
  • 同时暴露了 login(..., ..., Listener) 接口,可供调用者调用

View 层实现

  • View 层负责实例化 Model 层,并与 Presenter 层绑定
  • 老样子,创建 BaseFragment<V , P extends IBasePresenter<V>> 基类
/**
* @author fishinwater-1999
* @version 2019-11-12
*/
public abstract class BaseFragment<V , P extends IBasePresenter<V>> extends Fragment { /**
* Presenter 层
*/
private P mBaseResister; @Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 自动绑定
if (mBaseResister == null) {
mBaseResister = createProsenter();
}
} /**
* 在这里确定要生成的 Presenter 对象类型
* @return
*/
public abstract P createProsenter(); /**
* 获得 Presenter 对象
* @return
*/
public P getPresenter() {
if (mBaseResister == null) {
createProsenter();
}
return mBaseResister;
} /**
* 碎片销毁时解绑
*/
@Override
public void onStop() {
super.onStop();
mBaseResister = null;
}
}
  • 实现 View 层逻辑
  • View 层只负责用户界面响应
/**
* @author fishinwater-1999
*/
public class LoginFragment extends BaseFragment<ILoginView, LogPresenter> implements ILoginView { private static final String TAG = "LoginFragment"; private LogViewModel mLogViewModel; private LoginFragmentBinding binding; @Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
binding = DataBindingUtil.inflate(inflater, R.layout.login_fragment, container, false);
View view = binding.getRoot();
binding.setLogCallback(getLogActivity());
binding.setFragment(this);
return view;
} @Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
if (mLogViewModel == null) {
mLogViewModel = new LogViewModel();
}
} public void login(View v) {
getPresenter().login(
getUserName(),
getUserPwd(),
this);
} @Override
public LogPresenter createPresenter() {
if (mLogViewModel == null) {
mLogViewModel = new LogViewModel();
}
return new LogPresenter(mLogViewModel);
} @Override
public String getUserName() {
return binding.userAccount.getText().toString();
} @Override
public String getUserPwd() {
return binding.userPassword.getText().toString();
} @Override
public void showLoginSuccess(String response) {
Toast.makeText(getActivity(), "登录成功", Toast.LENGTH_LONG).show();
SharedPreferencesUtil.putString(getActivity(), SharedPreferencesUtil.PRE_NAME_SITUP, SharedPreferencesUtil.USER_ID, response);
ARouter.getInstance().build(RouteUtils.MainActivity).navigation();
getActivity().finish();
} @Override
public void showLoginFailed(ErrCode errCode) {
if (errCode == ErrCode.WRONG_USER_NAME) {
Toast.makeText(getActivity(), "用户名错误", Toast.LENGTH_LONG).show();
}else if (errCode == ErrCode.WRONG_USER_PWD){
Toast.makeText(getActivity(), "密码错误", Toast.LENGTH_LONG).show();
}else if (errCode == ErrCode.WRONG_NET_WORK) {
Toast.makeText(getActivity(), "未知,请检查网络", Toast.LENGTH_LONG).show();
}
}
}
  • 这里我使用了 DataBinding 的形式,对数据进行绑定
  • 当然,你也可以选用 ButterKnife 等优秀的三方库
  • 那么为什么我选 DataBinding 呢?亲儿子 懂吧? /坏笑

运行

  • 关于 测序的大致便是如此了
  • 至于细枝末节的东西大家可以直接到这个库里面看,地址在文末

更多模块实战 FIWKeepApp

  • 这里我将上述过程写在我的 Demo 里,地址在 GitHub 大家可以直接查看改仓库源码,记得给我点个 star 哦~:

  • Demo 地址:FIWKeepApp - LoginFragment

总结

  • 想必看到这儿的读者对 Retrofit 的使用都已近有了一定的了解,但 Retrofit 的好处并不只是这些,还有很多跟深入的只是需要了解,但本文限于篇幅,无法向大家一一介绍
  • 对于我前面的 FIWKeepApp 这个仓库,我将一步步转换到 Retrofit + OkHttp 的形式下,欢迎大家关注我的 这个仓库,进行学习,也欢迎各位老铁给个 star
  • 后面我还会对 Android 的各种知识点、Framework 层源码,三方库等进行解析,欢迎大家关注 _yuanhao 博客园 及时接收更多优质博文!

「2020 新手必备 」极速入门 Retrofit + OkHttp 网络框架到实战,这一篇就够了!的更多相关文章

  1. Solution -「2020.12.26」 模拟赛

    0x00 前言 一些吐槽. 考得很变态诶,看每道题平均两秒的时限就知道了... T1 降智了想到后缀懒得打. T2 口胡了假优化,结果和暴力分一样?? T3 黑题还绑点?? \(50 + 80 + 0 ...

  2. [新手必备]Python 基础入门必学知识点笔记

    Python 作为近几年越来越流行的语言,吸引了大量的学员开始学习,为了方便新手小白在学习过程中,更加快捷方便的查漏补缺.根据网上各种乱七八糟的资料以及实验楼的 Python 基础内容整理了一份极度适 ...

  3. 《IM开发新手入门一篇就够:从零开发移动端IM》

        登录 立即注册 TCP/IP详解 资讯 动态 社区 技术精选 首页   即时通讯网›专项技术区›IM开发新手入门一篇就够:从零开发移动端IM   帖子 打赏 分享 发表评论162     想开 ...

  4. LaTeX 有哪些「新手须知」的内容?

    孟晨 ,在 LaTeX 话题下写错 LaTeX 名字的,一律… 陈硕等 137 人赞同 这是个好问题,虽然提问提得很大.不是很好答,权当抛砖引玉了. 天字第一号原则:不要到网上抄代码,尤其是似懂非懂的 ...

  5. 「编程羽录」上线,程序员必备的这些技能你能get到嘛?

    大家好,我是小羽. 好久不见,给大家带来个好消息,小羽的全新专题「编程羽录」系列正式上新,主要是介绍一些关于面试题和经验总结的文章. 会为大家提供一些技术栈之外,程序员还需要的其他方面硬核知识,做到全 ...

  6. [译]聊聊C#中的泛型的使用(新手勿入) Seaching TreeVIew WPF 可编辑树Ztree的使用(包括对后台数据库的增删改查) 字段和属性的区别 C# 遍历Dictionary并修改其中的Value 学习笔记——异步 程序员常说的「哈希表」是个什么鬼?

    [译]聊聊C#中的泛型的使用(新手勿入)   写在前面 今天忙里偷闲在浏览外文的时候看到一篇讲C#中泛型的使用的文章,因此加上本人的理解以及四级没过的英语水平斗胆给大伙进行了翻译,当然在翻译的过程中发 ...

  7. Note -「Mobius 反演」光速入门

    目录 Preface 数论函数 积性函数 Dirichlet 卷积 Dirichlet 卷积中的特殊函数 Mobius 函数 & Mobius 反演 Mobius 函数 Mobius 反演 基 ...

  8. Python 极速入门指南

    前言 转载于本人博客. 面向有编程经验者的极速入门指南. 大部分内容简化于 W3School,翻译不一定准确,因此标注了英文. 包括代码一共两万字符左右,预计阅读时间一小时. 目前我的博客长文显示效果 ...

  9. Let’s do this!新手程序员的入门指南(转)

    计算机科学(Computer Science)无疑是现在最热门的学科之一,这领域的工作薪水高.工作时间弹性,而且科技业对工程师.开发者的需求至今有增无减,科技龙头们随时虎视眈眈着出色的程式开发者.创意 ...

随机推荐

  1. 用倍增法构造后缀数组中的SA及RANK数组

    感觉后缀数组很难学的说= = 不过总算是啃下来了 首先 我们需要理解一下倍增法构造的原理 设原串的长度为n 对于每个子串 我们将它用'\0'补成长度为2^k的串(2^k-1<n<=2^k) ...

  2. 重新认识new

    前言 感谢大佬:https://www.cnblogs.com/luxiaoxun/archive/2012/08/10/2631812.html www.cplusplus.com 因为这段时间在重 ...

  3. 汇编指令ADD

    格式: ADD OPRD1,OPRD2 功能: 两数相加(不带进位) 例子: add ax,bx add ax,ax 解释:

  4. rm -rf无法删除文件解决方法

    # 列出 file.sh 文件的属性 lsattr file.sh # 列出当前目录下所有文件以及文件夹的属性 lsattr # 为 file.sh 文件增加 i 标识 chattr +i file. ...

  5. javascript获取网页宽高,屏幕宽高,屏幕分辨率等

    ​ <script> var s = ""; s += "\r\n网页可见区域宽:"+ document.body.clientWidth; s + ...

  6. c++primer,自定义一个复数类

    #include<iostream> #include<string> #include<vector> #include<algorithm> #in ...

  7. Python字节码与解释器学习

    参考:http://blog.jobbole.com/55327/ http://blog.jobbole.com/56300/ http://blog.jobbole.com/56761/ 1. 在 ...

  8. Cocos2d 之FlyBird开发---GameAbout类

    |   版权声明:本文为博主原创文章,未经博主允许不得转载.(笔者才疏学浅,如有错误,请多多指教) 一般像游戏关于的这种界面中,主要显示的是游戏的玩法等. GameAbout.h #ifndef _G ...

  9. list中的所有值转换为字符串,以及list拼接成一个字符串

    import stringlis=[1,2,3,'abc']fw=open('hello.txt','w',encoding='utf-8')# print(''.join(str(lis).repl ...

  10. 安装软件 学习linux命令

    nm -D /usr/lib64/libstdc++.so.6 | grep GLIBCnm dumps named symbols, -D for dynamic libs, and grep fo ...