「2020 新手必备 」极速入门 Retrofit + OkHttp 网络框架使用,这一篇就够了!

老生常谈

  • 什么是 Retrofit
  • Retrofit 早已不是什么新技术了,想必看到这篇博客的你们都早已熟知,这里就不啰嗦了,简单介绍下:
  • Retrofit 是一个针对 Java 和 Android 的设计的 REST 客户机。它经过基于 REST 的 web 服务检索和上传 JSON (或其余结构化数据)变得相对容易。在使用中,您能够配置用于数据序列化的转换器。对于 JSON ,一般使用Gson ,可是能够添加自定义转换器来处理 XML 或其余协议。Retrofit 对 HTTP 请求使用 OkHttp 库。

A type-safe HTTP client for Android and Javajava

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

本文流程

福利

依赖注入

  • 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~ 你们直接用便可android

  • 别急,前面也说了 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 页面为例:git

百度图片 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 每日壁纸接口

JSON

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

具体这个对象怎么得到,你们能够联系后端,或者百度搜下 JsonFormat 插件使用或者 JSON 对象生成器,门路不少这里都告诉大家啦github

生成接口对象

  • 老样子,先看看代码
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 哦~:web

  • Demo 地址:FIWKeepApp - LoginFragment后端

总结

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

加油!
相关文章
相关标签/搜索