咱们真的须要使用RxJava+Retrofit吗?

Android.jpg

前言

能够说RxJava+Retrofit是整个2016年Android 开发圈内最受关注的的组合。各大Android论坛上有大量以RxJava+Retrofit+xxx 为标题的文章,此类文章也备受你们的关注。这个组合仿佛已经成为了Android开发的必备组件,项目里没使用这个组合好像本身都out了似的。javascript

平心而论,RxJava和Retrofit 相较于以往的各类框架(如 AsyncHttpClient,Volley等 )学习和使用起来会有一些难度;RxJava 强大而又庞大的操做符,Retrofit采用注解风格定义接口,都会让初学者花费很多功夫,绕很多圈子,踩大量的坑。既然这样,那么就会有人怀疑,咱们真的须要学习RxJava和Retrofit吗?java

任意一款须要联网的APP,最典型的套路就是请求后端数据,解析数据进行UI更新;响应用户操做,再次请求数据,更新UI。这里咱们就从最基础的网络请求出发,带着疑问,逐步了解一下Retrofit的前生今世,看一看RxJava和Retrofit的价值所在。react

Android Http

最基础的实现方式

初学Android开发时,还在上大学,那会儿还不知有AsyncHttpClient,Volley,OKHttp 这么方便的框架存在于这个世界上;一个简单的网络请求一般要写一大段代码。android

使用HttpURLConnection实现网络请求

class MyTask extends AsyncTask<String, Void, String> {

        @Override
        protected String doInBackground(String... params) {
            InputStream mInputStream = null;
            HttpURLConnection connection = getHttpUrlConnection(params[0]);
            String result = "";
            try {
                connection.connect();
                int statusCode = connection.getResponseCode();
                String response = connection.getResponseMessage();
                mInputStream = connection.getInputStream();
                InputStreamReader inputStreamReader = new InputStreamReader(mInputStream);
                BufferedReader reader = new BufferedReader(inputStreamReader);
                StringBuffer sb = new StringBuffer();
                String line;
                while ((line = reader.readLine()) != null) {
                    sb.append(line + "\n");
                }

                result = "StatusCode: " + statusCode + "\n"
                        + "Response" + response + "\n"
                        + sb.toString();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return result;
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            tv.setText(s);
        }
    }

    private HttpURLConnection getHttpUrlConnection(String url) {
        HttpURLConnection connection = null;
        try {
            URL mUrl = new URL(url);
            connection = (HttpURLConnection) mUrl.openConnection();
            connection.setConnectTimeout(20000);
            connection.setReadTimeout(40000);
            connection.setRequestMethod("GET");
            connection.setRequestProperty("Content-Type", "application/json");
            connection.setRequestProperty("Accept", "application/json");
            connection.setRequestProperty("Charset", "utf-8");
            connection.setRequestProperty("Content-Length", "0");

        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

        return connection;
    }复制代码
new MyTask().execute(BASE_URL);复制代码

这段代码的逻辑很简单,就是将网络请求的结果显示在一个TextView上。很大一部分的内容都是在执行HttpURLConnection 相关的配置及初始化工做。git

记得第一次经过网络请求把数据显示的Android模拟器(那时候仍是穷学生,买不起Android手机)的屏幕上时,虽然只是一大堆别人看不懂的json字符串,可是感受本身就要上天了,如今想一想真是。。。。。github

即使是这么长的一段代码,尚未包含网络请求异常的内容,若是加上网络请求失败处理的逻辑,将使得整个代码结构更加臃肿庞大。json

网络请求框架的涌现

一款联网的APP至少会有十几回的网络请求,更多的就没法估计了。所以,每一次的网络请求不可能像上面那样写。因此,咱们须要封装,将一些固定的操做统一处理;固然已经有许多大神比我早想到了这个问题,便出现了许多对网络请求进行封装的库。后端

  • AsyncHttpClient(底层基于HttpClient)
  • afinal(FinalHttp,一样是基于HttpClient封装)
  • xUtils (基于afinal)
  • Volley(Google官方出品)
  • okHttp
  • NoHttp (我的开发)

这里列出的几个库当中,我的使用AsyncHttpClient较多,AsyncHttpClient 的确很是好用,可是后来伴随着Android sdk 23 中HttpClient的废弃也逐渐被遗忘。
afinal和xUtils 都没有在实际项目中没用过,不作评价。api

Volley做为Google官方在2013年I/O 大会上推出的库,相较于AsyncHttpClient 更强大。安全

下面简单列举一个使用Volley进行get请求的demo。

Volley 简单使用

添加依赖:

compile 'com.mcxiaoke.volley:library:1.0.19'复制代码
protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mContext = this;
        queue = Volley.newRequestQueue(mContext);
        setContentView(R.layout.activity_http_volley_demo);
        tv = (TextView) findViewById(R.id.editText);

        final StringRequest request = new StringRequest(Request.Method.GET, BASE_URL,
                new ResponseSuccessListener(), new ResponseFailListener());
        findViewById(R.id.volley).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                queue.add(request);
            }
        });

    }

    private class ResponseSuccessListener implements com.android.volley.Response.Listener<String> {

        @Override
        public void onResponse(String response) {
            tv.setText(response);
        }
    }

    private class ResponseFailListener implements Response.ErrorListener {

        @Override
        public void onErrorResponse(VolleyError error) {
            Toast.makeText(mContext, error.toString(), Toast.LENGTH_SHORT).show();
        }
    }复制代码

