├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── york │ │ └── com │ │ └── retrofit2rxjavademo │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-web.png │ ├── java │ │ └── york │ │ │ └── com │ │ │ └── retrofit2rxjavademo │ │ │ ├── MyApplication.java │ │ │ ├── activity │ │ │ ├── MainActivity.java │ │ │ └── MockDataActivity.java │ │ │ ├── di │ │ │ ├── component │ │ │ │ └── AppComponent.java │ │ │ ├── module │ │ │ │ ├── AppModule.java │ │ │ │ ├── InjectorModule.java │ │ │ │ ├── MainModule.java │ │ │ │ └── NetworkModule.java │ │ │ └── scope │ │ │ │ ├── AppScope.java │ │ │ │ ├── ControllerScope.java │ │ │ │ ├── NetworkScope.java │ │ │ │ └── PerActivityScope.java │ │ │ ├── entity │ │ │ ├── ContentBean.java │ │ │ ├── HttpResult.java │ │ │ ├── MockBean.java │ │ │ └── TestBean.java │ │ │ ├── gsonconverter │ │ │ ├── CustomGsonConverterFactory.java │ │ │ ├── CustomGsonRequestBodyConverter.java │ │ │ └── CustomGsonResponseBodyConverter.java │ │ │ ├── http │ │ │ ├── ExceptionEngine.java │ │ │ ├── MockApi.java │ │ │ ├── MovieService.java │ │ │ ├── ServiceFactory.java │ │ │ └── exception │ │ │ │ ├── ApiException.java │ │ │ │ ├── ErrorType.java │ │ │ │ └── ServerException.java │ │ │ ├── progress │ │ │ ├── ProgressCancelListener.java │ │ │ └── ProgressDialogHandler.java │ │ │ ├── subscribers │ │ │ ├── BaseSubscriber.java │ │ │ ├── CommonSubscriber.java │ │ │ └── RxSubscriber.java │ │ │ ├── transformer │ │ │ ├── DefaultTransformer.java │ │ │ ├── ErrorTransformer.java │ │ │ └── SchedulerTransformer.java │ │ │ └── utils │ │ │ ├── DialogHelper.java │ │ │ ├── LoggerInterceptor.java │ │ │ ├── NetworkUtil.java │ │ │ └── OkHttpUtils.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ └── activity_mock_data.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── york │ └── com │ └── retrofit2rxjavademo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── language └── README-EN.md ├── screenshots ├── common.gif └── rx.gif ├── settings.gradle └── video ├── device-2017-03-27-161516.mp4 ├── device-2017-03-27-161818.mp4 ├── device-2017-03-27-162233.mp4 └── device-2017-03-27-162338.mp4 /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea 5 | .DS_Store 6 | /build 7 | /captures 8 | 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk8 3 | android: 4 | components: 5 | - tools 6 | - build-tools-25.0.2 7 | - android-25 8 | - extra-android-support 9 | - extra-android-m2repository 10 | - extra-google-m2repository 11 | licenses: 12 | - 'android-sdk-preview-license-.+' 13 | - 'android-sdk-license-.+' 14 | - 'google-gdk-license-.+' 15 | - '.+' 16 | before_script: 17 | - chmod +x gradlew 18 | - mkdir "$ANDROID_HOME/licenses" || true 19 | - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license" 20 | - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" 21 | script: ./gradlew clean build -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 YorkYu 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Retrofit2RxjavaDemo 2 | [![Build Status](https://travis-ci.org/ysmintor/Retrofit2RxjavaDemo.svg?branch=master)](https://travis-ci.org/ysmintor/Retrofit2RxjavaDemo) 3 | *** 4 | 5 | # Help 6 | 谁愿意使用本Demo后有时间,希望有人可以在使用本Demo后发有时间来帮助,愿意做contributor的联系我ysmintor@gmail.com。 7 | 8 | 9 | 10 | ### 无Dagger2示例 11 | Dagger2的使用可以简化相关依赖,所以把Dagger2使用集成在一起,所以之前的无dagger2放在[nodagger2 branch](https://github.com/ysmintor/Retrofit2RxjavaDemo/tree/nodagger2)中,不使用Dagger2的可以查看这个分支。 12 | 13 | 14 | ### 效果展示 15 | 16 | ![rx](screenshots/rx.gif) 17 | ![common](screenshots/common.gif) 18 | 19 | 本文主要介绍了使用Retrofit2配合Rxjava[这里指Rxjava1, 暂时未适配Rxjava2] 来处理非restful的网络请求结果。一般而言请求的非REST结果如下,包括一个code代表结果的状态和msg表示描述,以及data对应的真实的数据。我们的目的就是直接取`data`对应的数据,如果是数组那么取的也是数组`list`类型的,如果有错误,那么就会调用onError()方法。更多演示效果请查看[video](video)目录下的视频。 20 | 21 | ```json 22 | { 23 | "code": 0, //请求返回状态码 0表示成功,其它则为对应的错误类型 24 | "msg": "请求成功", //请求返回状态 25 | "data": 26 | { //返回结果 27 | "phone": "010-62770334;010-62782051", //电话 28 | "website": "www.tsinghua.edu.cn", //官网 29 | "email": "zsb@mail.tsinghua.edu.cn", //邮箱 30 | "address": "北京市海淀区清华大学", //地址 31 | "zipcode": "0102770334", //邮编 32 | "name": "清华大学", //学校名称 33 | "img": "http://img.jidichong.com/school/3.png", //学校logo图片 34 | "parent": "教育部", //隶属部门 35 | "type": " 211 985", //学校类型 36 | "profile": "xasd", //简介 37 | "info": "院士:68人 博士点:198个 硕士点:181个", //说明 38 | "city": "北京" //所在省市 39 | } 40 | } 41 | ``` 42 | 43 | 而Retrofit2 请求的结果一般都分为Header 和Body。在获取这些数据后再在Rxjava subscriber 中的onNext()来处理比较麻烦。所以这个demo介绍了如何结果Retrofit2与Rxjava1来处理数据并在成功时得到data字段的数据,服务端返回有错误是进入onError(),当是单个实体的时候,在onNext中就直接得到这个结果,如果是一个数组的时候,则是返回List 这种形式。同时也简化了定义gson实体不用每次都加code, msg这样一层。 44 | 45 | 46 | 47 | 48 | ---------- 49 | 50 | 使用方法: 51 | 52 | ### 1. 配置对应的外层实体。 53 | 54 | 例如下面。开发中一般都非标准的REST都是一个数据(百度开放平台的接口就基本都是这种形式),一个状态码和一个消息。其中data的类型是泛型,可以在生成请求的api指定实际返回的类型,如果为空的情况可以使用String。 而code 和message是对应于服务端定义的代码code和返回的错误信息。如果你的后台后台字段不同,你可以按需要修改这个HttpResult的相应字段。 55 | 56 | ```java 57 | { 58 | // code 为返回的状态码, message 为返回的消息, 演示的没有这两个字段,考虑到真实的环境中基本包含就在这里写定值 59 | private int code = 0; 60 | private String message = "OK"; 61 | 62 | //用来模仿Data 63 | @SerializedName(value = "subjects") 64 | private T data; 65 | } 66 | ``` 67 | 68 | ### 2. 同Retrofit2一样要定义接口。 69 | 如下。这里仅仅是有GET的接口在demo里,POST, PUT, DELETE, QUERY等都是一样的。`HttpResult`里T的类型就是指定泛型data的具体类型。可以使用String, JSONObject,定义的实体等等。 70 | 71 | 另外有朋友问题访问参数是JSON对象怎么办 _(Body Paramter JSON Object)_?这其实就是将你的参数设置成一个已经定义的实体,我也给出一个项目中的接口。下面的post就是这种方式。关于请求的REST方式我会在文章后面放出详细的参考,若你不熟悉请参考这些文章。 72 | ```java 73 | 74 | @GET("mock3") 75 | Observable> getMock3(); 76 | 77 | @GET("mock1") 78 | Observable>> getMock1(); 79 | 80 | @GET("mock4") 81 | Observable> getMock4(); 82 | 83 | @GET("mock2") 84 | Observable> getMock2(); 85 | ``` 86 | 87 | ### 3. 请求网络。 88 | 89 | 直接调用 90 | ```java 91 | public static MockApi mockApi() { 92 | return ServiceFactory.createService(MockApi.class); 93 | } 94 | ``` 95 | 96 | 使用一个默认的`.compose(new DefaultTransformer>())`可以非常方便地进行转化成了需要的`Observable`。如下代码中那样进行了线程的转换,错误的处理在这个transformer,可以自定义自己的transformer。 97 | ```java 98 | return observable 99 | .subscribeOn(Schedulers.io()) 100 | .observeOn(Schedulers.newThread()) 101 | .compose(ErrorTransformer.getInstance()) 102 | .observeOn(AndroidSchedulers.mainThread()); 103 | ``` 104 | 105 | 另外准备了常用的subscriber,包含了网络连接的错误处理,例如非200状态,另外是服务端(业务)错误的处理,默认是将错误编码和错误信息在控制台和手机上输出。 106 | 提供的`RxSubscriber`和 `CommonScriber`中的`onNext()`必须实现。 107 | 108 | 建议使用`RxLifecycle`防止使用`RxJava`内存泄露。其它方面使用同RxJava与Retrofit2结合的使用是相同的,所以得到结果您若有若要对数据进行处理仍然是链式调用。 109 | 110 | --- 111 | 112 | # 关于错误处理方面介绍 113 | 114 | 主要使用了RxJava中的`onErrorResumeNext`,遇到错误后将错误通过`ExceptionEngine.handleException(throwable)`进行处理。 115 | 116 | ```java 117 | @Override 118 | public Observable call(Observable> responseObservable) { 119 | return responseObservable.map(new Func1, T>() { 120 | @Override 121 | public T call(HttpResult httpResult) { 122 | // 通过对返回码进行业务判断决定是返回错误还是正常取数据 123 | if (httpResult.getCode() != ErrorType.SUCCESS) throw new ServerException(httpResult.getMessage(), httpResult.getCode()); 124 | return httpResult.getData(); 125 | } 126 | }).onErrorResumeNext(new Func1>() { 127 | @Override 128 | public Observable call(Throwable throwable) { 129 | //ExceptionEngine为处理异常的驱动器 130 | throwable.printStackTrace(); 131 | return Observable.error(ExceptionEngine.handleException(throwable)); 132 | } 133 | }); 134 | } 135 | ``` 136 | 其中的ApiException包括code和错误的详情 137 | ```java 138 | public class ApiException extends Exception { 139 | // 异常处理,为速度,不必要设置getter和setter 140 | public int code; 141 | public String message; 142 | 143 | public ApiException(Throwable throwable, int code) { 144 | super(throwable); 145 | this.code = code; 146 | } 147 | } 148 | ``` 149 | 150 | __重点在于下面这个处理。你可以再定义自己的业务相关的错误,目前常用的都已经有了,而业相关的错误大部分通过接收的数据直接显示了。若不是直接显示的情况,你完全可以在提供的subscriber里去实现。__ 151 | ```java 152 | public class ExceptionEngine { 153 | //对应HTTP的状态码 154 | private static final int UNAUTHORIZED = 401; 155 | private static final int FORBIDDEN = 403; 156 | private static final int NOT_FOUND = 404; 157 | private static final int REQUEST_TIMEOUT = 408; 158 | private static final int INTERNAL_SERVER_ERROR = 500; 159 | private static final int BAD_GATEWAY = 502; 160 | private static final int SERVICE_UNAVAILABLE = 503; 161 | private static final int GATEWAY_TIMEOUT = 504; 162 | 163 | public static ApiException handleException(Throwable e){ 164 | ApiException ex; 165 | if (e instanceof HttpException){ //HTTP错误 166 | HttpException httpException = (HttpException) e; 167 | ex = new ApiException(e, ErrorType.HTTP_ERROR); 168 | switch(httpException.code()){ 169 | case UNAUTHORIZED: 170 | ex.message = "当前请求需要用户验证"; 171 | break; 172 | case FORBIDDEN: 173 | ex.message = "服务器已经理解请求,但是拒绝执行它"; 174 | break; 175 | case NOT_FOUND: 176 | ex.message = "服务器异常,请稍后再试"; 177 | break; 178 | case REQUEST_TIMEOUT: 179 | ex.message = "请求超时"; 180 | break; 181 | case GATEWAY_TIMEOUT: 182 | ex.message = "作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应"; 183 | break; 184 | case INTERNAL_SERVER_ERROR: 185 | ex.message = "服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理"; 186 | break; 187 | case BAD_GATEWAY: 188 | ex.message = "作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应"; 189 | break; 190 | case SERVICE_UNAVAILABLE: 191 | ex.message = "由于临时的服务器维护或者过载,服务器当前无法处理请求"; 192 | break; 193 | default: 194 | ex.message = "网络错误"; //其它均视为网络错误 195 | break; 196 | } 197 | return ex; 198 | } else if (e instanceof ServerException){ //服务器返回的错误 199 | ServerException resultException = (ServerException) e; 200 | ex = new ApiException(resultException, resultException.code); 201 | ex.message = resultException.message; 202 | return ex; 203 | } else if (e instanceof JsonParseException 204 | || e instanceof JSONException 205 | || e instanceof ParseException){ 206 | ex = new ApiException(e, ErrorType.PARSE_ERROR); 207 | ex.message = "解析错误"; //均视为解析错误 208 | return ex; 209 | }else if(e instanceof ConnectException || e instanceof SocketTimeoutException || e instanceof ConnectTimeoutException){ 210 | ex = new ApiException(e, ErrorType.NETWORD_ERROR); 211 | ex.message = "连接失败"; //均视为网络错误 212 | return ex; 213 | } 214 | else { 215 | ex = new ApiException(e, ErrorType.UNKNOWN); 216 | ex.message = "未知错误"; //未知错误 217 | return ex; 218 | } 219 | } 220 | } 221 | ``` 222 | 223 | 关于处理服务器在错误时将错误信息直接放在data字段,即data字段在结果成功和失败对应的类型不定。处理思路是自定义GsonConverter,可以查看Demo里的`MockDataActivity`去看使用方法,其实就修改Retofit2初始化传入的GsonConverter。关键是对于`CustomGsonResponseBodyConverter`的修改。 224 | ```java 225 | @Override 226 | public T convert(ResponseBody value) throws IOException { 227 | String response = value.string(); 228 | JsonElement jsonElement = jsonParser.parse(response); 229 | int parseCode = jsonElement.getAsJsonObject().get("code").getAsInt(); 230 | // 231 | if (parseCode != ErrorType.SUCCESS) { 232 | value.close(); 233 | String msg = jsonElement.getAsJsonObject().get("data").getAsString(); 234 | throw new ServerException(msg, parseCode); 235 | } else { 236 | 237 | MediaType contentType = value.contentType(); 238 | Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8; 239 | InputStream inputStream = new ByteArrayInputStream(response.getBytes()); 240 | Reader reader = new InputStreamReader(inputStream, charset); 241 | JsonReader jsonReader = gson.newJsonReader(reader); 242 | 243 | try { 244 | return adapter.read(jsonReader); 245 | } finally { 246 | value.close(); 247 | } 248 | } 249 | } 250 | ``` 251 | 252 | 这里先解析code字段再进行判断,所以处理这种服务器返回的话,是需要将上面的`"code"`和`"data"`替换成你服务端具体的字段。 253 | 254 | 255 | 256 | ------------------- 257 | 258 | 259 | 260 | 261 | ## Update 262 | ### 2017-07-12 263 | 增加了`Dagger` 2.11(依赖注入)的使用,非`Dagger2`方式放在不同的`Branch noDagger2`中方便不使用Dagger2用户 264 | ### 2017-03-28 265 | 更新说明文档,修正格式化错位,解决在atom下tab长度与github长度不对应的显示问题。 266 | ### 2017-03-27 267 | 更新文档,增加短视频说明,增加错误信息放在data字段的说明。 268 | ### 2017-03-07 269 | 270 | 添加处理非REST接口在token失效时或code异常时,错误信息放在data字段的解析办法处理。 271 | 272 | ```json 273 | { 274 | code:-1 275 | data:"token失效" 276 | } 277 | ``` 278 | ```json 279 | { 280 | code:0 281 | data:{name:"xiaoming", age:23} 282 | } 283 | ``` 284 | 285 | 针对上面的JSON都要在同一个接口里处理,解决办法都是两次解析的办法,第一次取到code并且判断,不是期望的值进行处理。期望的值可按原路处理。这里采用了修改GsonConverter的办法。 286 | 287 | ### 2016-12-26 288 | 解决了执行onCompleted()之后执行onError()的问题 289 | ```java 290 | if (!isUnsubscribed()) 291 | { 292 | unsubscribe(); 293 | } 294 | ``` 295 | 296 | ### 2016-10-13 297 | 298 | 修正了服务端code没有处理,返回为错误时认为是json实体解析问题。 299 | 300 | ``` java 301 | if (httpResult.getCode() != ErrorType.SUCCESS || httpResult.getCode() != ErrorType.SUCCESS) 302 | ``` 303 | 304 | 305 | ------------------------ 306 | 307 | 308 | ## Thanks 309 | - [Retrofit自定义GsonConverter处理请求错误异常处理](http://blog.csdn.net/jdsjlzx/article/details/52145131) 310 | - [你真的会用Retrofit2吗?Retrofit2完全教程](http://www.jianshu.com/p/308f3c54abdd) 311 | - [Error handling in RxJava](http://blog.danlew.net/2015/12/08/error-handling-in-rxjava/) 312 | - [Android基于Retrofit2.0 封装的超好用的RetrofitClient工具类]( http://www.jianshu.com/p/29c2a9ac5abf) 313 | - [Retrofit2.0 再次封装:](http://www.jianshu.com/p/7edc1cce6b93) 314 | - [Retrofit + RxAndroid 实践总结](http://www.jianshu.com/p/f48f6d31314b) 315 | - [RxJava 与 Retrofit 结合的最佳实践]( https://gank.io/post/56e80c2c677659311bed9841) 316 | - [你真的会用Retrofit2吗?Retrofit2完全教程](http://www.jianshu.com/p/308f3c54abdd) 317 | 318 | ## Contact Me 319 | - Github: github.com/ysmintor 320 | - Email: ysmintor@gmail.com 321 | 322 | 323 | ## License 324 | 325 | Copyright 2016 - 2017 YorkYu. All rights reserved. 326 | 327 | Licensed under the Apache License, Version 2.0 (the "License"); 328 | you may not use this file except in compliance with the License. 329 | You may obtain a copy of the License at 330 | 331 | http://www.apache.org/licenses/LICENSE-2.0 332 | 333 | Unless required by applicable law or agreed to in writing, software 334 | distributed under the License is distributed on an "AS IS" BASIS, 335 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 336 | See the License for the specific language governing permissions and 337 | limitations under the License. 338 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 26 5 | buildToolsVersion '25.0.3' 6 | 7 | defaultConfig { 8 | applicationId "york.com.retrofit2rxjavademo" 9 | minSdkVersion 19 10 | targetSdkVersion 26 11 | versionCode 1 12 | versionName "1.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | lintOptions { 21 | abortOnError false 22 | } 23 | splits { 24 | density { 25 | enable true 26 | exclude 'ldpi', 'mdpi' 27 | compatibleScreens 'normal', 'large', 'xlarge' 28 | } 29 | } 30 | } 31 | 32 | dependencies { 33 | compile fileTree(include: ['*.jar'], dir: 'libs') 34 | compile 'com.squareup.retrofit2:retrofit:2.1.0' 35 | compile 'com.android.support:appcompat-v7:25.2.0' 36 | compile 'io.reactivex:rxjava:1.1.8' 37 | compile 'io.reactivex:rxandroid:1.2.1' 38 | compile 'com.squareup.retrofit2:converter-gson:2.1.0' 39 | compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' 40 | compile 'com.jakewharton:butterknife:7.0.1' 41 | compile 'com.squareup.okhttp3:okhttp:3.4.1' 42 | compile 'com.squareup.okhttp3:logging-interceptor:3.4.1' 43 | compile 'com.trello:rxlifecycle:0.6.1' 44 | compile 'com.trello:rxlifecycle-components:0.6.1' 45 | 46 | // rxbinding 47 | compile 'com.jakewharton.rxbinding:rxbinding:0.4.0' 48 | compile 'com.jakewharton.rxbinding:rxbinding-support-v4:0.4.0' 49 | compile 'com.jakewharton.rxbinding:rxbinding-design:0.4.0' 50 | compile 'com.jakewharton.rxbinding:rxbinding-recyclerview-v7:0.4.0' 51 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 52 | 53 | // dagger2 54 | compile 'com.google.dagger:dagger-android:2.11' 55 | compile 'com.google.dagger:dagger-android-support:2.11' // if you use the support libraries 56 | annotationProcessor 'com.google.dagger:dagger-android-processor:2.11' 57 | annotationProcessor 'com.google.dagger:dagger-compiler:2.11' 58 | testCompile 'junit:junit:4.12' 59 | } 60 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in D:\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/york/com/retrofit2rxjavademo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ysmintor/Retrofit2RxjavaDemo/62134e3bddc9d4aebcfc4508be42de7676231309/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/MyApplication.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo; 2 | 3 | import android.app.Activity; 4 | import android.app.Application; 5 | 6 | import javax.inject.Inject; 7 | 8 | import dagger.android.AndroidInjector; 9 | import dagger.android.DispatchingAndroidInjector; 10 | import dagger.android.HasActivityInjector; 11 | import york.com.retrofit2rxjavademo.di.component.DaggerAppComponent; 12 | import york.com.retrofit2rxjavademo.di.module.NetworkModule; 13 | 14 | /** 15 | * @author YorkYu 16 | * @version V1.0 17 | * @Project: Retrofit2RxjavaDemo 18 | * @Package york.com.retrofit2rxjavademo 19 | * @Description: 20 | * @time 2016/7/25 17:08 21 | */ 22 | public class MyApplication extends Application implements HasActivityInjector { 23 | @Inject 24 | DispatchingAndroidInjector dispatchingActivityInjector; 25 | 26 | private static final String sBASE_URL = "http://rap.taobao.org/mockjsdata/15987/"; 27 | private static final int sDEFAULT_TIMEOUT = 10; 28 | 29 | @Override 30 | public void onCreate() { 31 | super.onCreate(); 32 | 33 | DaggerAppComponent 34 | .builder() 35 | .application(this) 36 | .network(new NetworkModule(sBASE_URL, sDEFAULT_TIMEOUT)) 37 | .build() 38 | .inject(this); 39 | } 40 | 41 | 42 | @Override 43 | public AndroidInjector activityInjector() { 44 | return dispatchingActivityInjector; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.activity; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.util.Log; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.TextView; 10 | import android.widget.Toast; 11 | 12 | import java.util.List; 13 | 14 | import javax.inject.Inject; 15 | 16 | import butterknife.Bind; 17 | import butterknife.ButterKnife; 18 | import butterknife.OnClick; 19 | import dagger.android.AndroidInjection; 20 | import dagger.android.AndroidInjector; 21 | import dagger.android.support.AndroidSupportInjection; 22 | import dagger.android.support.AndroidSupportInjectionModule; 23 | import york.com.retrofit2rxjavademo.R; 24 | import york.com.retrofit2rxjavademo.entity.MockBean; 25 | import york.com.retrofit2rxjavademo.entity.TestBean; 26 | import york.com.retrofit2rxjavademo.http.ServiceFactory; 27 | import york.com.retrofit2rxjavademo.http.exception.ApiException; 28 | import york.com.retrofit2rxjavademo.subscribers.CommonSubscriber; 29 | import york.com.retrofit2rxjavademo.subscribers.RxSubscriber; 30 | import york.com.retrofit2rxjavademo.transformer.DefaultTransformer; 31 | 32 | public class MainActivity extends AppCompatActivity { 33 | private static String TAG = "MainActivity"; 34 | @Bind(R.id.tv_result_one) 35 | TextView resultOne; 36 | @Bind(R.id.tv_result_two) 37 | TextView resultTwo; 38 | @Bind(R.id.btn_rxsubscriber) 39 | Button mBtnRxsubscriber; 40 | @Bind(R.id.btn_common) 41 | Button mBtnCommon; 42 | @Bind(R.id.btn_converter) 43 | Button mBtnConverter; 44 | private Context mContext; 45 | 46 | @Inject 47 | ServiceFactory mServiceFactory; 48 | 49 | @Inject 50 | TestBean mTestBean; 51 | 52 | @Inject 53 | TestBean mTestBean2; 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) { 56 | AndroidInjection.inject(this); 57 | super.onCreate(savedInstanceState); 58 | setContentView(R.layout.activity_main); 59 | ButterKnife.bind(this); 60 | mContext = this; 61 | Log.w(TAG, "onCreate: aa" ); 62 | Log.w(TAG, mTestBean.toString()); 63 | Log.w(TAG, mTestBean2.toString()); 64 | } 65 | 66 | @OnClick({R.id.btn_rxsubscriber, R.id.btn_common, R.id.btn_converter, R.id.btn_error}) 67 | public void onClick(View view) { 68 | switch (view.getId()) { 69 | case R.id.btn_rxsubscriber: 70 | withDialog(); 71 | break; 72 | case R.id.btn_common: 73 | withoutDialog(); 74 | break; 75 | case R.id.btn_converter: 76 | MockDataActivity.start(this); 77 | break; 78 | case R.id.btn_error: 79 | showError(); 80 | break; 81 | } 82 | } 83 | // Example use with CommonSubscriber which does not contain progress bar 84 | private void withoutDialog() { 85 | mServiceFactory.mockApi() 86 | .getMock1() 87 | .compose(new DefaultTransformer>()) 88 | .subscribe(new CommonSubscriber>(mContext) { 89 | // 必须重写 90 | @Override 91 | public void onNext(List mockBeen) { 92 | Toast.makeText(mContext, "onNext", Toast.LENGTH_SHORT).show(); 93 | resultTwo.setText("begin >>>>>>>>>>>>>>>>.\n" + mockBeen); 94 | Log.d("main", "onNext: " + mockBeen); 95 | } 96 | 97 | // 若无自定义的需求可以不用重写 98 | // !!!!注意参数为ApiException 类型 99 | @Override 100 | protected void onError(ApiException ex) { 101 | super.onError(ex); 102 | Toast.makeText(mContext, "onError " + " exception code =" + ex.code + "exception message = " + ex.message, Toast.LENGTH_SHORT).show(); 103 | } 104 | 105 | // 若无自定义的需求可以不用重写 106 | @Override 107 | public void onCompleted() { 108 | super.onCompleted(); 109 | Toast.makeText(mContext, "onCompleted", Toast.LENGTH_SHORT).show(); 110 | } 111 | }); 112 | } 113 | // Example use with RxSubscriber which contains progress bar 114 | private void withDialog() { 115 | mServiceFactory.mockApi() 116 | .getMock4() 117 | .compose(new DefaultTransformer()) 118 | .subscribe(new RxSubscriber(mContext) { 119 | // 必须重写 120 | @Override 121 | public void onNext(MockBean mockBean) { 122 | Toast.makeText(mContext, "onNext", Toast.LENGTH_SHORT).show(); 123 | resultOne.setText("Single bean begin >>>>>>>>>>>>>>>>.\n" + mockBean); 124 | Log.d("main", "onNext: " + mockBean); 125 | } 126 | }); 127 | } 128 | 129 | private void showError() { 130 | mServiceFactory.mockApi() 131 | .getMock2() 132 | .compose(new DefaultTransformer()) 133 | .subscribe(new CommonSubscriber(mContext) { 134 | @Override 135 | public void onNext(MockBean mockBean) { 136 | resultOne.setText("Single bean begin >>>>>>>>>>>>>>>>." + mockBean); 137 | } 138 | }); 139 | } 140 | } 141 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/activity/MockDataActivity.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.activity; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.util.Log; 8 | import android.view.View; 9 | import android.widget.Button; 10 | import android.widget.TextView; 11 | 12 | import javax.inject.Inject; 13 | 14 | import dagger.android.AndroidInjection; 15 | import retrofit2.Retrofit; 16 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 17 | import york.com.retrofit2rxjavademo.R; 18 | import york.com.retrofit2rxjavademo.entity.MockBean; 19 | import york.com.retrofit2rxjavademo.entity.TestBean; 20 | import york.com.retrofit2rxjavademo.gsonconverter.CustomGsonConverterFactory; 21 | import york.com.retrofit2rxjavademo.http.ServiceFactory; 22 | import york.com.retrofit2rxjavademo.subscribers.RxSubscriber; 23 | import york.com.retrofit2rxjavademo.transformer.DefaultTransformer; 24 | 25 | 26 | public class MockDataActivity extends AppCompatActivity { 27 | private static final String TAG = "MockDataActivity"; 28 | private TextView mTv; 29 | private Button mBtn1; 30 | private Button mBtn2; 31 | private Context mContext; 32 | 33 | 34 | @Inject 35 | ServiceFactory mServiceFactory; 36 | 37 | @Inject 38 | TestBean mTestBean; 39 | 40 | @Inject 41 | TestBean mTestBean2; 42 | @Override 43 | protected void onCreate(Bundle savedInstanceState) { 44 | AndroidInjection.inject(this); 45 | super.onCreate(savedInstanceState); 46 | setContentView(R.layout.activity_mock_data); 47 | mContext = this; 48 | mTv = (TextView) findViewById(R.id.textView); 49 | mBtn1 = (Button) findViewById(R.id.button); 50 | mBtn2 = (Button) findViewById(R.id.button2); 51 | 52 | Log.w(TAG, mTestBean.toString()); 53 | Log.w(TAG, mTestBean2.toString()); 54 | 55 | // 使用自定义Converter处理message在错误时返回在data字段 56 | /* sRetrefit = new Retrofit.Builder() 57 | .client(ServiceFactory.getOkHttpClient()) 58 | .baseUrl(BASE_URL) 59 | // 使用自定义Converter处理message在错误时返回在data字段 60 | .addConverterFactory(CustomGsonConverterFactory.create()) 61 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 62 | .build();*/ 63 | 64 | mBtn1.setOnClickListener(new View.OnClickListener() { 65 | @Override 66 | public void onClick(View v) { 67 | mServiceFactory.mockApi2() 68 | .getMock3() 69 | .compose(new DefaultTransformer()) 70 | .subscribe(new RxSubscriber(mContext) { 71 | @Override 72 | public void onNext(MockBean mockBean) { 73 | mTv.setText(mockBean.toString()); 74 | } 75 | }); 76 | } 77 | }); 78 | 79 | mBtn2.setOnClickListener(new View.OnClickListener() { 80 | @Override 81 | public void onClick(View v) { 82 | mServiceFactory.mockApi2() 83 | .getMock4() 84 | .compose(new DefaultTransformer()) 85 | .subscribe(new RxSubscriber(mContext) { 86 | @Override 87 | public void onNext(MockBean mockBean) { 88 | mTv.setText(mockBean.toString()); 89 | } 90 | }); 91 | } 92 | }); 93 | } 94 | 95 | 96 | 97 | public static void start(Context context) { 98 | Intent starter = new Intent(context, MockDataActivity.class); 99 | context.startActivity(starter); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/di/component/AppComponent.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.di.component; 2 | 3 | import dagger.BindsInstance; 4 | import dagger.Component; 5 | import dagger.android.AndroidInjectionModule; 6 | import york.com.retrofit2rxjavademo.MyApplication; 7 | import york.com.retrofit2rxjavademo.di.module.AppModule; 8 | import york.com.retrofit2rxjavademo.di.module.InjectorModule; 9 | import york.com.retrofit2rxjavademo.di.module.NetworkModule; 10 | import york.com.retrofit2rxjavademo.di.scope.AppScope; 11 | 12 | /** 13 | * @author: YorkYu 14 | * @version: V2.0.0 15 | * @project: Retrofit2RxjavaDemo 16 | * @package: york.com.retrofit2rxjavademo.di.component 17 | * @description: description 18 | * @date: 2017/7/7 19 | * @time: 17:30 20 | */ 21 | @AppScope 22 | @Component(modules = { 23 | /* Use AndroidInjectionModule.class if you're not using support library */ 24 | AndroidInjectionModule.class, 25 | InjectorModule.class, 26 | NetworkModule.class, 27 | AppModule.class}) 28 | public interface AppComponent { 29 | 30 | @Component.Builder 31 | interface Builder { 32 | @BindsInstance 33 | Builder application(MyApplication application); 34 | 35 | Builder network(NetworkModule networkModule); 36 | 37 | AppComponent build(); 38 | } 39 | 40 | void inject(MyApplication app); 41 | } 42 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/di/module/AppModule.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.di.module; 2 | 3 | import android.content.Context; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import york.com.retrofit2rxjavademo.MyApplication; 8 | import york.com.retrofit2rxjavademo.di.scope.AppScope; 9 | 10 | /** 11 | * @author: YorkYu 12 | * @version: V2.0.0 13 | * @project: Retrofit2RxjavaDemo 14 | * @package: york.com.retrofit2rxjavademo.di.module 15 | * @description: description 16 | * @date: 2017/7/7 17 | * @time: 17:31 18 | */ 19 | @Module 20 | public class AppModule { 21 | @Provides 22 | @AppScope 23 | Context provideContext(MyApplication application) { 24 | return application.getApplicationContext(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/di/module/InjectorModule.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.di.module; 2 | 3 | import dagger.Module; 4 | import dagger.android.ContributesAndroidInjector; 5 | import york.com.retrofit2rxjavademo.activity.MainActivity; 6 | import york.com.retrofit2rxjavademo.activity.MockDataActivity; 7 | import york.com.retrofit2rxjavademo.di.scope.ControllerScope; 8 | import york.com.retrofit2rxjavademo.di.scope.PerActivityScope; 9 | 10 | /** 11 | * @author: YorkYu 12 | * @version: V2.0.0 13 | * @project: Retrofit2RxjavaDemo 14 | * @package: york.com.retrofit2rxjavademo.di.module 15 | * @description: 用来连接各个Activity Service 等 16 | * @date: 2017/7/11 17 | * @time: 11:45 18 | */ 19 | @Module 20 | public abstract class InjectorModule { 21 | 22 | @PerActivityScope 23 | @ContributesAndroidInjector(modules = MainModule.class) 24 | abstract MainActivity contributeMainActivityInjector(); 25 | 26 | @PerActivityScope 27 | @ContributesAndroidInjector(modules = MainModule.class) 28 | abstract MockDataActivity contributeMockDataActivityInjector(); 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/di/module/MainModule.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.di.module; 2 | 3 | import dagger.Module; 4 | import dagger.Provides; 5 | import york.com.retrofit2rxjavademo.di.scope.PerActivityScope; 6 | import york.com.retrofit2rxjavademo.entity.TestBean; 7 | 8 | /** 9 | * @author: YorkYu 10 | * @version: V2.0.0 11 | * @project: Retrofit2RxjavaDemo 12 | * @package: york.com.retrofit2rxjavademo.di.module 13 | * @description: description 14 | * @date: 2017/7/11 15 | * @time: 17:45 16 | */ 17 | @Module 18 | public class MainModule { 19 | @Provides 20 | @PerActivityScope 21 | TestBean provideTestBean() { 22 | return new TestBean("ma", 13); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/di/module/NetworkModule.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.di.module; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import android.content.SharedPreferences; 6 | import android.preference.PreferenceManager; 7 | 8 | import com.google.gson.FieldNamingPolicy; 9 | import com.google.gson.Gson; 10 | import com.google.gson.GsonBuilder; 11 | 12 | import java.util.concurrent.TimeUnit; 13 | 14 | import javax.inject.Named; 15 | import javax.inject.Singleton; 16 | 17 | import dagger.Module; 18 | import dagger.Provides; 19 | import okhttp3.Cache; 20 | import okhttp3.OkHttpClient; 21 | import okhttp3.logging.HttpLoggingInterceptor; 22 | import retrofit2.Retrofit; 23 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 24 | import retrofit2.converter.gson.GsonConverterFactory; 25 | import york.com.retrofit2rxjavademo.MyApplication; 26 | import york.com.retrofit2rxjavademo.di.scope.AppScope; 27 | import york.com.retrofit2rxjavademo.di.scope.AppScope; 28 | import york.com.retrofit2rxjavademo.gsonconverter.CustomGsonConverterFactory; 29 | import york.com.retrofit2rxjavademo.http.ServiceFactory; 30 | import york.com.retrofit2rxjavademo.http.exception.ServerException; 31 | 32 | /** 33 | * @author: YorkYu 34 | * @version: V2.0.0 35 | * @project: Retrofit2RxjavaDemo 36 | * @package: york.com.retrofit2rxjavademo.di.module 37 | * @description: description 38 | * @date: 2017/7/7 39 | * @time: 17:31 40 | */ 41 | @Module 42 | public class NetworkModule { 43 | // private String mBaseUrl = "http://rap.taobao.org/mockjsdata/15987/"; 44 | private String mBaseUrl; 45 | private int DEFAULT_TIMEOUT = 10; 46 | 47 | // Constructor needs one parameter to instantiate. 48 | public NetworkModule(String baseUrl, int default_timeout) { 49 | this.mBaseUrl = baseUrl; 50 | this.DEFAULT_TIMEOUT = default_timeout; 51 | } 52 | 53 | // Dagger will only look for methods annotated with @Provides 54 | @Provides 55 | @AppScope 56 | // Application reference must come from AppModule.class 57 | SharedPreferences providesSharedPreferences(MyApplication application) { 58 | return PreferenceManager.getDefaultSharedPreferences(application); 59 | } 60 | 61 | @Provides 62 | @AppScope 63 | Cache provideOkHttpCache(MyApplication application) { 64 | int cacheSize = 10 * 1024 * 1024; // 10 MiB 65 | Cache cache = new Cache(application.getCacheDir(), cacheSize); 66 | return cache; 67 | } 68 | 69 | @Provides 70 | @AppScope 71 | Gson provideGson() { 72 | GsonBuilder gsonBuilder = new GsonBuilder(); 73 | gsonBuilder.setFieldNamingPolicy(FieldNamingPolicy.IDENTITY); 74 | return gsonBuilder.create(); 75 | } 76 | 77 | @Provides 78 | @AppScope 79 | @Named("cached") 80 | OkHttpClient provideOkHttpClient(Cache cache) { 81 | OkHttpClient client = 82 | new OkHttpClient.Builder() 83 | .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) 84 | .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) 85 | .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) 86 | .addNetworkInterceptor( 87 | new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) 88 | .retryOnConnectionFailure(true) 89 | .cache(cache) 90 | .build(); 91 | return client; 92 | } 93 | 94 | @Provides 95 | @AppScope 96 | @Named("noncached") 97 | OkHttpClient provideNonCachedOkHttpClient() { 98 | OkHttpClient client = 99 | new OkHttpClient.Builder() 100 | .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) 101 | .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) 102 | .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) 103 | .addNetworkInterceptor( 104 | new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) 105 | .retryOnConnectionFailure(true) 106 | .build(); 107 | return client; 108 | } 109 | 110 | @Provides 111 | @AppScope 112 | Retrofit provideRetrofit(Gson gson, @Named("cached") OkHttpClient okHttpClient) { 113 | return new Retrofit.Builder() 114 | .addConverterFactory(GsonConverterFactory.create(gson)) 115 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 116 | .client(okHttpClient) 117 | .baseUrl(mBaseUrl) 118 | .build(); 119 | } 120 | 121 | 122 | /** 123 | * 使用自定义Converter处理message在错误时返回在data字段 124 | * @param gson 125 | * @param okHttpClient 126 | * @return 127 | */ 128 | @Provides 129 | @AppScope 130 | @Named("custom_converter") 131 | Retrofit provideCustomConverterRetrofit(Gson gson, @Named("cached") OkHttpClient okHttpClient) { 132 | return new Retrofit.Builder() 133 | .addConverterFactory(CustomGsonConverterFactory.create(gson)) 134 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 135 | .client(okHttpClient) 136 | .baseUrl(mBaseUrl) 137 | .build(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/di/scope/AppScope.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.di.scope; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * @author: YorkYu 10 | * @version: V2.0.0 11 | * @project: Retrofit2RxjavaDemo 12 | * @package: york.com.retrofit2rxjavademo.di.scope 13 | * @description: AppScope like singleton in the whole application 14 | * @date: 2017/7/7 15 | * @time: 17:32 16 | */ 17 | @Scope 18 | @Retention(RetentionPolicy.CLASS) 19 | public @interface AppScope { 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/di/scope/ControllerScope.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.di.scope; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * @author: YorkYu 10 | * @version: V2.0.0 11 | * @project: Retrofit2RxjavaDemo 12 | * @package: york.com.retrofit2rxjavademo.di.scope 13 | * @description: ControllerScope 14 | * @date: 2017/7/7 15 | * @time: 17:32 16 | */ 17 | @Scope 18 | @Retention(RetentionPolicy.CLASS) 19 | public @interface ControllerScope { 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/di/scope/NetworkScope.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.di.scope; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * @author: YorkYu 10 | * @version: V2.0.0 11 | * @project: Retrofit2RxjavaDemo 12 | * @package: york.com.retrofit2rxjavademo.di.scope 13 | * @description: AppScope like singleton in the whole application 14 | * @date: 2017/7/7 15 | * @time: 17:32 16 | */ 17 | @Scope 18 | @Retention(RetentionPolicy.CLASS) 19 | public @interface NetworkScope { 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/di/scope/PerActivityScope.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.di.scope; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * @author: YorkYu 10 | * @version: V2.0.0 11 | * @project: Retrofit2RxjavaDemo 12 | * @package: york.com.retrofit2rxjavademo.di.scope 13 | * @description: ControllerScope 14 | * @date: 2017/7/7 15 | * @time: 17:32 16 | */ 17 | @Scope 18 | @Retention(RetentionPolicy.CLASS) 19 | public @interface PerActivityScope { 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/entity/ContentBean.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.entity; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Created by liukun on 16/3/5. 7 | */ 8 | public class ContentBean { 9 | 10 | private String id; 11 | private String alt; 12 | private String year; 13 | private String title; 14 | private String original_title; 15 | private List genres; 16 | private List casts; 17 | private List directors; 18 | private Avatars images; 19 | 20 | @Override 21 | public String toString() { 22 | return "ContentBean.id=" + id 23 | + " ContentBean.title=" + title 24 | + " ContentBean.year=" + year 25 | + " ContentBean.originalTitle=" + original_title + casts.toString() + directors.toString() + " | "; 26 | } 27 | 28 | public String getId() { 29 | return id; 30 | } 31 | 32 | public void setId(String id) { 33 | this.id = id; 34 | } 35 | 36 | public String getAlt() { 37 | return alt; 38 | } 39 | 40 | public void setAlt(String alt) { 41 | this.alt = alt; 42 | } 43 | 44 | public String getYear() { 45 | return year; 46 | } 47 | 48 | public void setYear(String year) { 49 | this.year = year; 50 | } 51 | 52 | public String getTitle() { 53 | return title; 54 | } 55 | 56 | public void setTitle(String title) { 57 | this.title = title; 58 | } 59 | 60 | public String getOriginal_title() { 61 | return original_title; 62 | } 63 | 64 | public void setOriginal_title(String original_title) { 65 | this.original_title = original_title; 66 | } 67 | 68 | public List getGenres() { 69 | return genres; 70 | } 71 | 72 | public void setGenres(List genres) { 73 | this.genres = genres; 74 | } 75 | 76 | public List getCasts() { 77 | return casts; 78 | } 79 | 80 | public void setCasts(List casts) { 81 | this.casts = casts; 82 | } 83 | 84 | public List getDirectors() { 85 | return directors; 86 | } 87 | 88 | public void setDirectors(List directors) { 89 | this.directors = directors; 90 | } 91 | 92 | public Avatars getImages() { 93 | return images; 94 | } 95 | 96 | public void setImages(Avatars images) { 97 | this.images = images; 98 | } 99 | 100 | private class Cast{ 101 | private String id; 102 | private String name; 103 | private String alt; 104 | private Avatars avatars; 105 | 106 | public String getId() { 107 | return id; 108 | } 109 | 110 | public void setId(String id) { 111 | this.id = id; 112 | } 113 | 114 | public String getName() { 115 | return name; 116 | } 117 | 118 | public void setName(String name) { 119 | this.name = name; 120 | } 121 | 122 | public String getAlt() { 123 | return alt; 124 | } 125 | 126 | public void setAlt(String alt) { 127 | this.alt = alt; 128 | } 129 | 130 | public Avatars getAvatars() { 131 | return avatars; 132 | } 133 | 134 | public void setAvatars(Avatars avatars) { 135 | this.avatars = avatars; 136 | } 137 | 138 | @Override 139 | public String toString() { 140 | return "cast.id=" + id + " cast.name=" + name + " | "; 141 | } 142 | } 143 | 144 | private class Avatars{ 145 | private String small; 146 | private String medium; 147 | private String large; 148 | 149 | public String getSmall() { 150 | return small; 151 | } 152 | 153 | public void setSmall(String small) { 154 | this.small = small; 155 | } 156 | 157 | public String getMedium() { 158 | return medium; 159 | } 160 | 161 | public void setMedium(String medium) { 162 | this.medium = medium; 163 | } 164 | 165 | public String getLarge() { 166 | return large; 167 | } 168 | 169 | public void setLarge(String large) { 170 | this.large = large; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/entity/HttpResult.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.entity; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | /** 6 | * Created by liukun on 16/3/5. 7 | */ 8 | public class HttpResult { 9 | 10 | // code 为返回的状态码, message 为返回的消息, 演示的没有这两个字段,考虑到真实的环境中基本包含就在这里写定值 11 | private int code; 12 | // this will receive message or status, msg as message field 13 | @SerializedName(value = "message", alternate = {"status", "msg"}) 14 | private String message; 15 | 16 | public int getCode() { 17 | return code; 18 | } 19 | 20 | public String getMessage() { 21 | return message; 22 | } 23 | 24 | //用来模仿Data 25 | @SerializedName(value = "data", alternate = {"subjects", "result"}) 26 | private T data; 27 | 28 | public T getData() { 29 | return data; 30 | } 31 | 32 | public void setData(T data) { 33 | this.data = data; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/entity/MockBean.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.entity; 2 | 3 | /** 4 | * @author YorkYu 5 | * @version V1.0 6 | * @Project: Retrofit2RxjavaDemo 7 | * @Package york.com.retrofit2rxjavademo.entity 8 | * @Description: MockBean 9 | * @time 2017/3/27 10:54 10 | */ 11 | public class MockBean { 12 | /** 13 | * address : 北京市海淀区清华大学 14 | * website : www.tsinghua.edu.cn 15 | * email : zsb@mail.tsinghua.edu.cn 16 | * parent : 教育部 17 | * type : 211 985 18 | * phone : 010-62770334;010-62782051 19 | * info : 院士:68人 博士点:198个 硕士点:181个 20 | * city : 北京 21 | * name : 清华大学 22 | * profile : 清华简历清华简历清华简历清华简历 23 | * img : http://img.jidichong.com/school/3.png 24 | * zipcode : 01022 25 | */ 26 | 27 | private String address; 28 | private String website; 29 | private String email; 30 | private String parent; 31 | private String type; 32 | private String phone; 33 | private String info; 34 | private String city; 35 | private String name; 36 | private String profile; 37 | private String img; 38 | private String zipcode; 39 | 40 | public String getAddress() { 41 | return address; 42 | } 43 | 44 | public void setAddress(String address) { 45 | this.address = address; 46 | } 47 | 48 | public String getWebsite() { 49 | return website; 50 | } 51 | 52 | public void setWebsite(String website) { 53 | this.website = website; 54 | } 55 | 56 | public String getEmail() { 57 | return email; 58 | } 59 | 60 | public void setEmail(String email) { 61 | this.email = email; 62 | } 63 | 64 | public String getParent() { 65 | return parent; 66 | } 67 | 68 | public void setParent(String parent) { 69 | this.parent = parent; 70 | } 71 | 72 | public String getType() { 73 | return type; 74 | } 75 | 76 | public void setType(String type) { 77 | this.type = type; 78 | } 79 | 80 | public String getPhone() { 81 | return phone; 82 | } 83 | 84 | public void setPhone(String phone) { 85 | this.phone = phone; 86 | } 87 | 88 | public String getInfo() { 89 | return info; 90 | } 91 | 92 | public void setInfo(String info) { 93 | this.info = info; 94 | } 95 | 96 | public String getCity() { 97 | return city; 98 | } 99 | 100 | public void setCity(String city) { 101 | this.city = city; 102 | } 103 | 104 | public String getName() { 105 | return name; 106 | } 107 | 108 | public void setName(String name) { 109 | this.name = name; 110 | } 111 | 112 | public String getProfile() { 113 | return profile; 114 | } 115 | 116 | public void setProfile(String profile) { 117 | this.profile = profile; 118 | } 119 | 120 | public String getImg() { 121 | return img; 122 | } 123 | 124 | public void setImg(String img) { 125 | this.img = img; 126 | } 127 | 128 | public String getZipcode() { 129 | return zipcode; 130 | } 131 | 132 | public void setZipcode(String zipcode) { 133 | this.zipcode = zipcode; 134 | } 135 | 136 | @Override 137 | public String toString() { 138 | return "MockBean{" + 139 | "address='" + address + '\'' + 140 | ", website='" + website + '\'' + 141 | ", email='" + email + '\'' + 142 | ", parent='" + parent + '\'' + 143 | ", type='" + type + '\'' + 144 | ", phone='" + phone + '\'' + 145 | ", info='" + info + '\'' + 146 | ", city='" + city + '\'' + 147 | ", name='" + name + '\'' + 148 | ", profile='" + profile + '\'' + 149 | ", img='" + img + '\'' + 150 | ", zipcode='" + zipcode + '\'' + 151 | '}'; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/entity/TestBean.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.entity; 2 | 3 | /** 4 | * @author: YorkYu 5 | * @version: V2.0.0 6 | * @project: Retrofit2RxjavaDemo 7 | * @package: york.com.retrofit2rxjavademo.entity 8 | * @description: description 9 | * @date: 2017/7/11 10 | * @time: 17:47 11 | */ 12 | public class TestBean { 13 | String name; 14 | int age; 15 | 16 | public TestBean(String name, int age) { 17 | this.name = name; 18 | this.age = age; 19 | } 20 | 21 | @Override 22 | public String toString() { 23 | return "TestBean{" + 24 | "name='" + name + '\'' + 25 | ", age=" + age + 26 | '}'; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/gsonconverter/CustomGsonConverterFactory.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.gsonconverter; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.TypeAdapter; 5 | import com.google.gson.reflect.TypeToken; 6 | 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.Type; 9 | 10 | import okhttp3.RequestBody; 11 | import okhttp3.ResponseBody; 12 | import retrofit2.Converter; 13 | import retrofit2.Retrofit; 14 | 15 | /** 16 | * @author YorkYu 17 | * @version V1.0 18 | * @Description: 19 | * @time 2017/3/7 10:37 20 | */ 21 | public final class CustomGsonConverterFactory extends Converter.Factory{ 22 | /** 23 | * Create an instance using a default {@link Gson} instance for conversion. Encoding to JSON and 24 | * decoding from JSON (when no charset is specified by a header) will use UTF-8. 25 | */ 26 | public static CustomGsonConverterFactory create() { 27 | return create(new Gson()); 28 | } 29 | 30 | /** 31 | * Create an instance using {@code gson} for conversion. Encoding to JSON and 32 | * decoding from JSON (when no charset is specified by a header) will use UTF-8. 33 | */ 34 | public static CustomGsonConverterFactory create(Gson gson) { 35 | return new CustomGsonConverterFactory(gson); 36 | } 37 | 38 | private final Gson gson; 39 | 40 | private CustomGsonConverterFactory(Gson gson) { 41 | if (gson == null) throw new NullPointerException("gson == null"); 42 | this.gson = gson; 43 | } 44 | 45 | @Override 46 | public Converter responseBodyConverter(Type type, Annotation[] annotations, 47 | Retrofit retrofit) { 48 | TypeAdapter adapter = gson.getAdapter(TypeToken.get(type)); 49 | return new CustomGsonResponseBodyConverter<>(gson, adapter); 50 | } 51 | 52 | @Override 53 | public Converter requestBodyConverter(Type type, 54 | Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { 55 | TypeAdapter adapter = gson.getAdapter(TypeToken.get(type)); 56 | return new CustomGsonRequestBodyConverter<>(gson, adapter); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/gsonconverter/CustomGsonRequestBodyConverter.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.gsonconverter; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.TypeAdapter; 5 | import com.google.gson.stream.JsonWriter; 6 | 7 | import java.io.IOException; 8 | import java.io.OutputStreamWriter; 9 | import java.io.Writer; 10 | import java.nio.charset.Charset; 11 | 12 | import okhttp3.MediaType; 13 | import okhttp3.RequestBody; 14 | import okio.Buffer; 15 | import retrofit2.Converter; 16 | 17 | /** 18 | * @author YorkYu 19 | * @version V1.0 20 | * @Description: 21 | * @time 2017/3/7 10:43 22 | */ 23 | final class CustomGsonRequestBodyConverter implements Converter { 24 | private static final MediaType MEDIA_TYPE = MediaType.parse("application/json; charset=UTF-8"); 25 | private static final Charset UTF_8 = Charset.forName("UTF-8"); 26 | 27 | private final Gson gson; 28 | private final TypeAdapter adapter; 29 | 30 | CustomGsonRequestBodyConverter(Gson gson, TypeAdapter adapter) { 31 | this.gson = gson; 32 | this.adapter = adapter; 33 | } 34 | 35 | @Override 36 | public RequestBody convert(T value) throws IOException { 37 | Buffer buffer = new Buffer(); 38 | Writer writer = new OutputStreamWriter(buffer.outputStream(), UTF_8); 39 | JsonWriter jsonWriter = gson.newJsonWriter(writer); 40 | adapter.write(jsonWriter, value); 41 | jsonWriter.close(); 42 | return RequestBody.create(MEDIA_TYPE, buffer.readByteString()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/gsonconverter/CustomGsonResponseBodyConverter.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.gsonconverter; 2 | 3 | 4 | import com.google.gson.Gson; 5 | import com.google.gson.JsonElement; 6 | import com.google.gson.JsonParser; 7 | import com.google.gson.TypeAdapter; 8 | import com.google.gson.stream.JsonReader; 9 | 10 | import java.io.ByteArrayInputStream; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.io.InputStreamReader; 14 | import java.io.Reader; 15 | import java.nio.charset.Charset; 16 | 17 | import okhttp3.MediaType; 18 | import okhttp3.ResponseBody; 19 | import retrofit2.Converter; 20 | import york.com.retrofit2rxjavademo.http.exception.ErrorType; 21 | import york.com.retrofit2rxjavademo.http.exception.ServerException; 22 | 23 | import static okhttp3.internal.Util.UTF_8; 24 | 25 | 26 | /** 27 | * @author YorkYu 28 | * @version V1.0 29 | * @Description: 30 | * @time 2017/3/7 10:30 31 | */ 32 | final class CustomGsonResponseBodyConverter implements Converter { 33 | private final Gson gson; 34 | private final TypeAdapter adapter; 35 | private final JsonParser jsonParser; 36 | 37 | CustomGsonResponseBodyConverter(Gson gson, TypeAdapter adapter) { 38 | this.gson = gson; 39 | this.adapter = adapter; 40 | jsonParser = new JsonParser(); 41 | } 42 | 43 | @Override 44 | public T convert(ResponseBody value) throws IOException { 45 | String response = value.string(); 46 | JsonElement jsonElement = jsonParser.parse(response); 47 | int parseCode = jsonElement.getAsJsonObject().get("code").getAsInt(); 48 | // 49 | if (parseCode != ErrorType.SUCCESS) { 50 | value.close(); 51 | String msg = jsonElement.getAsJsonObject().get("data").getAsString(); 52 | throw new ServerException(msg, parseCode); 53 | } else { 54 | 55 | MediaType contentType = value.contentType(); 56 | Charset charset = contentType != null ? contentType.charset(UTF_8) : UTF_8; 57 | InputStream inputStream = new ByteArrayInputStream(response.getBytes()); 58 | Reader reader = new InputStreamReader(inputStream, charset); 59 | JsonReader jsonReader = gson.newJsonReader(reader); 60 | 61 | try { 62 | return adapter.read(jsonReader); 63 | } finally { 64 | value.close(); 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/http/ExceptionEngine.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.http; 2 | 3 | import android.net.ParseException; 4 | 5 | import com.google.gson.JsonParseException; 6 | 7 | import org.apache.http.conn.ConnectTimeoutException; 8 | import org.json.JSONException; 9 | 10 | import java.net.ConnectException; 11 | import java.net.SocketTimeoutException; 12 | 13 | import retrofit2.adapter.rxjava.HttpException; 14 | import york.com.retrofit2rxjavademo.http.exception.ApiException; 15 | import york.com.retrofit2rxjavademo.http.exception.ErrorType; 16 | import york.com.retrofit2rxjavademo.http.exception.ServerException; 17 | 18 | 19 | public class ExceptionEngine { 20 | //对应HTTP的状态码 21 | private static final int UNAUTHORIZED = 401; 22 | private static final int FORBIDDEN = 403; 23 | private static final int NOT_FOUND = 404; 24 | private static final int REQUEST_TIMEOUT = 408; 25 | private static final int INTERNAL_SERVER_ERROR = 500; 26 | private static final int BAD_GATEWAY = 502; 27 | private static final int SERVICE_UNAVAILABLE = 503; 28 | private static final int GATEWAY_TIMEOUT = 504; 29 | 30 | public static ApiException handleException(Throwable e){ 31 | ApiException ex; 32 | if (e instanceof HttpException){ //HTTP错误 33 | HttpException httpException = (HttpException) e; 34 | ex = new ApiException(e, ErrorType.HTTP_ERROR); 35 | switch(httpException.code()){ 36 | case UNAUTHORIZED: 37 | ex.message = "当前请求需要用户验证"; 38 | break; 39 | case FORBIDDEN: 40 | ex.message = "服务器已经理解请求,但是拒绝执行它"; 41 | break; 42 | case NOT_FOUND: 43 | ex.message = "服务器异常,请稍后再试"; 44 | break; 45 | case REQUEST_TIMEOUT: 46 | ex.message = "请求超时"; 47 | break; 48 | case GATEWAY_TIMEOUT: 49 | ex.message = "作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应"; 50 | break; 51 | case INTERNAL_SERVER_ERROR: 52 | ex.message = "服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理"; 53 | break; 54 | case BAD_GATEWAY: 55 | ex.message = "作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应"; 56 | break; 57 | case SERVICE_UNAVAILABLE: 58 | ex.message = "由于临时的服务器维护或者过载,服务器当前无法处理请求"; 59 | break; 60 | default: 61 | ex.message = "网络错误"; //其它均视为网络错误 62 | break; 63 | } 64 | return ex; 65 | } else if (e instanceof ServerException){ //服务器返回的错误 66 | ServerException resultException = (ServerException) e; 67 | ex = new ApiException(resultException, resultException.code); 68 | ex.message = resultException.message; 69 | return ex; 70 | } else if (e instanceof JsonParseException 71 | || e instanceof JSONException 72 | || e instanceof ParseException){ 73 | ex = new ApiException(e, ErrorType.PARSE_ERROR); 74 | ex.message = "解析错误"; //均视为解析错误 75 | return ex; 76 | }else if(e instanceof ConnectException || e instanceof SocketTimeoutException || e instanceof ConnectTimeoutException){ 77 | ex = new ApiException(e, ErrorType.NETWORD_ERROR); 78 | ex.message = "连接失败"; //均视为网络错误 79 | return ex; 80 | } 81 | else { 82 | ex = new ApiException(e, ErrorType.UNKNOWN); 83 | ex.message = "未知错误"; //未知错误 84 | return ex; 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/http/MockApi.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.http; 2 | 3 | import java.util.List; 4 | 5 | import retrofit2.http.GET; 6 | import rx.Observable; 7 | import york.com.retrofit2rxjavademo.entity.HttpResult; 8 | import york.com.retrofit2rxjavademo.entity.MockBean; 9 | 10 | /** 11 | * @author YorkYu 12 | * @version V1.0 13 | * @Project: Retrofit2RxjavaDemo 14 | * @Package york.com.retrofit2rxjavademo.http 15 | * @Description: 16 | * @time 2017/3/7 15:38 17 | */ 18 | public interface MockApi { 19 | @GET("mock3") 20 | Observable> getMock3(); 21 | 22 | @GET("mock1") 23 | Observable>> getMock1(); 24 | 25 | @GET("mock4") 26 | Observable> getMock4(); 27 | 28 | @GET("mock2") 29 | Observable> getMock2(); 30 | } 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/http/MovieService.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.http; 2 | 3 | import java.util.List; 4 | 5 | import retrofit2.http.GET; 6 | import retrofit2.http.Query; 7 | import rx.Observable; 8 | import york.com.retrofit2rxjavademo.entity.ContentBean; 9 | import york.com.retrofit2rxjavademo.entity.HttpResult; 10 | 11 | /** 12 | * Created by liukun on 16/3/9. 13 | */ 14 | public interface MovieService { 15 | 16 | // @GET("top250") 17 | // Call getTopMovie(@Query("start") int start, @Query("count") int count); 18 | 19 | // @GET("top250") 20 | // Observable getTopMovie(@Query("start") int start, @Query("count") int count); 21 | 22 | // @GET("top250") 23 | // Observable>> getTopMovie(@Query("start") int start, @Query("count") int count); 24 | 25 | @GET("top250") 26 | Observable>> getTopMovie(@Query("start") int start, @Query("count") int count); 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/http/ServiceFactory.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.http; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Named; 5 | 6 | import okhttp3.OkHttpClient; 7 | import retrofit2.Retrofit; 8 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 9 | import retrofit2.converter.gson.GsonConverterFactory; 10 | import york.com.retrofit2rxjavademo.di.scope.AppScope; 11 | import york.com.retrofit2rxjavademo.di.scope.NetworkScope; 12 | 13 | /** 14 | * Created by York on 2016/7/23. 15 | */ 16 | @AppScope 17 | public class ServiceFactory { 18 | 19 | /* 20 | public static final String BASE_URL = "http://rap.taobao.org/mockjsdata/15987/"; 21 | private static final int DEFAULT_TIMEOUT = 10; 22 | */ 23 | private final Retrofit mRetrofit; 24 | private final Retrofit mCustomConverterRetrofit; 25 | 26 | @Inject 27 | public ServiceFactory(Retrofit retrofit, @Named("custom_converter") Retrofit customConverterRetrofit) { 28 | this.mRetrofit = retrofit; 29 | this.mCustomConverterRetrofit = customConverterRetrofit; 30 | } 31 | 32 | 33 | public T createService(Class serviceClazz) { 34 | return mRetrofit.create(serviceClazz); 35 | } 36 | 37 | public T createService( Retrofit retrofit, Class serviceClazz) { 38 | return retrofit.create(serviceClazz); 39 | } 40 | 41 | 42 | 43 | 44 | /** 45 | * 创建 46 | * 47 | * @param serviceClazz 48 | * @param okHttpClient 外部传入自定义okhttp,如上传文件时加长timeout时间 49 | * @param 50 | * @return 51 | * dagger2 中通过不同的Retrofit实例来实现 52 | */ 53 | /* public T createService(Class serviceClazz, OkHttpClient okHttpClient) { 54 | Retrofit retrofit = new Retrofit.Builder() 55 | .client(okHttpClient) 56 | .baseUrl(BASE_URL) 57 | .addConverterFactory(GsonConverterFactory.create()) 58 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 59 | .build(); 60 | 61 | return retrofit.create(serviceClazz); 62 | }*/ 63 | 64 | /** 65 | * 向外部提供api请求 66 | * @return 67 | */ 68 | public MovieService movieApi() { 69 | return createService(MovieService.class); 70 | } 71 | 72 | public MockApi mockApi() { 73 | return createService(MockApi.class); 74 | } 75 | 76 | /** 77 | * 解决返回message在data字段 78 | * @return 79 | */ 80 | public MockApi mockApi2() { 81 | return createService(mCustomConverterRetrofit, MockApi.class); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/http/exception/ApiException.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.http.exception; 2 | 3 | 4 | public class ApiException extends Exception { 5 | // 异常处理,为速度,不必要设置getter和setter 6 | public int code; 7 | public String message; 8 | 9 | public ApiException(Throwable throwable, int code) { 10 | super(throwable); 11 | this.code = code; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/http/exception/ErrorType.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.http.exception; 2 | 3 | /** 4 | * 约定异常 5 | */ 6 | 7 | public interface ErrorType { 8 | /** 9 | * 正常 10 | */ 11 | int SUCCESS = 0; 12 | /** 13 | * 未知错误 14 | */ 15 | int UNKNOWN = 1000; 16 | /** 17 | * 解析错误 18 | */ 19 | int PARSE_ERROR = 1001; 20 | /** 21 | * 网络错误 22 | */ 23 | int NETWORD_ERROR = 1002; 24 | /** 25 | * 协议出错 26 | */ 27 | int HTTP_ERROR = 1003; 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/http/exception/ServerException.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.http.exception; 2 | 3 | /** 4 | * @author YorkYu 5 | * @version V1.0 6 | * @Project: Supplier 7 | * @Package mall.b2b.meixun.com.supplier.net 8 | * @Description: 9 | * @time 2016/8/11 9:20 10 | */ 11 | public class ServerException extends RuntimeException{ 12 | // 异常处理,为速度,不必要设置getter和setter 13 | public int code; 14 | public String message; 15 | 16 | public ServerException(String message, int code) { 17 | super(message); 18 | this.code = code; 19 | this.message = message; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/progress/ProgressCancelListener.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.progress; 2 | 3 | /** 4 | * Created by liukun on 16/3/10. 5 | */ 6 | public interface ProgressCancelListener { 7 | void onCancelProgress(); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/progress/ProgressDialogHandler.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.progress; 2 | 3 | import android.app.ProgressDialog; 4 | import android.content.Context; 5 | import android.content.DialogInterface; 6 | import android.os.Handler; 7 | import android.os.Message; 8 | 9 | /** 10 | * Created by liukun on 16/3/10. 11 | */ 12 | public class ProgressDialogHandler extends Handler { 13 | 14 | public static final int SHOW_PROGRESS_DIALOG = 1; 15 | public static final int DISMISS_PROGRESS_DIALOG = 2; 16 | 17 | private ProgressDialog pd; 18 | 19 | private Context context; 20 | private boolean cancelable; 21 | private ProgressCancelListener mProgressCancelListener; 22 | 23 | public ProgressDialogHandler(Context context, ProgressCancelListener mProgressCancelListener, 24 | boolean cancelable) { 25 | super(); 26 | this.context = context; 27 | this.mProgressCancelListener = mProgressCancelListener; 28 | this.cancelable = cancelable; 29 | } 30 | 31 | private void initProgressDialog(){ 32 | if (pd == null) { 33 | pd = new ProgressDialog(context); 34 | 35 | pd.setCancelable(cancelable); 36 | 37 | if (cancelable) { 38 | pd.setOnCancelListener(new DialogInterface.OnCancelListener() { 39 | @Override 40 | public void onCancel(DialogInterface dialogInterface) { 41 | mProgressCancelListener.onCancelProgress(); 42 | } 43 | }); 44 | } 45 | 46 | if (!pd.isShowing()) { 47 | pd.show(); 48 | } 49 | } 50 | } 51 | 52 | private void dismissProgressDialog(){ 53 | if (pd != null) { 54 | pd.dismiss(); 55 | pd = null; 56 | } 57 | } 58 | 59 | @Override 60 | public void handleMessage(Message msg) { 61 | switch (msg.what) { 62 | case SHOW_PROGRESS_DIALOG: 63 | initProgressDialog(); 64 | break; 65 | case DISMISS_PROGRESS_DIALOG: 66 | dismissProgressDialog(); 67 | break; 68 | } 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/subscribers/BaseSubscriber.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.subscribers; 2 | 3 | import rx.Subscriber; 4 | import york.com.retrofit2rxjavademo.http.exception.ApiException; 5 | 6 | /** 7 | * @author YorkYu 8 | * @version V1.0 9 | * @Project: Retrofit2RxjavaDemo 10 | * @Package york.com.retrofit2rxjavademo.subscribers 11 | * @Description: 12 | * @time 2016/8/11 10:48 13 | */ 14 | public abstract class BaseSubscriber extends Subscriber { 15 | @Override 16 | public void onError(Throwable e) { 17 | if(e instanceof ApiException){ 18 | onError((ApiException)e); 19 | }else{ 20 | onError(new ApiException(e,1000)); 21 | } 22 | } 23 | 24 | /** 25 | * 错误回调 26 | */ 27 | protected abstract void onError(ApiException ex); 28 | } -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/subscribers/CommonSubscriber.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.subscribers; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import android.widget.Toast; 6 | 7 | import york.com.retrofit2rxjavademo.http.exception.ApiException; 8 | import york.com.retrofit2rxjavademo.utils.NetworkUtil; 9 | 10 | 11 | /** 12 | * @author YorkYu 13 | * @version V1.0 14 | * @Project: Retrofit2RxjavaDemo 15 | * @Package york.com.retrofit2rxjavademo.subscribers 16 | * @Description: 17 | * @time 2016/8/11 10:54 18 | */ 19 | public abstract class CommonSubscriber extends BaseSubscriber { 20 | public CommonSubscriber(Context context) { 21 | this.mContext = context; 22 | } 23 | private static final String TAG = CommonSubscriber.class.getSimpleName(); 24 | private Context mContext; 25 | @Override 26 | public void onStart() { 27 | super.onStart(); 28 | 29 | // if NetworkAvailable no ! must to call onCompleted 30 | if (!NetworkUtil.isNetworkAvailable(mContext)) { 31 | Toast.makeText(mContext, "当前无网络,请检查网络情况", Toast.LENGTH_SHORT).show(); 32 | onCompleted(); 33 | 34 | // 无网络执行complete后取消注册防调用onError 35 | if (!isUnsubscribed()) { 36 | unsubscribe(); 37 | } 38 | } else { 39 | Log.d(TAG, "network available"); 40 | } 41 | } 42 | 43 | @Override 44 | public void onCompleted() { 45 | Log.d(TAG, "onCompleted~ "); 46 | } 47 | 48 | @Override 49 | protected void onError(ApiException ex) { 50 | Log.e(TAG, "onError: " + ex.message + "code: " + ex.code); 51 | Toast.makeText(mContext, ex.message, Toast.LENGTH_SHORT).show(); 52 | } 53 | 54 | @Override 55 | public abstract void onNext(T t); 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/subscribers/RxSubscriber.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.subscribers; 2 | 3 | import android.content.Context; 4 | import android.util.Log; 5 | import android.widget.Toast; 6 | 7 | import york.com.retrofit2rxjavademo.http.exception.ApiException; 8 | import york.com.retrofit2rxjavademo.utils.DialogHelper; 9 | import york.com.retrofit2rxjavademo.utils.NetworkUtil; 10 | 11 | 12 | /** 13 | * @author YorkYu 14 | * @version V1.0 15 | * @Project: Retrofit2RxjavaDemo 16 | * @Package york.com.retrofit2rxjavademo.subscribers 17 | * @Description: 18 | * @time 2016/8/11 10:54 19 | */ 20 | public abstract class RxSubscriber extends BaseSubscriber { 21 | public RxSubscriber(Context context) { 22 | this.mContext = context; 23 | } 24 | private static final String TAG = RxSubscriber.class.getSimpleName(); 25 | private Context mContext; 26 | @Override 27 | public void onStart() { 28 | super.onStart(); 29 | 30 | // if NetworkAvailable no ! must to call onCompleted 31 | if (!NetworkUtil.isNetworkAvailable(mContext)) { 32 | Toast.makeText(mContext, "当前无网络,请检查网络情况", Toast.LENGTH_SHORT).show(); 33 | onCompleted(); 34 | 35 | // 无网络执行complete后取消注册防调用onError 36 | if (!isUnsubscribed()) { 37 | unsubscribe(); 38 | } 39 | } else { 40 | DialogHelper.showProgressDlg(mContext, "正在加载数据"); 41 | } 42 | } 43 | 44 | @Override 45 | public void onCompleted() { 46 | DialogHelper.stopProgressDlg(); 47 | } 48 | 49 | @Override 50 | protected void onError(ApiException ex) { 51 | DialogHelper.stopProgressDlg(); 52 | Log.e(TAG, "onError: " + ex.message + "code: " + ex.code); 53 | Toast.makeText(mContext, ex.message , Toast.LENGTH_SHORT).show(); 54 | } 55 | 56 | @Override 57 | public abstract void onNext(T t); 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/transformer/DefaultTransformer.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.transformer; 2 | 3 | 4 | import rx.Observable; 5 | import rx.android.schedulers.AndroidSchedulers; 6 | import rx.schedulers.Schedulers; 7 | import york.com.retrofit2rxjavademo.entity.HttpResult; 8 | 9 | /** 10 | * Created by York on 2016/7/23. 11 | */ 12 | public class DefaultTransformer 13 | implements Observable.Transformer, T> { 14 | 15 | @Override 16 | public Observable call(Observable> observable) { 17 | return observable 18 | .subscribeOn(Schedulers.io()) 19 | .observeOn(Schedulers.newThread()) 20 | .compose(ErrorTransformer.getInstance()) 21 | .observeOn(AndroidSchedulers.mainThread()); 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/transformer/ErrorTransformer.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.transformer; 2 | 3 | 4 | import rx.Observable; 5 | import rx.functions.Func1; 6 | import york.com.retrofit2rxjavademo.entity.HttpResult; 7 | import york.com.retrofit2rxjavademo.http.ExceptionEngine; 8 | import york.com.retrofit2rxjavademo.http.exception.ErrorType; 9 | import york.com.retrofit2rxjavademo.http.exception.ServerException; 10 | 11 | /** 12 | * Created by York on 2016/7/23. 13 | * 加入了对错误处理,已经比较完整了 14 | */ 15 | public class ErrorTransformer implements Observable.Transformer, T>{ 16 | 17 | @Override 18 | public Observable call(Observable> responseObservable) { 19 | return responseObservable.map(new Func1, T>() { 20 | @Override 21 | public T call(HttpResult httpResult) { 22 | // 通过对返回码进行业务判断决定是返回错误还是正常取数据 23 | // if (httpResult.getCode() != 200) throw new RuntimeException(httpResult.getMessage()); 24 | if (httpResult.getCode() != ErrorType.SUCCESS || httpResult.getCode() != ErrorType.SUCCESS) throw new ServerException(httpResult.getMessage(), httpResult.getCode()); 25 | return httpResult.getData(); 26 | } 27 | }).onErrorResumeNext(new Func1>() { 28 | @Override 29 | public Observable call(Throwable throwable) { 30 | //ExceptionEngine为处理异常的驱动器 31 | throwable.printStackTrace(); 32 | return Observable.error(ExceptionEngine.handleException(throwable)); 33 | } 34 | }); 35 | } 36 | 37 | public static ErrorTransformer create() { 38 | return new ErrorTransformer<>(); 39 | } 40 | 41 | private static ErrorTransformer instance = null; 42 | 43 | private ErrorTransformer(){ 44 | } 45 | /** 46 | * 双重校验锁单例(线程安全) 47 | */ 48 | public static ErrorTransformer getInstance() { 49 | if (instance == null) { 50 | synchronized (ErrorTransformer.class) { 51 | if (instance == null) { 52 | instance = new ErrorTransformer<>(); 53 | } 54 | } 55 | } 56 | return instance; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/transformer/SchedulerTransformer.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.transformer; 2 | 3 | import rx.Observable; 4 | import rx.android.schedulers.AndroidSchedulers; 5 | import rx.schedulers.Schedulers; 6 | 7 | /** 8 | * Created by York on 2016/7/23. 9 | */ 10 | public class SchedulerTransformer implements Observable.Transformer { 11 | @Override 12 | public Observable call(Observable observable) { 13 | return observable 14 | .subscribeOn(Schedulers.io()) 15 | .observeOn(AndroidSchedulers.mainThread()); 16 | } 17 | 18 | public static SchedulerTransformer create() { 19 | return new SchedulerTransformer<>(); 20 | } 21 | 22 | private static SchedulerTransformer instance = null; 23 | 24 | private SchedulerTransformer(){ 25 | } 26 | /** 27 | * 双重校验锁单例(线程安全) 28 | */ 29 | public static SchedulerTransformer getInstance() { 30 | if (instance == null) { 31 | synchronized (SchedulerTransformer.class) { 32 | if (instance == null) { 33 | instance = new SchedulerTransformer<>(); 34 | } 35 | } 36 | } 37 | return instance; 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/utils/DialogHelper.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.utils; 2 | 3 | import android.app.Dialog; 4 | import android.app.ProgressDialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.support.v7.app.AlertDialog; 8 | 9 | /** 10 | * @author YorkYu 11 | * @version V1.0 12 | * @Project: Retrofit2RxjavaDemo 13 | * @Package york.com.retrofit2rxjavademo.utils 14 | * @Description: 15 | * @time 2016/8/11 10:44 16 | */ 17 | public class DialogHelper { 18 | /** 19 | * 通用Dialog 20 | * 21 | */ 22 | // 因为本类不是activity所以通过继承接口的方法获取到点击的事件 23 | public interface OnOkClickListener { 24 | abstract void onOkClick(); 25 | } 26 | 27 | /** 28 | * Listener 29 | */ 30 | public interface OnCancelClickListener { 31 | abstract void onCancelClick(); 32 | } 33 | 34 | private static AlertDialog mDialog; 35 | 36 | public static void showDialog(Context context, String title, String content, final OnOkClickListener listenerYes, 37 | final OnCancelClickListener listenerNo) { 38 | showDialog(context, context.getString(android.R.string.ok), context.getString(android.R.string.cancel), title, content, listenerYes, listenerNo); 39 | } 40 | 41 | public static void showDialog(Context context, String ok, String cancel, String title, String content, final OnOkClickListener listenerYes, 42 | final OnCancelClickListener listenerNo) { 43 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 44 | builder.setMessage(content); 45 | // 设置title 46 | builder.setTitle(title); 47 | // 设置确定按钮,固定用法声明一个按钮用这个setPositiveButton 48 | builder.setPositiveButton(ok, 49 | new DialogInterface.OnClickListener() { 50 | public void onClick(DialogInterface dialog, int which) { 51 | // 如果确定被电击 52 | if (listenerYes != null) { 53 | listenerYes.onOkClick(); 54 | } 55 | mDialog = null; 56 | } 57 | }); 58 | // 设置取消按钮,固定用法声明第二个按钮要用setNegativeButton 59 | builder.setNegativeButton(cancel, 60 | new DialogInterface.OnClickListener() { 61 | public void onClick(DialogInterface dialog, int which) { 62 | // 如果取消被点击 63 | if (listenerNo != null) { 64 | listenerNo.onCancelClick(); 65 | } 66 | mDialog = null; 67 | } 68 | }); 69 | 70 | // 控制这个dialog可不可以按返回键,true为可以,false为不可以 71 | builder.setCancelable(false); 72 | // 显示dialog 73 | mDialog = builder.create(); 74 | if (!mDialog.isShowing()) 75 | mDialog.show(); 76 | } 77 | 78 | public static void showDialog(Context context, int ok, int cancel, int title, int content, final OnOkClickListener listenerYes, 79 | final OnCancelClickListener listenerNo) { 80 | showDialog(context, context.getString(ok), context.getString(cancel), context.getString(title), context.getString(content), listenerYes, listenerNo); 81 | } 82 | 83 | static ProgressDialog progressDlg = null; 84 | 85 | /** 86 | * 启动进度条 87 | * 88 | * @param strMessage 进度条显示的信息 89 | * @param // 当前的activity 90 | */ 91 | public static void showProgressDlg(Context ctx, String strMessage) { 92 | 93 | if (null == progressDlg) { 94 | if (ctx == null) return; 95 | progressDlg = new ProgressDialog(ctx); 96 | //设置进度条样式 97 | progressDlg.setProgressStyle(ProgressDialog.STYLE_SPINNER); 98 | //提示的消息 99 | progressDlg.setMessage(strMessage); 100 | progressDlg.setIndeterminate(false); 101 | progressDlg.setCancelable(true); 102 | progressDlg.show(); 103 | } 104 | } 105 | 106 | public static void showProgressDlg(Context ctx) { 107 | showProgressDlg(ctx, ""); 108 | } 109 | 110 | /** 111 | * 结束进度条 112 | */ 113 | public static void stopProgressDlg() { 114 | if (null != progressDlg && progressDlg.isShowing()) { 115 | progressDlg.dismiss(); 116 | progressDlg = null; 117 | } 118 | if (null != dialog && dialog.isShowing()) { 119 | dialog.dismiss(); 120 | dialog = null; 121 | } 122 | } 123 | 124 | private static Dialog dialog; 125 | 126 | // public static void showDialogForLoading(Context context, String msg, boolean cancelable) { 127 | // if (null == dialog) { 128 | // if (null == context) return; 129 | // View view = LayoutInflater.from(context).inflate(R.layout.layout_loading_dialog, null); 130 | // TextView loadingText = (TextView)view.findViewById(R.id.loading_tip_text); 131 | // loadingText.setText(msg); 132 | // 133 | // dialog = new Dialog(context, R.style.loading_dialog_style); 134 | // dialog.setCancelable(cancelable); 135 | // dialog.setCanceledOnTouchOutside(cancelable); 136 | // dialog.setContentView(view, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT)); 137 | // Activity activity = (Activity) context; 138 | // if (activity.isFinishing()) return; 139 | // dialog.show(); 140 | // } 141 | // } 142 | 143 | /** 144 | * 取消ProgressDialog的时候,取消对observable的订阅,同时也取消了http请求 后期做处理 145 | */ 146 | /*@Override 147 | public void onCancelProgress() { 148 | if (!this.isUnsubscribed()) { 149 | this.unsubscribe(); 150 | } 151 | }*/ 152 | } 153 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/utils/LoggerInterceptor.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.utils; 2 | 3 | import android.text.TextUtils; 4 | import android.util.Log; 5 | 6 | import java.io.IOException; 7 | 8 | import okhttp3.Headers; 9 | import okhttp3.Interceptor; 10 | import okhttp3.MediaType; 11 | import okhttp3.Request; 12 | import okhttp3.RequestBody; 13 | import okhttp3.Response; 14 | import okhttp3.ResponseBody; 15 | import okio.Buffer; 16 | 17 | /** 18 | * Created by zhy on 16/3/1. 19 | */ 20 | public class LoggerInterceptor implements Interceptor 21 | { 22 | public static final String TAG = "OkHttpUtils"; 23 | private String tag; 24 | private boolean showResponse; 25 | 26 | public LoggerInterceptor(String tag, boolean showResponse) 27 | { 28 | if (TextUtils.isEmpty(tag)) 29 | { 30 | tag = TAG; 31 | } 32 | this.showResponse = showResponse; 33 | this.tag = tag; 34 | } 35 | 36 | public LoggerInterceptor(String tag) 37 | { 38 | this(tag, false); 39 | } 40 | 41 | @Override 42 | public Response intercept(Chain chain) throws IOException 43 | { 44 | Request request = chain.request(); 45 | logForRequest(request); 46 | Response response = chain.proceed(request); 47 | return logForResponse(response); 48 | } 49 | 50 | private Response logForResponse(Response response) 51 | { 52 | try 53 | { 54 | //===>response log 55 | Log.e(tag, "========response'log======="); 56 | Response.Builder builder = response.newBuilder(); 57 | Response clone = builder.build(); 58 | Log.e(tag, "url : " + clone.request().url()); 59 | Log.e(tag, "code : " + clone.code()); 60 | Log.e(tag, "protocol : " + clone.protocol()); 61 | if (!TextUtils.isEmpty(clone.message())) 62 | Log.e(tag, "message : " + clone.message()); 63 | 64 | if (showResponse) 65 | { 66 | ResponseBody body = clone.body(); 67 | if (body != null) 68 | { 69 | MediaType mediaType = body.contentType(); 70 | if (mediaType != null) 71 | { 72 | Log.e(tag, "responseBody's contentType : " + mediaType.toString()); 73 | if (isText(mediaType)) 74 | { 75 | String resp = body.string(); 76 | Log.e(tag, "responseBody's content : " + resp); 77 | 78 | body = ResponseBody.create(mediaType, resp); 79 | return response.newBuilder().body(body).build(); 80 | } else 81 | { 82 | Log.e(tag, "responseBody's content : " + " maybe [file part] , too large too print , ignored!"); 83 | } 84 | } 85 | } 86 | } 87 | 88 | Log.e(tag, "========response'log=======end"); 89 | } catch (Exception e) 90 | { 91 | // e.printStackTrace(); 92 | } 93 | 94 | return response; 95 | } 96 | 97 | private void logForRequest(Request request) 98 | { 99 | try 100 | { 101 | String url = request.url().toString(); 102 | Headers headers = request.headers(); 103 | 104 | Log.e(tag, "========request'log======="); 105 | Log.e(tag, "method : " + request.method()); 106 | Log.e(tag, "url : " + url); 107 | if (headers != null && headers.size() > 0) 108 | { 109 | Log.e(tag, "headers : " + headers.toString()); 110 | } 111 | RequestBody requestBody = request.body(); 112 | if (requestBody != null) 113 | { 114 | MediaType mediaType = requestBody.contentType(); 115 | if (mediaType != null) 116 | { 117 | Log.e(tag, "requestBody's contentType : " + mediaType.toString()); 118 | if (isText(mediaType)) 119 | { 120 | Log.e(tag, "requestBody's content : " + bodyToString(request)); 121 | } else 122 | { 123 | Log.e(tag, "requestBody's content : " + " maybe [file part] , too large too print , ignored!"); 124 | } 125 | } 126 | } 127 | Log.e(tag, "========request'log=======end"); 128 | } catch (Exception e) 129 | { 130 | // e.printStackTrace(); 131 | } 132 | } 133 | 134 | private boolean isText(MediaType mediaType) 135 | { 136 | if (mediaType.type() != null && mediaType.type().equals("text")) 137 | { 138 | return true; 139 | } 140 | if (mediaType.subtype() != null) 141 | { 142 | if (mediaType.subtype().equals("json") || 143 | mediaType.subtype().equals("xml") || 144 | mediaType.subtype().equals("html") || 145 | mediaType.subtype().equals("webviewhtml") 146 | ) 147 | return true; 148 | } 149 | return false; 150 | } 151 | 152 | private String bodyToString(final Request request) 153 | { 154 | try 155 | { 156 | final Request copy = request.newBuilder().build(); 157 | final Buffer buffer = new Buffer(); 158 | copy.body().writeTo(buffer); 159 | return buffer.readUtf8(); 160 | } catch (final IOException e) 161 | { 162 | return "something error when show requestBody."; 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/utils/NetworkUtil.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.utils; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | import android.telephony.TelephonyManager; 7 | 8 | import java.io.IOException; 9 | import java.net.HttpURLConnection; 10 | import java.net.InetAddress; 11 | import java.net.NetworkInterface; 12 | import java.net.SocketException; 13 | import java.net.URL; 14 | import java.util.Enumeration; 15 | 16 | public class NetworkUtil { 17 | 18 | public static int NET_CNNT_BAIDU_OK = 1; // NetworkAvailable 19 | public static int NET_CNNT_BAIDU_TIMEOUT = 2; // no NetworkAvailable 20 | public static int NET_NOT_PREPARE = 3; // Net no ready 21 | public static int NET_ERROR = 4; //net error 22 | private static int TIMEOUT = 3000; // TIMEOUT 23 | 24 | 25 | /** 26 | * check NetworkAvailable 27 | * @param context 28 | * @return 29 | */ 30 | public static boolean isNetworkAvailable(Context context) { 31 | ConnectivityManager manager = (ConnectivityManager) context.getApplicationContext().getSystemService( 32 | Context.CONNECTIVITY_SERVICE); 33 | if (null == manager) 34 | return false; 35 | NetworkInfo info = manager.getActiveNetworkInfo(); 36 | if (null == info || !info.isAvailable()) 37 | return false; 38 | return true; 39 | } 40 | 41 | /** 42 | * 得到ip地址 43 | * 44 | * @return 45 | */ 46 | public static String getLocalIpAddress() { 47 | String ret = ""; 48 | try { 49 | for (Enumeration en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { 50 | NetworkInterface intf = en.nextElement(); 51 | for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { 52 | InetAddress inetAddress = enumIpAddr.nextElement(); 53 | if (!inetAddress.isLoopbackAddress()) { 54 | ret = inetAddress.getHostAddress().toString(); 55 | } 56 | } 57 | } 58 | } catch (SocketException ex) { 59 | ex.printStackTrace(); 60 | } 61 | return ret; 62 | } 63 | 64 | /** 65 | * 返回当前网络状态 66 | * 67 | * @param context 68 | * @return 69 | */ 70 | public static int getNetState(Context context) { 71 | try { 72 | ConnectivityManager connectivity = (ConnectivityManager) context 73 | .getSystemService(Context.CONNECTIVITY_SERVICE); 74 | if (connectivity != null) { 75 | NetworkInfo networkinfo = connectivity.getActiveNetworkInfo(); 76 | if (networkinfo != null) { 77 | if (networkinfo.isAvailable() && networkinfo.isConnected()) { 78 | if (!connectionNetwork()) 79 | return NET_CNNT_BAIDU_TIMEOUT; 80 | else 81 | return NET_CNNT_BAIDU_OK; 82 | } else { 83 | return NET_NOT_PREPARE; 84 | } 85 | } 86 | } 87 | } catch (Exception e) { 88 | e.printStackTrace(); 89 | } 90 | return NET_ERROR; 91 | } 92 | 93 | /** 94 | *ping "http://www.baidu.com" 95 | * @return 96 | */ 97 | static private boolean connectionNetwork() { 98 | boolean result = false; 99 | HttpURLConnection httpUrl = null; 100 | try { 101 | httpUrl = (HttpURLConnection) new URL("http://www.baidu.com") 102 | .openConnection(); 103 | httpUrl.setConnectTimeout(TIMEOUT); 104 | httpUrl.connect(); 105 | result = true; 106 | } catch (IOException e) { 107 | } finally { 108 | if (null != httpUrl) { 109 | httpUrl.disconnect(); 110 | } 111 | httpUrl = null; 112 | } 113 | return result; 114 | } 115 | 116 | /** 117 | * check is3G 118 | * @param context 119 | * @return boolean 120 | */ 121 | public static boolean is3G(Context context) { 122 | ConnectivityManager connectivityManager = (ConnectivityManager) context 123 | .getSystemService(Context.CONNECTIVITY_SERVICE); 124 | NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo(); 125 | if (activeNetInfo != null 126 | && activeNetInfo.getType() == ConnectivityManager.TYPE_MOBILE) { 127 | return true; 128 | } 129 | return false; 130 | } 131 | 132 | /** 133 | * isWifi 134 | * @param context 135 | * @return boolean 136 | */ 137 | public static boolean isWifi(Context context) { 138 | ConnectivityManager connectivityManager = (ConnectivityManager) context 139 | .getSystemService(Context.CONNECTIVITY_SERVICE); 140 | NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo(); 141 | if (activeNetInfo != null 142 | && activeNetInfo.getType() == ConnectivityManager.TYPE_WIFI) { 143 | return true; 144 | } 145 | return false; 146 | } 147 | 148 | /** 149 | * is2G 150 | * @param context 151 | * @return boolean 152 | */ 153 | public static boolean is2G(Context context) { 154 | ConnectivityManager connectivityManager = (ConnectivityManager) context 155 | .getSystemService(Context.CONNECTIVITY_SERVICE); 156 | NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo(); 157 | if (activeNetInfo != null 158 | && (activeNetInfo.getSubtype() == TelephonyManager.NETWORK_TYPE_EDGE 159 | || activeNetInfo.getSubtype() == TelephonyManager.NETWORK_TYPE_GPRS || activeNetInfo 160 | .getSubtype() == TelephonyManager.NETWORK_TYPE_CDMA)) { 161 | return true; 162 | } 163 | return false; 164 | } 165 | 166 | /** 167 | * is wifi on 168 | */ 169 | public static boolean isWifiEnabled(Context context) { 170 | ConnectivityManager mgrConn = (ConnectivityManager) context 171 | .getSystemService(Context.CONNECTIVITY_SERVICE); 172 | TelephonyManager mgrTel = (TelephonyManager) context 173 | .getSystemService(Context.TELEPHONY_SERVICE); 174 | return ((mgrConn.getActiveNetworkInfo() != null && mgrConn 175 | .getActiveNetworkInfo().getState() == NetworkInfo.State.CONNECTED) || mgrTel 176 | .getNetworkType() == TelephonyManager.NETWORK_TYPE_UMTS); 177 | } 178 | 179 | } 180 | -------------------------------------------------------------------------------- /app/src/main/java/york/com/retrofit2rxjavademo/utils/OkHttpUtils.java: -------------------------------------------------------------------------------- 1 | package york.com.retrofit2rxjavademo.utils; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | import okhttp3.OkHttpClient; 6 | import okhttp3.logging.HttpLoggingInterceptor; 7 | 8 | /** 9 | * Created by zhy on 15/8/17. 10 | * Modified by Yorkyu on 2016/7/25 11 | */ 12 | public class OkHttpUtils 13 | { 14 | public static final long DEFAULT_MILLISECONDS = 10_000L; // ten seconds 15 | private volatile static OkHttpUtils mInstance; 16 | private static OkHttpClient mOkHttpClient; 17 | 18 | public OkHttpUtils(OkHttpClient okHttpClient) 19 | { 20 | if (okHttpClient == null) 21 | { 22 | mOkHttpClient = new OkHttpClient.Builder() 23 | .connectTimeout(DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS) 24 | .readTimeout(DEFAULT_MILLISECONDS, TimeUnit.MILLISECONDS) 25 | .addNetworkInterceptor( 26 | new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)) 27 | .retryOnConnectionFailure(true) 28 | .build(); 29 | 30 | } else 31 | { 32 | mOkHttpClient = okHttpClient; 33 | } 34 | 35 | } 36 | 37 | // 由外部提供OkhttpClient 38 | public static OkHttpUtils initClient(OkHttpClient okHttpClient) 39 | { 40 | if (mInstance == null) 41 | { 42 | synchronized (OkHttpUtils.class) 43 | { 44 | if (mInstance == null) 45 | { 46 | mInstance = new OkHttpUtils(okHttpClient); 47 | } 48 | } 49 | } 50 | return mInstance; 51 | } 52 | 53 | public static OkHttpUtils getInstance() 54 | { 55 | return initClient(null); 56 | } 57 | 58 | public static OkHttpClient getOkHttpClient() { 59 | return mOkHttpClient; 60 | } 61 | } 62 | 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 12 | 26 | 27 | 31 | 32 | 44 | 45 | 46 | 47 | 66 | 67 | 74 | 75 | 90 | 91 | 92 | 93 | 103 | 104 | 105 | 106 |