Retrofit攻略---一步一步的打造网络访问的航空母舰

最开始使用AndroidStudio的时候,各种不适应,各种怀恋Eclipse,写了几千行代码勉强熟悉了AndroidStudio后,感觉AndroidStudio不要太棒了。

学习Retrofit也是这样,遇到麻烦就想去用以前用过的框架,熟悉了过后我现在连吃个汤圆都喜欢串着吃,囧….

像OkHttp一样使用

当然实际的项目开发中,不可能给我们时间慢慢去适应、去学习,要考虑技术风险和学习成本、团队共用。所以我才觉得Retrofit这一点好贴心,基于OkHttp实现,也就是说我当我遇到我不知道该怎么实现的功能的时候,可以无缝切换到OkHttp上面实现需求。

看看下面的代码,同样申明一个请求call,同样的异步call.enqueue,同样的回调Callback,甚至连mDatas = new Gson().fromJson(res, new TypeToken<List<SearchImage>>Gson解析都是从前的味道。但是我们已经开始在使用Retrofit了,不知不觉,潜移默化中已经前进了一小步。

    private void initBtnBasic() {
        mBtnBasic.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Retrofit build = new Retrofit.Builder().baseUrl("http://zhuangbi.info/").build();
                Call<ResponseBody> call = build.create(SearchApi.class).basicSearch("牛逼");
                call.enqueue(new Callback<ResponseBody>() {
                    @Override
                    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                        try {
                            String res = response.body().string();
                            mDatas = new Gson().fromJson(res, new TypeToken<List<SearchImage>>() {
                            }.getType());
                            for (SearchImage searchImage : mDatas) {
                                Log.d(TAG, "onResponse: " + searchImage.toString());
                            }
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    @Override
                    public void onFailure(Call<ResponseBody> call, Throwable t) {
                        Log.e(TAG, "onFailure: ", t);
                    }
                });
            }
        });
    }
public interface SearchApi {

    @GET("search")
    Call<ResponseBody> basicSearch(@Query("q") String qTitle);
}

当然,100%的雷同也不好嘛,来了一趟总得学点东西,一下接触陌生知识太多,跨步前进容易扯到蛋不可取,原地踏步也不爽,小步慢行。

这里我们构建请求URL的事后使用了一个接口SearchApi

Retrofit

Retrofit.Builder().baseUrl("http://zhuangbi.info/").build().create(SearchApi.class).basicSearch("牛逼");

OkHttp

new Request.Builder()
                .url("http://zhuangbi.info/search?q=" + getString("牛逼"))
                .build();

聪明的我们一看就知道

 @GET("search")
    Call<ResponseBody> basicSearch(@Query("q") String qTitle);

这里的@GET("search")表示使用get请求,同时search是get命令路径的一部分

Path

当然,你也可以把他写到参数里面,Like This

   @GET("{path}")
    Call<ResponseBody> basicSearch(@Path("path") String path, @Query("q") String qTitle);

这样用就行了:

Retrofit.Builder().baseUrl("http://zhuangbi.info/").build().create(SearchApi.class).basicSearch("search","牛逼");

Query & QueryMap

@Query("q") String qTitle

Query是查询参数,就相当于http://zhuangbi.info/search?q=牛逼

?q=牛逼 部分

也许你会想如果要查询很多参数,岂不是也要配置很多函数参数里面,然后让函数变得巨长

当然不用@QueryMap为你排忧解难

    @GET("search")
    Call<ResponseBody> basicSearch(@QueryMap Map<String, String> map);

就这么简单,轻松进入到Retrofit的地盘

组装转换器—Gson工具升级

    private void initTrans() {
        final Retrofit build = new Retrofit.Builder()
                .baseUrl("http://zhuangbi.info/")
                .addConverterFactory(GsonConverterFactory.create())//添加转换器
                .build();

        mBtnTrans.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Call<List<SearchImage>> call = build.create(SearchApi.class).transSearch("牛逼");
                call.enqueue(new Callback<List<SearchImage>>() {
                    @Override
                    public void onResponse(Call<List<SearchImage>> call, Response<List<SearchImage>> response) {
                        mDatas.clear();
                        //使用转换器,内部已经帮我们实现转换,省去了new Gson().form(jsonStr,new TypeToken<>())的过程
                        mDatas = response.body();
                        for (SearchImage searchImage : mDatas) {
                            Log.d(TAG, "onResponse: " + searchImage.toString());
                        }
                    }

                    @Override
                    public void onFailure(Call<List<SearchImage>> call, Throwable t) {
                        Log.e(TAG, "onFailure: ", t);
                    }
                });
            }
        });
    }