这段代码和上面的功能同样,都是将网络请求的结果显示在TextView。可是经过Volley对http请求进行一次封装后,咱们再也不关注网络请求的具体细节,而是将重点放在了对请求结果的处理上;网络请求不管成功仍是失败,咱们均可以不少好的应对。

并且在Volley中,异步网络请求的回调方法已然处于UI线程中,这样咱们就能够直接在回调方法中进行UI更新了。

能够说,使用Volley已经能够很是方便的处理Android 网络请求的相关内容了。既然如此,为何还会有OKHttp和Retrofit的出现呢?他们的优点又在哪里呢?

OKHttp 简单介绍

okHttp 是由square 推出的一个网络请求库,包括Retrofit也是由其开发,这里为square点个赞。

使用以前加入依赖

compile 'com.squareup.okhttp3:okhttp:3.4.1'
    compile 'com.squareup.okio:okio:1.11.0'复制代码

okHttp 网络请求实现

findViewById(R.id.get).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                tv.setText("");
                loading.setVisibility(View.VISIBLE);
                client = new OkHttpClient();
                Request.Builder builder = new Request.Builder()
                        .url(BASE_URL)
                        .method("GET", null);

                request = builder.build();
                Call mCall = client.newCall(request);
                mCall.enqueue(new MyCallback());
            }
        });

private class MyCallback implements Callback {

        @Override
        public void onFailure(Call call, IOException e) {
            Message msg = new Message();
            msg.what = 100;
            msg.obj = e;
            handler.sendMessage(msg);
        }

        @Override
        public void onResponse(Call call, Response response) throws IOException {
            Message msg = new Message();
            msg.what = 200;
            msg.obj = response.body().string();
            handler.sendMessage(msg);
        }
    }

class MyHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            loading.setVisibility(View.GONE);
            switch (msg.what) {
                case 100:
                    Object e = msg.obj;
                    Toast.makeText(mContext, e.toString(), Toast.LENGTH_SHORT).show();
                    break;
                case 200:
                    String response = (String) msg.obj;
                    tv.setText(response);
                    break;
                case 300:
                    int percent = msg.arg1;
                    Log.e("llll", "the percent is " + percent);
                    if (percent < 100) {
                        progressDialog.setProgress(percent);
                    } else {
                        progressDialog.dismiss();
                        Glide.with(mContext).load(FILE_PATH).into(imageView);
                    }
                    break;
                default:
                    break;
            }
        }
    }复制代码

这里必须了解的是,okHttp的回调方法,并不处于UI 线程中,对网络请求结果若是涉及UI 线程的操做,须要使用Handler。这么看来,okHttp 貌似反而不如Volley了。其实否则,okhttp的封装套路和Volley,AsyncHttp不是一个级别的,不能和后二者做比较,okhttp 和HttpClient、HttpUriConneciton 才是一个级别的产物,相较于这二者,okhttp显然强大了许多。

因此,OKHttp不只仅能够用于Android开发,Java开发也是OK的。

Retrofit

A type-safe HTTP client for Android and Java

一个针对Android和Java类型安全的http客户端

上面这句话,就是Squire对Retrofit的说明,言简意赅。Retrofit实际上是对okhttp 作了进一步的封装,有了okhttp 的基础,使用Retrofit会很容易。

下面就来看看,使用Retrofit作网络请求又是一种怎样的体验。

这里为了方便咱们使用

api.github.com/

做为网络请求的接口基地址

使用以前加入依赖:

compile 'com.squareup.retrofit2:retrofit:2.1.0'复制代码

定义接口

public interface GithubService {

    @GET("users/{user}")
    Call<ResponseBody> getUserString(@Path("user") String user);

}复制代码

