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

来源:互联网 发布:九阴真经女捏脸数据 编辑:程序博客网 时间:2024/06/05 18:12

最开始使用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);

毕竟官方都强调Retrofit这货是一个类型安全的Http Client,这时候安全就体现出来了。省去了在回调函数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,有更多

Action1Subscription

方式让我们选择

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实现,看看用着这种方式实现方式对比传统方式在上述代码表达出来的,线程切换、防止重复点击的实现优势

组装拦截器—调试神器

悄悄告诉你,我写了快一周Retrofit,也没有找到他的,请求方式,请求头在哪儿打印,翻遍了整个Retrofit源代码,甚至OKHttp3的大部分源码,人家一直好慌好么。直到在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());                                           }                                       }                                   });                               }                           }                );    }

是不是很简单呀,慢慢的我明白了为啥他不放到基础包里面了,如下

  1. 可以减少包的体积啊
  2. 你肯定不希望正式版本把你们公司Api接口,访问信息头通通暴露在Log里面
    以前咱们是怎么办的? 设置一个boolean isDebug控制日志输出,如果是Release版本就把它设置为,不可输出。但是人是会犯错的啊,万一那个包一不小心忘了改这个参数,不就完蛋了。
    所以直接使用Glide里面把日志包配置到Debug版本,Release包没有,避免这种低级错误。

有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

3 0