所谓转换器,其实再刚才的代码基础上也没多大改动嘛,不过是再构造的时候加了一句.addConverterFactory(GsonConverterFactory.create())//添加转换器

Callback里面的json转换不用再使用

                Gson gson = new Gson();
                mDatas = gson.fromJson(resStr, new TypeToken<List<SearchImage>>() {
                }.getType());

直接使用mDatas = response.body();就可以获取,当然请求的函数泛型也要改写

    @GET("search")
    Call<List<SearchImage>> transSearch(@Query("q") String qTitle);

省去了在回调函数new Gson的干扰,代码变得跟简洁。

当然,这里使用的是Gson,如果你需要JackSon,FastJson选择合适的解析器传递进去就行了。

如果,你需要自定义解析规则,也是没问题的,这需要自己去实现,但是这不是我们今天要说的重点,放到后面再写一个这种例子。

总之—使用了解析器,让我们的代码可读性变得更好,代码也更简洁。

组装适配器—打通去往RxJava的道路

 private void initAdapt() {

        final Retrofit build = new Retrofit.Builder()
                .baseUrl("http://zhuangbi.info/")
                .addConverterFactory(GsonConverterFactory.create())//添加转换器
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加适配器 连接RxJava
                .build();
        //使用RxBinding处理点击事件 RxBinding基于RxJava实现
        RxView.clicks(mBtnAdapter)
                .debounce(500, TimeUnit.MILLISECONDS) //排除500ms内重复点击事件
                .observeOn(AndroidSchedulers.mainThread())//切换到主线程执行响应内容,相当于在回调方法中执行runOnUiThread方法
                .subscribe(new Action1<Void>() {
                    @Override
                    public void call(Void aVoid) {
                        //不需要Call了,直接使用Observable,顺滑的切换到RxJava领域
                        Observable<List<SearchImage>> observable = build.create(SearchApi.class)
                                .search("牛逼")
                                .subscribeOn(Schedulers.io())
                                .observeOn(AndroidSchedulers.mainThread());
                        //这里主要是为了演示,代码简洁,实际中不要这样,
                        // 还要考虑取消一个请求,返回错误值处理,业务需求等等
                        observable.subscribe(new Action1<List<SearchImage>>() {
                            @Override
                            public void call(List<SearchImage> searchImages) {
                                for (SearchImage searchImage : searchImages) {
                                    Log.d(TAG, "call: " + searchImage.toString());
                                }
                            }
                        });
                    }
                });
    }

和解析器一样,适配器也就是加了一句.addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加适配器 连接

代码虽然简洁,作用巨大,注意现在我们已经不用Call来做请求了,我们使用Observable,这直接就可以利用RxJava操作。

  1. 线程切换,是这种感觉
 .subscribeOn(Schedulers.io())
 .observeOn(AndroidSchedulers.mainThread());

2.以前的回调Callback变成了监听者处理事件observable.subscribe,有更多

Action1
Subscription

方式让我们选择

3.链式调用,整个逻辑清晰明了

但是注意一点:

对API调用了 observeOn(MainThread) 之后,线程会跑在主线程上,包括 onComplete 也是, unsubscribe 也在主线程,然后如果这时候调用 call.cancel 会导致 NetworkOnMainThreadException ,所以一定要在 retrofit 的API调用.subscribeOn(io).observeOn(MainThread) 之后加一句 unsubscribeOn(io)

完整的就是

Api
.subscribeOn(io)
.observeOn(MainThread)
.unsubscribeOn(io)

哦,这里取消一个命令和call的方式不同,用的是unsubscribeOn(io),Call对应的方法是cancel()

