Repository: liujingxing/rxhttp Branch: master Commit: abf9696ae585 Files: 230 Total size: 760.0 KB Directory structure: gitextract_l14p6_sk/ ├── .github/ │ └── FUNDING.yml ├── .gitignore ├── LICENSE ├── README.md ├── README_zh.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── example/ │ │ └── httpsender/ │ │ └── ExampleInstrumentedTest.java │ ├── main/ │ │ ├── AndroidManifest.xml │ │ ├── java/ │ │ │ └── com/ │ │ │ └── example/ │ │ │ └── httpsender/ │ │ │ ├── AppHolder.java │ │ │ ├── DownloadMultiAdapter.java │ │ │ ├── ExceptionHelper.java │ │ │ ├── LoggingEventListener.kt │ │ │ ├── MainActivity.java │ │ │ ├── MyViewModel.kt │ │ │ ├── OnError.java │ │ │ ├── Presenter.kt │ │ │ ├── RxHttpManager.java │ │ │ ├── Tip.java │ │ │ ├── ToolBarActivity.java │ │ │ ├── adapter/ │ │ │ │ └── FragmentPageAdapter.java │ │ │ ├── entity/ │ │ │ │ ├── Article.java │ │ │ │ ├── DownloadTask.java │ │ │ │ ├── ErrorInfo.java │ │ │ │ ├── Location.java │ │ │ │ ├── Name.java │ │ │ │ ├── NewsDataXml.java │ │ │ │ ├── NewsXml.java │ │ │ │ ├── PageList.java │ │ │ │ ├── Response.java │ │ │ │ ├── Url.kt │ │ │ │ └── User.java │ │ │ ├── fragment/ │ │ │ │ ├── AwaitFragment.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ ├── FlowFragment.kt │ │ │ │ ├── MultiDownloadFragment.java │ │ │ │ └── RxJavaFragment.kt │ │ │ ├── interceptor/ │ │ │ │ ├── RedirectInterceptor.java │ │ │ │ └── TokenInterceptor.java │ │ │ ├── kt/ │ │ │ │ ├── Activity.kt │ │ │ │ ├── KotlinExtensions.kt │ │ │ │ └── Uri.kt │ │ │ ├── param/ │ │ │ │ ├── GetEncryptParam.java │ │ │ │ ├── PostEncryptFormParam.java │ │ │ │ ├── PostEncryptJsonParam.kt │ │ │ │ └── PostEncryptJsonParam1.java │ │ │ ├── parser/ │ │ │ │ ├── Android10DownloadFactory.kt │ │ │ │ ├── ResponseParser.kt │ │ │ │ └── java/ │ │ │ │ ├── DoubleTypeParser.java │ │ │ │ └── ResponseParser.java │ │ │ ├── utils/ │ │ │ │ └── Preferences.java │ │ │ ├── view/ │ │ │ │ └── ScaleTransitionPagerTitleView.java │ │ │ └── vm/ │ │ │ ├── MultiTaskAwaitDownloader.kt │ │ │ ├── MultiTaskDownloader.kt │ │ │ └── MultiTaskFlowDownloader.kt │ │ └── res/ │ │ ├── drawable/ │ │ │ └── ic_launcher_background.xml │ │ ├── drawable-v24/ │ │ │ └── ic_launcher_foreground.xml │ │ ├── layout/ │ │ │ ├── await_fragment.xml │ │ │ ├── download_multi_adapter.xml │ │ │ ├── flow_fragment.xml │ │ │ ├── main_activity.xml │ │ │ ├── multi_download_fragment.xml │ │ │ ├── rxjava_fragment.xml │ │ │ └── toolbar_activity.xml │ │ ├── menu/ │ │ │ └── download.xml │ │ ├── mipmap-anydpi-v26/ │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── values/ │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ ├── styles.xml │ │ │ └── themes.xml │ │ └── xml/ │ │ └── network_config.xml │ └── test/ │ └── java/ │ ├── com/ │ │ └── rxhttp/ │ │ └── compiler/ │ │ ├── AbstractTestSymbolProcessor.kt │ │ ├── KspProcessorTest.kt │ │ ├── TestParser1.kt │ │ └── TestParser2.kt │ └── com.example.httpsender/ │ └── AsyncTest.java ├── build.gradle ├── gradle/ │ ├── libs.versions.toml │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── jitpack.yml ├── maven.gradle ├── maven_dependency.md ├── rxhttp/ │ ├── build.gradle │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── rxhttp/ │ │ │ ├── AwaitTransform.kt │ │ │ ├── CallFactoryExt.kt │ │ │ ├── Platform.java │ │ │ ├── RxHttpPlugins.java │ │ │ └── wrapper/ │ │ │ ├── CallFactory.kt │ │ │ ├── OkHttpCompat.java │ │ │ ├── cache/ │ │ │ │ ├── CacheManager.java │ │ │ │ ├── CacheMode.java │ │ │ │ ├── CacheStrategy.java │ │ │ │ ├── HeadersVary.java │ │ │ │ └── InternalCache.java │ │ │ ├── callback/ │ │ │ │ ├── Consumer.java │ │ │ │ ├── Function.java │ │ │ │ ├── IConverter.java │ │ │ │ ├── JsonConverter.java │ │ │ │ ├── OutputStreamFactory.kt │ │ │ │ ├── ProgressCallback.java │ │ │ │ └── ProgressCallbackHelper.kt │ │ │ ├── converter/ │ │ │ │ └── GsonConverter.java │ │ │ ├── cookie/ │ │ │ │ ├── CookieStore.java │ │ │ │ └── ICookieJar.java │ │ │ ├── coroutines/ │ │ │ │ ├── Await.kt │ │ │ │ ├── CallAwait.kt │ │ │ │ └── CallFlow.kt │ │ │ ├── entity/ │ │ │ │ ├── DownloadOffSize.java │ │ │ │ ├── EmptyResponseBody.java │ │ │ │ ├── ExpandOutputStream.java │ │ │ │ ├── FileRequestBody.java │ │ │ │ ├── KeyValuePair.java │ │ │ │ ├── OkResponse.java │ │ │ │ ├── ParameterizedTypeImpl.java │ │ │ │ ├── Progress.java │ │ │ │ ├── UpFile.java │ │ │ │ └── UriRequestBody.java │ │ │ ├── exception/ │ │ │ │ ├── CacheReadFailedException.java │ │ │ │ ├── HttpStatusCodeException.java │ │ │ │ ├── ParseException.java │ │ │ │ └── ProxyException.java │ │ │ ├── intercept/ │ │ │ │ ├── CacheInterceptor.kt │ │ │ │ ├── LogInterceptor.kt │ │ │ │ └── RangeInterceptor.java │ │ │ ├── param/ │ │ │ │ ├── AbstractBodyParam.java │ │ │ │ ├── AbstractParam.java │ │ │ │ ├── BodyParam.kt │ │ │ │ ├── FormParam.java │ │ │ │ ├── ICache.java │ │ │ │ ├── IHeaders.java │ │ │ │ ├── IParam.java │ │ │ │ ├── IPart.java │ │ │ │ ├── IRequest.java │ │ │ │ ├── JsonArrayParam.java │ │ │ │ ├── JsonParam.java │ │ │ │ ├── Method.java │ │ │ │ ├── NoBodyParam.java │ │ │ │ └── Param.java │ │ │ ├── parse/ │ │ │ │ ├── OkResponseParser.java │ │ │ │ ├── Parser.java │ │ │ │ ├── SmartParser.java │ │ │ │ ├── StreamParser.kt │ │ │ │ └── TypeParser.java │ │ │ ├── progress/ │ │ │ │ └── ProgressRequestBody.java │ │ │ ├── ssl/ │ │ │ │ └── HttpsUtils.java │ │ │ └── utils/ │ │ │ ├── BuildUtil.java │ │ │ ├── CacheUtil.java │ │ │ ├── Converter.java │ │ │ ├── Converter.kt │ │ │ ├── GsonUtil.java │ │ │ ├── JSONStringer.java │ │ │ ├── Json.kt │ │ │ ├── LogTime.java │ │ │ ├── LogUtil.java │ │ │ ├── PathEncoder.kt │ │ │ ├── Speeder.kt │ │ │ ├── TypeUtil.java │ │ │ ├── Uri.kt │ │ │ └── Utils.kt │ │ └── java-templates/ │ │ └── rxhttp/ │ │ └── internal/ │ │ └── RxHttpVersion.kt │ └── test/ │ └── java/ │ └── rxhttp/ │ └── wrapper/ │ └── entity/ │ └── ParameterizedTypeImplTest.java ├── rxhttp-annotation/ │ ├── build.gradle │ └── src/ │ └── main/ │ └── java/ │ └── rxhttp/ │ └── wrapper/ │ └── annotation/ │ ├── Converter.java │ ├── DefaultDomain.java │ ├── Domain.java │ ├── OkClient.java │ ├── Param.java │ └── Parser.java ├── rxhttp-compiler/ │ ├── build.gradle │ └── src/ │ └── main/ │ ├── java/ │ │ └── com/ │ │ └── rxhttp/ │ │ └── compiler/ │ │ ├── Constants.kt │ │ ├── KaptProcessor.kt │ │ ├── KspProcessor.kt │ │ ├── RxJavaVersion.kt │ │ ├── Variables.kt │ │ ├── common/ │ │ │ ├── KtUtil.kt │ │ │ ├── ObservableUtil.kt │ │ │ └── StringUtil.kt │ │ ├── kapt/ │ │ │ ├── BaseRxHttpGenerator.kt │ │ │ ├── ClassHelper.kt │ │ │ ├── ConverterVisitor.kt │ │ │ ├── DefaultDomainVisitor.kt │ │ │ ├── DomainVisitor.kt │ │ │ ├── OkClientVisitor.kt │ │ │ ├── ParamsVisitor.kt │ │ │ ├── ParserVisitor.kt │ │ │ ├── RxHttpExtensions.kt │ │ │ ├── RxHttpGenerator.kt │ │ │ ├── RxHttpWrapper.kt │ │ │ ├── Utils.kt │ │ │ └── maven/ │ │ │ ├── KaptRxJava2Processor.kt │ │ │ └── KaptRxJava3Processor.kt │ │ └── ksp/ │ │ ├── BaseRxHttpGenerator.kt │ │ ├── ClassHelper.kt │ │ ├── ConverterVisitor.kt │ │ ├── DefaultDomainVisitor.kt │ │ ├── DomainVisitor.kt │ │ ├── KClassHelper.kt │ │ ├── Ksp.kt │ │ ├── OkClientVisitor.kt │ │ ├── ParamsVisitor.kt │ │ ├── ParserVisitor.kt │ │ ├── RxHttpExtensions.kt │ │ ├── RxHttpGenerator.kt │ │ └── RxHttpWrapper.kt │ └── resources/ │ └── META-INF/ │ ├── gradle/ │ │ └── incremental.annotation.processors │ └── services/ │ ├── com.google.devtools.ksp.processing.SymbolProcessorProvider │ └── javax.annotation.processing.Processor ├── rxhttp-converter/ │ ├── converter-fastjson/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── rxhttp/ │ │ └── wrapper/ │ │ └── converter/ │ │ └── FastJsonConverter.java │ ├── converter-jackson/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── rxhttp/ │ │ └── wrapper/ │ │ └── converter/ │ │ └── JacksonConverter.java │ ├── converter-moshi/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── rxhttp/ │ │ └── wrapper/ │ │ └── converter/ │ │ └── MoshiConverter.java │ ├── converter-protobuf/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── rxhttp/ │ │ └── wrapper/ │ │ └── converter/ │ │ └── ProtoConverter.java │ ├── converter-serialization/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ └── src/ │ │ └── main/ │ │ └── java/ │ │ └── rxhttp/ │ │ └── wrapper/ │ │ └── converter/ │ │ └── SerializationConverter.kt │ └── converter-simplexml/ │ ├── .gitignore │ ├── build.gradle │ └── src/ │ └── main/ │ └── java/ │ └── rxhttp/ │ └── wrapper/ │ └── converter/ │ └── XmlConverter.java └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/FUNDING.yml ================================================ # These are supported funding model platforms github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: # Replace with a single Ko-fi username tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry liberapay: # Replace with a single Liberapay username issuehunt: # Replace with a single IssueHunt username otechie: # Replace with a single Otechie username lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry custom: https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa6d3941c2c944e59831640fa0ece60d~tplv-k3u1fbpfcp-watermark.image ================================================ FILE: .gitignore ================================================ # Built application files *.apk *.ap_ # Files for the ART/Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ out/ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log # Android Studio Navigation editor temp files .navigation/ # Android Studio captures folder captures/ # IntelliJ *.iml .idea/workspace.xml .idea/tasks.xml .idea/gradle.xml .idea/assetWizardSettings.xml .idea/dictionaries .idea/libraries .idea/caches # Keystore files # Uncomment the following line if you do not want to check your keystore files in. #*.jks # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild # Google Services (e.g. APIs or Firebase) google-services.json # Freeline freeline.py freeline/ freeline_project_description.json # fastlane fastlane/report.xml fastlane/Preview.html fastlane/screenshots fastlane/test_output fastlane/readme.md .idea/codeStyles/Project.xml .idea/misc.xml .idea/modules.xml .idea/runConfigurations.xml .idea/vcs.xml .idea gradlew.bat gradlew ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # RxHttp English | [中文文档](https://github.com/liujingxing/rxhttp/blob/master/README_zh.md) [![](https://jitpack.io/v/liujingxing/rxhttp.svg)](https://jitpack.io/#liujingxing/rxhttp) # [(RxHttp 3.0 更新指南,升级必看)](https://github.com/liujingxing/rxhttp/wiki/RxHttp-3.0-%E6%9B%B4%E6%96%B0%E6%8C%87%E5%8D%97%EF%BC%8C%E5%8D%87%E7%BA%A7%E5%BF%85%E7%9C%8B) # 使用RxHttp的知名App 抖音旗下***汽水音乐***(app v18.1.0 设置/关于汽水音乐/开源软件声明 可查) # A type-safe HTTP client for Android. Written based on OkHttp ![sequence_chart_en.jpg](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/2e25f9c6aa694b57bd43871eff95cecd~tplv-k3u1fbpfcp-watermark.image)
Await Flow RxJava
(Kotlin)
RxJava
(Java)
```java //await return User //tryAwait return User? val user = RxHttp.get("/server/..") .add("key", "value") .toAwait() .await() //or awaitResult return kotlin.Result RxHttp.get("/server/..") .add("key", "value") .toAwait() .awaitResult { //Success }.onFailure { //Failure } ``` ```java RxHttp.get("/server/..") .add("key", "value") .toFlow() .catch { //Failure }.collect { //Success } ``` ```java RxHttp.get("/server/..") .add("key", "value") .toObservable() .subscribe({ //Success }, { //Failure }) ``` ```java RxHttp.get("/server/..") .add("key", "value") .toObservable(User.class) .subscribe(user - > { //Success }, throwable -> { //Failure }) ```
## 1、Feature - Support kotlin coroutines, RxJava2, RxJava3 - Support Gson, Xml, ProtoBuf, FastJson and other third-party data parsing tools - Supports automatic closure of requests in FragmentActivity, Fragment, View, ViewModel, and any class - Support global encryption and decryption, add common parameters and headers, network cache, all support a request set up separately ## 2、usage 1、Adding dependencies and configurations ### Required
1、Add jitpack to your build.gradle ```java allprojects { repositories { maven { url "https://jitpack.io" } } } ```
2、Java 8 or higher ```java android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } ```
3、Add RxHttp dependency ```kotlin plugins { // kapt/ksp choose one // id 'kotlin-kapt' id 'com.google.devtools.ksp' version '2.3.4' } dependencies { def rxhttp_version = '3.5.1' implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation "com.github.liujingxing.rxhttp:rxhttp:$rxhttp_version" // ksp/kapt/annotationProcessor choose one ksp "com.github.liujingxing.rxhttp:rxhttp-compiler:$rxhttp_version" } ```
### Optional ### 1、Converter ```kotlin implementation "com.github.liujingxing.rxhttp:converter-serialization:$rxhttp_version" implementation "com.github.liujingxing.rxhttp:converter-fastjson:$rxhttp_version" implementation "com.github.liujingxing.rxhttp:converter-jackson:$rxhttp_version" implementation "com.github.liujingxing.rxhttp:converter-moshi:$rxhttp_version" implementation "com.github.liujingxing.rxhttp:converter-protobuf:$rxhttp_version" implementation "com.github.liujingxing.rxhttp:converter-simplexml:$rxhttp_version" ``` ### 2、RxJava
RxHttp + RxJava3 ```java implementation 'io.reactivex.rxjava3:rxjava:3.1.6' implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' implementation 'com.github.liujingxing.rxlife:rxlife-rxjava3:2.2.2' //RxJava3, Automatic close request ```
RxHttp + RxJava2 ```java implementation 'io.reactivex.rxjava2:rxjava:2.2.8' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'com.github.liujingxing.rxlife:rxlife-rxjava2:2.2.2' //RxJava2, Automatic close request ```
ksp passes the RxJava version ```java ksp { arg("rxhttp_rxjava", "3.1.6") } ```
Kapt passes the RxJava version ```java kapt { arguments { arg("rxhttp_rxjava", "3.1.6") } } ```
javaCompileOptions passes the RxJava version ```java android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [ rxhttp_rxjava: '3.1.6', ] } } } } ```
### 3、set RxHttp class package name
ksp pass package name ```java ksp { arg("rxhttp_package", "rxhttp.xxx") } ```
kapt pass package name ```java kapt { arguments { arg("rxhttp_package", "rxhttp.xxx") } } ```
javaCompileOptions pass package name ```java android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [ rxhttp_package: 'rxhttp.xxx' ] } } } } ```
**Finally, rebuild the project, which is necessary** 2、Initialize the SDK This step is optional ```java RxHttpPlugins.init(OkHttpClient) .setDebug(boolean) .setOnParamAssembly(Consumer) .... ``` 3、Configuration BaseUrl This step is optional ```java public class Url { //Add the @defaultDomain annotation to BASE_URL @DefaultDomain public static BASE_URL = "https://..." } ``` 4、Perform the requested ```java // java RxHttp.get("/service/...") //1、You can choose get,postFrom,postJson etc .addQuery("key", "value") //add query param .addHeader("headerKey", "headerValue") //add request header .toObservable(Student.class) //2、Use the toXxx method to determine the return value type, customizable .subscribe(student -> { //3、Subscribing observer //Success callback,Default IO thread }, throwable -> { //Abnormal callback }); // kotlin RxHttp.postForm("/service/...") //post FormBody .add("key", "value") //add param to body .addQuery("key1", "value1") //add query param .addFile("file", File(".../1.png")) //add file to body .toObservable() .subscribe({ student -> //Default IO thread }, { throwable -> }) // kotlin coroutine val students = RxHttp.postJson("/service/...") //1、post {application/json; charset=utf-8} .toAwaitList() //2、Use the toXxx method to determine the return value type, customizable .await() //3、Get the return value, await is the suspend method ``` ## 3、Advanced usage  1、Close the request ```java //In Rxjava2 , Automatic close request RxHttp.get("/service/...") .toObservableString() .as(RxLife.as(this)) //The Activity destroys and automatically closes the request .subscribe(s -> { //Default IO thread }, throwable -> { }); //In Rxjava3 , Automatic close request RxHttp.get("/service/...") .toObservableString() .to(RxLife.to(this)) //The Activity destroys and automatically closes the request .subscribe(s -> { //Default IO thread }, throwable -> { }); //In RxJava2/RxJava3, close the request manually Disposable disposable = RxHttp.get("/service/...") .toObservableString() .subscribe(s -> { //Default IO thread }, throwable -> { }); disposable.dispose(); //Close the request at the appropriate time ``` ## 4、ProGuard If you are using RxHttp v2.2.8 or above the shrinking and obfuscation rules are included automatically. Otherwise you must manually add the options in [rxhttp.pro](https://github.com/liujingxing/rxhttp/blob/master/rxhttp/src/main/resources/META-INF/proguard/rxhttp.pro). ## 5、Donations If this project helps you a lot and you want to support the project's development and maintenance of this project, feel free to scan the following QR code for donation. Your donation is highly appreciated. Thank you! ![donations.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa6d3941c2c944e59831640fa0ece60d~tplv-k3u1fbpfcp-watermark.image?) # Licenses ``` Copyright 2019 liujingxing Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ================================================ FILE: README_zh.md ================================================ # RxHttp [English](https://github.com/liujingxing/rxhttp/blob/master/README.md) | 中文文档 [![](https://jitpack.io/v/liujingxing/rxhttp.svg)](https://jitpack.io/#liujingxing/rxhttp) ![](https://img.shields.io/badge/API-9+-blue.svg) [![](https://img.shields.io/badge/change-更新日志-success.svg)](https://github.com/liujingxing/rxhttp/wiki/%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97) [![](https://img.shields.io/badge/FAQ-常见问题-success.svg)](https://github.com/liujingxing/rxhttp/wiki/FAQ) [![](https://img.shields.io/badge/掘金-@刘一刀-blue.svg)](https://juejin.cn/user/272334612601559/posts) # [(RxHttp 3.0 更新指南,升级必看)](https://github.com/liujingxing/rxhttp/wiki/RxHttp-3.0-%E6%9B%B4%E6%96%B0%E6%8C%87%E5%8D%97%EF%BC%8C%E5%8D%87%E7%BA%A7%E5%BF%85%E7%9C%8B) # 使用RxHttp的知名App 抖音旗下***汽水音乐***(app v18.1.0 设置/关于汽水音乐/开源软件声明 可查) ***加我微信 ljx-studio 拉你进微信群(备注RxHttp)*** # 1、主要优势 ***1. 30秒即可上手,学习成本极低*** ***2. 史上最优雅的支持 Kotlin 协程*** ***3. 史上最优雅的处理多个BaseUrl及动态BaseUrl*** ***4. 史上最优雅的对错误统一处理,且不打破Lambda表达式*** ***5. 史上最优雅的文件上传/下载/断点下载/进度监听,已适配Android 10*** ***6. 支持Gson、Xml、ProtoBuf、FastJson等第三方数据解析工具*** ***7. 支持Get、Post、Put、Delete等任意请求方式,可自定义请求方式*** ***8. 支持在Activity/Fragment/View/ViewModel/任意类中,自动关闭请求*** ***9. 支持全局加解密、添加公共参数及头部、网络缓存,均支持对某个请求单独设置*** # 2、请求三部曲 ![](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/29bdab825c4f42c0983c58777f3af675~tplv-k3u1fbpfcp-watermark.image) ***代码表示, [toObservableXxx、toAwaitXxx、toFlowXxx方法介绍点这里](https://github.com/liujingxing/rxhttp/wiki/RxJava%E3%80%81Await%E3%80%81Flow-%E5%AF%B9%E5%BA%94%E7%9A%84-asXxx%E3%80%81toXxx%E3%80%81toFlowXxx%E6%96%B9%E6%B3%95%E4%BB%8B%E7%BB%8D)***
Await Flow RxJava
(Kotlin)
RxJava
(Java)
```java //同步式写法 val user = RxHttp.get("/server/..") .add("key", "value") .toAwait() .await() //tryAwait return User? //回调式写法 RxHttp.get("/server/..") .add("key", "value") .toAwait() .awaitResult { //成功回调 }.onFailure { //异常回调 } ``` ```java RxHttp.get("/server/..") .add("key", "value") .toFlow() .catch { //异常回调 }.collect { //成功回调 } ``` ```java RxHttp.get("/server/..") .add("key", "value") .toObservable() .subscribe({ //成功回调 }, { //异常回调 }) ``` ```java RxHttp.get("/server/..") .add("key", "value") .toObservable(User.class) .subscribe(user - > { //成功回调 }, throwable -> { //异常回调 }) ```
***RxHttp与Retrofit对比*** | 功能说明 | RxHttp | [Retrofit](https://github.com/square/retrofit) | | --- | :---: | :---: | | 版本| v3.2.6| v2.9.0 | | 状态| 维护中| 维护中 | | 标准RESTful风格| ✅ | ✅ | | 学习成本| 低 | 高| | 扩展性| 高| 高| | 源码大小| 73k | 75k | | jar包大小| 210k | 123k | | RxJava| RxJava ❌
RxJava2✅
RxJava3✅| RxJava ✅
RxJava2✅
RxJava3✅| | Kotlin协程| ✅ | ✅ | | Flow流| ✅ | ✅ | | Converter| Gson✅
Jackson✅
fastJson✅
Moshi✅
Protobuf✅
simplexml✅
kotlinx.serialization✅
自定义✅
| Gson✅
Jackson✅
fastJson✅
Moshi✅
Protobuf✅
simplexml✅
kotlinx.serialization✅
自定义✅
| | 关闭请求 | 手动✅
自动✅
批量✅| 手动✅
自动✅
批量✅ | | 文件上传/下载/进度监听| ✅ | ❌需再次封装| | Android 10分区存储| ✅ | ❌需再次封装| | 公共参数| ✅| ❌需再次封装 | | 多域名/动态域名| ✅好用 | ✅一般 | | 日志打印| ✅| ✅ | | Json数据格式化输出| ✅| ❌需再次封装 | | 业务code统一判断| ✅ | ❌需再次封装| | 请求缓存| ✅ | ❌需再次封装| | 全局加解密| ✅ | ❌需再次封装 | | 部分字段解密 | ✅ | ❌需再次封装 | **说明** 也许你有会有疑问,RxHttp源码大小仅比retrofit大6k的情况下,jar包大小为何会大一倍多?功能太多导致的代码臃肿?并不是,而是由kotlin导致的,在RxHttp内部,为了支持`Await/Flow`,运用了大量的kotlin内联方法及扩展方法,这些方法在编译为字节码后,都会相对较大,其中[AwaitTransform.kt](https://github.com/liujingxing/rxhttp/blob/master/rxhttp/src/main/java/rxhttp/AwaitTransform.kt)、[CallFactoryToAwait.kt](https://github.com/liujingxing/rxhttp/blob/master/rxhttp/src/main/java/rxhttp/CallFactoryToAwait.kt)、[CallFactoryToFlow.kt](https://github.com/liujingxing/rxhttp/blob/master/rxhttp/src/main/java/rxhttp/CallFactoryToFlow.kt)这3个kotlin文件,编译字节码后,就接近70k # 3、相关文档 30秒上手教程:[30秒上手新一代Http请求神器RxHttp](https://juejin.im/post/5cfcbbcbe51d455a694f94df) Flow文档:[RxHttp + Flow 三步搞定任意请求](https://juejin.cn/post/7017604875764629540) Await文档:[RxHttp ,比Retrofit 更优雅的协程体验](https://juejin.im/post/5e77604fe51d4527066eb81a#heading-2) RxJava及核心Api介绍:[RxHttp 让你眼前一亮的Http请求框架](https://juejin.im/post/5ded221a518825125d14a1d4) wiki详细文档:https://github.com/liujingxing/rxhttp/wiki (此文档会持续更新) ## [(RxHttp 3.0 更新指南,升级必看)](https://github.com/liujingxing/rxhttp/wiki/RxHttp-3.0-%E6%9B%B4%E6%96%B0%E6%8C%87%E5%8D%97%EF%BC%8C%E5%8D%87%E7%BA%A7%E5%BF%85%E7%9C%8B) 自动关闭请求用到的RxLife类,详情请查看[RxLife库](https://github.com/liujingxing/rxlife) # 4、上手准备 ***1、RxHttp依赖有3种方式,选择其中一种就好, [ksp、kapt、annotationProcessor 如何选择点击这里](https://github.com/liujingxing/rxhttp/wiki/ksp%E3%80%81kapt%E3%80%81annotationProcessor-%E7%94%A8%E6%B3%95%E5%8F%8A%E5%8C%BA%E5%88%AB)*** ***2、asXxx方法内部通过RxJava实现,如需使用,需额外依赖RxJava并告知RxHttp你依赖的Rxjava版本*** ***3、RxHttp已适配`OkHttp 3.12.0 - v4.12.0`版本(4.3.0除外), 如需兼容21以下,请依赖`OkHttp 3.12.x`,该版本最低要求 API 9*** ***4、[Maven依赖点击这里](https://github.com/liujingxing/rxhttp/blob/master/maven_dependency.md)*** ## 4.1、必须
annotationProcessor依赖 ```gradle //1、项目的build.gradle文件 allprojects { repositories { maven { url "https://jitpack.io" } } } //2、java 8或更高 android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } //3、添加依赖 dependencies { def rxhttp_version = '3.5.1' implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation "com.github.liujingxing.rxhttp:rxhttp:$rxhttp_version" annotationProcessor "com.github.liujingxing.rxhttp:rxhttp-compiler:$rxhttp_version" } ```
kapt依赖 ```gradle //1、项目的build.gradle文件 allprojects { repositories { maven { url "https://jitpack.io" } } } //2、java 8或更高 android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } //3、添加插件及依赖 plugins { id 'kotlin-kapt' } dependencies { def rxhttp_version = '3.5.1' implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation "com.github.liujingxing.rxhttp:rxhttp:$rxhttp_version" kapt "com.github.liujingxing.rxhttp:rxhttp-compiler:$rxhttp_version" } ```
ksp依赖 ```gradle //1、项目的build.gradle文件 allprojects { repositories { maven { url "https://jitpack.io" } } } //2、java 8或更高,及配置sourceSets android { compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } } //3、添加插件及依赖 plugins { id 'com.google.devtools.ksp' version '2.3.4' } dependencies { def rxhttp_version = '3.5.1' implementation 'com.squareup.okhttp3:okhttp:4.12.0' implementation "com.github.liujingxing.rxhttp:rxhttp:$rxhttp_version" ksp "com.github.liujingxing.rxhttp:rxhttp-compiler:$rxhttp_version" } ```
## 4.2、可选 ### 4.2.1、配置RxJava 如果你需要结合`toObservableXxx`方法发请求,就需要额外依赖`RxJava`,并且告知`rxhttp`你依赖的`RxJava`版本号 - ***依赖RxJava,RxJava2/RxJava3选其一*** ```java //RxJava3 implementation 'io.reactivex.rxjava3:rxjava:3.1.6' implementation 'io.reactivex.rxjava3:rxandroid:3.0.2' implementation 'com.github.liujingxing.rxlife:rxlife-rxjava3:2.2.2' //管理RxJava3生命周期,页面销毁,关闭请求 //RxJava2 implementation 'io.reactivex.rxjava2:rxjava:2.2.8' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'com.github.liujingxing.rxlife:rxlife-rxjava2:2.2.2' //管理RxJava2生命周期,页面销毁,关闭请求 ``` - ***通过ksp/kapt/annotationProcessor,其中一种方式传递RxJava版本号***
通过ksp传递RxJava版本 ```java ksp { arg("rxhttp_rxjava", "3.1.6") } ```
通过kapt传递RxJava版本 ```java kapt { arguments { arg("rxhttp_rxjava", "3.1.6") } } ```
通过annotationProcessor传递RxJava版本 ```java android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [ //使用asXxx方法时必须,传入你依赖的RxJava版本 rxhttp_rxjava: '3.1.6', ] } } } } ```
### 4.2.2、配置Converter ```kotlin //非必须,根据自己需求选择 RxHttp默认内置了GsonConverter implementation "com.github.liujingxing.rxhttp:converter-serialization:$rxhttp_version" implementation "com.github.liujingxing.rxhttp:converter-fastjson:$rxhttp_version" implementation "com.github.liujingxing.rxhttp:converter-jackson:$rxhttp_version" implementation "com.github.liujingxing.rxhttp:converter-moshi:$rxhttp_version" implementation "com.github.liujingxing.rxhttp:converter-protobuf:$rxhttp_version" implementation "com.github.liujingxing.rxhttp:converter-simplexml:$rxhttp_version" ``` ### 4.2.3、指定RxHttp相关类的存放目录 如果你有多个module依赖`rxhttp-compiler`(不建议这么做,一般base module依赖就好),则每个module下都会生成`RxHttp`类,且目录相同,在运行或打包时,就会出现RxHttp类冲突的问题,此时就需要你自定义RxHttp的存放目录,也就是RxHttp类的包名,`ksp/kapt/annotationProcessor`选择其中一种方式就好
通过ksp指定RxHttp相关类包名 ```java ksp { arg("rxhttp_package", "rxhttp") //指定RxHttp类包名,可随意指定 } ```
通过kapt指定RxHttp相关类包名 ```java kapt { arguments { arg("rxhttp_package", "rxhttp") //指定RxHttp类包名,可随意指定 } } ```
通过javaCompileOptions指定RxHttp相关类包名 ```java android { defaultConfig { javaCompileOptions { annotationProcessorOptions { arguments = [ rxhttp_package: 'rxhttp', //指定RxHttp类包名,可随意指定 ] } } } } ```
最后,***rebuild一下(此步骤是必须的)*** ,就会自动生成RxHttp类 # 5、混淆 - `RxHttp v2.2.8`及以上版本,无需添加任何混淆规则,将你自己的Bean类Keep下就好 - `RxHttp v2.2.8`以下版本,将[RxHttp 混淆规则](https://github.com/liujingxing/rxhttp/wiki/关于混淆),添加到自己项目中,并将你自己的Bean类Keep下 # 6、Demo演示 > 更多功能,请[下载apk](https://github.com/liujingxing/rxhttp/blob/master/screen/app-debug.apk)体验 # 7、Donations 如果它对你帮助很大,并且你很想支持库的后续开发和维护,那么你可以扫下方二维码随意打赏我,就当是请我喝杯咖啡或是啤酒,开源不易,感激不尽 ![donations.png](https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/fa6d3941c2c944e59831640fa0ece60d~tplv-k3u1fbpfcp-watermark.image?) # Licenses ``` Copyright 2019 liujingxing Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ``` ================================================ FILE: app/.gitignore ================================================ #/build /release ================================================ FILE: app/build.gradle ================================================ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) id 'kotlin-kapt' id 'com.google.devtools.ksp' version libs.versions.ksp } android { namespace 'com.example.httpsender' compileSdk 35 defaultConfig { applicationId "com.example.rxhttp" minSdk 23 targetSdk 35 versionCode 1 versionName "1.0" multiDexEnabled true testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } } compileOptions { sourceCompatibility JavaVersion.VERSION_17 targetCompatibility JavaVersion.VERSION_17 } buildFeatures { buildConfig = true dataBinding = true } lint { abortOnError false checkReleaseBuilds false } } ksp { arg("rxhttp_rxjava", libs.versions.rxjava.get()) // arg("rxhttp_package", "rxhttp") } kapt { arguments { arg("rxhttp_rxjava", libs.versions.rxjava.get()) //可传入rxjava2、rxjava3或具体版本号,如 3.1.1 // arg("rxhttp_package", "rxhttp") //设置RxHttp相关类的包名,多module依赖时,需要配置不同的包名 } } dependencies { implementation libs.androidx.multidex implementation libs.androidx.appcompat implementation libs.androidx.recyclerview implementation libs.androidx.constraintlayout implementation libs.github.magicindicator implementation libs.androidx.fragment.ktx implementation libs.androidx.lifecycle.runtime.ktx implementation libs.androidx.lifecycle.livedata.ktx implementation libs.androidx.lifecycle.viewmodel.ktx implementation libs.androidx.lifecycle.service implementation projects.rxhttp ksp projects.rxhttpCompiler // kapt projects.rxhttpCompiler implementation libs.okhttp testImplementation libs.mockwebserver // implementation libs.rxhttp // ksp libs.rxhttp.compiler // kapt libs.rxhttp.compiler //管理RxJava及生命周期,Activity/Fragment 销毁,自动关闭未完成的请求 implementation libs.github.rxlife.rxjava3 implementation libs.rxandroid implementation libs.rxjava // implementation libs.rxhttp.converter.serialization // implementation libs.rxhttp.converter.fastjson // implementation libs.rxhttp.converter.jackson // implementation libs.rxhttp.converter.moshi // implementation libs.rxhttp.converter.protobuf // implementation libs.rxhttp.converter.simplexml implementation projects.rxhttpConverter.converterSerialization implementation projects.rxhttpConverter.converterFastjson implementation projects.rxhttpConverter.converterSimplexml implementation projects.rxhttpConverter.converterProtobuf implementation projects.rxhttpConverter.converterMoshi implementation projects.rxhttpConverter.converterJackson testImplementation libs.junit androidTestImplementation libs.androidx.junit androidTestImplementation libs.androidx.espresso.core implementation libs.utilcodex testImplementation libs.kotlin.compile.testing.ksp testImplementation libs.kotlinpoet testImplementation libs.kotlinpoet.ksp testImplementation projects.rxhttpCompiler } ================================================ FILE: app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} # Uncomment this to preserve the line number information for # debugging stack traces. #-keepattributes SourceFile,LineNumberTable # If you keep the line number information, uncomment this to # hide the original source file name. #-renamesourcefileattribute SourceFile -keepclassmembers class com.example.httpsender.entity.** { (); #R8 full mode下, 默认构造方法不保留 !transient ; } # With R8 full mode generic signatures are stripped for classes that are not kept. -keep,allowobfuscation,allowshrinking class com.example.httpsender.entity.Response -keep,allowobfuscation,allowshrinking class com.example.httpsender.entity.PageList #依赖simple-xml后打包失败,需加入以下规则 -dontwarn android.content.res.** #依赖fastjson后打包失败,需加入以下规则 -dontwarn javax.ws.rs.** # Please add these rules to your existing keep rules in order to suppress warnings. # This is generated automatically by the Android Gradle plugin. -dontwarn com.google.common.collect.ArrayListMultimap -dontwarn com.google.common.collect.Multimap -dontwarn java.awt.Color -dontwarn java.awt.Font -dontwarn java.awt.Point -dontwarn java.awt.Rectangle -dontwarn javax.money.CurrencyUnit -dontwarn javax.money.Monetary -dontwarn javax.ws.rs.Consumes -dontwarn javax.ws.rs.Produces -dontwarn javax.ws.rs.core.Response -dontwarn javax.ws.rs.core.StreamingOutput -dontwarn javax.ws.rs.ext.MessageBodyReader -dontwarn javax.ws.rs.ext.MessageBodyWriter -dontwarn javax.ws.rs.ext.Provider -dontwarn org.glassfish.jersey.internal.spi.AutoDiscoverable -dontwarn org.javamoney.moneta.Money -dontwarn org.joda.time.DateTime -dontwarn org.joda.time.DateTimeZone -dontwarn org.joda.time.Duration -dontwarn org.joda.time.Instant -dontwarn org.joda.time.LocalDate -dontwarn org.joda.time.LocalDateTime -dontwarn org.joda.time.LocalTime -dontwarn org.joda.time.Period -dontwarn org.joda.time.ReadablePartial -dontwarn org.joda.time.format.DateTimeFormat -dontwarn org.joda.time.format.DateTimeFormatter -dontwarn springfox.documentation.spring.web.json.Json ================================================ FILE: app/src/androidTest/java/com/example/httpsender/ExampleInstrumentedTest.java ================================================ package com.example.httpsender; import android.content.Context; import android.util.Log; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; import org.junit.Test; import org.junit.runner.RunWith; import io.reactivex.rxjava3.disposables.Disposable; import rxhttp.wrapper.param.RxHttp; /** * Instrumented test, which will execute on an Android device. * * @see Testing documentation */ @RunWith(AndroidJUnit4.class) public class ExampleInstrumentedTest { @Test public void useAppContext() { // Context of the app under test. Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); Disposable subscribe = RxHttp.get("https://www.wanandroid.com/article/list/0/json") .toObservableString() .subscribe(s -> { System.out.println(s); }, throwable -> { Log.e("LJX", "useAppContext"); }); while (!subscribe.isDisposed()) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } ================================================ FILE: app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: app/src/main/java/com/example/httpsender/AppHolder.java ================================================ package com.example.httpsender; import android.app.Application; import android.content.Context; import androidx.multidex.MultiDex; /** * User: ljx * Date: 2019/3/31 * Time: 09:11 */ //@HiltAndroidApp public class AppHolder extends Application { private static AppHolder instance; public static AppHolder getInstance() { return instance; } @Override public void onCreate() { super.onCreate(); instance = this; RxHttpManager.init(this); } protected void attachBaseContext(Context base) { super.attachBaseContext(base); MultiDex.install(this); } } ================================================ FILE: app/src/main/java/com/example/httpsender/DownloadMultiAdapter.java ================================================ package com.example.httpsender; import android.annotation.SuppressLint; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import com.example.httpsender.DownloadMultiAdapter.MyViewHolder; import com.example.httpsender.entity.DownloadTask; import com.example.httpsender.vm.MultiTaskDownloader; import java.text.DecimalFormat; import java.util.List; import java.util.Locale; /** * User: ljx * Date: 2019-06-07 * Time: 11:10 */ public class DownloadMultiAdapter extends RecyclerView.Adapter { private OnItemClickListener mOnItemClickListener; private List mDownloadTasks; public DownloadMultiAdapter(List downloadTasks) { mDownloadTasks = downloadTasks; } @NonNull @Override public MyViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) { View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.download_multi_adapter, viewGroup, false); return new MyViewHolder(view); } @SuppressLint("DefaultLocale") @Override public void onBindViewHolder(@NonNull MyViewHolder viewHolder, int i) { DownloadTask data = mDownloadTasks.get(i); viewHolder.progressBar.setProgress((int) (data.getProgress()*100)); viewHolder.tvProgress.setText(String.format("%.1f%%", data.getProgress() * 100)); viewHolder.btPause.setOnClickListener(v -> { mOnItemClickListener.onItemClick(v, data, i); }); String currentSize = new DecimalFormat("0.0").format(data.getCurrentSize() * 1.0f / 1024 / 1024); String totalSize = new DecimalFormat("0.0").format(data.getTotalSize() * 1.0f / 1024 / 1024); viewHolder.tvSize.setText(String.format("%sM/%sM", currentSize, totalSize)); int state = data.getState(); if (state == MultiTaskDownloader.IDLE) { viewHolder.tvWaiting.setText("未开始"); viewHolder.btPause.setText("开始"); } else if (state == MultiTaskDownloader.WAITING) { viewHolder.tvWaiting.setText("等待中.."); viewHolder.btPause.setText("取消"); } else if (state == MultiTaskDownloader.DOWNLOADING) { viewHolder.tvWaiting.setText(getSpeed(data.getSpeed())); long remainingTime = data.getRemainingTime(); if (remainingTime > 0) { viewHolder.tvWaiting.append(" 预估还需" + getRemainingTime(remainingTime)); } viewHolder.btPause.setText("暂停"); } else if (state == MultiTaskDownloader.PAUSED) { viewHolder.tvWaiting.setText("已暂停"); viewHolder.btPause.setText("继续下载"); } else if (state == MultiTaskDownloader.COMPLETED) { viewHolder.tvWaiting.setText("已完成"); viewHolder.btPause.setText("已完成"); } else if (state == MultiTaskDownloader.FAIL) { viewHolder.tvWaiting.setText("下载失败"); viewHolder.btPause.setText("重新下载"); } else if (state == MultiTaskDownloader.CANCEL) { viewHolder.tvWaiting.setText("已取消"); viewHolder.btPause.setText("继续下载"); } } private String getSpeed(long speed) { float kb = speed * 1.0f / 1024; if (kb > 1000) { return String.format(Locale.getDefault(), "%.2fMB/s", kb / 1024); } else { return ((int) kb) + "KB/s"; } } private String getRemainingTime(long time) { if (time < 300) { return time + "秒"; } else { long minute = time / 60; if (time % 60 > 0) minute++; return minute + "分"; } } @Override public int getItemCount() { return mDownloadTasks.size(); } @Override public long getItemId(int position) { return mDownloadTasks.get(position).hashCode(); } static class MyViewHolder extends RecyclerView.ViewHolder { ProgressBar progressBar; TextView tvProgress; TextView tvSize; Button btPause; TextView tvWaiting; MyViewHolder(@NonNull View itemView) { super(itemView); tvWaiting = itemView.findViewById(R.id.tv_waiting); tvProgress = itemView.findViewById(R.id.tv_progress); tvSize = itemView.findViewById(R.id.tv_size); progressBar = itemView.findViewById(R.id.progress_bar); btPause = itemView.findViewById(R.id.bt_pause); } } public void setOnItemClickListener(OnItemClickListener onItemClickListener) { mOnItemClickListener = onItemClickListener; } public interface OnItemClickListener { void onItemClick(View view, T data, int position); } } ================================================ FILE: app/src/main/java/com/example/httpsender/ExceptionHelper.java ================================================ package com.example.httpsender; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; /** * 异常处理帮助类 * User: ljx * Date: 2019/04/29 * Time: 11:15 */ public class ExceptionHelper { @SuppressWarnings("deprecation") public static boolean isNetworkConnected(Context context) { if (context != null) { ConnectivityManager mConnectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo mNetworkInfo = mConnectivityManager.getActiveNetworkInfo(); if (mNetworkInfo != null) { return mNetworkInfo.isAvailable(); } } return false; } } ================================================ FILE: app/src/main/java/com/example/httpsender/LoggingEventListener.kt ================================================ package com.example.httpsender import android.util.Log import okhttp3.Call import okhttp3.Connection import okhttp3.EventListener import okhttp3.Handshake import okhttp3.HttpUrl import okhttp3.Protocol import okhttp3.Request import okhttp3.Response import java.io.IOException import java.net.InetAddress import java.net.InetSocketAddress import java.net.Proxy import java.util.concurrent.TimeUnit /** * User: ljx * Date: 2022/4/22 * Time: 16:05 */ class LoggingEventListener : EventListener() { private var startNs: Long = 0 override fun callStart(call: Call) { startNs = System.nanoTime() logWithTime("callStart: ${call.request()}") } override fun proxySelectStart(call: Call, url: HttpUrl) { logWithTime("proxySelectStart: $url") } override fun proxySelectEnd(call: Call, url: HttpUrl, proxies: List) { logWithTime("proxySelectEnd: $proxies") } override fun dnsStart(call: Call, domainName: String) { logWithTime("dnsStart: $domainName") } override fun dnsEnd(call: Call, domainName: String, inetAddressList: List) { logWithTime("dnsEnd: $inetAddressList") } override fun connectStart(call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy) { logWithTime("connectStart: $inetSocketAddress $proxy") } override fun secureConnectStart(call: Call) { logWithTime("secureConnectStart") } override fun secureConnectEnd(call: Call, handshake: Handshake?) { logWithTime("secureConnectEnd: $handshake") } override fun connectEnd( call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy, protocol: Protocol? ) { logWithTime("connectEnd: $protocol") } override fun connectFailed( call: Call, inetSocketAddress: InetSocketAddress, proxy: Proxy, protocol: Protocol?, ioe: IOException ) { logWithTime("connectFailed: $protocol $ioe") } override fun connectionAcquired(call: Call, connection: Connection) { logWithTime("connectionAcquired: $connection") } override fun connectionReleased(call: Call, connection: Connection) { logWithTime("connectionReleased") } override fun requestHeadersStart(call: Call) { logWithTime("requestHeadersStart") } override fun requestHeadersEnd(call: Call, request: Request) { logWithTime("requestHeadersEnd") } override fun requestBodyStart(call: Call) { logWithTime("requestBodyStart") } override fun requestBodyEnd(call: Call, byteCount: Long) { logWithTime("requestBodyEnd: byteCount=$byteCount") } override fun requestFailed(call: Call, ioe: IOException) { logWithTime("requestFailed: $ioe") } override fun responseHeadersStart(call: Call) { logWithTime("responseHeadersStart") } override fun responseHeadersEnd(call: Call, response: Response) { logWithTime("responseHeadersEnd: $response") } override fun responseBodyStart(call: Call) { logWithTime("responseBodyStart") } override fun responseBodyEnd(call: Call, byteCount: Long) { logWithTime("responseBodyEnd: byteCount=$byteCount") } override fun responseFailed(call: Call, ioe: IOException) { logWithTime("responseFailed: $ioe") } override fun callEnd(call: Call) { logWithTime("callEnd") } override fun callFailed(call: Call, ioe: IOException) { logWithTime("callFailed: $ioe") } override fun canceled(call: Call) { logWithTime("canceled") } override fun satisfactionFailure(call: Call, response: Response) { logWithTime("satisfactionFailure: $response") } override fun cacheHit(call: Call, response: Response) { logWithTime("cacheHit: $response") } override fun cacheMiss(call: Call) { logWithTime("cacheMiss") } override fun cacheConditionalHit(call: Call, cachedResponse: Response) { logWithTime("cacheConditionalHit: $cachedResponse") } private fun logWithTime(message: String) { val timeMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) Log.e("LJX", "[$timeMs ms] $message") } } ================================================ FILE: app/src/main/java/com/example/httpsender/MainActivity.java ================================================ package com.example.httpsender; import android.content.Context; import android.graphics.Color; import android.os.Bundle; import android.view.View; import android.view.ViewGroup.LayoutParams; import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import androidx.activity.EdgeToEdge; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.graphics.Insets; import androidx.core.view.OnApplyWindowInsetsListener; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.databinding.DataBindingUtil; import androidx.fragment.app.Fragment; import com.example.httpsender.adapter.FragmentPageAdapter; import com.example.httpsender.databinding.MainActivityBinding; import com.example.httpsender.fragment.AwaitFragment; import com.example.httpsender.fragment.FlowFragment; import com.example.httpsender.fragment.MultiDownloadFragment; import com.example.httpsender.fragment.RxJavaFragment; import com.example.httpsender.view.ScaleTransitionPagerTitleView; import net.lucode.hackware.magicindicator.MagicIndicator; import net.lucode.hackware.magicindicator.ViewPagerHelper; import net.lucode.hackware.magicindicator.buildins.UIUtil; import net.lucode.hackware.magicindicator.buildins.commonnavigator.CommonNavigator; import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter; import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerIndicator; import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerTitleView; import net.lucode.hackware.magicindicator.buildins.commonnavigator.indicators.LinePagerIndicator; import net.lucode.hackware.magicindicator.buildins.commonnavigator.titles.SimplePagerTitleView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity { private MainActivityBinding mBinding; private final List mDataList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); EdgeToEdge.enable(this); mBinding = DataBindingUtil.setContentView(this, R.layout.main_activity); ViewCompat.setOnApplyWindowInsetsListener(mBinding.getRoot(), new OnApplyWindowInsetsListener() { @NonNull @Override public WindowInsetsCompat onApplyWindowInsets(@NonNull View v, @NonNull WindowInsetsCompat insets) { Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); mBinding.magicIndicator.setPadding(0, systemBars.top, 0, 0); LayoutParams layoutParams = mBinding.magicIndicator.getLayoutParams(); layoutParams.height = (int) (getResources().getDisplayMetrics().density * 50 + systemBars.top); mBinding.magicIndicator.setLayoutParams(layoutParams); return insets; } }); mBinding.viewPager.setOffscreenPageLimit(4); mDataList.add("RxJava"); mDataList.add("Await"); mDataList.add("Flow"); mDataList.add("Multi Download"); List fragments = new ArrayList<>(); fragments.add(new RxJavaFragment()); fragments.add(new AwaitFragment()); fragments.add(new FlowFragment()); fragments.add(new MultiDownloadFragment()); mBinding.viewPager.setAdapter(new FragmentPageAdapter(getSupportFragmentManager(), fragments, mDataList)); initMagicIndicator(); } public void initMagicIndicator() { MagicIndicator magicIndicator = mBinding.magicIndicator; CommonNavigator commonNavigator = new CommonNavigator(this); commonNavigator.setAdapter(new CommonNavigatorAdapter() { @Override public int getCount() { return mDataList == null ? 0 : mDataList.size(); } @Override public IPagerTitleView getTitleView(Context context, final int index) { SimplePagerTitleView simplePagerTitleView = new ScaleTransitionPagerTitleView(context); simplePagerTitleView.setPadding(dp2px(5), 0, 0, 0); simplePagerTitleView.setText(mDataList.get(index)); simplePagerTitleView.setTextSize(18); simplePagerTitleView.setNormalColor(Color.parseColor("#c8e6c9")); simplePagerTitleView.setSelectedColor(Color.WHITE); simplePagerTitleView.setOnClickListener(v -> mBinding.viewPager.setCurrentItem(index)); return simplePagerTitleView; } @Override public IPagerIndicator getIndicator(Context context) { LinePagerIndicator indicator = new LinePagerIndicator(context); indicator.setMode(LinePagerIndicator.MODE_EXACTLY); indicator.setLineHeight(UIUtil.dip2px(context, 3)); indicator.setLineWidth(UIUtil.dip2px(context, 30)); indicator.setRoundRadius(UIUtil.dip2px(context, 3)); indicator.setStartInterpolator(new AccelerateInterpolator()); indicator.setYOffset(dp2px(3)); indicator.setEndInterpolator(new DecelerateInterpolator(2.0f)); indicator.setColors(Color.WHITE); return indicator; } }); magicIndicator.setNavigator(commonNavigator); ViewPagerHelper.bind(magicIndicator, mBinding.viewPager); } private int dp2px(double dpValue) { float density = getResources().getDisplayMetrics().density; return (int) (dpValue * density + 0.5); } } ================================================ FILE: app/src/main/java/com/example/httpsender/MyViewModel.kt ================================================ package com.example.httpsender import android.app.Application import android.util.Log import androidx.lifecycle.viewModelScope import com.example.httpsender.entity.Article import com.example.httpsender.entity.PageList import com.rxjava.rxlife.ScopeViewModel import com.rxjava.rxlife.lifeOnMain import io.reactivex.rxjava3.core.Observable import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.launch import rxhttp.awaitResult import rxhttp.retry import rxhttp.timeout import rxhttp.wrapper.param.RxHttp import rxhttp.wrapper.param.toAwaitResponse import java.util.concurrent.TimeUnit /** * User: ljx * Date: 2019-05-31 * Time: 21:50 */ class MyViewModel(application: Application) : ScopeViewModel(application) { fun startInterval() { Observable.interval(1, 1, TimeUnit.SECONDS) .lifeOnMain(this) .subscribe { Log.e("LJX", "MyViewModel aLong=$it") } } fun testRetry() = viewModelScope.launch { RxHttp.get("/article/list/0/json") .toAwaitResponse>() .timeout(100) .retry(2, 1000) { it is TimeoutCancellationException } .awaitResult { Log.e("LJX", "pageList=$it") } .onFailure { Log.e("LJX", "it=$it") } } } ================================================ FILE: app/src/main/java/com/example/httpsender/OnError.java ================================================ package com.example.httpsender; import com.example.httpsender.entity.ErrorInfo; import io.reactivex.rxjava3.functions.Consumer; /** * RxJava 错误回调 ,加入网络异常处理 * User: ljx * Date: 2019/04/29 * Time: 11:15 */ public interface OnError extends Consumer { @Override default void accept(Throwable throwable) throws Exception { onError(new ErrorInfo(throwable)); } void onError(ErrorInfo error) throws Exception; } ================================================ FILE: app/src/main/java/com/example/httpsender/Presenter.kt ================================================ package com.example.httpsender import android.util.Log import androidx.lifecycle.LifecycleOwner import com.example.httpsender.entity.Article import com.example.httpsender.entity.PageList import com.rxjava.rxlife.BaseScope import com.rxjava.rxlife.life import io.reactivex.rxjava3.core.Observable import kotlinx.coroutines.* import rxhttp.* import rxhttp.wrapper.cache.CacheMode import rxhttp.wrapper.param.RxHttp import rxhttp.wrapper.param.toAwaitResponse import java.util.concurrent.TimeUnit /** * User: ljx * Date: 2019-05-26 * Time: 15:20 */ class Presenter(owner: LifecycleOwner) : BaseScope(owner) { fun test() { Observable.interval(1, 1, TimeUnit.SECONDS) .life(this) //这里的this 为Scope接口对象 .subscribe { Log.e("LJX", "accept aLong=$it") } } fun testRetry() { val coroutineScope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) coroutineScope.launch { RxHttp.postForm("/article/query/0/json") .add("k", "性能优化") .setCacheMode(CacheMode.ONLY_NETWORK) .toAwaitResponse>() .delay(100) .startDelay(100) .onErrorReturnItem(PageList()) .timeout(1000) .retry(2, 1000) { it is TimeoutCancellationException } .awaitResult { Log.e("RxHttp", "onNext = $it") }.onFailure { Log.e("RxHttp", "onError = $it") } } } } ================================================ FILE: app/src/main/java/com/example/httpsender/RxHttpManager.java ================================================ package com.example.httpsender; import android.app.Application; import java.io.File; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; import rxhttp.RxHttpPlugins; import rxhttp.wrapper.annotation.Converter; import rxhttp.wrapper.annotation.OkClient; import rxhttp.wrapper.callback.IConverter; import rxhttp.wrapper.converter.FastJsonConverter; import rxhttp.wrapper.converter.XmlConverter; import rxhttp.wrapper.cookie.CookieStore; import rxhttp.wrapper.param.Method; import rxhttp.wrapper.ssl.HttpsUtils; import rxhttp.wrapper.ssl.HttpsUtils.SSLParams; /** * 本类所有配置都是非必须的,根据自己需求选择就好 * User: ljx * Date: 2019-11-26 * Time: 20:44 */ public class RxHttpManager { @Converter(name = "XmlConverter") //非必须 public static IConverter xmlConverter = XmlConverter.create(); @Converter(name = "FastJsonConverter") //非必须 public static IConverter fastJsonConverter = FastJsonConverter.create(); @OkClient(name = "SimpleClient", className = "Simple") //非必须 public static OkHttpClient simpleClient = new OkHttpClient.Builder().build(); public static void init(Application context) { File file = new File(context.getExternalCacheDir(), "RxHttpCookie"); SSLParams sslParams = HttpsUtils.getSslSocketFactory(); OkHttpClient client = new OkHttpClient.Builder() .cookieJar(new CookieStore(file)) .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) .sslSocketFactory(sslParams.sSLSocketFactory, sslParams.trustManager) //添加信任证书 .hostnameVerifier((hostname, session) -> true) //忽略host验证 // .followRedirects(false) //禁制OkHttp的重定向操作,我们自己处理重定向 // .addInterceptor(new RedirectInterceptor()) // .addInterceptor(new TokenInterceptor()) .build(); //设置缓存策略,非必须 // File cacheFile = new File(context.getExternalCacheDir(), "RxHttpCache"); //RxHttp初始化,非必须 RxHttpPlugins.init(client) //自定义OkHttpClient对象 .setDebug(BuildConfig.DEBUG, false, 2) //调试模式/分段打印/json数据缩进空间 // .setCache(cacheFile, 1000 * 100, CacheMode.REQUEST_NETWORK_FAILED_READ_CACHE) // .setExcludeCacheKeys("time") //设置一些key,不参与cacheKey的组拼 // .setResultDecoder(s -> s) //设置数据解密/解码器,非必须 // .setConverter(FastJsonConverter.create()) //设置全局的转换器,非必须 .setOnParamAssembly(p -> { //设置公共参数,非必须 //1、可根据不同请求添加不同参数,每次发送请求前都会被回调 //2、如果希望部分请求不回调这里,发请求前调用RxHttp#setAssemblyEnabled(false)即可 Method method = p.getMethod(); if (method.isGet()) { p.add("method", "get"); } else if (method.isPost()) { //Post请求 p.add("method", "post"); } p.add("versionName", "1.0.0")//添加公共参数 .add("time", System.currentTimeMillis()) .addHeader("deviceType", "android"); //添加公共请求头 }); } } ================================================ FILE: app/src/main/java/com/example/httpsender/Tip.java ================================================ package com.example.httpsender; import android.os.Handler; import android.os.Looper; import android.widget.Toast; /** * 可在任意线程执行本类方法 * User: ljx * Date: 2017/3/8 * Time: 10:31 */ public class Tip { private static Handler mHandler = new Handler(Looper.getMainLooper()); private static Toast mToast; public static void show(int msgResId) { show(msgResId, false); } public static void show(int msgResId, boolean timeLong) { show(AppHolder.getInstance().getString(msgResId), timeLong); } public static void show(CharSequence msg) { show(msg, false); } public static void show(final CharSequence msg, final boolean timeLong) { runOnUiThread(() -> { if (mToast != null) { mToast.cancel(); } int duration = timeLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT; mToast = Toast.makeText(AppHolder.getInstance(), msg, duration); mToast.show(); }); } private static void runOnUiThread(Runnable runnable) { if (Looper.getMainLooper() == Looper.myLooper()) { runnable.run(); } else { mHandler.post(runnable); } } } ================================================ FILE: app/src/main/java/com/example/httpsender/ToolBarActivity.java ================================================ package com.example.httpsender; import android.os.Build; import android.view.MenuItem; import android.view.ViewGroup; import androidx.annotation.LayoutRes; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.widget.Toolbar; import androidx.databinding.DataBindingUtil; import androidx.databinding.ViewDataBinding; /** * 需要ToolBar的activity都必须继承本类 */ public abstract class ToolBarActivity extends AppCompatActivity { protected Toolbar toolbar; protected ActionBar actionBar; private ViewGroup mContainer; public T bindingInflate(@LayoutRes int layoutResID) { initView(); return DataBindingUtil.inflate(getLayoutInflater(), layoutResID, mContainer, true); } @Override public void setContentView(@LayoutRes int layoutResID) { initView(); getLayoutInflater().inflate(layoutResID, mContainer, true); } private void initView() { super.setContentView(R.layout.toolbar_activity); mContainer = findViewById(R.id.container); toolbar = findViewById(R.id.toolbar); setSupportActionBar(toolbar); actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayHomeAsUpEnabled(true); } } @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId == android.R.id.home) { boolean popSuccess = getSupportFragmentManager().popBackStackImmediate(); if (popSuccess) return true; finish(); } return super.onOptionsItemSelected(item); } public void setToolbarColor(int color) { toolbar.setBackgroundColor(color); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { getWindow().setStatusBarColor(color); } } @Override public void setTitle(CharSequence title) { super.setTitle(title); if (toolbar == null) return; toolbar.setTitle(title); } @Override public void setTitle(int titleId) { super.setTitle(titleId); if (toolbar == null) return; toolbar.setTitle(titleId); } protected int dp2px(float dpValue) { final float scale = getResources().getDisplayMetrics().density; return (int) (dpValue * scale + 0.5f); } } ================================================ FILE: app/src/main/java/com/example/httpsender/adapter/FragmentPageAdapter.java ================================================ package com.example.httpsender.adapter; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import java.util.Arrays; import java.util.List; /** * User: ljx * Date: 2018/6/9 * Time: 13:53 */ public class FragmentPageAdapter extends FragmentPagerAdapter { private List mFragments; private List mTitles; public FragmentPageAdapter(FragmentManager fm, List fragments, String[] titles) { this(fm, fragments, Arrays.asList(titles)); } public FragmentPageAdapter(FragmentManager fm, List fragments, List titles) { super(fm); mFragments = fragments; mTitles = titles; } @Override public int getCount() { return mFragments.size(); } @Override public CharSequence getPageTitle(int position) { return mTitles.get(position); } @Override public Fragment getItem(int position) { return mFragments.get(position); } } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/Article.java ================================================ package com.example.httpsender.entity; /** * 文章实体类 * User: ljx * Date: 2019-09-24 * Time: 10:23 */ public class Article { /** * apkLink : * audit : 1 * author : * chapterId : 76 * chapterName : 项目架构 * collect : false * courseId : 13 * desc : * envelopePic : * fresh : true * id : 9300 * link : https://mp.weixin.qq.com/s/_6p6vfce7m5E8AwGDg2cZg * niceDate : 10小时前 * niceShareDate : 11小时前 * origin : * prefix : * projectLink : * publishTime : 1569255115000 * shareDate : 1569249942000 * shareUser : ZYLAB * superChapterId : 74 * superChapterName : 热门专题 * tags : [] * title : Android 开发中的架构模式 -- MVC / MVP / MVVM * type : 0 * userId : 10577 * visible : 1 * zan : 0 */ private String apkLink; private int audit; private String author; private int chapterId; private String chapterName; private boolean collect; private int courseId; private String desc; private String envelopePic; private boolean fresh; private int id; private String link; private String niceDate; private String niceShareDate; private String origin; private String prefix; private String projectLink; private long publishTime; private long shareDate; private String shareUser; private int superChapterId; private String superChapterName; private String title; private int type; private int userId; private int visible; private int zan; public String getApkLink() { return apkLink; } public int getAudit() { return audit; } public String getAuthor() { return author; } public int getChapterId() { return chapterId; } public String getChapterName() { return chapterName; } public boolean isCollect() { return collect; } public int getCourseId() { return courseId; } public String getDesc() { return desc; } public String getEnvelopePic() { return envelopePic; } public boolean isFresh() { return fresh; } public int getId() { return id; } public String getLink() { return link; } public String getNiceDate() { return niceDate; } public String getNiceShareDate() { return niceShareDate; } public String getOrigin() { return origin; } public String getPrefix() { return prefix; } public String getProjectLink() { return projectLink; } public long getPublishTime() { return publishTime; } public long getShareDate() { return shareDate; } public String getShareUser() { return shareUser; } public int getSuperChapterId() { return superChapterId; } public String getSuperChapterName() { return superChapterName; } public String getTitle() { return title; } public int getType() { return type; } public int getUserId() { return userId; } public int getVisible() { return visible; } public int getZan() { return zan; } public void setApkLink(String apkLink) { this.apkLink = apkLink; } public void setAudit(int audit) { this.audit = audit; } public void setAuthor(String author) { this.author = author; } public void setChapterId(int chapterId) { this.chapterId = chapterId; } public void setChapterName(String chapterName) { this.chapterName = chapterName; } public void setCollect(boolean collect) { this.collect = collect; } public void setCourseId(int courseId) { this.courseId = courseId; } public void setDesc(String desc) { this.desc = desc; } public void setEnvelopePic(String envelopePic) { this.envelopePic = envelopePic; } public void setFresh(boolean fresh) { this.fresh = fresh; } public void setId(int id) { this.id = id; } public void setLink(String link) { this.link = link; } public void setNiceDate(String niceDate) { this.niceDate = niceDate; } public void setNiceShareDate(String niceShareDate) { this.niceShareDate = niceShareDate; } public void setOrigin(String origin) { this.origin = origin; } public void setPrefix(String prefix) { this.prefix = prefix; } public void setProjectLink(String projectLink) { this.projectLink = projectLink; } public void setPublishTime(long publishTime) { this.publishTime = publishTime; } public void setShareDate(long shareDate) { this.shareDate = shareDate; } public void setShareUser(String shareUser) { this.shareUser = shareUser; } public void setSuperChapterId(int superChapterId) { this.superChapterId = superChapterId; } public void setSuperChapterName(String superChapterName) { this.superChapterName = superChapterName; } public void setTitle(String title) { this.title = title; } public void setType(int type) { this.type = type; } public void setUserId(int userId) { this.userId = userId; } public void setVisible(int visible) { this.visible = visible; } public void setZan(int zan) { this.zan = zan; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Article article = (Article) o; return id == article.id; } @Override public int hashCode() { return id; } } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/DownloadTask.java ================================================ package com.example.httpsender.entity; /** * User: ljx * Date: 2019-06-08 * Time: 10:09 */ public class DownloadTask { private String url; private String localPath; private float progress; private long currentSize; private long totalSize; private long speed; private long remainingTime; private int state; //0=未开始 1=等待中 2=下载中 3=暂停中 4=已完成 5=下载失败 6=已取消 public DownloadTask(String url) { this.url = url; } public int getState() { return state; } public void setState(int state) { this.state = state; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getLocalPath() { return localPath; } public void setLocalPath(String localPath) { this.localPath = localPath; } public long getSpeed() { return speed; } public void setSpeed(long speed) { this.speed = speed; } public long getRemainingTime() { return remainingTime; } public void setRemainingTime(long remainingTime) { this.remainingTime = remainingTime; } public float getProgress() { return progress; } public void setProgress(float progress) { this.progress = progress; } public long getCurrentSize() { return currentSize; } public void setCurrentSize(long currentSize) { this.currentSize = currentSize; } public long getTotalSize() { return totalSize; } public void setTotalSize(long totalSize) { this.totalSize = totalSize; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; DownloadTask task = (DownloadTask) o; return url.equals(task.url); } @Override public int hashCode() { return url.hashCode(); } } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/ErrorInfo.java ================================================ package com.example.httpsender.entity; import android.text.TextUtils; import com.example.httpsender.AppHolder; import com.example.httpsender.ExceptionHelper; import com.example.httpsender.R; import com.example.httpsender.Tip; import com.google.gson.JsonSyntaxException; import java.net.ConnectException; import java.net.SocketTimeoutException; import java.net.UnknownHostException; import java.util.concurrent.TimeoutException; import rxhttp.wrapper.exception.HttpStatusCodeException; import rxhttp.wrapper.exception.ParseException; /** * Http请求错误信息 * User: ljx * Date: 2019-06-26 * Time: 14:26 */ public class ErrorInfo { private int errorCode; //仅指服务器返回的错误码 private String errorMsg; //错误文案,网络错误、请求失败错误、服务器返回的错误等文案 private Throwable throwable; //异常信息 public ErrorInfo(Throwable throwable) { this.throwable = throwable; String errorMsg = null; if (throwable instanceof UnknownHostException) { if (!ExceptionHelper.isNetworkConnected(AppHolder.getInstance())) { errorMsg = getString(R.string.network_error); } else { errorMsg = getString(R.string.notify_no_network); } } else if (throwable instanceof SocketTimeoutException || throwable instanceof TimeoutException) { //前者是通过OkHttpClient设置的超时引发的异常,后者是对单个请求调用timeout方法引发的超时异常 errorMsg = getString(R.string.time_out_please_try_again_later); } else if (throwable instanceof ConnectException) { errorMsg = getString(R.string.esky_service_exception); } else if (throwable instanceof HttpStatusCodeException) { //请求失败异常 String code = throwable.getLocalizedMessage(); if ("416".equals(code)) { errorMsg = "请求范围不符合要求"; } else { errorMsg = throwable.getMessage(); } } else if (throwable instanceof JsonSyntaxException) { //请求成功,但Json语法异常,导致解析失败 errorMsg = "数据解析失败,请稍后再试"; } else if (throwable instanceof ParseException) { // ParseException异常表明请求成功,但是数据不正确 String errorCode = throwable.getLocalizedMessage(); this.errorCode = Integer.parseInt(errorCode); errorMsg = throwable.getMessage(); if (TextUtils.isEmpty(errorMsg)) errorMsg = errorCode;//errorMsg为空,显示errorCode } else { errorMsg = throwable.getMessage(); } this.errorMsg = errorMsg; } public int getErrorCode() { return errorCode; } public String getErrorMsg() { return errorMsg; } public Throwable getThrowable() { return throwable; } public boolean show() { Tip.show(TextUtils.isEmpty(errorMsg) ? throwable.getMessage() : errorMsg); return true; } /** * @param standbyMsg 备用的提示文案 */ public boolean show(String standbyMsg) { Tip.show(TextUtils.isEmpty(errorMsg) ? standbyMsg : errorMsg); return true; } /** * @param standbyMsg 备用的提示文案 */ public boolean show(int standbyMsg) { Tip.show(TextUtils.isEmpty(errorMsg) ? AppHolder.getInstance().getString(standbyMsg) : errorMsg); return true; } public String getString(int resId) { return AppHolder.getInstance().getString(resId); } } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/Location.java ================================================ package com.example.httpsender.entity; /** * User: ljx * Date: 2019-11-18 * Time: 14:51 */ public class Location { private double longitude; private double latitude; public Location(double longitude, double latitude) { this.longitude = longitude; this.latitude = latitude; } public double getLongitude() { return longitude; } public double getLatitude() { return latitude; } } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/Name.java ================================================ package com.example.httpsender.entity; /** * User: ljx * Date: 2019-11-18 * Time: 16:19 */ public class Name { String name; public Name(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/NewsDataXml.java ================================================ package com.example.httpsender.entity; import org.simpleframework.xml.Attribute; import org.simpleframework.xml.ElementList; import org.simpleframework.xml.Root; import java.util.ArrayList; import java.util.List; /** * User: ljx * Date: 2019-11-22 * Time: 23:34 */ @Root(name = "body", strict = false) //name:要解析的xml数据的头部 public class NewsDataXml { @Attribute public String copyright; //属性 @ElementList(required = true, inline = true, entry = "route") //标志是集合 public List newsXmls = new ArrayList<>(); } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/NewsXml.java ================================================ package com.example.httpsender.entity; import org.simpleframework.xml.Attribute; import org.simpleframework.xml.Root; /** * User: ljx * Date: 2019-11-22 * Time: 23:34 */ @Root(name = "route", strict = false) //要解析的xml数据的头部 public class NewsXml { @Attribute public String tag; @Attribute public String title; } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/PageList.java ================================================ package com.example.httpsender.entity; import java.util.ArrayList; import java.util.List; /** * User: ljx * Date: 2018/10/21 * Time: 13:16 */ public class PageList { private int curPage; //当前页数 private int pageCount; //总页数 private int total; //总条数 private List datas; public int getCurPage() { return curPage; } public int getPageCount() { return pageCount; } public int getTotal() { return total; } public List getDatas() { return datas; } public void setCurPage(int curPage) { this.curPage = curPage; } public void setPageCount(int pageCount) { this.pageCount = pageCount; } public void setTotal(int total) { this.total = total; } public void setDatas(ArrayList datas) { this.datas = datas; } } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/Response.java ================================================ package com.example.httpsender.entity; /** * User: ljx * Date: 2018/10/21 * Time: 13:16 */ public class Response { private int errorCode; private String errorMsg; private T data; public int getCode() { return errorCode; } public String getMsg() { return errorMsg; } public T getData() { return data; } public void setErrorCode(int errorCode) { this.errorCode = errorCode; } public void setErrorMsg(String errorMsg) { this.errorMsg = errorMsg; } public void setData(T data) { this.data = data; } } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/Url.kt ================================================ package com.example.httpsender.entity import rxhttp.wrapper.annotation.DefaultDomain import rxhttp.wrapper.annotation.Domain /** * User: ljx * Date: 2020/2/27 * Time: 23:55 */ object Url { @JvmField @DefaultDomain //设置为默认域名 var baseUrl = "https://www.wanandroid.com/" const val UPLOAD_URL = "http://t.xinhuo.com/index.php/Api/Pic/uploadPic" const val DOWNLOAD_URL = "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk" } ================================================ FILE: app/src/main/java/com/example/httpsender/entity/User.java ================================================ package com.example.httpsender.entity; /** * User: ljx * Date: 2019-12-04 * Time: 12:13 */ public class User { private static User mUser; private String token; public static User get() { if (mUser == null) { synchronized (User.class) { if (mUser == null) mUser = new User(); } } return mUser; } public String getToken() { return token; } public void setToken(String token) { this.token = token; } } ================================================ FILE: app/src/main/java/com/example/httpsender/fragment/AwaitFragment.kt ================================================ package com.example.httpsender.fragment import android.graphics.Color import android.net.Uri import android.os.Bundle import android.view.View import androidx.lifecycle.lifecycleScope import com.example.httpsender.R import com.example.httpsender.databinding.AwaitFragmentBinding import com.example.httpsender.entity.* import com.example.httpsender.kt.errorMsg import com.example.httpsender.kt.show import com.google.gson.Gson import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch import rxhttp.* import rxhttp.wrapper.param.RxHttp import rxhttp.wrapper.param.toAwaitResponse import java.io.File import java.util.* /** * 使用 协程(RxHttp + Await) 发请求 * * ``` * val user = RxHttp.postXxx("/service/...") * .add("key", "value") * .toAwait() * .awaitResult { * val user = it * }.onFailure { * val throwable = it * } *``` * * User: ljx * Date: 2020/4/24 * Time: 18:16 */ class AwaitFragment : BaseFragment(), View.OnClickListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.await_fragment) } override fun AwaitFragmentBinding.onViewCreated(savedInstanceState: Bundle?) { click = this@AwaitFragment } //发送Get请求,获取文章列表 private suspend fun AwaitFragmentBinding.sendGet(view: View) { RxHttp.get("/article/list/0/json") .toAwaitResponse>() .toAwaitOkResponse() .awaitResult { val list = it.body() val response = it.raw() val headers = it.headers() tvResult.text = Gson().toJson(list) }.onFailure { tvResult.text = it.errorMsg //失败回调 it.show() } } //发送Post表单请求,根据关键字查询文章 private suspend fun AwaitFragmentBinding.sendPostForm(view: View) { RxHttp.postForm("/article/query/0/json") .add("k", "性能优化") .toAwaitResponse>() .awaitResult { tvResult.text = Gson().toJson(it) }.onFailure { tvResult.text = it.errorMsg //失败回调 it.show() } } //发送Post Json请求,此接口不通,通过日志可以看到,发送出去的json对象 private suspend fun AwaitFragmentBinding.sendPostJson(view: View) { /* 发送以下User对象 {"name":"张三","sex":1,"height":180,"weight":70, "interest":["羽毛球","游泳"], "location":{"latitude":30.7866,"longitude":120.6788}, "address":{"street":"科技园路.","city":"江苏苏州","country":"中国"}} */ val interestList: MutableList = ArrayList() //爱好 interestList.add("羽毛球") interestList.add("游泳") val address = """ {"street":"科技园路.","city":"江苏苏州","country":"中国"} """.trimIndent() RxHttp.postJson("/article/list/0/json") .add("name", "张三") .add("sex", 1) .addAll("""{"height":180,"weight":70}""") //通过addAll系列方法添加多个参数 .add("interest", interestList) //添加数组对象 .add("location", Location(120.6788, 30.7866)) //添加位置对象 .addJsonElement("address", address) //通过字符串添加一个对象 .toAwaitString() .awaitResult { tvResult.text = it }.onFailure { tvResult.text = it.errorMsg //失败回调 it.show() } } //发送Post JsonArray请求,通过日志可以看到,发送出去的json数组 private suspend fun AwaitFragmentBinding.sendPostJsonArray(view: View) { /* 发送以下Json数组 [{"name":"张三"},{"name":"李四"},{"name":"王五"},{"name":"赵六"},{"name":"杨七"}] */ val names: MutableList = ArrayList() names.add(Name("赵六")) names.add(Name("杨七")) RxHttp.postJsonArray("/article/list/0/json") .add("name", "张三") .add(Name("李四")) .addJsonElement("""{"name":"王五"}""") .addAll(names) .toAwaitString() .awaitResult { tvResult.text = it }.onFailure { tvResult.text = it.errorMsg //失败回调 it.show() } } //此接口不同,但通过日志可以看到,发送出去的是xml数据,如果收到也是xml数据,则会自动解析为我们指定的对象 private suspend fun AwaitFragmentBinding.xmlConverter(view: View) { RxHttp.postBody("http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=sf-muni") .setBody(Name("张三")) .setXmlConverter() .toAwait() .awaitResult { tvResult.text = Gson().toJson(it) }.onFailure { tvResult.text = it.errorMsg //失败回调 it.show() } } /** * android 10之前 或 沙盒目录(Android/data/packageName/)下的文件上传 * * 注意:这里并非通过 [Await] 实现的, 而是通过 [Flow] 监听的进度,因为在监听上传进度这块,Flow性能更优,且更简单 * * 如不需要监听进度,toFlow 方法不要传进度回调即可 */ private suspend fun AwaitFragmentBinding.upload(v: View) { RxHttp.postForm(Url.UPLOAD_URL) .addFile("file", File("xxxx/1.png")) .toFlow() .onProgress { //上传进度回调,0-100,仅在进度有更新时才会回调 val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已上传的字节大小 val totalSize = it.totalSize //要上传的总字节大小 tvResult.append("\n$it") }.catch { tvResult.append("\n${it.errorMsg}") //失败回调 it.show() }.collect { tvResult.append("\n上传成功 : $it") } } /** * android 10 及以上文件上传 ,兼容Android 10以下 * * 注意:这里并非通过 [Await] 实现的, 而是通过 [Flow] 监听的进度,因为在监听上传进度这块,Flow性能更优,且更简单 * * 如不需要监听进度,toFlow 方法不要传进度回调即可 */ private suspend fun AwaitFragmentBinding.uploadAndroid10(v: View) { //真实环境,需要调用文件选择器,拿到Uri对象 val uri = Uri.parse("content://media/external/downloads/13417") RxHttp.postForm(Url.UPLOAD_URL) .addPart(requireContext(), "file", uri) .toFlow() .onProgress { //上传进度回调,0-100,仅在进度有更新时才会回调 val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已上传的字节大小 val totalSize = it.totalSize //要上传的总字节大小 tvResult.append("\n$it") }.catch { tvResult.append("\n${it.errorMsg}") //失败回调 it.show() }.collect { tvResult.append("\n上传成功 : $it") } } /** * Android 10以下 或 下载文件到沙盒目录下,下载可以直接传入file的绝对路径 * * 如不需要监听下载进度,toDownload 方法不要传进度回调即可 */ private suspend fun AwaitFragmentBinding.download(view: View) { // val destPath = "${requireContext().externalCacheDir}/${System.currentTimeMillis()}.apk" // RxHttp.get(Url.DOWNLOAD_URL) // .toDownloadAwait(destPath) { // val currentProgress = it.progress //当前进度 0-100 // val currentSize = it.currentSize //当前已下载的字节大小 // val totalSize = it.totalSize //要下载的总字节大小 // tvResult.append(it.toString()) // }.awaitResult { // tvResult.append("\n下载完成, $it") // }.onFailure { // //异常回调 // tvResult.append("\n${it.errorMsg}") // it.show() // } } /** * 断点下载 * Android 10以下 或 下载文件到沙盒目录下,下载可以直接传入file的绝对路径 * 如不需要监听下载进度,toDownload 方法不要传进度回调即可 */ private suspend fun AwaitFragmentBinding.appendDownload(view: View) { // val destPath = "${requireContext().externalCacheDir}/Miaobo.apk" // RxHttp.get(Url.DOWNLOAD_URL) // .toDownloadAwait(destPath, true) { // val currentProgress = it.progress //当前进度 0-100 // val currentSize = it.currentSize //当前已下载的字节大小 // val totalSize = it.totalSize //要下载的总字节大小 // tvResult.append(it.toString()) // }.awaitResult { // tvResult.append("\n下载完成, $it") // }.onFailure { // //异常回调 // tvResult.append("\n${it.errorMsg}") // it.show() // } } /** * Android 10 及以上下载,兼容Android 10以下 * 如不需要监听下载进度,toDownload 方法不要传进度回调即可 */ private suspend fun AwaitFragmentBinding.downloadAndroid10(view: View) { // val factory = Android10DownloadFactory(requireContext(), "miaobo.apk") // RxHttp.get(Url.DOWNLOAD_URL) // .toDownloadAwait(factory) { // val currentProgress = it.progress //当前进度 0-100 // val currentSize = it.currentSize //当前已下载的字节大小 // val totalSize = it.totalSize //要下载的总字节大小 // tvResult.append(it.toString()) // }.awaitResult { // tvResult.append("\n下载完成, $it") // }.onFailure { // //异常回调 // tvResult.append("\n${it.errorMsg}") // it.show() // } } /** * Android 10 及以上断点下载,兼容Android 10以下 * 如不需要监听下载进度,toDownload 方法不要传进度回调即可 */ private suspend fun AwaitFragmentBinding.appendDownloadAndroid10(view: View) { // val factory = Android10DownloadFactory(requireContext(), "miaobo.apk") // RxHttp.get(Url.DOWNLOAD_URL) // .toDownloadAwait(factory, true) { // val currentProgress = it.progress //当前进度 0-100 // val currentSize = it.currentSize //当前已下载的字节大小 // val totalSize = it.totalSize //要下载的总字节大小 // tvResult.append(it.toString()) // }.awaitResult { // tvResult.append("\n下载完成, $it") // }.onFailure { // //异常回调 // tvResult.append("\n${it.errorMsg}") // it.show() // } } private fun AwaitFragmentBinding.clearLog(view: View) { tvResult.text = "" tvResult.setBackgroundColor(Color.TRANSPARENT) } override fun onClick(v: View) { mBinding.run { lifecycleScope.launch { when (v.id) { R.id.sendGet -> sendGet(v) R.id.sendPostForm -> sendPostForm(v) R.id.sendPostJson -> sendPostJson(v) R.id.sendPostJsonArray -> sendPostJsonArray(v) R.id.xmlConverter -> xmlConverter(v) R.id.upload -> upload(v) R.id.upload10 -> uploadAndroid10(v) R.id.download -> download(v) R.id.download_append -> appendDownload(v) R.id.download10 -> downloadAndroid10(v) R.id.download10_append -> appendDownloadAndroid10(v) R.id.bt_clear -> clearLog(v) } } } } } ================================================ FILE: app/src/main/java/com/example/httpsender/fragment/BaseFragment.kt ================================================ package com.example.httpsender.fragment import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.LayoutRes import androidx.databinding.DataBindingUtil import androidx.databinding.ViewDataBinding import androidx.fragment.app.Fragment /** * User: ljx * Date: 2020/6/2 * Time: 11:45 */ abstract class BaseFragment : Fragment() { @LayoutRes private var layoutId = 0 protected lateinit var mBinding: T fun setContentView(@LayoutRes layoutId: Int) { this.layoutId = layoutId } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { mBinding = DataBindingUtil.inflate(inflater, layoutId, container, false) return mBinding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) mBinding.onViewCreated(savedInstanceState) } open fun T.onViewCreated(savedInstanceState: Bundle?) { } } ================================================ FILE: app/src/main/java/com/example/httpsender/fragment/FlowFragment.kt ================================================ package com.example.httpsender.fragment import android.graphics.Color import android.net.Uri import android.os.Bundle import android.view.View import androidx.lifecycle.lifecycleScope import com.example.httpsender.R import com.example.httpsender.databinding.FlowFragmentBinding import com.example.httpsender.entity.Article import com.example.httpsender.entity.Location import com.example.httpsender.entity.Name import com.example.httpsender.entity.NewsDataXml import com.example.httpsender.entity.PageList import com.example.httpsender.entity.Url import com.example.httpsender.kt.errorMsg import com.example.httpsender.kt.show import com.example.httpsender.parser.Android10DownloadFactory import com.google.gson.Gson import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch import rxhttp.toDownloadFlow import rxhttp.toFlow import rxhttp.wrapper.param.RxHttp import rxhttp.wrapper.param.toFlowResponse import java.io.File /** * 使用 协程(RxHttp + Flow) 发请求 * * ``` * RxHttp.postXxx("/service/...") * .add("key", "value") * .toFlow() * .catch { * val throwable = it * }.collect { * val user = it * } *``` * User: ljx * Date: 2021/9/18 * Time: 20:16 */ class FlowFragment : BaseFragment(), View.OnClickListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.flow_fragment) } override fun FlowFragmentBinding.onViewCreated(savedInstanceState: Bundle?) { click = this@FlowFragment } //发送Get请求,获取文章列表 private suspend fun FlowFragmentBinding.sendGet(view: View) { RxHttp.get("/article/list/0/json") .toFlowResponse>() .catch { tvResult.text = it.errorMsg it.show() }.collect { tvResult.text = Gson().toJson(it) } } //发送Post表单请求,根据关键字查询文章 private suspend fun FlowFragmentBinding.sendPostForm(view: View) { RxHttp.postForm("/article/query/0/json") .add("k", "性能优化") .toFlowResponse>() .catch { tvResult.text = it.errorMsg it.show() }.collect { tvResult.text = Gson().toJson(it) } } //发送Post Json请求,此接口不通,通过日志可以看到,发送出去的json对象 private suspend fun FlowFragmentBinding.sendPostJson(view: View) { /* 发送以下User对象 {"name":"张三","sex":1,"height":180,"weight":70, "interest":["羽毛球","游泳"], "location":{"latitude":30.7866,"longitude":120.6788}, "address":{"street":"科技园路.","city":"江苏苏州","country":"中国"}} */ val interestList: MutableList = ArrayList() //爱好 interestList.add("羽毛球") interestList.add("游泳") val address = """ {"street":"科技园路.","city":"江苏苏州","country":"中国"} """.trimIndent() RxHttp.postJson("/article/list/0/json") .add("name", "张三") .add("sex", 1) .addAll("""{"height":180,"weight":70}""") //通过addAll系列方法添加多个参数 .add("interest", interestList) //添加数组对象 .add("location", Location(120.6788, 30.7866)) //添加位置对象 .addJsonElement("address", address) //通过字符串添加一个对象 .toFlow() .catch { tvResult.text = it.errorMsg it.show() }.collect { tvResult.text = it } } //发送Post JsonArray请求,通过日志可以看到,发送出去的json数组 private suspend fun FlowFragmentBinding.sendPostJsonArray(view: View) { /* 发送以下Json数组 [{"name":"张三"},{"name":"李四"},{"name":"王五"},{"name":"赵六"},{"name":"杨七"}] */ val names: MutableList = ArrayList() names.add(Name("赵六")) names.add(Name("杨七")) RxHttp.postJsonArray("/article/list/0/json") .add("name", "张三") .add(Name("李四")) .addJsonElement("""{"name":"王五"}""") .addAll(names) .toFlow() .catch { tvResult.text = it.errorMsg it.show() }.collect { tvResult.text = it } } //此接口不同,但通过日志可以看到,发送出去的是xml数据,如果收到也是xml数据,则会自动解析为我们指定的对象 private suspend fun FlowFragmentBinding.xmlConverter(view: View) { RxHttp.postBody("http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=sf-muni") .setBody(Name("张三")) .setXmlConverter() .toFlow() .catch { tvResult.text = it.errorMsg it.show() }.collect { tvResult.text = Gson().toJson(it) } } /** * android 10之前 或 沙盒目录(Android/data/packageName/)下的文件上传 * * 如不需要监听进度,toFlow 方法不要传进度回调即可 */ private suspend fun FlowFragmentBinding.upload(v: View) { RxHttp.postForm(Url.UPLOAD_URL) .addFile("file", File("xxxx/1.png")) .toFlow() .onProgress { //上传进度回调,0-100,仅在进度有更新时才会回调 val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已上传的字节大小 val totalSize = it.totalSize //要上传的总字节大小 tvResult.append("\n$it") }.catch { tvResult.append("\n${it.errorMsg}") //失败回调 it.show() }.collect { tvResult.append("\n上传成功 : $it") } } /** * android 10 及以上文件上传 ,兼容Android 10以下 * * 如不需要监听进度,toFlow 方法不要传进度回调即可 */ private suspend fun FlowFragmentBinding.uploadAndroid10(v: View) { //真实环境,需要调用文件选择器,拿到Uri对象 val uri = Uri.parse("content://media/external/downloads/13417") RxHttp.postForm(Url.UPLOAD_URL) .addPart(requireContext(), "file", uri) .toFlow() .onProgress { //上传进度回调,0-100,仅在进度有更新时才会回调 val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已上传的字节大小 val totalSize = it.totalSize //要上传的总字节大小 tvResult.append("\n$it") }.catch { tvResult.append("\n${it.errorMsg}") //失败回调 it.show() }.collect { tvResult.append("\n上传成功 : $it") } } /** * Android 10以下 或 下载文件到沙盒目录下,下载可以直接传入file的绝对路径 * * 如不需要监听下载进度,toFlow 方法不要传进度回调即可 */ private suspend fun FlowFragmentBinding.download(view: View) { val destPath = "${requireContext().externalCacheDir}/${System.currentTimeMillis()}.apk" RxHttp.get(Url.DOWNLOAD_URL) .toDownloadFlow(destPath) .onProgress { val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已下载的字节大小 val totalSize = it.totalSize //要下载的总字节大小 tvResult.append("\n$it") }.catch { tvResult.append("\n${it.errorMsg}") //异常回调 it.show() }.collect { tvResult.append("\n下载完成, $it") } } /** * 断点下载 * Android 10以下 或 下载文件到沙盒目录下,下载可以直接传入file的绝对路径 * * 如不需要监听下载进度,toFlow 方法不要传进度回调即可 */ private suspend fun FlowFragmentBinding.appendDownload(view: View) { val destPath = "${requireContext().externalCacheDir}/Miaobo.apk" RxHttp.get(Url.DOWNLOAD_URL) .toDownloadFlow(destPath, true) .onProgress { val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已下载的字节大小 val totalSize = it.totalSize //要下载的总字节大小 tvResult.append("\n$it") }.catch { tvResult.append("\n${it.errorMsg}") it.show() }.collect { tvResult.append("\n下载完成, $it") } } /** * Android 10 及以上下载,兼容Android 10以下 * * 如不需要监听下载进度,toFlow 方法不要传进度回调即可 */ private suspend fun FlowFragmentBinding.downloadAndroid10(view: View) { val factory = Android10DownloadFactory(requireContext(), "miaobo.apk") RxHttp.get(Url.DOWNLOAD_URL) .toDownloadFlow(factory) .onProgress { val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已下载的字节大小 val totalSize = it.totalSize //要下载的总字节大小 tvResult.append("\n$it") }.catch { tvResult.append("\n${it.errorMsg}") it.show() }.collect { tvResult.append("\n下载完成, $it") } } /** * Android 10 及以上断点下载,兼容Android 10以下 * * 如不需要监听下载进度,toFlow 方法不要传进度回调即可 */ private suspend fun FlowFragmentBinding.appendDownloadAndroid10(view: View) { val factory = Android10DownloadFactory(requireContext(), "miaobo.apk") RxHttp.get(Url.DOWNLOAD_URL) .toDownloadFlow(factory, true) .onProgress { val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已下载的字节大小 val totalSize = it.totalSize //要下载的总字节大小 tvResult.append("\n$it") }.catch { //异常回调 tvResult.append("\n${it.errorMsg}") it.show() }.collect { tvResult.append("\n下载完成, $it") } } private fun FlowFragmentBinding.clearLog(view: View) { tvResult.text = "" tvResult.setBackgroundColor(Color.TRANSPARENT) } override fun onClick(v: View) { mBinding.run { lifecycleScope.launch { when (v.id) { R.id.sendGet -> sendGet(v) R.id.sendPostForm -> sendPostForm(v) R.id.sendPostJson -> sendPostJson(v) R.id.sendPostJsonArray -> sendPostJsonArray(v) R.id.xmlConverter -> xmlConverter(v) R.id.upload -> upload(v) R.id.upload10 -> uploadAndroid10(v) R.id.download -> download(v) R.id.download_append -> appendDownload(v) R.id.download10 -> downloadAndroid10(v) R.id.download10_append -> appendDownloadAndroid10(v) R.id.bt_clear -> clearLog(v) } } } } } ================================================ FILE: app/src/main/java/com/example/httpsender/fragment/MultiDownloadFragment.java ================================================ package com.example.httpsender.fragment; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.SimpleItemAnimator; import com.example.httpsender.DownloadMultiAdapter; import com.example.httpsender.DownloadMultiAdapter.OnItemClickListener; import com.example.httpsender.R; import com.example.httpsender.Tip; import com.example.httpsender.databinding.MultiDownloadFragmentBinding; import com.example.httpsender.entity.DownloadTask; import com.example.httpsender.vm.MultiTaskDownloader; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; /** * User: ljx * Date: 2021/9/25 * Time: 18:08 */ public class MultiDownloadFragment extends BaseFragment implements OnItemClickListener, OnClickListener { private final String[] downloadUrl = { // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/uS12nZLR.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/BYGanTMW.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/Iu9hZLL8.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/DdKLk5VX.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/Byww5X8k.ts", "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?111",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?222",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?333",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?444",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?555",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?666",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?777",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?888",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?999",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?101",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?102",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?103",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?104",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?105",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?106",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?107",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk?108",//探探 "https://apk-ssl.tancdn.com/3.5.3_276/%E6%8E%A2%E6%8E%A2.apk",//探探 // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/OUkREagY.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/ZJUsgPSd.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/I5ivzoXR.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/PFPXapY7.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/tJj2JTVy.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/mj2fFYjH.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/MOXijkzw.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/uiwVyFej.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/HijAOXaK.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/h2mFS6ef.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/vf8fjmJd.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/0jsVXFSa.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/aZfnIVnP.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/l7cddTCQ.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/QaMi23d0.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/ljLywei6.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/FQpwzm4U.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/4oW2C2iZ.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/57OL3KeG.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/vYlV9nTw.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/XUmd5HWF.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/btVvEY5r.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/eJRKGoaP.ts", // "https://hnzy4.jinhaiwzhs.com:65/20210625/mndoRF1O/2000kb/hls/iz5kx1X1.ts", }; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.multi_download_fragment); } @Override public void onViewCreated(@NotNull MultiDownloadFragmentBinding binding, @Nullable Bundle savedInstanceState) { super.onViewCreated(binding, savedInstanceState); binding.setClick(this); ArrayList allTask = new ArrayList<>(); //所有下载任务 for (int i = 0; i < downloadUrl.length; i++) { String url = downloadUrl[i]; DownloadTask task = new DownloadTask(url); String suffix = url.substring(url.lastIndexOf(".")); task.setLocalPath(getContext().getExternalCacheDir() + "/" + i + suffix); allTask.add(task); } RecyclerView recyclerView = binding.recyclerView; MultiTaskDownloader.addTasks(allTask); DownloadMultiAdapter multiAdapter = new DownloadMultiAdapter(MultiTaskDownloader.getAllTask()); multiAdapter.setOnItemClickListener(this); recyclerView.setAdapter(multiAdapter); ((SimpleItemAnimator) recyclerView.getItemAnimator()).setSupportsChangeAnimations(false); MultiTaskDownloader.getLiveTask().observe(getViewLifecycleOwner(), task -> { int index = MultiTaskDownloader.getAllTask().indexOf(task); if (index != -1) { //任务有更新,刷新单个item multiAdapter.notifyItemChanged(index); } }); } @Override public void onClick(View v) { int id = v.getId(); if (id == R.id.start_all) { MultiTaskDownloader.startAllDownloadTask(); } else if (id == R.id.cancel_all) { MultiTaskDownloader.cancelAllTask(); } } @Override public void onItemClick(View view, DownloadTask task, int position) { if (view.getId() == R.id.bt_pause) { int curState = task.getState(); //任务当前状态 if (curState == MultiTaskDownloader.IDLE //未开始->开始下载 || curState == MultiTaskDownloader.PAUSED //暂停下载->继续下载 || curState == MultiTaskDownloader.CANCEL //已取消->重新开始下载 || curState == MultiTaskDownloader.FAIL //下载失败->重新下载 ) { MultiTaskDownloader.download(task); } else if (curState == MultiTaskDownloader.WAITING) { //等待中->取消下载 MultiTaskDownloader.removeWaitTask(task); } else if (curState == MultiTaskDownloader.DOWNLOADING) { //下载中->暂停下载 MultiTaskDownloader.pauseTask(task); } else if (curState == MultiTaskDownloader.COMPLETED) { //任务已完成 Tip.show("该任务已完成"); } } } } ================================================ FILE: app/src/main/java/com/example/httpsender/fragment/RxJavaFragment.kt ================================================ package com.example.httpsender.fragment import android.graphics.Color import android.net.Uri import android.os.Bundle import android.view.View import com.example.httpsender.R import com.example.httpsender.databinding.RxjavaFragmentBinding import com.example.httpsender.entity.Article import com.example.httpsender.entity.Location import com.example.httpsender.entity.Name import com.example.httpsender.entity.NewsDataXml import com.example.httpsender.entity.PageList import com.example.httpsender.entity.Url import com.example.httpsender.kt.errorMsg import com.example.httpsender.kt.show import com.example.httpsender.parser.Android10DownloadFactory import com.google.gson.Gson import com.rxjava.rxlife.life import com.rxjava.rxlife.lifeOnMain import rxhttp.wrapper.param.RxHttp import rxhttp.wrapper.param.toObservable import rxhttp.wrapper.param.toObservableResponse import java.io.File /** * 使用 RxHttp + RxJava 发请求 * * ``` * RxHttp.postXxx("/service/...") * .add("key", "value") * .toObservable() * .subscribe(user -> { * * }, throwable -> { * * }) * ``` * User: ljx * Date: 2020/4/24 * Time: 18:16 */ class RxJavaFragment : BaseFragment(), View.OnClickListener { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.rxjava_fragment) } override fun RxjavaFragmentBinding.onViewCreated(savedInstanceState: Bundle?) { click = this@RxJavaFragment } //发送Get请求,获取文章列表 fun RxjavaFragmentBinding.sendGet(view: View?) { RxHttp.get("/article/list/0/json") .addQuery("aa") .addQuery("bb","") .toObservableResponse>() .lifeOnMain(this@RxJavaFragment) .subscribe({ tvResult.text = Gson().toJson(it) }, { tvResult.text = it.errorMsg it.show() }) } //发送Post表单请求,根据关键字查询文章 fun RxjavaFragmentBinding.sendPostForm(view: View?) { RxHttp.postForm("/article/query/0/json") .add("k", "性能优化") .toObservableResponse>() .lifeOnMain(this@RxJavaFragment) //感知生命周期,并在主线程回调 .subscribe({ tvResult.text = Gson().toJson(it) }, { tvResult.text = it.errorMsg it.show() }) } //发送Post Json请求,此接口不通,通过日志可以看到,发送出去的json对象 fun RxjavaFragmentBinding.sendPostJson(view: View?) { //发送以下User对象 /* { "name": "张三", "sex": 1, "height": 180, "weight": 70, "interest": [ "羽毛球", "游泳" ], "location": { "latitude": 30.7866, "longitude": 120.6788 }, "address": { "street": "科技园路.", "city": "江苏苏州", "country": "中国" } } */ val interestList: MutableList = ArrayList() //爱好 interestList.add("羽毛球") interestList.add("游泳") val address = "{\"street\":\"科技园路.\",\"city\":\"江苏苏州\",\"country\":\"中国\"}" RxHttp.postJson("/article/list/0/json") .add("name", "张三") .add("sex", 1) .addAll("{\"height\":180,\"weight\":70}") //通过addAll系列方法添加多个参数 .add("interest", interestList) //添加数组对象 .add("location", Location(120.6788, 30.7866)) //添加位置对象 .addJsonElement("address", address) //通过字符串添加一个对象 .toObservableString() .lifeOnMain(this@RxJavaFragment)//感知生命周期,并在主线程回调 .subscribe({ tvResult.text = it }, { tvResult.text = it.errorMsg it.show() }) } //发送Post JsonArray请求,通过日志可以看到,发送出去的json数组 fun RxjavaFragmentBinding.sendPostJsonArray(view: View?) { //发送以下Json数组 /* [ { "name": "张三" }, { "name": "李四" }, { "name": "王五" }, { "name": "赵六" }, { "name": "杨七" } ] */ val names: MutableList = ArrayList() names.add(Name("赵六")) names.add(Name("杨七")) RxHttp.postJsonArray("/article/list/0/json") .add("name", "张三") .add(Name("李四")) .addJsonElement("{\"name\":\"王五\"}") .addAll(names) .toObservableString() .lifeOnMain(this@RxJavaFragment) .subscribe({ tvResult.text = it }, { tvResult.text = it.errorMsg //失败回调 it.show() }) } //此接口不同,但通过日志可以看到,发送出去的是xml数据,如果收到也是xml数据,则会自动解析为我们指定的对象 fun RxjavaFragmentBinding.xmlConverter(view: View?) { RxHttp.postBody("http://webservices.nextbus.com/service/publicXMLFeed?command=routeConfig&a=sf-muni") .setBody(Name("张三")) .setXmlConverter() .toObservable() .lifeOnMain(this@RxJavaFragment) //感知生命周期,并在主线程回调 .subscribe({ tvResult.text = Gson().toJson(it) }, { tvResult.text = it.errorMsg //失败回调 it.show() }) } /** * android 10之前 或 沙盒目录(Android/data/packageName/)下的文件上传,如不需要监听进度,注释掉 upload 方法即可 */ private fun RxjavaFragmentBinding.upload(v: View) { RxHttp.postForm(Url.UPLOAD_URL) .addFile("file", File("xxxx/1.png")) .toObservableString() .onMainProgress { //上传进度回调,0-100,仅在进度有更新时才会回调 val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已上传的字节大小 val totalSize = it.totalSize //要上传的总字节大小 tvResult.append("\n$it") } .life(this@RxJavaFragment) //页面销毁,自动关闭请求 .subscribe({ tvResult.append("\n上传成功 : $it") }, { tvResult.append("\n${it.errorMsg}") it.show() }) } /** * android 10 及以上文件上传 ,兼容Android 10以下,如不需要监听进度,注释掉 upload 方法即可 */ private fun RxjavaFragmentBinding.uploadAndroid10(v: View) { //真实环境,需要调用文件选择器,拿到Uri对象 val uri = Uri.parse("content://media/external/downloads/13417") RxHttp.postForm(Url.UPLOAD_URL) .addPart(requireContext(), "file", uri) .toObservableString() .onMainProgress { //上传进度回调,0-100,仅在进度有更新时才会回调 val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已上传的字节大小 val totalSize = it.totalSize //要上传的总字节大小 tvResult.append("\n$it") } .life(this@RxJavaFragment) //页面销毁,自动关闭请求 .subscribe({ tvResult.append("\n上传成功 : $it") }, { tvResult.append("\n${it.errorMsg}") //失败回调 it.show() }) } /** * Android 10以下 或 下载文件到沙盒目录下,下载可以直接传入file的绝对路径 * * 如不需要监听下载进度,asDownload 方法不要传进度回调即可 */ private fun RxjavaFragmentBinding.download(view: View) { val destPath = "${requireContext().externalCacheDir}/${System.currentTimeMillis()}.apk" RxHttp.get(Url.DOWNLOAD_URL) .toDownloadObservable(destPath) .onMainProgress { val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已下载的字节大小 val totalSize = it.totalSize //要下载的总字节大小 tvResult.append("\n$it") } .life(this@RxJavaFragment) //感知生命周期,并在主线程回调 .subscribe({ tvResult.append("\n下载完成, $it") }, { //下载失败 tvResult.append("\n${it.errorMsg}") it.show() }) } /** * 断点下载 * Android 10以下 或 下载文件到沙盒目录下,下载可以直接传入file的绝对路径 * 如不需要监听下载进度,asAppendDownload 方法不要传进度回调即可 */ private fun RxjavaFragmentBinding.appendDownload(view: View) { val destPath = "${requireContext().externalCacheDir}/Miaobo.apk" RxHttp.get(Url.DOWNLOAD_URL) .toDownloadObservable(destPath, true) .onMainProgress { val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已下载的字节大小 val totalSize = it.totalSize //要下载的总字节大小 tvResult.append("\n$it") } .life(this@RxJavaFragment) //感知生命周期,并在主线程回调 .subscribe({ tvResult.append("\n下载完成, $it") }, { //下载失败 tvResult.append("\n${it.errorMsg}") it.show() }) } /** * Android 10 及以上下载,兼容Android 10以下 * 如不需要监听下载进度,asDownload 方法不要传进度回调即可 */ private fun RxjavaFragmentBinding.downloadAndroid10(view: View) { val factory = Android10DownloadFactory(requireContext(), "miaobo.apk") RxHttp.get(Url.DOWNLOAD_URL) .toDownloadObservable(factory) .onMainProgress { val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已下载的字节大小 val totalSize = it.totalSize //要下载的总字节大小 tvResult.append("\n$it") } .life(this@RxJavaFragment) //感知生命周期,并在主线程回调 .subscribe({ tvResult.append("\n下载完成, $it") }, { //下载失败 tvResult.append("\n${it.errorMsg}") it.show() }) } /** * Android 10 及以上断点下载,兼容Android 10以下 * 如不需要监听下载进度,asAppendDownload 方法不要传进度回调即可 */ private fun RxjavaFragmentBinding.appendDownloadAndroid10(view: View) { val factory = Android10DownloadFactory(requireContext(), "miaobo.apk") RxHttp.get(Url.DOWNLOAD_URL) .toDownloadObservable(factory, true) .onMainProgress { val currentProgress = it.progress //当前进度 0-100 val currentSize = it.currentSize //当前已下载的字节大小 val totalSize = it.totalSize //要下载的总字节大小 tvResult.append("\n$it") } .life(this@RxJavaFragment) //感知生命周期,并在主线程回调 .subscribe({ tvResult.append("\n下载完成, $it") }, { //下载失败 tvResult.append("\n${it.errorMsg}") it.show() }) } private fun RxjavaFragmentBinding.clearLog(view: View?) { tvResult.text = "" tvResult.setBackgroundColor(Color.TRANSPARENT) } override fun onClick(v: View) { mBinding.apply { when (v.id) { R.id.sendGet -> sendGet(v) R.id.sendPostForm -> sendPostForm(v) R.id.sendPostJson -> sendPostJson(v) R.id.sendPostJsonArray -> sendPostJsonArray(v) R.id.xmlConverter -> xmlConverter(v) R.id.upload -> upload(v) R.id.upload10 -> uploadAndroid10(v) R.id.download -> download(v) R.id.download_append -> appendDownload(v) R.id.download10 -> downloadAndroid10(v) R.id.download10_append -> appendDownloadAndroid10(v) R.id.bt_clear -> clearLog(v) } } } } ================================================ FILE: app/src/main/java/com/example/httpsender/interceptor/RedirectInterceptor.java ================================================ package com.example.httpsender.interceptor; import java.io.IOException; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; /** * 处理重定向的拦截器,非必须 * User: ljx * Date: 2019-12-17 * Time: 23:24 */ public class RedirectInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { okhttp3.Request request = chain.request(); Response response = chain.proceed(request); int code = response.code(); if (code == 308) { //获取重定向的地址 String location = response.headers().get("Location"); //重新构建请求 Request newRequest = request.newBuilder().url(location).build(); response.close(); response = chain.proceed(newRequest); } return response; } } ================================================ FILE: app/src/main/java/com/example/httpsender/interceptor/TokenInterceptor.java ================================================ package com.example.httpsender.interceptor; import com.example.httpsender.entity.User; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.util.concurrent.atomic.AtomicReference; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; import rxhttp.wrapper.param.RxHttp; /** * token 失效,自动刷新token,然后再次发送请求,用户无感知 * User: ljx * Date: 2019-12-04 * Time: 11:56 */ public class TokenInterceptor implements Interceptor { //保存刷新后的token private final AtomicReference atomicToken = new AtomicReference<>(); //token刷新时间 @NotNull @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); Response originalResponse = chain.proceed(request); String code = originalResponse.header("xxx"); //其中xxx,自己跟服务端协定 if ("-1".equals(code)) { //token 失效 这里根据自己的业务需求写判断条件 atomicToken.set(null); return handleTokenInvalid(chain, request); } return originalResponse; } //处理token失效问题, 同步刷新 private Response handleTokenInvalid(Chain chain, Request request) throws IOException { boolean success = refreshToken(); Request newRequest; if (success) { //刷新成功,重新添加token newRequest = request.newBuilder() .header("xxx", atomicToken.get()) //其中xxx,自己跟服务端协定 .build(); } else { newRequest = request; } return chain.proceed(newRequest); } //刷新token, 考虑到有并发情况,故这里需要加锁 private boolean refreshToken() { //token不等于null,说明已经刷新 if (atomicToken.get() != null) return true; synchronized (this) { //再次判断是否已经刷新 if (atomicToken.get() != null) return true; try { //根据自己业务,同步刷新token, 注意这里千万不能异步 String token = RxHttp.postForm("/refreshToken/...") .executeString(); atomicToken.set(token); User.get().setToken(token); //保存最新的token return true; } catch (IOException e) { return false; } } } } ================================================ FILE: app/src/main/java/com/example/httpsender/kt/Activity.kt ================================================ package com.example.httpsender.kt import android.app.Activity import android.content.Intent import androidx.fragment.app.Fragment import kotlin.reflect.KClass /** * User: ljx * Date: 2020/5/15 * Time: 16:33 */ fun Activity.startActivity(clazz: KClass, block: (Intent.() -> Unit)? = null) { val intent = Intent(this, clazz.java).apply { block?.invoke(this) } startActivity(intent) } fun Fragment.startActivity(clazz: KClass, block: (Intent.() -> Unit)? = null) { val intent = Intent(activity, clazz.java).apply { block?.invoke(this) } startActivity(intent) } ================================================ FILE: app/src/main/java/com/example/httpsender/kt/KotlinExtensions.kt ================================================ package com.example.httpsender.kt import android.content.Context import android.net.ConnectivityManager import com.example.httpsender.AppHolder import com.example.httpsender.Tip import com.google.gson.JsonSyntaxException import kotlinx.coroutines.TimeoutCancellationException import rxhttp.wrapper.exception.HttpStatusCodeException import rxhttp.wrapper.exception.ParseException import java.net.ConnectException import java.net.SocketTimeoutException import java.net.UnknownHostException import java.util.concurrent.TimeoutException /** * User: ljx * Date: 2020-02-07 * Time: 21:04 */ fun Throwable.show() { errorMsg.show() } fun String.show() { Tip.show(this) } val Throwable.errorCode: Int get() = when (this) { is HttpStatusCodeException -> this.statusCode //Http状态码异常 is ParseException -> this.errorCode.toIntOrNull() ?: -1 //业务code异常 else -> -1 } val Throwable.errorMsg: String get() { return if (this is UnknownHostException) { //网络异常 if (!isNetworkConnected(AppHolder.getInstance())) "当前无网络,请检查你的网络设置" else "网络连接不可用,请稍后重试!" } else if ( this is SocketTimeoutException //okhttp全局设置超时 || this is TimeoutException //rxjava中的timeout方法超时 || this is TimeoutCancellationException //协程超时 ) { "连接超时,请稍后再试" } else if (this is ConnectException) { "网络不给力,请稍候重试!" } else if (this is HttpStatusCodeException) { //请求失败异常 "Http状态码异常 $message" } else if (this is JsonSyntaxException) { //请求成功,但Json语法异常,导致解析失败 "数据解析失败,请检查数据是否正确" } else if (this is ParseException) { // ParseException异常表明请求成功,但是数据不正确 this.message ?: errorCode //msg为空,显示code } else { message ?: this.toString() } } private fun isNetworkConnected(context: Context): Boolean { val mConnectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val mNetworkInfo = mConnectivityManager.activeNetworkInfo if (mNetworkInfo != null) { return mNetworkInfo.isAvailable } return false } ================================================ FILE: app/src/main/java/com/example/httpsender/kt/Uri.kt ================================================ package com.example.httpsender.kt import android.content.Context import android.net.Uri import android.provider.MediaStore import android.util.Log /** * User: ljx * Date: 2020/9/24 * Time: 15:33 */ fun Uri.dimQuery(context: Context, displayName: String) { context.contentResolver.query(this, null, "_display_name LIKE '%$displayName%'",null, null)?.use { while (it.moveToNext()) { val id = it.getString(it.getColumnIndex(MediaStore.MediaColumns._ID)) val name = it.getString(it.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)) val data = it.getString(it.getColumnIndex(MediaStore.MediaColumns.DATA)) //注意: 通过这种方式获取的文件size,在文件被手动删除后,读取到的是不准确的 val size = it.getString(it.getColumnIndex(MediaStore.MediaColumns.SIZE)) val dateAdded = it.getString(it.getColumnIndex(MediaStore.MediaColumns.DATE_ADDED)) val dateModified = it.getString(it.getColumnIndex(MediaStore.MediaColumns.DATE_MODIFIED)) Log.e("LJX", "id=$id size=$size name=$name data=$data dateAdded=$dateAdded dateModified=$dateModified") } } } fun Uri.dimDelete(context: Context, displayName: String) { val delete = context.contentResolver.delete(this, "_display_name LIKE '%$displayName%'", null) Log.e("LJX", "delete=$delete") } fun Uri.delete(context: Context, displayName: String) { val delete = context.contentResolver.delete(this, "_display_name=?", arrayOf(displayName)) Log.e("LJX", "delete=$delete") } ================================================ FILE: app/src/main/java/com/example/httpsender/param/GetEncryptParam.java ================================================ package com.example.httpsender.param; import android.graphics.Point; import java.io.IOException; import java.util.List; import java.util.Map; import okhttp3.HttpUrl; import rxhttp.wrapper.annotation.Param; import rxhttp.wrapper.entity.KeyValuePair; import rxhttp.wrapper.param.Method; import rxhttp.wrapper.param.NoBodyParam; /** * 加密get请求 * User: ljx * Date: 2019-09-12 * Time: 17:25 */ @Param(methodName = "getEncrypt") public class GetEncryptParam extends NoBodyParam { public GetEncryptParam(String url) { super(url, Method.GET); } @SafeVarargs public final GetEncryptParam test(List a, Map map, T[]... b) throws IOException, IllegalArgumentException { return this; } @Override public HttpUrl getHttpUrl() { StringBuilder paramsBuilder = new StringBuilder(); //存储加密后的参数 List queryParam = getQueryParam(); if (queryParam != null) { for (KeyValuePair pair : getQueryParam()) { //这里遍历所有添加的参数,可对参数进行加密操作 String key = pair.getKey(); Object value = pair.getValue(); //加密逻辑自己写 } } String simpleUrl = getSimpleUrl(); //拿到请求Url if (paramsBuilder.length() == 0) return HttpUrl.get(simpleUrl); return HttpUrl.get(simpleUrl + "?" + paramsBuilder); //将加密后的参数和url组拼成HttpUrl对象并返回 } } ================================================ FILE: app/src/main/java/com/example/httpsender/param/PostEncryptFormParam.java ================================================ package com.example.httpsender.param; import rxhttp.wrapper.annotation.Param; import rxhttp.wrapper.param.FormParam; import rxhttp.wrapper.param.Method; /** * 此类中自己声明的所有public方法(构造方法除外)都会在RxHttp$PostEncryptFormParam类中一一生成, * 并一一对应调用。如: RxHttp$PostEncryptFormParam.test(int,int)方法内部会调用本类的test(int,int)方法 * User: ljx * Date: 2019-09-11 * Time: 11:52 */ @Param(methodName = "postEncryptForm") public class PostEncryptFormParam extends FormParam { public PostEncryptFormParam(String url) { super(url, Method.POST); } public PostEncryptFormParam(String url, Method method) { super(url, method); } public PostEncryptFormParam test1(String s) { return this; } //此方法会在 public PostEncryptFormParam test2(long a, float b) { return this; } public int add(int a, int b) { return a + b; } } ================================================ FILE: app/src/main/java/com/example/httpsender/param/PostEncryptJsonParam.kt ================================================ package com.example.httpsender.param import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaType import okhttp3.RequestBody import okhttp3.RequestBody.Companion.toRequestBody import rxhttp.wrapper.annotation.Param import rxhttp.wrapper.param.JsonParam import rxhttp.wrapper.param.Method import rxhttp.wrapper.utils.GsonUtil /** * User: ljx * Date: 2019/1/25 * Time: 19:32 */ @Param(methodName = "postEncryptJson") class PostEncryptJsonParam(url: String) : JsonParam(url, Method.POST) { private var MEDIA_TYPE_JSON: MediaType = "application/json; charset=utf-8".toMediaType() /** * @return 根据自己的业务需求返回对应的RequestBody */ override fun getRequestBody(): RequestBody { //我们要发送Post请求,参数以加密后的json形式发出 //第一步,将参数转换为Json字符串 val json = if (bodyParam == null) "" else GsonUtil.toJson(bodyParam) //第二步,加密 val encryptByte = encrypt(json, "RxHttp") //第三部,创建RequestBody并返回 return encryptByte!!.toRequestBody(MEDIA_TYPE_JSON) } /** * @param content 要加密的字符串 * @param password 密码 * @return 加密后的字节数组 */ private fun encrypt(content: String, password: String): ByteArray? { //加码代码省略 return null } } ================================================ FILE: app/src/main/java/com/example/httpsender/param/PostEncryptJsonParam1.java ================================================ package com.example.httpsender.param; import okhttp3.RequestBody; import rxhttp.wrapper.annotation.Param; import rxhttp.wrapper.param.AbstractBodyParam; import rxhttp.wrapper.param.Method; /** * User: ljx * Date: 2019-09-11 * Time: 11:52 */ @Param(methodName = "postEncryptJson1") public class PostEncryptJsonParam1 extends AbstractBodyParam { public PostEncryptJsonParam1(String url) { super(url, Method.POST); } @Override public RequestBody getRequestBody() { return null; } public void test() { } @Override public PostEncryptJsonParam1 add(String key, Object value) { return null; } } ================================================ FILE: app/src/main/java/com/example/httpsender/parser/Android10DownloadFactory.kt ================================================ package com.example.httpsender.parser import android.content.ContentValues import android.content.Context import android.net.Uri import android.os.Build import android.os.Environment import android.provider.MediaStore import androidx.annotation.RequiresApi import okhttp3.Response import rxhttp.wrapper.callback.UriFactory import rxhttp.wrapper.utils.query import java.io.File /** * User: ljx * Date: 2020/9/11 * Time: 17:43 * * @param context Context * @param filename 文件名 * @param relativePath 文件相对路径,可取值: * [Environment.DIRECTORY_DOWNLOADS] * [Environment.DIRECTORY_DCIM] * [Environment.DIRECTORY_PICTURES] * [Environment.DIRECTORY_MUSIC] * [Environment.DIRECTORY_MOVIES] * [Environment.DIRECTORY_DOCUMENTS] * ... */ class Android10DownloadFactory @JvmOverloads constructor( context: Context, private val filename: String, private val relativePath: String = Environment.DIRECTORY_DOWNLOADS ) : UriFactory(context) { /** * [MediaStore.Files.getContentUri] * [MediaStore.Downloads.EXTERNAL_CONTENT_URI] * [MediaStore.Audio.Media.EXTERNAL_CONTENT_URI] * [MediaStore.Video.Media.EXTERNAL_CONTENT_URI] * [MediaStore.Images.Media.EXTERNAL_CONTENT_URI] */ @RequiresApi(Build.VERSION_CODES.Q) fun getInsertUri() = MediaStore.Downloads.EXTERNAL_CONTENT_URI override fun query(): Uri? { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { getInsertUri().query(context, filename, relativePath) } else { val file = File("${Environment.getExternalStorageDirectory()}/$relativePath/$filename") Uri.fromFile(file) } } override fun insert(response: Response): Uri { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { val uri = getInsertUri().query(context, filename, relativePath) /* * 通过查找,要插入的Uri已经存在,就无需再次插入 * 否则会出现新插入的文件,文件名被系统更改的现象,因为insert不会执行覆盖操作 */ if (uri != null) return uri ContentValues().run { put(MediaStore.MediaColumns.RELATIVE_PATH, relativePath) //下载到指定目录 put(MediaStore.MediaColumns.DISPLAY_NAME, filename) //文件名 //取contentType响应头作为文件类型 put(MediaStore.MediaColumns.MIME_TYPE, response.body?.contentType().toString()) context.contentResolver.insert(getInsertUri(), this) //当相同路径下的文件,在文件管理器中被手动删除时,就会插入失败 } ?: throw NullPointerException("Uri insert failed. Try changing filename") } else { val file = File("${Environment.getExternalStorageDirectory()}/$relativePath/$filename") Uri.fromFile(file) } } } ================================================ FILE: app/src/main/java/com/example/httpsender/parser/ResponseParser.kt ================================================ package com.example.httpsender.parser import com.example.httpsender.entity.PageList import com.example.httpsender.entity.Response import rxhttp.wrapper.annotation.Parser import rxhttp.wrapper.exception.ParseException import rxhttp.wrapper.parse.TypeParser import rxhttp.wrapper.utils.convertTo import java.io.IOException import java.lang.reflect.Type /** * 输入T,输出T,并对code统一判断 * User: ljx * Date: 2018/10/23 * Time: 13:49 * * 如果使用协程发送请求,wrappers属性可不设置,设置了也无效 */ @Parser(name = "Response", wrappers = [PageList::class]) open class ResponseParser : TypeParser { /** * 此构造方法可适用任意Class对象,但更多用于带泛型的Class对象,如:List>> * * 如Java环境中调用 * toObservable(new ResponseParser>>(){}) * 等价于kotlin环境下的 * toObservableResponse>>() * * 注:此构造方法一定要用protected关键字修饰,否则调用此构造方法将拿不到泛型类型 */ protected constructor() : super() /** * 该解析器会生成以下系列方法,前3个kotlin环境调用,后4个Java环境调用,所有方法内部均会调用本构造方法 * toFlowResponse() * toAwaitResponse() * toObservableResponse() * toObservableResponse(Type) * toObservableResponse(Class) * toObservableResponseList(Class) * toObservableResponsePageList(Class) * * Flow/Await下 toXxxResponse> 等同于 toObservableResponsePageList(Class) */ constructor(type: Type) : super(type) @Throws(IOException::class) override fun onParse(response: okhttp3.Response): T { val data: Response = response.convertTo(Response::class, *types) var t = data.data //获取data字段 if (t == null && types[0] === String::class.java) { /* * 考虑到有些时候服务端会返回:{"errorCode":0,"errorMsg":"关注成功"} 类似没有data的数据 * 此时code正确,但是data字段为空,直接返回data的话,会报空指针错误, * 所以,判断泛型为String类型时,重新赋值,并确保赋值不为null */ @Suppress("UNCHECKED_CAST") t = data.msg as T } if (data.code != 0 || t == null) { //code不等于0,说明数据不正确,抛出异常 throw ParseException(data.code.toString(), data.msg, response) } return t } } ================================================ FILE: app/src/main/java/com/example/httpsender/parser/java/DoubleTypeParser.java ================================================ package com.example.httpsender.parser.java; import android.util.Pair; import com.example.httpsender.entity.Response; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.lang.reflect.Type; import rxhttp.wrapper.entity.ParameterizedTypeImpl; import rxhttp.wrapper.exception.ParseException; import rxhttp.wrapper.parse.TypeParser; import rxhttp.wrapper.utils.Converter; /** * 大于等于2个泛型的解析器,可以参考此类 * User: ljx * Date: 2018/10/23 * Time: 13:49 */ //@Parser(name = "DoubleType") public class DoubleTypeParser extends TypeParser> { protected DoubleTypeParser() { super(); } public DoubleTypeParser(Type fType, Type sType) { super(fType, sType); } @Override public Pair onParse(@NotNull okhttp3.Response response) throws IOException { Type pairType = ParameterizedTypeImpl.getParameterized(Pair.class, types); Response> data = Converter.convertTo(response, Response.class, pairType); Pair t = data.getData(); //获取data字段 if (data.getCode() != 0 || t == null) {//code不等于0,说明数据不正确,抛出异常 throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response); } return t; } } ================================================ FILE: app/src/main/java/com/example/httpsender/parser/java/ResponseParser.java ================================================ package com.example.httpsender.parser.java; import com.example.httpsender.entity.Response; import org.jetbrains.annotations.NotNull; import java.io.IOException; import java.lang.reflect.Type; import rxhttp.wrapper.exception.ParseException; import rxhttp.wrapper.parse.TypeParser; import rxhttp.wrapper.utils.Converter; /** * 输入T,输出T,并对code统一判断 * User: ljx * Date: 2018/10/23 * Time: 13:49 */ //@Parser(name = "Response", wrappers = {PageList.class}) public class ResponseParser extends TypeParser { /** * 此构造方法可适用任意Class对象,但更多用于带泛型的Class对象,如:List>> *

* 如Java环境中调用 * toObservable(new ResponseParser>>(){}) * 等价与kotlin环境下的 * toObservableResponse>>() *

* 注:此构造方法一定要用protected关键字修饰,否则调用此构造方法将拿不到泛型类型 */ protected ResponseParser() { super(); } /** * 该解析器会生成以下系列方法,前3个kotlin环境调用,后4个Java环境调用,所有方法内部均会调用本构造方法 * toFlowResponse() * toAwaitResponse() * toObservableResponse() * toObservableResponse(Type) * toObservableResponse(Class) * toObservableResponseList(Class) * toObservableResponsePageList(Class) *

* Flow/Await下 toXxxResponse> 等价与 toObservableResponsePageList(Class) */ public ResponseParser(Type type) { super(type); } @SuppressWarnings("unchecked") @Override public T onParse(@NotNull okhttp3.Response response) throws IOException { Response data = Converter.convertTo(response, Response.class, types); T t = data.getData(); //获取data字段 if (t == null && types[0] == String.class) { /* * 考虑到有些时候服务端会返回:{"errorCode":0,"errorMsg":"关注成功"} 类似没有data的数据 * 此时code正确,但是data字段为空,直接返回data的话,会报空指针错误, * 所以,判断泛型为String类型时,重新赋值,并确保赋值不为null */ t = (T) data.getMsg(); } if (data.getCode() != 0 || t == null) {//code不等于0,说明数据不正确,抛出异常 throw new ParseException(String.valueOf(data.getCode()), data.getMsg(), response); } return t; } } ================================================ FILE: app/src/main/java/com/example/httpsender/utils/Preferences.java ================================================ package com.example.httpsender.utils; import android.content.SharedPreferences; import android.preference.PreferenceManager; import com.example.httpsender.AppHolder; /** * User: hqs * Date: 2016/5/10 * Time: 19:07 */ public class Preferences { private static SharedPreferences sharedPreferences; private static SharedPreferences.Editor editor; private Preferences() { } private static SharedPreferences getInstance() { if (sharedPreferences == null) { synchronized (Preferences.class) { if (sharedPreferences == null) { sharedPreferences = PreferenceManager.getDefaultSharedPreferences(AppHolder.getInstance()); } } } return sharedPreferences; } public static SharedPreferences.Editor getEditor() { if (editor == null) { editor = getInstance().edit(); } return editor; } public static String getValue(String key, String defaultValue) { return getInstance().getString(key, defaultValue); } public static void setValue(String key, String value) { getEditor().putString(key, value).commit(); } public static int getValue(String key, int defaultValue) { return getInstance().getInt(key, defaultValue); } public static void setValue(String key, int value) { getEditor().putInt(key, value).commit(); } public static void setFloat(String key, float value) { getEditor().putFloat(key, value).commit(); } public static float getFloat(String key, float defaultValue) { return getInstance().getFloat(key, defaultValue); } public static boolean getValue(String key, boolean defaultValue) { return getInstance().getBoolean(key, defaultValue); } public static void setValue(String key, boolean value) { getEditor().putBoolean(key, value).commit(); } public static long getValue(String key, long defaultValue) { return getInstance().getLong(key, defaultValue); } public static void setValue(String key, long value) { getEditor().putLong(key, value).commit(); } } ================================================ FILE: app/src/main/java/com/example/httpsender/view/ScaleTransitionPagerTitleView.java ================================================ package com.example.httpsender.view; import android.content.Context; import net.lucode.hackware.magicindicator.buildins.commonnavigator.titles.ColorTransitionPagerTitleView; /** * 带颜色渐变和缩放的指示器标题 * 博客: http://hackware.lucode.net * Created by hackware on 2016/6/26. */ public class ScaleTransitionPagerTitleView extends ColorTransitionPagerTitleView { private float mMinScale = 0.75f; public ScaleTransitionPagerTitleView(Context context) { super(context); } @Override public void onEnter(int index, int totalCount, float enterPercent, boolean leftToRight) { super.onEnter(index, totalCount, enterPercent, leftToRight); // 实现颜色渐变 setScaleX(mMinScale + (1.0f - mMinScale) * enterPercent); setScaleY(mMinScale + (1.0f - mMinScale) * enterPercent); } @Override public void onLeave(int index, int totalCount, float leavePercent, boolean leftToRight) { super.onLeave(index, totalCount, leavePercent, leftToRight); // 实现颜色渐变 setScaleX(1.0f + (mMinScale - 1.0f) * leavePercent); setScaleY(1.0f + (mMinScale - 1.0f) * leavePercent); } public float getMinScale() { return mMinScale; } public void setMinScale(float minScale) { mMinScale = minScale; } } ================================================ FILE: app/src/main/java/com/example/httpsender/vm/MultiTaskAwaitDownloader.kt ================================================ package com.example.httpsender.vm import androidx.lifecycle.MutableLiveData import com.example.httpsender.Tip import com.example.httpsender.entity.DownloadTask import com.example.httpsender.utils.Preferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import okhttp3.internal.http2.StreamResetException import okio.ByteString.Companion.encodeUtf8 import rxhttp.RxHttpPlugins import rxhttp.awaitResult import rxhttp.wrapper.param.RxHttp import java.io.File import java.util.* /** * User: ljx * Date: 2020/7/12 * Time: 18:00 */ object MultiTaskAwaitDownloader { const val IDLE = 0 //未开始,闲置状态 const val WAITING = 1 //等待中状态 const val DOWNLOADING = 2 //下载中 const val PAUSED = 3 //已暂停 const val COMPLETED = 4 //已完成 const val FAIL = 5 //下载失败 const val CANCEL = 6 //取消状态,等待时被取消 private const val MAX_TASK_COUNT = 3 //最大并发数 @JvmStatic val liveTask = MutableLiveData() //用于刷新UI @JvmStatic val allTask = ArrayList() //所有下载任务 private val waitTask = LinkedList() //等待下载的任务 private val downloadingTask = LinkedList() //下载中的任务 //记录每个文件的总大小,key为文件url private val lengthMap = HashMap() @JvmStatic fun addTasks(tasks: ArrayList) { val allTaskList = allTask tasks.forEach { if (!allTaskList.contains(it)) { val md5Key = it.url.encodeUtf8().md5().hex() val length = Preferences.getValue(md5Key, -1L) if (length != -1L) { it.totalSize = length it.currentSize = File(it.localPath).length() it.progress = it.currentSize * 1.0f / it.totalSize lengthMap[it.url] = length if (it.currentSize > 0) { it.state = PAUSED } if (it.totalSize == it.currentSize) { //如果当前size等于总size,则任务文件下载完成,注意: 这个判断不是100%准确,最好能对文件做md5校验 it.state = COMPLETED } } allTaskList.add(it) } } } //开始下载所有任务 @JvmStatic fun startAllDownloadTask() { val allTaskList = allTask allTaskList.forEach { if (it.state != COMPLETED && it.state != DOWNLOADING) { download(it) } } } @JvmStatic fun download(task: DownloadTask) { if (downloadingTask.size >= MAX_TASK_COUNT) { //超过最大下载数,添加进等待队列 task.state = WAITING updateTask(task) waitTask.offer(task) return } task.state = DOWNLOADING updateTask(task) downloadingTask.add(task) //如果想使用RxJava或Await下载,更改以下代码即可 CoroutineScope(Dispatchers.Main).launch { // RxHttp.get(task.url) // .tag(task.url) //记录tag,手动取消下载时用到 // .toDownloadAwait(task.localPath, true) { // //下载进度回调,0-100,仅在进度有更新时才会回调 // task.progress = it.progress //当前进度 0-100 // task.currentSize = it.currentSize //当前已下载的字节大小 // task.totalSize = it.totalSize //要下载的总字节大小 // updateTask(task) // val key = task.url // val length = lengthMap[key] // if (length != task.totalSize) { // //服务器返回的文件总大小与本地的不一致,则更新 // lengthMap[key] = task.totalSize // saveTotalSize(lengthMap) // } // }.awaitResult { // Tip.show("下载成功") // task.state = COMPLETED // }.onFailure { // //手动取消下载时,会收到StreamResetException异常,不做任何处理 // if (it !is StreamResetException) { // Tip.show("下载失败") // task.state = FAIL // } // } //下载结束,不管任务成功还是失败,如果还有在等待的任务,都开启下一个任务 updateTask(task) downloadingTask.remove(task) waitTask.poll()?.let { download(it) } } } private fun saveTotalSize(map: HashMap) { val editor = Preferences.getEditor() for ((key, value) in map) { val md5Key = key.encodeUtf8().md5().hex() editor.putLong(md5Key, value) } editor.commit() } //关闭所有任务 @JvmStatic fun cancelAllTask() { var iterator = waitTask.iterator() while (iterator.hasNext()) { val task = iterator.next() task.state = CANCEL iterator.remove() updateTask(task) } iterator = downloadingTask.iterator() while (iterator.hasNext()) { val task = iterator.next() iterator.remove() RxHttpPlugins.cancelAll(task.url) task.state = CANCEL updateTask(task) } } //等待中->取消下载 @JvmStatic fun removeWaitTask(task: DownloadTask) { waitTask.remove(task) task.state = CANCEL updateTask(task) } //暂停下载 @JvmStatic fun pauseTask(task: DownloadTask) { //根据tag取消下载 RxHttpPlugins.cancelAll(task.url) task.state = PAUSED updateTask(task) } @JvmStatic fun haveTaskExecuting(): Boolean { return waitTask.size > 0 || downloadingTask.size > 0 } //发送通知,更新UI private fun updateTask(task: DownloadTask) { liveTask.value = task } } ================================================ FILE: app/src/main/java/com/example/httpsender/vm/MultiTaskDownloader.kt ================================================ package com.example.httpsender.vm import android.annotation.SuppressLint import androidx.lifecycle.MutableLiveData import com.example.httpsender.Tip import com.example.httpsender.entity.DownloadTask import com.example.httpsender.utils.Preferences import okhttp3.internal.http2.StreamResetException import okio.ByteString.Companion.encodeUtf8 import rxhttp.RxHttpPlugins import rxhttp.wrapper.param.RxHttp import java.io.File import java.util.* /** * User: ljx * Date: 2020/7/12 * Time: 18:00 */ object MultiTaskDownloader { const val IDLE = 0 //未开始,闲置状态 const val WAITING = 1 //等待中状态 const val DOWNLOADING = 2 //下载中 const val PAUSED = 3 //已暂停 const val COMPLETED = 4 //已完成 const val FAIL = 5 //下载失败 const val CANCEL = 6 //取消状态,等待时被取消 private const val MAX_TASK_COUNT = 3 //最大并发数 @JvmStatic val liveTask = MutableLiveData() //用于刷新UI @JvmStatic val allTask = ArrayList() //所有下载任务 private val waitTask = LinkedList() //等待下载的任务 private val downloadingTask = LinkedList() //下载中的任务 //记录每个文件的总大小,key为文件url private val lengthMap = HashMap() @JvmStatic fun addTasks(tasks: ArrayList) { val allTaskList = allTask tasks.forEach { if (!allTaskList.contains(it)) { val md5Key = it.url.encodeUtf8().md5().hex() val length = Preferences.getValue(md5Key, -1L) if (length != -1L) { it.totalSize = length it.currentSize = File(it.localPath).length() it.progress = it.currentSize * 1.0f / it.totalSize lengthMap[it.url] = length if (it.currentSize > 0) { it.state = PAUSED } if (it.totalSize == it.currentSize) { //如果当前size等于总size,则任务文件下载完成,注意: 这个判断不是100%准确,最好能对文件做md5校验 it.state = COMPLETED } } allTaskList.add(it) } } } //开始下载所有任务 @JvmStatic fun startAllDownloadTask() { val allTaskList = allTask allTaskList.forEach { if (it.state != COMPLETED && it.state != DOWNLOADING) { download(it) } } } @SuppressLint("CheckResult") @JvmStatic fun download(task: DownloadTask) { if (downloadingTask.size >= MAX_TASK_COUNT) { //超过最大下载数,添加进等待队列 task.state = WAITING updateTask(task) waitTask.offer(task) return } task.state = DOWNLOADING updateTask(task) downloadingTask.add(task) //如果想使用Await或Flow下载,更改以下代码即可 RxHttp.get(task.url) .tag(task.url) //记录tag,手动取消下载时用到 .toDownloadObservable(task.localPath, true) .onMainProgress { //下载进度回调,0-100,仅在进度有更新时才会回调 task.speed = it.speed task.remainingTime = it.calculateRemainingTime() task.progress = it.fraction //当前进度 [0.0, 1.0] task.currentSize = it.currentSize //当前已下载的字节大小 task.totalSize = it.totalSize //要下载的总字节大小 updateTask(task) val key = task.url val length = lengthMap[key] if (length != task.totalSize) { //服务器返回的文件总大小与本地的不一致,则更新 lengthMap[key] = task.totalSize saveTotalSize(lengthMap) } } .doFinally { updateTask(task) //不管任务成功还是失败,如果还有在等待的任务,都开启下一个任务 downloadingTask.remove(task) waitTask.poll()?.let { download(it) } } .subscribe({ Tip.show("下载完成") task.state = COMPLETED }, { //手动取消下载时,会收到StreamResetException异常,不做任何处理 if (it !is StreamResetException){ Tip.show("下载失败") task.state = FAIL } }) } private fun saveTotalSize(map: HashMap) { val editor = Preferences.getEditor() for ((key, value) in map) { val md5Key = key.encodeUtf8().md5().hex() editor.putLong(md5Key, value) } editor.commit() } //关闭所有任务 @JvmStatic fun cancelAllTask() { var iterator = waitTask.iterator() while (iterator.hasNext()) { val task = iterator.next() task.state = CANCEL iterator.remove() updateTask(task) } iterator = downloadingTask.iterator() while (iterator.hasNext()) { val task = iterator.next() iterator.remove() RxHttpPlugins.cancelAll(task.url) task.state = CANCEL updateTask(task) } } //等待中->取消下载 @JvmStatic fun removeWaitTask(task: DownloadTask) { waitTask.remove(task) task.state = CANCEL updateTask(task) } //暂停下载 @JvmStatic fun pauseTask(task: DownloadTask) { //根据tag取消下载 RxHttpPlugins.cancelAll(task.url) task.state = PAUSED task.speed = 0 task.remainingTime = -1 updateTask(task) } @JvmStatic fun haveTaskExecuting(): Boolean { return waitTask.size > 0 || downloadingTask.size > 0 } //发送通知,更新UI private fun updateTask(task: DownloadTask) { liveTask.value = task } } ================================================ FILE: app/src/main/java/com/example/httpsender/vm/MultiTaskFlowDownloader.kt ================================================ package com.example.httpsender.vm import androidx.lifecycle.MutableLiveData import com.example.httpsender.Tip import com.example.httpsender.entity.DownloadTask import com.example.httpsender.utils.Preferences import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.catch import kotlinx.coroutines.launch import okhttp3.internal.http2.StreamResetException import okio.ByteString.Companion.encodeUtf8 import rxhttp.RxHttpPlugins import rxhttp.toDownloadFlow import rxhttp.wrapper.param.RxHttp import java.io.File import java.util.* /** * User: ljx * Date: 2020/7/12 * Time: 18:00 */ object MultiTaskFlowDownloader { const val IDLE = 0 //未开始,闲置状态 const val WAITING = 1 //等待中状态 const val DOWNLOADING = 2 //下载中 const val PAUSED = 3 //已暂停 const val COMPLETED = 4 //已完成 const val FAIL = 5 //下载失败 const val CANCEL = 6 //取消状态,等待时被取消 private const val MAX_TASK_COUNT = 3 //最大并发数 @JvmStatic val liveTask = MutableLiveData() //用于刷新UI @JvmStatic val allTask = ArrayList() //所有下载任务 private val waitTask = LinkedList() //等待下载的任务 private val downloadingTask = LinkedList() //下载中的任务 //记录每个文件的总大小,key为文件url private val lengthMap = HashMap() @JvmStatic fun addTasks(tasks: ArrayList) { val allTaskList = allTask tasks.forEach { if (!allTaskList.contains(it)) { val md5Key = it.url.encodeUtf8().md5().hex() val length = Preferences.getValue(md5Key, -1L) if (length != -1L) { it.totalSize = length it.currentSize = File(it.localPath).length() it.progress = it.currentSize * 1.0f / it.totalSize lengthMap[it.url] = length if (it.currentSize > 0) { it.state = PAUSED } if (it.totalSize == it.currentSize) { //如果当前size等于总size,则任务文件下载完成,注意: 这个判断不是100%准确,最好能对文件做md5校验 it.state = COMPLETED } } allTaskList.add(it) } } } //开始下载所有任务 @JvmStatic fun startAllDownloadTask() { val allTaskList = allTask allTaskList.forEach { if (it.state != COMPLETED && it.state != DOWNLOADING) { download(it) } } } @JvmStatic fun download(task: DownloadTask) { if (downloadingTask.size >= MAX_TASK_COUNT) { //超过最大下载数,添加进等待队列 task.state = WAITING updateTask(task) waitTask.offer(task) return } task.state = DOWNLOADING updateTask(task) downloadingTask.add(task) //如果想使用RxJava或Flow下载,更改以下代码即可 CoroutineScope(Dispatchers.Main).launch { RxHttp.get(task.url) .tag(task.url) //记录tag,手动取消下载时用到 .toDownloadFlow(task.localPath, true) .onProgress{ //下载进度回调,0-100,仅在进度有更新时才会回调 task.progress = it.fraction //当前进度 [0.0, 1.0] task.currentSize = it.currentSize //当前已下载的字节大小 task.totalSize = it.totalSize //要下载的总字节大小 updateTask(task) val key = task.url val length = lengthMap[key] if (length != task.totalSize) { //服务器返回的文件总大小与本地的不一致,则更新 lengthMap[key] = task.totalSize saveTotalSize(lengthMap) } }.catch { //手动取消下载时,会收到StreamResetException异常,不做任何处理 if (it !is StreamResetException) { Tip.show("下载失败") task.state = FAIL } }.collect { Tip.show("下载成功") task.state = COMPLETED } //下载结束,不管任务成功还是失败,如果还有在等待的任务,都开启下一个任务 updateTask(task) downloadingTask.remove(task) waitTask.poll()?.let { download(it) } } } private fun saveTotalSize(map: HashMap) { val editor = Preferences.getEditor() for ((key, value) in map) { val md5Key = key.encodeUtf8().md5().hex() editor.putLong(md5Key, value) } editor.commit() } //关闭所有任务 @JvmStatic fun cancelAllTask() { var iterator = waitTask.iterator() while (iterator.hasNext()) { val task = iterator.next() task.state = CANCEL iterator.remove() updateTask(task) } iterator = downloadingTask.iterator() while (iterator.hasNext()) { val task = iterator.next() iterator.remove() RxHttpPlugins.cancelAll(task.url) task.state = CANCEL updateTask(task) } } //等待中->取消下载 @JvmStatic fun removeWaitTask(task: DownloadTask) { waitTask.remove(task) task.state = CANCEL updateTask(task) } //暂停下载 @JvmStatic fun pauseTask(task: DownloadTask) { //根据tag取消下载 RxHttpPlugins.cancelAll(task.url) task.state = PAUSED updateTask(task) } @JvmStatic fun haveTaskExecuting(): Boolean { return waitTask.size > 0 || downloadingTask.size > 0 } //发送通知,更新UI private fun updateTask(task: DownloadTask) { liveTask.value = task } } ================================================ FILE: app/src/main/res/drawable/ic_launcher_background.xml ================================================ ================================================ FILE: app/src/main/res/drawable-v24/ic_launcher_foreground.xml ================================================ ================================================ FILE: app/src/main/res/layout/await_fragment.xml ================================================