这里咱们使用http中的get 方法获取users这个接口下,当前user的具体信息,参数为当前user名。返回内容为Http请求的ResponseBody。

Retrofit 返回ResponseBody

private void SimpleRetrofit() {
        OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
        Retrofit.Builder builder = new Retrofit.Builder()
                .baseUrl(BASE_URL);
        Retrofit retrofit = builder.client(httpClient.build()).build();
        GithubService simpleService = retrofit.create(GithubService.class);
        Call<ResponseBody> call = simpleService.getUserString(name);
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                loading.dismiss();
                try {
                    String result = response.body().string();
                    Gson gson = new Gson();
                    GithubUserBean bean = gson.fromJson(result, GithubUserBean.class);
                    setUserView(bean);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                loading.dismiss();
            }
        });
    }

private void setUserView(GithubUserBean user) {
        if (user != null) {
            viewShell.removeAllViews();
            View view = LayoutInflater.from(mContext).inflate(R.layout.user_item_layout, null);
            TextView title = (TextView) view.findViewById(R.id.title);
            TextView id = (TextView) view.findViewById(R.id.userId);
            TextView creteaTime = (TextView) view.findViewById(R.id.createTime);
            TextView updateTime = (TextView) view.findViewById(R.id.updateTime);
            TextView bio = (TextView) view.findViewById(R.id.bio);
            ImageView avatar = (ImageView) view.findViewById(R.id.avatar);

            title.setText("Name: " + user.getLogin());
            bio.setText("Bio: " + user.getBio());
            id.setText("Id: " + String.valueOf(user.getId()));
            creteaTime.setText("createTime: " + user.getCreated_at());
            updateTime.setText("updateTime: " + user.getUpdated_at());
            Glide.with(mContext).load(user.getAvatar_url()).into(avatar);

            viewShell.addView(view);
        } else {
            Toast.makeText(mContext, "result is null", Toast.LENGTH_SHORT).show();
        }
    }复制代码

GitHubUserBean 为网络请求结果json数据所对应的实体类。

经过这段代码,咱们在最终的回调方法里能够友好的处理请求结果,失败时onFailure方法执行。成功时,onResponse方法执行,咱们在这里用Gson解析返回的数据,并进行UI更新操做(setUserView(bean)),

这里咱们这样作有些啰嗦,Gson转换的方式都是相似,惟一不一样的只是每次网络请求结果对应的实体类;所以咱们能够借助强大的Retrofit帮助咱们完成Gson转换的步骤。固然,若是在你所在的开发环境中,接口返回的并非json格式的数据,也没有问题的。

convert

上图是Retrofit官网对可转换类型给出的介绍。有这么多种,固然了若是大家家服务器返回的数据格式比较神奇,你也能够自定义转换类。

好了,言归正传,这里仍是以Json 格式数据为例。

添加依赖:

compile 'com.squareup.retrofit2:converter-gson:2.1.0'复制代码

注意这里converter-gson 的版本号,要和以前Retrofit的版本号保持一致。

咱们从新定义接口:

public interface GithubService {
    @GET("users/{user}")
    Call<GithubUserBean> getUser(@Path("user") String user);

}复制代码

这里咱们用GithubUserBean取代ResponseBody,直接将其做为返回类型。

Retrofit 返回对象

private void LazyRetrofit() {
        OkHttpClient.Builder httpClient = new OkHttpClient.Builder();
        Retrofit.Builder builder = new Retrofit.Builder()
                .baseUrl(BASE_URL)
                .addConverterFactory(GsonConverterFactory.create());
        Retrofit retrofit = builder.client(httpClient.build()).build();
        GithubService service = retrofit.create(GithubService.class);
        Call<GithubUserBean> call = service.getUser(name);
        call.enqueue(new Callback<GithubUserBean>() {
            @Override
            public void onResponse(Call<GithubUserBean> call, Response<GithubUserBean> response) {
                GithubUserBean bean = response.body();
                setUserView(bean);
                loading.dismiss();
            }

            @Override
            public void onFailure(Call<GithubUserBean> call, Throwable t) {
                loading.dismiss();
            }
        });
    }复制代码

这里的实现方式和上面基本类似,只是多了一行

.addConverterFactory(GsonConverterFactory.create());复制代码

这样,咱们在onResponse中得到就是对象,再也不须要作额外的转换工做,能够直接使用。

Retrofit 简单封装

这里咱们能够看到,Retrofit使用有着必定的套路,因此咱们能够将Retrofit初始化相关得内容作一次简单的封装。