这里还加入了一点小元素,注意我们使用的是

 RxView.clicks(mBtnAdapter)
                .debounce(500, TimeUnit.MILLISECONDS) //排除500ms内重复点击事件
                .observeOn(AndroidSchedulers.mainThread())//切换到主线程执行响应内容,相当于在回调方法中执行runOnUiThread方法
                .subscribe(new Action1<Void>() {
                    @Override
                    public void call(Void aVoid) {

取代

  mBtnTrans.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {}

这是使用了RxBinding这个库,基于RxJava实现,看看用着这种方式实现方式对比传统方式在上述代码表达出来的,线程切换、防止重复点击的实现优势

组装拦截器—调试神器

我写了,快一周也没有找到他的,请求方式,请求头在哪儿打印,一直好慌。知道再OkHttp的Github上看到HttpLoggingInterceptor这个库。

使用方式:导入

    compile ‘com.squareup.okhttp3:logging-interceptor:3.3.1‘
   private void initLog() {

        HttpLoggingInterceptor logging = new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS);
        //自定义拦截方式
        HttpLoggingInterceptor logger = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                Log.v("Retrofit", message);
            }
        }).setLevel(HttpLoggingInterceptor.Level.BASIC);
        OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(logger).build();
        final Retrofit build = new Retrofit.Builder()
                .baseUrl("http://zhuangbi.info/")
                .addConverterFactory(GsonConverterFactory.create())//添加转换器
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())//添加适配器 连接RxJava
                .client(httpClient)
                .build();
        RxView.clicks(mBtnLog)
                .debounce(300, TimeUnit.MILLISECONDS)
                .subscribe(new Action1<Void>() {
                               @Override
                               public void call(Void aVoid) {
                                   //不需要Call了,直接使用Observable,顺滑的切换到RxJava领域
                                   Observable<List<SearchImage>> observable = build.create(SearchApi.class)
                                           .search("牛逼")
                                           .subscribeOn(Schedulers.io())
                                           .observeOn(AndroidSchedulers.mainThread());
                                   //这里主要是为了演示,代码简洁,实际中不要这样,
                                   // 还要考虑取消一个请求,返回错误值处理,业务需求等等
                                   observable.subscribe(new Action1<List<SearchImage>>() {
                                       @Override
                                       public void call(List<SearchImage> searchImages) {
                                           for (SearchImage searchImage : searchImages) {
                                               Log.d(TAG, "call: " + searchImage.toString());
                                           }
                                       }
                                   });
                               }
                           }
                );
    }

有4个请求层次,一般BASIC和HEADER用得比较多

  public enum Level {
    /** No logs. */
    NONE,
    /**
     * Logs request and response lines.
     *
     * <p>Example:
     * <pre>{@code
     * --> POST /greeting http/1.1 (3-byte body)
     *
     * <-- 200 OK (22ms, 6-byte body)
     * }</pre>
     */
    BASIC,
    /**
     * Logs request and response lines and their respective headers.
     *
     * <p>Example:
     * <pre>{@code
     * --> POST /greeting http/1.1
     * Host: example.com
     * Content-Type: plain/text
     * Content-Length: 3
     * --> END POST
     *
     * <-- 200 OK (22ms)
     * Content-Type: plain/text
     * Content-Length: 6
     * <-- END HTTP
     * }</pre>
     */
    HEADERS,
    /**
     * Logs request and response lines and their respective headers and bodies (if present).
     *
     * <p>Example:
     * <pre>{@code
     * --> POST /greeting http/1.1
     * Host: example.com
     * Content-Type: plain/text
     * Content-Length: 3
     *
     * Hi?
     * --> END GET
     *
     * <-- 200 OK (22ms)
     * Content-Type: plain/text
     * Content-Length: 6
     *
     * Hello!
     * <-- END HTTP
     * }</pre>
     */
    BODY
  }

还可以通过自定义HttpLoggingInterceptor logger = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger()个性化打造自己的网络数据请求打印。

组装监听器—监听Response下行数据

把拦截器,进一步升华,比如下载数据的时候,拦截下Response,读取里面的信息,然后再传输给下一层,不就可以实现监听下行数据么。

先实现一个监听器

public interface ProcessListener {
    /**
     * @param current     已完成字节数
     * @param total       总字节数
     * @param isCompleted 是否已经完成
     */
    void onProcess(long current, long total, boolean isCompleted);
}

实现一个具备拦截能力的Response类

public class ProcessResponseBody extends ResponseBody {
    private ProcessListener mProcessListener;
    private ResponseBody mResponseBody;
    private BufferedSource mBufferedSource;

    public ProcessResponseBody(ResponseBody responseBody, ProcessListener processListener) {
        mProcessListener = processListener;
        mResponseBody = responseBody;
    }

    @Override
    public MediaType contentType() {
        return mResponseBody.contentType();
    }

    @Override
    public long contentLength() {
        return mResponseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (mBufferedSource == null) {
            mBufferedSource = Okio.buffer(source(mResponseBody.source()));
        }
        return mBufferedSource;
    }

