zoukankan      html  css  js  c++  java
  • 「2020 新手必备 」极速入门 Retrofit + OkHttp 网络框架到实战,这一篇就够了!

    老生常谈

    • 什么是 Retrofit
    • 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 页面为例:

    百度图片 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 对象生成器,门路很多这里都告诉你们啦

    生成接口对象

    • 老样子,先看看代码
    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 博客园 及时接收更多优质博文!

    加油!

  • 相关阅读:
    CodeForces 785D Anton and School
    CodeForces 785C Anton and Fairy Tale
    CodeForces 785B Anton and Classes
    CodeForces 785A Anton and Polyhedrons
    爱奇艺全国高校算法大赛初赛C
    爱奇艺全国高校算法大赛初赛B
    爱奇艺全国高校算法大赛初赛A
    EOJ 3265 七巧板
    EOJ 3256 拼音魔法
    EOJ 3262 黑心啤酒厂
  • 原文地址:https://www.cnblogs.com/yuanhao-1999/p/12095983.html
Copyright © 2011-2022 走看看