public class GenServiceUtil {
    private static final String BASE_URL = "https://api.github.com/";

    private static OkHttpClient.Builder httpClient = new OkHttpClient.Builder();

    private static Retrofit.Builder builder = new Retrofit.Builder()
            .baseUrl(BASE_URL)
            .addConverterFactory(GsonConverterFactory.create());

    private static Retrofit retrofit = builder.client(httpClient.build()).build();

    public static <S> S createService(Class<S> serviceClass) {
        return retrofit.create(serviceClass);
    }

}

private void EasyRetrofit() {
        GithubService service = GenServiceUtil.createService(GithubService.class);
        Call<GithubUserBean> call = service.getUser(name);
        call.enqueue(new Callback<GithubUserBean>() {
            @Override
            public void onResponse(Call<GithubUserBean> call, Response<GithubUserBean> response) {
                GithubUserBean bean = response.body();
                loading.dismiss();
                setUserView(bean);
            }

            @Override
            public void onFailure(Call<GithubUserBean> call, Throwable t) {
                loading.dismiss();
            }
        });
    }复制代码

咱们只需传入定义好的借口,会使代码简介许多。看到这里能够发现,Retrofit的确很厉害,那为何又要将他和RxJava结合在一块儿呢?下面咱们就来看看。

RxJava+Retrofit

关于什么是RxJava,这里再也不赘述,不了解的看以看看这里。这里咱们就看看将RxJava 和咱们以前的内容结合在一块儿会有怎样的效果。

首先,加入依赖

compile 'io.reactivex:rxjava:1.1.7'
    compile 'io.reactivex:rxandroid:1.2.1'复制代码

RxJava+Retrofit 实现

private void RxRetrofit() {
        GithubService service = GenServiceUtil.createService(GithubService.class);
        final Call<GithubUserBean> call = service.getUser(name);
        final Observable myObserable = Observable.create(new Observable.OnSubscribe<GithubUserBean>() {
            @Override
            public void call(Subscriber<? super GithubUserBean> subscriber) {
                Response<GithubUserBean> bean = null;
                try {
                    bean = call.execute();
                    subscriber.onNext(bean.body());

                } catch (IOException e) {
                    e.printStackTrace();
                    subscriber.onError(e);
                }

                subscriber.onCompleted();
            }
        });

        myObserable
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .map(new Func1<GithubUserBean, GithubUserBean>() {
                    @Override
                    public GithubUserBean call(GithubUserBean o) {
                        if (TextUtils.isEmpty(o.getBio())) {
                            o.setBio("nothing !");
                        }
                        return o;
                    }
                })
                .subscribe(new Subscriber<GithubUserBean>() {
                    @Override
                    public void onCompleted() {
                        loading.dismiss();
                    }

                    @Override
                    public void onError(Throwable e) {
                        loading.dismiss();
                    }

                    @Override
                    public void onNext(GithubUserBean o) {
                        setUserView(o);
                    }
                });

    }复制代码

这里有几点须要注意:

  • RxJava 自己最大的特定就是异步,所以这里咱们Retrofit执行网络请求的时候,使用了execute(同步请求),而再也不是enqueue。
  • RxJava 可使用subscribeOn和observeOn完美处理Observeable和Subscribe的执行线程问题。
  • 这里使用RxJava中map操做符,对返回内容中的为null或“” 的对象作了简单的处理。

咱们引入RxJava实现了一样的功能,却使得代码量增长了不少。不由要问,RxJava的价值到底在哪里呢?

RxJava + Retrofit 到底好在哪里

好了,为了说明为题,咱们添加一个接口

public interface GithubService {

    @GET("users/{user}")
    Call<GithubUserBean> getUser(@Path("user") String user);

    @GET("users/{user}/followers")Observable<List<UserFollowerBean>> followers(@Path("user") String usr);

}复制代码

固然这里依旧须要添加依赖:

compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
`复制代码

同时在Service的封装方法中添加

.addCallAdapterFactory(RxJavaCallAdapterFactory.create())复制代码

这样,RxJava就和Retrofit完美的关联在了一块儿。

咱们在接口中,定义followers()方法直接返回了Observable,由于Observable是RxJava的源头,并且Retrofit能够很好的支持RxJava,这样就很是方便了。

private void RxRetrofitList() {
        GithubService service = GenServiceUtil.createService(GithubService.class);
        Observable<List<UserFollowerBean>> myObserve = service.followers(name);
        myObserve
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Subscriber<List<UserFollowerBean>>() {
                    @Override
                    public void onCompleted() {
                        loading.dismiss();
                    }

                    @Override
                    public void onError(Throwable e) {
                        loading.dismiss();
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(List<UserFollowerBean> userFollowerBeen) {
                        setFollowersView(userFollowerBeen);
                    }
                });

    }复制代码

在接口中返回的内容就是Observable,所以不用再像以前同样单独定义Observable;在onNext 方法中,接收到返回的对象,更新UI。 这里若是咱们不使用RxJava,单独使用Retrofit实现这个过程是没有任何问题的; RxJava看似没有价值;可是假设如今出现以下之一的情景

  • 须要对返回的userFollowerBeen 这个list 进行按用户名从小到大的排序
  • 须要对返回的userFollowerBeen 这个list 进行按用户ID从小到大的排序
  • 若是返回的userFollowerBeen 这个list 中,某一项的头像地址为空,则不显示该项

.....

这种情景在实际开发中太常见了,试想若是没有RxJava;那么每一次需求的变动都意味着咱们须要去修改setFollowersView这个方法,需求一旦变动,就去修改这个方法,这样会不可避免的产生各类bug。那有没有办法不去修改这个方法呢?这个时候,就须要强大的RxJava了。

这里咱们就看看如何在不修改setFollowersView的前提下,实现对用户名从小到大的排序:

private void RxRetrofitList() {
        GithubService service = GenServiceUtil.createService(GithubService.class);
        Observable<List<UserFollowerBean>> myObserve = service.followers(name);
        myObserve
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .map(new Func1<List<UserFollowerBean>, List<UserFollowerBean>>() {
                    @Override
                    public List<UserFollowerBean> call(List<UserFollowerBean> userFollowerBeen) {
                        for (UserFollowerBean bean : userFollowerBeen) {
                            String name = "";
                            name = bean.getLogin().substring(0, 1).toUpperCase() + bean.getLogin().substring(1, bean.getLogin().length());
                            bean.setLogin(name);
                        }
                        return userFollowerBeen;
                    }
                })
                .map(new Func1<List<UserFollowerBean>, List<UserFollowerBean>>() {
                    @Override
                    public List<UserFollowerBean> call(List<UserFollowerBean> userFollowerBean) {
                        Collections.sort(userFollowerBean, new Comparator<UserFollowerBean>() {
                            @Override
                            public int compare(UserFollowerBean o1, UserFollowerBean o2) {
                                return o1.getLogin().compareTo(o2.getLogin());
                            }
                        });
                        return userFollowerBean;
                    }
                })
                .subscribe(new Subscriber<List<UserFollowerBean>>() {
                    @Override
                    public void onCompleted() {
                        loading.dismiss();
                    }

                    @Override
                    public void onError(Throwable e) {
                        loading.dismiss();
                        e.printStackTrace();
                    }

                    @Override
                    public void onNext(List<UserFollowerBean> userFollowerBeen) {
                        setFollowersView(userFollowerBeen);
                    }
                });

    }复制代码

RxJava 链式风格的代码

在代码中咱们使用RxJava的map 操做符,对返回数据作了两次处理,首先将全部用户名的首字母转换为大写字母;而后对整个list按照用户名从小到大排序。由于用户名中同时包含以大小写字母打头的内容,因此为了方便,咱们进行了一次转换大写的操做。

一样是随着需求变动,修改代码;可是你会发现,使用RxJava的方式,会下降出现bug的几率,并且就算是不一样的人去改,也会比较方便维护。

看到了吧,这就是RxJava的优势,固然这个例子也只是冰山一角。这里提到的map操做符只是RxJava庞大操做符集合中的一员,更特别的是,RxJava的操做符仍是能够自定义的,这样让咱们的代码有了无限的可能;RxJava的存在不只仅在于网络请求,能够用在别的方面;RxJava实际上是体现了一种思路,全部对数据的操做都在流上完成,将最终的结果返回给观察者。同时,若是返回的followers 列表有任何异常,RxJava的onError 方法会执行,这就方便咱们去处理异常数据了。

总结

通篇经过对Android 网络请求各类实现的总结,能够看到 相对于Volley,AsyncHttpClient 等库,RxJava+Retrofit 的优点并不是特别显著;在执行效率及功能上并没有大的亮点;对Volley进行良好的封装一样能够实现相似Retrofit自动转Gson的功能;RxJava+Retrofit 结合会让咱们写代码的方式更加有条理,虽然代码量会增多,但逻辑的清晰才是最重要的不是吗?因此,RxJava+Retrofit 组合不失为一种好的选择。

因此,赶忙拥抱RxJava+Retrofit吧!


文中全部源码地址github

相关文章
相关标签/搜索