    private Source source(Source source) {
        return new ForwardingSource(source) {
            long nowSize = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long readSize = super.read(sink, byteCount);
                nowSize += (readSize != -1 ? readSize : 0);
                mProcessListener.onProcess(nowSize, mResponseBody.contentLength(), readSize == -1);
                return readSize;
            }
        };
    }
}

调用

        final OkHttpClient httpClient = new OkHttpClient.Builder()
                .addInterceptor(new Interceptor() {
                    @Override
                    public okhttp3.Response intercept(Chain chain) throws IOException {
                        okhttp3.Response oldResponse = chain.proceed(chain.request());
                        return oldResponse.newBuilder()
                                .body(new ProcessResponseBody(oldResponse.body(), new ProcessListener() {
                                    @Override
                                    public void onProcess(long current, long total, boolean isCompleted) {
                                        Log.d(TAG, "onProcess: " + current + "/" + total + "---" + isCompleted);
                                    }
                                }))
                                .build();
                    }
                }).build();

查看Log

组装监听器2—获取Request上传进度

上传和下行,同理,实现一个具备拦截能力的Request即可

太快了,模拟器里面没有大一点的图片,本来是有进度条显示,看下日志吧

实现代码:

public class ProcessRequestBody extends RequestBody {
    private ProcessListener mProcessListener;
    private RequestBody mRequestBody;

    private BufferedSink mBufferedSink;

    public ProcessRequestBody(RequestBody requestBody, ProcessListener processListener) {
        mProcessListener = processListener;
        mRequestBody = requestBody;
    }

    @Override
    public long contentLength() throws IOException {
        return mRequestBody.contentLength();
    }

    @Override
    public MediaType contentType() {
        return mRequestBody.contentType();
    }

    @Override
    public void writeTo(BufferedSink sink) throws IOException {
        if (mBufferedSink == null) {
            mBufferedSink = Okio.buffer(sink(sink));
        }
        mRequestBody.writeTo(mBufferedSink);
        mBufferedSink.flush();
    }

    private Sink sink(Sink sink) {
        return new ForwardingSink(sink) {
            long writenBytes = 0L;

            @Override
            public void write(Buffer source, long byteCount) throws IOException {
                super.write(source, byteCount);
                writenBytes += byteCount;
                mProcessListener.onProcess(writenBytes, mRequestBody.contentLength(), writenBytes == mRequestBody.contentLength());
            }
        };
    }
}

调用

 final OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(new Interceptor() {
                    @Override
                    public okhttp3.Response intercept(Chain chain) throws IOException {

                        RequestBody requestBody = new ProcessRequestBody(chain.request().body(), new ProcessListener() {
                            @Override
                            public void onProcess(final long current, final long total, final boolean isCompleted) {

                                getActivity().runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        mDialog.show();
                                        mDialog.setProgress((int) (current * 100 / total));
                                        if (isCompleted) {
                                            mDialog.dismiss();
                                        }

                                    }
                                });
                                Log.d(TAG, "onProcess: " + current + "/" + total + isCompleted);
                            }
                        });

                        Request newRequest = chain.request().newBuilder().post(requestBody).build();
                        return chain.proceed(newRequest);

                    }
                })
                .build();

源码下载地址:https://github.com/zhouruikevin/RxJavaSamples-master

时间: 2024-10-29 19:09:51

Retrofit攻略---一步一步的打造网络访问的航空母舰的相关文章

花1台的钱入手2台【最能抗DDoS】阿里云主机【攻略】

花1台的钱入手2台[最能抗DDoS]阿里云主机[攻略]: 第一步:先申请0元半年 http://click.aliyun.com/m/335/:注:0元机器只有新帐号可申请第二步:再买6折37/月 http://click.aliyun.com/m/623/ :注:最长可买5个月这样,你花1台的钱就入手啦2台阿里云主机,神马企业门户.博客.论坛哒,非常足够啦 占便宜不想停的,还可以用SLB搞个小小集群第三步:再免费领150元的SLB代金券http://click.aliyun.com/m/596

Apache服务器故障排除攻略

Apache服务器故障排除攻略 应用服务器Apache浏览器配置管理网络应用 随着网络技术的普及.应用和Web技术的不断完善,Web服务已经成为互联网上重要的服务形式之一.原有的客户端/服务器模式正在逐渐被浏览器/服务器模式所取代.本文将重点Apache 服务器的故障排除的技巧. http://www.poluoluo.com/jzxy/200809/45669.html 一.检查配置文件的错误 Apache服务器的设置文件位于/etc/httpd/conf/目录下,传统上使用三个配置文件htt

解决Win8不能上网攻略第二版!三步秒杀原驱动

关于Win8消费者预览版的网卡驱动确实是令人头疼的事情,不少用户都抱怨无法联网而且驱动卸载十分麻烦.IT之家社区luoyousi 此前分享<图文教程:解决Win8消费者预览版不能上网问题>之后,仍有用户反馈不适用自己的情况:经过重装Windows8再次研究网卡驱动,luoyousi 与大家分享解决不能Win8上网的进阶版方法,希望可以帮助到更多的用户. 这次经过认真的研究,不用取消自动更新,不用设置全双工,半双工,不用更改任何设置.相比起原来的方法,这样真的能一劳永逸,不会再出现什么问题了.总

Retrofit全攻略——进阶篇

最近事比较多,距离上次写文章已经过去了一个月了.上一篇文章Retrofit全攻略--基础篇 介绍了Retrofit的基础用法,这篇文章介绍点进阶的用法. 打印网络日志 在开发阶段,为了方便调试,我们需要查看网络日志.因为Retrofit2.0+底层是采用的OKHttp请求的.可以给OKHttp设置拦截器,用来打印日志. 首先可以在app/build.gradle中添加依赖,这是官方的日志拦截器. compile 'com.squareup.okhttp3:logging-interceptor:

Retrofit全攻略——基础篇

实际开发过程中一般都会选择一些网络框架提升开发效率.随着Google对HttpClient 摒弃和Volley框架的逐渐没落.OkHttp開始异军突起.而Retrofit则对OkHttp进行了强制依赖,能够简单理解Retroifit在OKHttp基础上进一步完好. Retrofit是由Square公司出品的针对于Android和Java的类型安全的Httpclient,眼下推出了2.0+的版本号. Retrofit框架项目地址:https://github.com/square/retrofit

redis大型攻略!

redis大型攻略 一:redis的简介.下载和安装 二:redis主从 三:redis在线升级 四:redis多实例 五:redis常见的操作 六:reids配置详解 实验环境:CentOS-6.5-x86_64   redis-2.8.9.tar.gz 一:redis的简介.下载和安装 Redis是一个key-valu存储系统.和Memcached类似,它支持存储的value类型相对更多,包括string(字符串).list(链表).set(集合).zset(sorted set --有序集

Oracle 11g安装图文攻略

呵呵,花了一个多小时,左右把11g安装折腾好了.其中折腾SQL Developer 花了好长时间,总算搞定了.好了,先总结下安装步骤,希望给后面的童鞋提高安装效率.呵呵. 一.Oracle 下载 注意Oracle分成两个文件,下载完后,将两个文件解压到同一目录下即可. 路径名称中,最好不要出现中文,也不要出现空格等不规则字符. 官方下地址: http://www.oracle.com/technetwork/database/enterprise-edition/downloads/index.

图像识别DM8127开发攻略——序

DAVINCI(ARM+DSP)开发攻略已经整整两年多没写东西了,自从写完DAVINCI DM3730的开发攻略,就基本没什么精力去写,一直在忙DM3730平台客户定制的产品设计和大规模量产的任务. 2015年-2016年-2017上半年爆炸性的出货,公司只好全力去支持大客户,保证产品的质量.还有得了一个可爱的小宝宝,里里外外的事情重重压过来,不得不去处理,结果博客和公司网站基本处在停止更新状态,毕竟这年头大环境是实业误国,投机"兴邦",在这种情况下科技公司全力赚点伙食费不容易,所以博

U盘启动盘安装Win7/9/10系统攻略

UltraISO制作U盘启动盘安装Win7/9/10系统攻略 U盘安装好处就是不用使用笨拙的光盘,光盘还容易出现问题,无法读取的问题.U盘体积小,携带方便,随时都可以制作系统启动盘. U盘建议选择8G及以上大小的. 下面一步一步讲解如果制作U盘安装盘: 1.首先是要先下载操作系统,推荐大家去下面这个网站下载,都是MSDN微软原版,非常棒. http://msdn.itellyou.cn/ 这里我们以下载到的最新64位Win10为例介绍,我们下载的Win10版本如下图所示,下载好的文件为:cn_w