From 847d32650c55c24613e78c6544f76904ddc14630 Mon Sep 17 00:00:00 2001 From: drake Date: Tue, 1 Aug 2023 15:20:40 +0800 Subject: [PATCH] =?UTF-8?q?doc:=20=E9=87=8D=E6=9E=84=E5=BC=80=E5=8F=91?= =?UTF-8?q?=E6=96=87=E6=A1=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 37 +-- docs/api/styles/jetbrains-mono.css | 18 +- docs/auto-dialog.md | 73 ++--- docs/auto-page.md | 55 ---- docs/auto-pull.md | 38 +++ docs/auto-refresh.md | 40 ++- docs/auto-state.md | 45 ++- docs/cache.md | 64 ++-- docs/callback.md | 3 +- docs/cancel.md | 44 +-- docs/config.md | 152 ++++----- docs/convert-special.md | 129 -------- docs/converter-customize.md | 81 +++++ docs/converter-struct.md | 150 +++++++++ docs/converter.md | 281 ++-------------- docs/cookie.md | 14 +- docs/coroutine-request.md | 17 +- docs/css/extra.css | 303 ++---------------- docs/debounce.md | 36 +-- docs/default-response.md | 50 --- docs/download-file.md | 48 ++- docs/error-default.md | 29 -- docs/error-global.md | 41 ++- docs/error-single.md | 23 +- docs/{error-exception.md => error-throws.md} | 80 +++-- docs/error-tip.md | 80 +++-- docs/error.md | 25 ++ docs/exception-track.md | 28 -- docs/fastest.md | 48 ++- docs/https.md | 14 +- docs/img/book-open.svg | 1 + docs/img/code-preview.png | Bin 0 -> 26782 bytes docs/img/preview.png | Bin 0 -> 189960 bytes docs/index.md | 95 +++--- docs/interceptor.md | 31 +- docs/interval.md | 11 +- docs/issues.md | 27 +- docs/kotlin-serialization.md | 39 ++- docs/log-notice.md | 41 ++- docs/log-recorder.md | 54 ++-- docs/model-generate.md | 11 +- docs/okhttp-client.md | 62 ++-- docs/practice.md | 11 - docs/progress.md | 15 +- docs/read-cache.md | 59 ---- docs/repeat-request.md | 21 +- docs/request.md | 105 +++--- docs/scope.md | 48 +-- docs/switch-thread.md | 50 --- docs/sync-request.md | 34 +- docs/tag.md | 17 +- docs/thread.md | 49 +++ docs/timing.md | 9 +- docs/track.md | 24 ++ docs/updates.md | 2 +- docs/upload-file.md | 6 +- docs/view-model.md | 42 ++- mkdocs.yml | 96 ++++-- sample/proguard-rules.pro | 2 +- .../com/drake/net/sample/constants/Api.kt | 1 + .../drake/net/sample/mock/MockDispatcher.kt | 17 +- .../ui/fragment/EditDebounceFragment.kt | 7 +- .../com/drake/net/sample/vm/UserViewModel.kt | 19 +- sample/src/main/res/menu/menu_main.xml | 2 +- sample/src/main/res/navigation/nav_main.xml | 2 +- 65 files changed, 1219 insertions(+), 1837 deletions(-) delete mode 100644 docs/auto-page.md create mode 100644 docs/auto-pull.md delete mode 100644 docs/convert-special.md create mode 100644 docs/converter-customize.md create mode 100644 docs/converter-struct.md delete mode 100644 docs/default-response.md delete mode 100644 docs/error-default.md rename docs/{error-exception.md => error-throws.md} (53%) create mode 100644 docs/error.md delete mode 100644 docs/exception-track.md create mode 100644 docs/img/book-open.svg create mode 100644 docs/img/code-preview.png create mode 100644 docs/img/preview.png delete mode 100644 docs/practice.md delete mode 100644 docs/read-cache.md delete mode 100644 docs/switch-thread.md create mode 100644 docs/thread.md create mode 100644 docs/track.md diff --git a/README.md b/README.md index a809a12a0..a091e8488 100644 --- a/README.md +++ b/README.md @@ -8,31 +8,31 @@ | 下载体验

-

+

- + - +

-

+


-Android上可能是最强的网络框架, 基于[OkHttp](https://github.com/square/okhttp)/协程的非侵入式框架(不影响原有功能). 学习成本低/使用简单, 一行代码发起网络请求, 甚至无需初始化 +Net是基于[OkHttp](https://github.com/square/okhttp)/协程的非侵入式框架(可使用所有Api), 可升级OkHttp版本保持网络安全
-欢迎将本项目文档/注释进行国际化翻译, 感谢您的支持!
Welcome to international translation of this project's documents/notes, thank you for your support! [Net 1.x](https://github.com/liangjingkanji/Net/tree/1.x) 版本使用RxJava实现
[Net 2.x](https://github.com/liangjingkanji/Net/tree/2.x) 版本使用协程实现
+[Net-okhttp3](https://github.com/liangjingkanji/Net-okhttp3) Net3.x的Android低版本兼容库
[Net 3.x](https://github.com/liangjingkanji/Net/) 版本使用协程实现, 可自定义OkHttp版本
@@ -44,9 +44,9 @@ Welcome to international translation of this project's documents/notes, thank yo - [x] 专为Android而生 - [x] OkHttp最佳实践 - [x] 使用高性能Okio -- [x] 支持OkHttp所有功能/组件 +- [x] 支持OkHttp所有Api - [x] 随时升级OkHttp版本保证网络安全性 -- [x] 优秀的源码/注释/文档/示例 +- [x] 详细文档/低学习成本 - [x] 永远保持社区维护 ## 主要功能 @@ -83,20 +83,7 @@ Welcome to international translation of this project's documents/notes, thank yo ## 安装 -添加远程仓库根据创建项目的 Android Studio 版本有所不同 - -Android Studio Arctic Fox以下创建的项目 在项目根目录的 build.gradle 添加仓库 - -```groovy -allprojects { - repositories { - // ... - maven { url 'https://jitpack.io' } - } -} -``` - -Android Studio Arctic Fox以上创建的项目 在项目根目录的 settings.gradle 添加仓库 +Project 的 settings.gradle 添加仓库 ```kotlin dependencyResolutionManagement { @@ -107,15 +94,15 @@ dependencyResolutionManagement { } ``` -然后在 module 的 build.gradle 添加依赖框架 +Module 的 build.gradle 添加依赖框架 ```groovy implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0" // 协程(版本自定) implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0' -implementation 'com.squareup.okhttp3:okhttp:4.10.0' // 要求OkHttp4以上 +implementation 'com.squareup.okhttp3:okhttp:4.11.0' // 要求OkHttp4以上 implementation 'com.github.liangjingkanji:Net:3.6.0' ``` -如果你是在 Android 5 (API level 21)以下开发, 要求使用OkHttp3.x请使用: [Net-okhttp3](https://github.com/liangjingkanji/Net-okhttp3) +如果在 Android 5 (API level 21)以下开发, 请使用 [Net-okhttp3](https://github.com/liangjingkanji/Net-okhttp3)
## Contribute diff --git a/docs/api/styles/jetbrains-mono.css b/docs/api/styles/jetbrains-mono.css index 4592bb3a5..99c4b761a 100644 --- a/docs/api/styles/jetbrains-mono.css +++ b/docs/api/styles/jetbrains-mono.css @@ -1,33 +1,31 @@ @font-face{ font-family: 'JetBrains Mono'; - src: local('JetBrains Mono'), - url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Regular.woff2') format('woff2'), - url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Regular.ttf') format('truetype'); + src: local('Iosevka Curly Medium'), + url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/iosevka-curly/iosevka-curly-medium.woff2') format('woff2'); font-display: swap; font-weight: normal; font-style: normal; } @font-face{ font-family: 'JetBrains Mono'; - src: local('JetBrains Mono'), - url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Bold.woff2') format('woff2'), - url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/ttf/JetBrainsMono-Bold.ttf') format('truetype'); + src: local('Iosevka Curly Bold'), + url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/iosevka-curly/iosevka-curly-bold.woff2') format('woff2'); font-display: swap; font-weight: bold; font-style: normal; } @font-face{ font-family: 'HYZhengYuan'; - src: local('HYZhengYuan'), - url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYZhengYuan.ttf') format('truetype'); + src: local('HYYouYuan-55W'), + url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYYouYuan/HYYouYuan-55W.ttf') format('truetype'); font-display: swap; font-weight: normal; font-style: normal; } @font-face{ font-family: 'HYZhengYuan'; - src: local('HYZhengYuan-75W'), - url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYZhengYuan-75W.ttf') format('truetype'); + src: local('HYYouYuan-75W'), + url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYYouYuan/HYYouYuan-75W.ttf') format('truetype'); font-display: swap; font-weight: bold; font-style: normal; diff --git a/docs/auto-dialog.md b/docs/auto-dialog.md index b4ed29007..cbab287eb 100644 --- a/docs/auto-dialog.md +++ b/docs/auto-dialog.md @@ -1,9 +1,9 @@ -Net支持发起请求的时候自动弹出和关闭对话框(Loading Dialog) +Net支持发起请求开始时显示加载框, 请求结束时隐藏加载框(无论成败) -## 自动显示加载框 +## 自动显示 -只需要使用`scopeDialog`作用域即可. -```kotlin + +```kotlin hl_lines="1" scopeDialog { tvFragment.text = Post("dialog") { param("u_name", "drake") // 请求参数 @@ -14,14 +14,14 @@ scopeDialog {
-加载框默认使用的是Android原生加载框(MaterialDesign Dialog), 当然也提供参数传入指定每个请求的对话框 +默认是原生加载框(MaterialDesign Dialog), 可自定义 -## 指定单例加载框 +## 单例自定义 -就是仅针对当前网络请求指定加载框 +指定当前请求加载框 ```kotlin val dialog = BubbleDialog(requireActivity(), "加载中") @@ -36,61 +36,46 @@ scopeDialog(dialog) { -> 这里使用的iOS风格对话框: [BubbleDialog](https://liangjingkanji.github.io/Tooltip/bubble.html) +!!! quote "菊花加载对话框" + 示例使用的iOS风格对话框: [BubbleDialog](https://liangjingkanji.github.io/Tooltip/bubble.html) -## 指定全局加载框 +## 全局自定义 -在Application中配置Net时就可以设置默认的Dialog +初始化时指定加载对话框构造器`NetDialogFactory` -=== "初始配置全局加载框" - ```kotlin - NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { - setDialogFactory { // 全局加载对话框 - ProgressDialog(it).apply { - setMessage("我是全局自定义的加载对话框...") - } +```kotlin +NetConfig.initialize(Api.HOST, this) { + setDialogFactory { + ProgressDialog(it).apply { + setMessage("我是全局自定义的加载对话框...") } - } - ``` -=== "修改全局加载框" - ```kotlin - NetConfig.dialogFactory = NetDialogFactory { - ProgressDialog(it).apply { - setMessage("我是全局自定义的加载对话框...") } - } - ``` - -
- -如果不想修改全局加载框样式只是修改加载框文本, 可以覆盖文本(国际化同理) +} +``` -在当前项目下的values目录下的strings.xml添加以下一行可以修改文本 +如仅修改加载对话框文本, 在项目`values`目录的strings.xml添加以下 ```xml 加载中 ``` -## 生命周期 +!!! question "自定义的加载框不是Dialog" + 由于`scopeDialog`只能指定Dialog类型, 因此只能手动实现`Dialog`接口 -使用`scopeDialog`发起请求后, Dialog分为以下三个生命周期 + 仅要求复写 [DialogCoroutineScope](https://github.com/liangjingkanji/Net/blob/2abf07e1d003ef44574278fd2010f3375225d964/net/src/main/java/com/drake/net/scope/DialogCoroutineScope.kt#L47) 内调用的`Dialog`方法 -|生命周期|描述| -|-|-| -|Dialog 显示|执行`scopeDialog`时显示加载框| -|Dialog 自动结束|作用域内任务结束时关闭加载框| -|Dialog 手动结束|加载框被手动取消时取消作用域内网络请求| +## 生命周期 -## 自定义加载对话框 +使用`scopeDialog`发起请求后, 分为三个生命周期 -我想要自定义加载框视图 +| 加载框状态 | 作用域 | +| ---------- | ----------------------------- | +| 显示 | 执行`scopeDialog`时显示加载框 | +| 隐藏 | 作用域内任务结束时隐藏加载框 | +| 手动取消 | 取消作用域内所有网络请求 | -- Dialog属于布局容器, 你可以继承Dialog然后创建属于自己的显示内容(类似Activity/Fragment), 比如该[iOS风格对话框](https://github.com/liangjingkanji/Tooltip/blob/HEAD/tooltip/src/main/java/com/drake/tooltip/dialog/BubbleDialog.kt) -
-我的加载框不是Dialog -- 虽然我们指定`scopeDialog`的加载框或者`setNetDialogFactory`时只允许传入一个Dialog对象, 但即使你使用的不是Dialog你也可以创建一个类继承Dialog, 然后在其生命周期函数中处理`自己特殊对话框`的展示和隐藏 diff --git a/docs/auto-page.md b/docs/auto-page.md deleted file mode 100644 index 2b94e98dc..000000000 --- a/docs/auto-page.md +++ /dev/null @@ -1,55 +0,0 @@ -阅读自动分页加载之前请先阅读自动刷新 - -Net属于低耦合框架, 分页加载同样需要依赖第三方组件: [BRV](https://github.com/liangjingkanji/BRV)(点击链接按文档依赖) - - -创建布局 -```xml - - - - - -``` - -创建列表 -```kotlin -rv_pull.linear().setup { - addType(R.layout.item_list) -} -``` - -创建网络 -```kotlin -page.onRefresh { - scope { - val data = Get("list") { - param("page", index) - }.await().data - addData(data.list) { - index < data.total - } - } -}.autoRefresh() -``` - -- `index` 属于PageRefreshLayout的字段, 每次上拉加载自动+1递增, 下拉刷新自动重置 -- ` data.total`属于服务器返回的`列表全部数量`的字段, 最终使用什么字段或者判断条件请自己根据项目不同决定 -- `addData` 属于PageRefreshLayout的函数 - ```kotlin - fun addData( - data: List?, - adapter: BindingAdapter? = null, - isEmpty: () -> Boolean = { data.isNullOrEmpty() }, - hasMore: BindingAdapter.() -> Boolean = { true } - ) - ``` - 具体请查看函数注释 \ No newline at end of file diff --git a/docs/auto-pull.md b/docs/auto-pull.md new file mode 100644 index 000000000..4e6746972 --- /dev/null +++ b/docs/auto-pull.md @@ -0,0 +1,38 @@ +首先请阅读上章节 [自动下拉刷新](auto-refresh.md), 已提及内容不再重复 + + +## 自动分页 + +提供`addData()`来简化分页, 开发者可以借鉴实现 + +```kotlin +page.onRefresh { + scope { + val data = Get("list") { + param("page", index) + }.await().data + addData(data.list) { + index < data.total + } + } +}.autoRefresh() +``` + +## 索引自增 +`index` 每次上拉加载自动++1, 刷新列表重置为`PageRefreshLayout.startIndex` + +## 有更多页 + +根据`hasMore`返回结果是否关闭上拉加载, `isEmpty`决定是否显示`空数据`缺省页 + +```kotlin +fun addData( + data: List?, + adapter: BindingAdapter? = null, + isEmpty: () -> Boolean = { data.isNullOrEmpty() }, + hasMore: BindingAdapter.() -> Boolean = { true } +) +``` + +
+1. [PageRefreshLayout 下拉刷新/上拉加载](https://liangjingkanji.github.io/BRV/refresh.html) \ No newline at end of file diff --git a/docs/auto-refresh.md b/docs/auto-refresh.md index 7dee3c407..5d6afc072 100644 --- a/docs/auto-refresh.md +++ b/docs/auto-refresh.md @@ -1,17 +1,17 @@ -Net属于低耦合框架, 自动下拉刷新需要依赖第三方组件: [BRV](https://github.com/liangjingkanji/BRV)(点击链接按文档依赖) +!!! success "模块化依赖" + 如果自己处理下拉刷新可跳过本章, Net可以仅仅作为简单的网络框架存在 +
+Net可依赖三方库 [BRV](https://github.com/liangjingkanji/BRV) 实现自动处理下拉刷新 -使用固定版本号替换+符号 - ```groovy -implementation 'com.github.liangjingkanji:BRV:+' +implementation 'com.github.liangjingkanji:BRV:+' // 使用固定版本号替换+符号 ``` -> 当然如果自己处理下拉刷新也是可以的, Net可以仅仅作为网络框架存在 +## PageRefreshLayout -创建PageRefreshLayout ```xml ``` -创建列表 +## 创建列表 + ```kotlin rv_push.linear().setup { addType(R.layout.item_list) } ``` -创建网络请求 +## 网络请求 + +1. 请求开始, 显示下拉刷新动画 +2. 请求成功, 显示`内容`缺省页 +3. 请求失败, 显示`错误`缺省页 + ```kotlin hl_lines="2" page.onRefresh { scope { @@ -46,19 +52,9 @@ page.onRefresh { }.autoRefresh() ``` -
- -> 注意高亮处使用的是`scope`而不是其他作用域, 只能使用scope, 否则无法跟随PageRefreshLayout生命周期等功能 - -
- -- 使用上和自动缺省页相似 -- BRV同样属于具备完善功能独立的RecyclerView框架 -- BRV的下拉刷新扩展自[SmartRefreshLayout_v2](https://github.com/scwang90/SmartRefreshLayout), 支持其全部功能且更多 - ## 生命周期 -|生命周期|描述| -|-|-| -|开始|PageRefreshLayout执行`showLoading/autoRefresh`后触发`onRefresh`, 然后开始网络请求| -|结束|PageRefreshLayout被销毁(例如其所在的Activity或Fragment被销毁), 网络请求自动取消| \ No newline at end of file +| 生命周期 | 描述 | +| -------- | -------------------------------------------------- | +| 开始 | `showLoading/autoRefresh`触发`onRefresh`, 开始请求 | +| 结束 | PageRefreshLayout被销毁, 请求自动取消 | \ No newline at end of file diff --git a/docs/auto-state.md b/docs/auto-state.md index 24536c4cc..20c07f870 100644 --- a/docs/auto-state.md +++ b/docs/auto-state.md @@ -1,27 +1,22 @@ -考虑到低耦合, 所以自定义缺省页需要导入第三方组件依赖(点击链接按照文档依赖), 当然如果你使用其他方式处理缺省页可以跳过本章. +!!! success "模块化依赖" + 如果自己处理缺省页可跳过本章, Net可以仅仅作为简单的网络框架存在 -依赖以下两种库其中之一即可支持自动显示缺省页 +
+Net可依赖三方库实现自动缺省页, 以下二选一依赖 -1. 依赖 [StateLayout](https://github.com/liangjingkanji/StateLayout)
-
- 使用固定版本号替换+符号 +1. 依赖 [StateLayout](https://github.com/liangjingkanji/StateLayout)
```groovy - implementation 'com.github.liangjingkanji:StateLayout:+' + implementation 'com.github.liangjingkanji:StateLayout:+' // 使用固定版本号替换+符号 ``` -1. 依赖 [BRV](https://github.com/liangjingkanji/BRV) (因为BRV包含StateLayout)
-
- 使用固定版本号替换+符号 +1. 依赖 [BRV](https://github.com/liangjingkanji/BRV) (因为BRV包含StateLayout)
```groovy - implementation 'com.github.liangjingkanji:BRV:+' + implementation 'com.github.liangjingkanji:BRV:+' // 使用固定版本号替换+符号 ``` -
-初始化缺省页 - -需要在`Application`里配置全局自定义的 `加载中/加载失败/空数据布局`,可以复制使用Demo里自定义的布局资源,Demo中的`App.kt`配置如下 +## 初始化 +在Application中初始化缺省页 ````kotlin -//全局缺省页配置 [https://github.com/liangjingkanji/StateLayout] StateConfig.apply { emptyLayout = R.layout.layout_empty loadingLayout = R.layout.layout_loading @@ -29,9 +24,9 @@ StateConfig.apply { } ```` +## 创建 -
-声明缺省页 +使用`StateLayout`包裹的内容即`内容`(content) ```xml ``` -自动显示缺省页 +## 网络请求 + +1. 请求开始, 显示`加载中`缺省页 +2. 请求成功, 显示`内容`缺省页 +3. 请求失败, 显示`错误`缺省页 ```kotlin state.onRefresh { @@ -63,12 +62,10 @@ state.onRefresh { ```
-> 注意高亮处使用的是`scope`而不是其他作用域, 只能使用scope, 否则无法跟随StateLayout生命周期(自动显示对应缺省页)等功能 - ## 生命周期 -|生命周期|描述| -|-|-| -|开始|StateLayout执行`showLoading`后触发`onRefresh`, 然后开始网络请求| -|结束|缺省页被销毁(例如其所在的Activity或Fragment被销毁), 网络请求自动取消| \ No newline at end of file +| 生命周期 | 描述 | +| -------- | ---------------------------------------------- | +| 开始 | `showLoading`触发`onRefresh`, 开始请求 | +| 结束 | 缺省页被销毁, 请求自动取消 | \ No newline at end of file diff --git a/docs/cache.md b/docs/cache.md index 919e8fccb..1bfffe954 100644 --- a/docs/cache.md +++ b/docs/cache.md @@ -1,4 +1,4 @@ -**网络请求中缓存至关重要, 而Net是最好的** +### 缓存优势 1. 页面秒开 2. 减少服务器压力 @@ -6,33 +6,35 @@ ### Net缓存特点 -1. 缓存任何请求方式, POST/GET/PUT/HEAD... -2. 缓存任何数据类型, File/图片/JSON/ProtoBuf/... -3. 限制最大缓存体积, 缓存遵守磁盘LRU缓存算法, 当缓存达到限制时, 将会删除最近最少使用的缓存 -4. 高性能DiskLruCache来实现统一缓存 +1. 缓存任何请求方式 +2. 缓存任何数据, File/图片/JSON/ProtoBuf等 +3. 限制最大缓存空间 +4. 使用`DiskLruCache`实现, 删除最近最少使用 ## 配置缓存 -不配置缓存设置是不会触发缓存的 +不配置`Cache`是不会启用缓存的 ```kotlin -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { - // ... - // 本框架支持Http缓存协议和强制缓存模式 - cache(Cache(cacheDir, 1024 * 1024 * 128)) // 缓存设置, 当超过maxSize最大值会根据最近最少使用算法清除缓存来限制缓存大小 +NetConfig.initialize(Api.HOST, this) { + // Net支持Http缓存协议和强制缓存模式 + // 当超过maxSize最大值会根据最近最少使用算法清除缓存来限制缓存大小 + cache(Cache(cacheDir, 1024 * 1024 * 128)) } ``` -这也是Net缓存强大之处, 和OkHttp共享缓存但是却可以缓存任何请求方式 + +!!! note "判断响应来自缓存" + 如果`Response.cacheResponse`不为null的时, 代表Response来自本地缓存 ## Http缓存协议 -这属于OkHttp默认的Http缓存协议控制, 要求满足一定条件 +OkHttp默认的Http缓存协议控制, 要求以下条件 -- 请求方式为Get -- URL的MD5值作为Key, 所以一旦URL发生改变即不会算同一缓存 -- 存在响应头存在缓存控制: [Cache-Control](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control) +- Get请求方式 +- 缓存使用Url为key, 因此Url改变会读不到缓存 +- 响应头控制缓存: [Cache-Control](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control)
-通过指定`CacheControl`也可以控制Http缓存协议(原理是添加请求头) +通过`setCacheControl`可以控制Http缓存协议(原理是添加请求头) ```kotlin scopeNetLife { @@ -44,10 +46,7 @@ scopeNetLife { } ``` -还可以指定缓存有效期, 更多使用请查看代码或者搜索[Cache-Control](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Headers/Cache-Control) - -
-如果你后端同事的技术水平无法实现Http标准缓存协议, 或者你需要缓存Get以外的请求方法. 下面我们介绍使用`强制缓存模式`来完全由客户端控制缓存 +如果无法实现Http标准缓存协议, 或要缓存Get以外的请求方法, 可以使用`强制缓存模式`来由客户端控制缓存 ## 强制缓存模式 @@ -62,22 +61,18 @@ scopeNetLife { } ``` -读取缓存失败会引发`NoCacheException`异常(被全局错误处理器接收), 前提是你没有使用读取缓存失败后请求网络模式 - | 强制缓存模式 | 描述 | |-|-| -| READ | 只读取缓存, 本操作并不会请求网络故不存在写入缓存 | +| READ | 只读取缓存, 读不到`NoCacheException` | | WRITE | 只请求网络, 强制写入缓存 | | READ_THEN_REQUEST | 先从缓存读取,如果失败再从网络读取, 强制写入缓存 | | REQUEST_THEN_READ | 先从网络读取,如果失败再从缓存读取, 强制写入缓存 | -> 如果`response.cacheResponse`不为null的时候即代表response来自于本地缓存, 强制缓存或Http缓存协议都如此 - ## 自定缓存Key -缓存Key默认是`请求方式+URL`后产生的sha1值(仅强制缓存模式有效), 并不会默认使用请求参数判断 +仅`强制缓存模式`有效, 缓存Key默认是`请求方式+URL`后产生的`sha1值` -如果你要实现区别请求参数的缓存请自定义缓存key, 如下 +如果要缓存区别请求参数, 请自定义缓存key ```kotlin scopeNetLife { @@ -91,8 +86,8 @@ scopeNetLife { ## 缓存有效期 -1. 缓存有效期只针对`强制缓存模式`, 标准Http缓存协议遵守协议本身的有效期 -1. 缓存有效期过期只是让缓存无效, 并不会被删除(即无法读取). 缓存删除遵守LRU(最近最少使用)原则在所有缓存体积达到配置的值时自动删除(即使缓存有效期未到) +1. 仅`强制缓存模式`有效, 标准Http缓存协议遵守协议本身的有效期 +1. 缓存有效期过期只是让缓存无效, 不会立即删除
根据(最近最少使用)原则在缓存空间达到配置值时删除(即使缓存有效期未到) ```kotlin scopeNetLife { @@ -106,7 +101,7 @@ scopeNetLife { ## 预览(缓存+网络) -这里可以用到Net的预览模式(preview)来实现, 其实不仅仅是预览缓存也可以用于回退请求 +预览又可以理解为回退请求, 一般用于秒开首页或者回退加载数据 ```kotlin scopeNetLife { @@ -124,9 +119,8 @@ scopeNetLife { } ``` -> 一般用于秒开首页或者回退加载数据. 我们可以在preview{}只加载缓存. 然后再执行scopeNetLife来请求网络, 做到缓存+网络双重加载的效果 - -有人可能觉得这和自己加载两次有什么区别, 区别是preview的方法参数可以控制加载 +!!! question "这和加载两次有什么区别?" + 区别是`preview`可以控制以下行为 -- `breakError` 读取缓存成功后不再处理错误信息, 默认false -- `breakLoading` 读取缓存成功后结束加载动画, 默认true + 1. `breakError` 读取缓存成功后不再处理错误信息, 默认false + 2. `breakLoading` 读取缓存成功后结束加载动画, 默认true diff --git a/docs/callback.md b/docs/callback.md index d1ad27e00..88d1c8c5f 100644 --- a/docs/callback.md +++ b/docs/callback.md @@ -1,6 +1,7 @@ Net支持OkHttp的原有的队列请求`Callback` -> Callback属于接口回调请求, 其代码冗余可读性不高, 并且无法支持并发请求协作 +!!! Failure "不推荐" + Callback属于接口回调, 其代码冗余, 且无法支持并发请求 ```kotlin diff --git a/docs/cancel.md b/docs/cancel.md index 8a5b5a23f..4820ef94d 100644 --- a/docs/cancel.md +++ b/docs/cancel.md @@ -1,7 +1,4 @@ -Net虽然支持自动跟随生命周期取消网络请求, 绝大部分场景也足够. 但是有时还是需要手动取消, 例如取消下载文件. -
- -Net取消协程作用域自动取消内部网络请求, 也支持任意位置取消指定网络请求. +部分场景需要手动取消请求, 例如取消下载 ```kotlin downloadScope = scopeNetLife { @@ -11,13 +8,10 @@ downloadScope = scopeNetLife { downloadScope.cancel() // 取消下载 ``` -完整示例: [源码](https://github.com/liangjingkanji/Net/blob/master/sample/src/main/java/com/drake/net/sample/ui/fragment/DownloadFileFragment.kt) - ## 任意位置取消 -发起请求的时候要求定义一个`Id`用于指定网络请求, 然后在需要的地方使用单例对象`Net.cancelId`取消请求. +发起请求时指定`Id` -创建请求 ```kotlin scopeNetLife { tvFragment.text = Get("api"){ @@ -26,15 +20,25 @@ scopeNetLife { } ``` -然后根据Id取消网络请求 -```kotlin -Net.cancelId("请求用户信息") - -Net.cancelGroup("请求分组名称") // 设置分组 -``` - -Group和Id在使用场景上有所区别, 预期上Group允许重复赋值给多个请求, Id仅允许赋值给一个请求, 但实际上都允许重复赋值
-在作用域中发起请求时会默认使用协程错误处理器作为Group: `setGroup(coroutineContext[CoroutineExceptionHandler])`
-如果你覆盖Group会导致协程结束不会自动取消请求 - -
\ No newline at end of file +=== "根据ID取消" + ``` kotlin + Net.cancelId("请求用户信息") + ``` +=== "根据Group取消" + ``` kotlin + Net.cancelGroup("请求分组名称") + ``` + +## Group和Id区别 + +| 函数 | 描述 | +|-|-| +| id | 请求唯一Id, 实际上重复也行, 但是取消请求时遍历到指定Id就会结束遍历 | +| group | 允许多个请求使用相同group, 在取消请求时会遍历所有分组的请求
| + +!!! warning "作用域结束请求自动取消" + 在`scopeXX()`作用域中发起请求时会默认使用当前协程错误处理器作为Group + ```kotlin + setGroup(coroutineContext[CoroutineExceptionHandler]) + ``` + 在作用域结束时 会`cancelGroup`, 所以如果你手动指定分组会导致无法自动取消 \ No newline at end of file diff --git a/docs/config.md b/docs/config.md index 05a16a6fc..983aaa090 100644 --- a/docs/config.md +++ b/docs/config.md @@ -1,124 +1,106 @@ -全局配置建议在Application的onCreate函数中配置 +全局配置应在`Application.onCreate`中配置 ## 初始配置 -=== "普通初始化" +两种方式初始配置, 不初始化也能直接使用 +=== "Net初始化" ```kotlin - class App : Application() { - override fun onCreate() { - super.onCreate() - - // https://github.com/liangjingkanji/Net/ 这是接口全局域名, 可以使用NetConfig.host进行单独的修改 - NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { - // 超时配置, 默认是10秒, 设置太长时间会导致用户等待过久 - connectTimeout(30, TimeUnit.SECONDS) - readTimeout(30, TimeUnit.SECONDS) - writeTimeout(30, TimeUnit.SECONDS) - setDebug(BuildConfig.DEBUG) - setConverter(SerializationConverter()) - } - } + NetConfig.initialize(Api.HOST, this) { + // 超时配置, 默认是10秒, 设置太长时间会导致用户等待过久 + connectTimeout(30, TimeUnit.SECONDS) + readTimeout(30, TimeUnit.SECONDS) + writeTimeout(30, TimeUnit.SECONDS) + setDebug(BuildConfig.DEBUG) + setConverter(SerializationConverter()) } ``` -=== "OkHttpClient.Builder" - +=== "OkHttp构造器初始化" ```kotlin - class App : Application() { - override fun onCreate() { - super.onCreate() - // https://github.com/liangjingkanji/Net/ 这是接口全局域名, 可以使用NetConfig.host进行单独的修改 - val okHttpClientBuilder = OkHttpClient.Builder() - .setDebug(BuildConfig.DEBUG) - .setConverter(SerializationConverter()) - .addInterceptor(LogRecordInterceptor(BuildConfig.DEBUG)) - - NetConfig.initialize("https://github.com/liangjingkanji/Net/", this, okHttpClientBuilder) - } - } + val okHttpClientBuilder = OkHttpClient.Builder() + .setDebug(BuildConfig.DEBUG) + .setConverter(SerializationConverter()) + .addInterceptor(LogRecordInterceptor(BuildConfig.DEBUG)) + NetConfig.initialize(Api.HOST, this, okHttpClientBuilder) ``` -> 配置都是可选项, 不是不初始化就不能使用. 如果你是Xposed或者多进程项目中必须初始化传入上下文或者赋值 `NetConfig.app = this` - -在initNet函数作用域中的this属于`OkHttpClient.Builder()`, 可以配置任何OkHttpClient.Builder的属性以外还支持以下Net独有配置 +!!! failure "强制初始化" + 如果是多进程项目(例如Xposed)必须初始化, 因为多进程无法自动指定Context -| 函数 | 描述 | +| 可配置选项 | 描述 | |-|-| -| setDebug | 是否输出网络日志, 和`LogRecordInterceptor`互不影响 | +| setDebug | 开启日志 | | setSSLCertificate | 配置Https证书 | | trustSSLCertificate | 信任所有Https证书 | -| setConverter | [配置数据转换器](converter.md), 将网络返回的数据转换成你想要的数据结构 | -| setRequestInterceptor | [配置请求拦截器](interceptor.md), 适用于添加全局请求头/参数 | -| setErrorHandler | [配置全局错误处理](error-global.md) | -| setDialogFactory | [配置全局对话框](auto-dialog.md) | +| setConverter | [转换器](converter-customize.md), 将请求结果转为任何类型 | +| setRequestInterceptor | [请求拦截器](interceptor.md), 全局请求头/请求参数 | +| setErrorHandler | [全局错误处理](error-global.md) | +| setDialogFactory | [全局对话框](auto-dialog.md) | + +!!! success "修改配置" + NetConfig存储所有全局配置变量, 可以后续修改, 且大部分支持单例指定配置 ## 重试次数 -默认情况下你设置超时时间即可, OkHttp内部也有重试机制. +可以添加`RetryInterceptor`拦截器即可实现失败以后会重试指定次数 -但是个别开发者需求指定重试次数则可以添加`RetryInterceptor`拦截器即可实现失败以后会重试指定次数 +默认情况下设置超时时间即可, OkHttp内部也有重试机制 ```kotlin -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { +NetConfig.initialize(Api.HOST, this) { // ... 其他配置 addInterceptor(RetryInterceptor(3)) // 如果全部失败会重试三次 } ``` +!!! warning "长时间阻碍用户交互" + OkHttp内部也有重试机制, 如果还添加重试拦截器可能导致请求时间过长, 长时间阻碍用户交互 -## 修改配置 - -[NetConfig](api/-net/com.drake.net/-net-config/index.html)的字段即为全局配置, 可随时修改 - -```kotlin -NetConfig.converter = MyNewConverter() // 修改全局数据转换器 -NetConfig.okHttpClient // 修改全局默认客户端 -``` - -更多请访问源码查看 - -## BaseUrl - -这个概念来源于Retrofit, 因为Retrofit无法动态修改Host, 但是这个Net支持随时修改. 以下介绍三种修改方式 - -1) 直接修改 +## 多域名 -```kotlin -NetConfig.host = "新的BaseUrl" -``` +概念源于`Retrofit`(称为BaseUrl), 因为Retrofit无法二次修改请求Host, 但Net支持随时修改 +以下介绍三种修改方式 -2) 传入路径 -如传入参数为路径(例如`/api/index`)会自动和`host`拼接组成完成URL, 但如果传入的以`http/https`开头的全路径则会直接作为请求URL +=== "修改Host" + ```kotlin + NetConfig.host = Api.HOST_2 + ``` -```kotlin -scopeNetLife { - val data = Get("https://github.com/liangjingkanji/Net/").await() -} -``` +=== "指定全路径" + 指定Path(例如`/api/index`)会自动和`NetConfig.host`拼接组成Url, 但指定以`http/https`开头的全路径则直接作为请求Url + ```kotlin + scopeNetLife { + val data = Get("https://github.com/path").await() + } + ``` -3) 使用拦截器 +=== "使用拦截器" + 请求时指定`tag`, 然后拦截器中根据tag判断修改host, 拦截器能修改所有请求/响应信息 -或者通过指定`tag`, 然后拦截器(interceptor)中根据tag动态修改host, 因为拦截器能修改一切请求参数 + ```kotlin + scopeNetLife { + val data = Get("/api/index", "User").await() // User即为tag + } + // 拦截器修改请求URL不做介绍 + ``` -```kotlin -scopeNetLife { - val data = Get("/api/index", "User").await() // User即为tag -} -// 拦截器修改请求URL不做介绍 -``` +## 网络安全配置 -## 多域名 +Net自动启用网络配置文件, 默认支持Http请求, 可自定义 -```kotlin -scopeNetLife { - Get("https://github.com/liangjingkanji/Net/").await() // 传入域名就会使用当前域名 - Get("/liangjingkanji/Net/").await() // 自动和NetConfig.host拼接 - Get(Api.Host2 + "/liangjingkanji/Net/").await() // 自己手动拼接 -} +```xml title="network_security_config.xml" + + + ``` -和BaseUrl一样你还可以在拦截器里面统一处理, 在拦截器里面判断tag或者path来拼接域名 - +!!! failure "无法打包Apk" + 当开发者自定义使用非同名`network_security_config`时网络配置文件时会无法打包Apk + 请添加`tools:replace` + ```kotlin title="AndroidManifest.xml" hl_lines="3" + + ``` diff --git a/docs/convert-special.md b/docs/convert-special.md deleted file mode 100644 index 6fe9fdcc4..000000000 --- a/docs/convert-special.md +++ /dev/null @@ -1,129 +0,0 @@ - -## 解析完整Json - -一般的解析过程是以下 - -1. 后端返回的JSON数据 - -```json -{ - "code":0, - "msg":"请求成功", - "data": { - "name": "彭于晏", - "age": 27, - "height": 180 - } -} -``` - -2. 创建数据模型 - -```kotlin -data class UserModel ( - var code:Int, - var msg:String, - var data:Data, -) { - data class Data(var name: String, var age: Int, var height: Int) -} -``` - -3. 发起网络请求 - -```kotlin -scopeNetLife { - val data = Get("api").await().data -} -``` - -## 解析Json中的字段 - -这样每次都要`await().data`才是你要的`data`对象. 有些人就想省略直接不写code和msg, 希望直接返回data. 那么在转换器里面就只解析data字段即可 - -简化数据对象 - -```kotlin -data class UserModel(var name: String, var age: Int, var height: Int) -``` - -转换器只解析data字段 - -```kotlin -class GsonConvert : JSONConvert(code = "code", message = "msg", success = "200") { - private val gson = GsonBuilder().serializeNulls().create() - - override fun String.parseBody(succeed: Type): S? { - val data = JSONObject(this).getString("data") - return gson.fromJson(data, succeed) - } -} -``` - -使用简化的数据对象作为泛型 - -```kotlin -scopeNetLife { - val data = Get("api").await() -} -``` - -## 解析Json数组 - -在Net中可以直接解析List等嵌套泛型数据, 解析List和普通对象没有区别 - -```kotlin -scopeNetLife { - tvFragment.text = Get>("list") { - converter = GsonConverter() // 单例转换器, 一般情况下是定义一个全局转换器 - }.await()[0].name -} -``` - -## 解析泛型数据类 - -这种方式在Retrofit中经常被使用到, 因为有些人认为code/msg也要使用. 实际上一般非成功错误码(例如200或者0)算业务定义错误应当在转换器抛出异常, 然后在错误处理回调中取获取错误码/信息 - ```kotlin - // 数据对象的基类 - open class BaseResult { - var code: Int = 0 - var msg: String = "" - var data: T? = null - } - - class Result(var name: String) : BaseResult() - ``` - -如果你成功错误码要求定义多个都算网络请求成功, 也是可以的并且不需要写泛型这么麻烦, Net转换器可以实现无论加不加`code/msg`都能正常解析返回 - -```kotlin -@kotlinx.serialization.Serializable -class Result(var data: String = "数据", var msg: String = "", var code:Int = 0) -``` - -```kotlin -@kotlinx.serialization.Serializable -class Result(var data: String = "数据") -``` - -查看源码`SerializationConverter`可以看到转换器内进行了回退解析策略, 当截取`data`解析失败后会完整解析整个Json - -```kotlin hl_lines="15" -code in 200..299 -> { // 请求成功 - val bodyString = response.body?.string() ?: return null - val kType = response.request.kType - ?: throw ConvertException(response, "Request does not contain KType") - return try { - val json = JSONObject(bodyString) // 获取JSON中后端定义的错误码和错误信息 - val srvCode = json.getString(this.code) - if (srvCode == success) { // 对比后端自定义错误码 - json.getString("data").parseBody(kType) - } else { // 错误码匹配失败, 开始写入错误异常 - val errorMessage = json.optString(message, NetConfig.app.getString(com.drake.net.R.string.no_error_message)) - throw ResponseException(response, errorMessage, tag = srvCode) // 将业务错误码作为tag传递 - } - } catch (e: JSONException) { // 固定格式JSON分析失败直接解析JSON - bodyString.parseBody(kType) - } -} -``` \ No newline at end of file diff --git a/docs/converter-customize.md b/docs/converter-customize.md new file mode 100644 index 000000000..ceec1c059 --- /dev/null +++ b/docs/converter-customize.md @@ -0,0 +1,81 @@ +Net自定义转换器可支持任何数据类型, 甚至`Bitmap` + +!!! failure "泛型和转换器关系" + 1. 如果`Post`, 那么`NetConverter.onConvert`返回值必须为Model + 2. 如果`Post`, 允许`NetConverter.onConvert`返回值为null + 3. 其他情况请抛出异常 + +```kotlin +scopeNetLife { + val userList = Get>("list") { + converter = GsonConverter() + }.await() +} +``` + +Net由于低耦合原则不自带任何序列化框架 + +## 设置转换器 + +=== "全局" + ```kotlin hl_lines="2" + NetConfig.initialize(Api.HOST, this) { + setConverter(SerializationConverter()) + } + ``` +=== "单例" + ```kotlin hl_lines="3" + scopeNetLife { + tvFragment.text = Get("api"){ + converter = SerializationConverter() + }.await() + } + ``` + +## 常见转换器 + +实现[JSONConverter](https://github.com/liangjingkanji/Net/blob/master/net/src/main/java/com/drake/net/convert/JSONConvert.kt)的`parseBody`方法使用自定义序列化框架解析 + +| 序列化框架 | 示例代码 | 描述 | +| ------------------------------------------------------------ | ------------------------------------------------------------ | -------------------- | +| [kotlin-serialization](https://github.com/Kotlin/kotlinx.serialization) | [SerializationConverter](https://github.com/liangjingkanji/Net/blob/HEAD/sample/src/main/java/com/drake/net/sample/converter/SerializationConverter.kt) | Kotlin官方序列化框架 | +| [kotlin-serialization](https://github.com/Kotlin/kotlinx.serialization) | [ProtobufConverter](https://github.com/liangjingkanji/Net/blob/HEAD/sample/src/main/java/com/drake/net/sample/converter/ProtobufConverter.kt) | Kotlin官方序列化框架 | +| [gson](https://github.com/google/gson) | [GsonConverter](https://github.com/liangjingkanji/Net/blob/HEAD/sample/src/main/java/com/drake/net/sample/converter/GsonConverter.kt) | 谷歌序列化框架 | +| [fastJson](https://github.com/alibaba/fastjson) | [FastJsonConverter](https://github.com/liangjingkanji/Net/blob/HEAD/sample/src/main/java/com/drake/net/sample/converter/FastJsonConverter.kt) | 阿里巴巴序列化框架 | +| [moshi](https://github.com/square/moshi) | [MoshiConverter](https://github.com/liangjingkanji/Net/blob/HEAD/sample/src/main/java/com/drake/net/sample/converter/MoshiConverter.kt) | Square序列化框架 | + +## 自定义转换器 + +实现`NetConverter`返回自定义请求结果 + +??? example "转换器实现非常简单" + ```kotlin title="NetConverter.kt" linenums="1" + interface NetConverter { + + @Throws(Throwable::class) + fun onConvert(succeed: Type, response: Response): R? + + companion object DEFAULT : NetConverter { + /** + * 返回结果应当等于泛型对象, 可空 + * @param succeed 请求要求返回的泛型类型 + * @param response 请求响应对象 + */ + override fun onConvert(succeed: Type, response: Response): R? { + return when { + succeed === String::class.java && response.isSuccessful -> response.body?.string() as R + succeed === ByteString::class.java && response.isSuccessful -> response.body?.byteString() as R + succeed is GenericArrayType && succeed.genericComponentType === Byte::class.java && response.isSuccessful -> response.body?.bytes() as R + succeed === File::class.java && response.isSuccessful -> response.file() as R + succeed === Response::class.java -> response as R + else -> throw ConvertException(response, "An exception occurred while converting the NetConverter.DEFAULT") + } + } + } + } + ``` + +转换器中可以根据需加上解密数据, token失效跳转登录, 限制多端登录等逻辑 + +1. 日志信息输出, 请阅读[日志记录器](log-recorder.md) +2. 转换器中抛出异常被全局错误处理捕获, 请阅读[全局错误处理](error-handle.md) \ No newline at end of file diff --git a/docs/converter-struct.md b/docs/converter-struct.md new file mode 100644 index 000000000..775a0a123 --- /dev/null +++ b/docs/converter-struct.md @@ -0,0 +1,150 @@ + +上一章节介绍如何序列化框架解析JSON, 而本章是介绍如何定义映射数据类 + +## JSON + +解析接口返回的完整JSON + +=== "JSON" + ```json + { + "code":0, + "msg":"请求成功", + "data": { + "name": "彭于晏", + "age": 27, + "height": 180 + } + } + ``` + +=== "数据类" + ```kotlin + data class UserModel ( + var code:Int, + var msg:String, + var data:Data, + ) { + data class Data(var name: String, var age: Int, var height: Int) + } + ``` + +=== "网络请求" + ```kotlin + scopeNetLife { + val data = Get("api").await().data + } + ``` + +??? warning "以上设计不合理" + 正常情况下Http状态码200时只返回有效数据 + ```json + { + "name": "彭于晏", + "age": 27, + "height": 180 + } + ``` + 任何非正常流程返回200状态码, 例如400(错误请求)/401(认证失败) + ```kotlin + { + "code":412302, + "msg":"密码错误", + } + ``` + 只要认为需要解析结构体情况下, 都应属于正常流程 + +## 剔除无效字段 + +以下演示仅解析`data`字段返回有效数据 + +此数据类只需要包含data值 + +```kotlin +data class UserModel(var name: String, var age: Int, var height: Int) +``` + +转换器只解析data字段 + +```kotlin +class GsonConvert : JSONConvert(code = "code", message = "msg", success = "200") { + private val gson = GsonBuilder().serializeNulls().create() + + override fun String.parseBody(succeed: Type): S? { + val data = JSONObject(this).getString("data") + return gson.fromJson(data, succeed) + } +} +``` + +请求直接返回 + +```kotlin +scopeNetLife { + val data = Get("api").await() +} +``` + +## 不规范数据 + +推荐在转换器中解析之前处理好数据 + +1. 字段值为`"null"`而不是`null`, 或者json在字符串中 + ```json + { + "data": "{ "title": "name" }" + "msg": "null" + } + ``` + ```kotlin title="替换为规范内容" + json = bodyString.replace("\"{", "{") + json = bodyString.replace("}\"", "}") + json = bodyString.replace("\"null\"", "null") + ``` + +2. 服务器成功时不返回数据或者返回`null` + ```kotlin + if (response.body == null || bodyString == "null") { + "{}".bodyString.parseBody(succeed) + } + ``` + +3. 字段值为null, 使用 [kotlin-serialization](kotlin-serialization.md) 自动使用字段默认值 + ```kotlin + { + "msg": null + } + ``` +4. 字段无引号或字段名为数字, 使用 [kotlin-serialization](kotlin-serialization.md) 可以禁用JSON规范限制 + ```json title="数字使用map解析" + { + "data": { + 23: 32 + } + } + ``` + ```kotlin hl_lines="3" title="禁用JSON规范限制" + val jsonDecoder = Json { + // ... + isLenient = true + } + ``` + + +## 泛型数据类 + +某些地区很多开发者习惯这么使用, 因为他们接口返回无关字段, 但是技术不够无法自定义转换器来简化取值 + +所以他们选择更复杂的方式: 使用泛型来简化 + +```kotlin +open class BaseResult { + var code: Int = 0 + var msg: String = "" + var data: T? = null +} + +class Result(var name: String) : BaseResult() +``` + +!!! quote "用加法解决问题的人,总有人愿意用乘法给你答案" \ No newline at end of file diff --git a/docs/converter.md b/docs/converter.md index d8d7f1fc3..aeda64d48 100644 --- a/docs/converter.md +++ b/docs/converter.md @@ -1,36 +1,16 @@ -Net可以通过自定义转换器支持任何数据类型转换, 甚至`List>`等嵌套泛型对象 +Net支持请求返回的数据类型取决于你的转换器实现 -例如请求动作Post`指定泛型为Model`, 则转换器NetConverter中的函数`onConvert返回值必须为Model`, 如果转换失败或者发生异常都算请求错误 +# Get<任何对象>("path").await() -> 注意如果你在转换器里面返回null. 那么指定的泛型也应当是可空类型, 例如`Post("api")` - -```kotlin -scopeNetLife { - val userList = Get>("list") { - converter = GsonConverter() - }.await() -} -``` - -
- -如果你要返回映射好的数据模型对象, 那么肯定要求创建转换器的. 本框架由于低耦合原则不自带解析框架 - -[常用的Json转换器-代码示例](https://github.com/liangjingkanji/Net/tree/master/sample/src/main/java/com/drake/net/sample/converter) - - - -## 默认返回数据类型 - -Net支持请求返回的数据类型取决于你的转换器(也就是支持返回任何对象), 默认情况不创建转换器也支持返回以下数据类型 +默认转换器支持返回以下数据类型 | 函数 | 描述 | |-|-| | String | 字符串 | | ByteArray | 字节数组 | -| ByteString | 内部定义的一种字符串对象 | -| Response | 最基础的响应 | +| ByteString | 更多功能的字符串对象 | | File | 文件对象, 这种情况其实应当称为[下载文件](download-file.md) | +| Response | 所有响应信息(响应体/响应头/请求信息等) | 使用示例 @@ -40,238 +20,31 @@ scopeNetLife { } ``` -??? summary "默认使用的是: [NetConverter.DEFAULT](https://github.com/liangjingkanji/Net/blob/master/net/src/main/java/com/drake/net/convert/NetConverter.kt)" - ```kotlin - val DEFAULT = object : NetConverter { - - override fun onConvert( - succeed: Type, - response: Response - ): R? { - return when (succeed) { - String::class.java -> response.body?.string() as R - ByteString::class.java -> response.body?.byteString() as R - ByteArray::class.java -> response.body?.bytes() as R - Response::class.java -> response as R - File::class.java -> response.file() as R - else -> throw ConvertException( - response, - "The default converter does not support this type" - ) - } - } - } - ``` - -假设这里没有你需要的数据类型请[自定义转换器](#_3)(例如返回Json或Protocol) - -## 设置转换器 -转换器分为全局和单例, 单例可以覆盖全局的转换器. 如果不设置转换器就会采用默认的转换器 - -=== "全局" - ```kotlin hl_lines="2" - NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { - setConverter(SerializationConverter()) - } - ``` -=== "单例" - ```kotlin hl_lines="3" - scopeNetLife { - tvFragment.text = Get("api"){ - converter = SerializationConverter() - }.await() - } - ``` - -## Json解析库转换器 - -一般业务我们可以直接继承[JSONConverter](https://github.com/liangjingkanji/Net/blob/master/net/src/main/java/com/drake/net/convert/JSONConvert.kt) -使用自己的JSON解析器解析数据, 完全自定义需求可以直接实现[NetConverter](https://github.com/liangjingkanji/Net/blob/master/net/src/main/java/com/drake/net/convert/NetConverter.kt)(比如直接转换IO流) - -=== "Gson" - - ```kotlin - class GsonConvert : JSONConvert(code = "code", message = "msg", success = "200") { - val gson = GsonBuilder().serializeNulls().create() - - override fun String.parseBody(succeed: Type): S? { - return gson.fromJson(this, succeed) - } - } - ``` - -=== "kotlin-serialization" - - ```kotlin - class SerializationConverter( - val success: String = "0", - val code: String = "code", - val message: String = "msg" - ) : NetConverter { - - private val jsonDecoder = Json { - ignoreUnknownKeys = true // JSON和数据模型字段可以不匹配 - coerceInputValues = true // 如果JSON字段是Null则使用默认值 - } - - override fun onConvert(succeed: Type, response: Response): R? { - try { - // 此处是为了继承默认转换器支持的返回类型 - return NetConverter.onConvert(succeed, response) - } catch (e: ConvertException) { - val code = response.code - when { - code in 200..299 -> { // 请求成功 - val bodyString = response.body?.string() ?: return null - val kType = response.request.kType() ?: return null - return try { - val json = JSONObject(bodyString) // 获取JSON中后端定义的错误码和错误信息 - if (json.getString(this.code) == success) { // 对比后端自定义错误码 - bodyString.parseBody(kType) - } else { // 错误码匹配失败, 开始写入错误异常 - val errorMessage = json.optString( - message, - NetConfig.app.getString(com.drake.net.R.string.no_error_message) - ) - throw ResponseException(response, errorMessage) - } - } catch (e: JSONException) { // 固定格式JSON分析失败直接解析JSON - bodyString.parseBody(kType) - } - } - code in 400..499 -> throw RequestParamsException(response, code.toString()) // 请求参数错误 - code >= 500 -> throw ServerResponseException(response, code.toString()) // 服务器异常错误 - else -> throw ConvertException(response) - } - } - } - - fun String.parseBody(succeed: KType): R? { - return jsonDecoder.decodeFromString(Json.serializersModule.serializer(succeed), this) as R - } - } - ``` - - SerializationConverter就是仿照JSONConverter代码实现 - -=== "FastJson" - - ```kotlin - class FastJsonConvert : JSONConvert(code = "code", message = "msg", success = "200") { - - override fun String.parseBody(succeed: Type): S? { - return JSON.parseObject(this, succeed) - } - } - ``` - -=== "Moshi" - - ```kotlin - class MoshiConvert : JSONConvert(code = "code", message = "msg", success = "200") { - val moshi = Moshi.Builder().build() - - override fun String.parseBody(succeed: Type): S? { - return moshi.adapter(succeed).fromJson(this) - } - } - ``` - -1. 使用转换器时请添加其依赖: [GSON](https://github.com/google/gson) | [kotlin-serialization](https://github.com/Kotlin/kotlinx.serialization) | [FastJson](https://github.com/alibaba/fastjson) | [Moshi](https://github.com/square/moshi) -2. 推荐使用 `kotlinx.Serialization`, 其可解析[任何泛型](kotlin-serialization.md) -3. Sample有完整代码示例 - -以上转换器示例是建立在数据结构为以下表格的固定格式下, 如果有特殊的业务可能需要自行修改 - -| 转换器参数 | 描述 | -|-|-| -| code | 即后端定义的`成功码`字段名 | -| message | 即后端定义的`错误消息`字段名 | -| success | 即`成功码`的值等于指定时才算网络请求成功 | - - - -比如截图中的意为, 当返回的Json中包含state字段且值为ok时请求才算是真正成功才会返回数据, 否则都会抛出异常. 其中message为错误信息字段名 - -假设简单的指定名称不能满足你复杂的业务逻辑, 请复制`JSONConvert`源码到你项目中修改或者直接自己实现`NetConverter` - -> 注意解析器(Gson或者Moshi)的解析对象记得定义为类成员, 这样可以不会导致每次解析都要创建一个新的解析对象, 减少内存消耗 -
- -## 自定义转换器 - -通过实现`NetConverter`接口可以编写自己的逻辑网络请求返回的数据, `NetConvert.DEFAULT`为默认的转换器支持返回File/String/Response等 - - - -框架中自带一个`JSONConverter`可以作为参考或者直接使用. 其可以转换JSON数据. - -??? summary "JSONConverter 源码" - ```kotlin - /** - * 常见的JSON转换器实现, 如果不满意继承实现自定义的业务逻辑 - * - * @param success 后端定义为成功状态的错误码值 - * @param code 错误码在JSON中的字段名 - * @param message 错误信息在JSON中的字段名 - */ - abstract class JSONConvert( - val success: String = "0", - val code: String = "code", - val message: String = "msg" - ) : NetConverter { - - override fun onConvert(succeed: Type, response: Response): R? { - try { - // 此处是为了继承默认转换器支持的返回类型 - return NetConverter.onConvert(succeed, response) - } catch (e: ConvertException) { - val code = response.code - when { - code in 200..299 -> { // 请求成功 - val bodyString = response.body?.string() ?: return null - return try { - val json = JSONObject(bodyString) // 获取JSON中后端定义的错误码和错误信息 - if (json.getString(this.code) == success) { // 对比后端自定义错误码 - bodyString.parseBody(succeed) - } else { // 错误码匹配失败, 开始写入错误异常 - val errorMessage = json.optString( - message, - NetConfig.app.getString(com.drake.net.R.string.no_error_message) - ) - throw ResponseException(response, errorMessage) - } - } catch (e: JSONException) { // 固定格式JSON分析失败直接解析JSON - bodyString.parseBody(succeed) - } - } - code in 400..499 -> throw RequestParamsException(response, code.toString()) // 请求参数错误 - code >= 500 -> throw ServerResponseException(response, code.toString()) // 服务器异常错误 - else -> throw ConvertException(response) +??? example "转换器实现非常简单" + ```kotlin title="NetConverter.kt" linenums="1" + interface NetConverter { + + @Throws(Throwable::class) + fun onConvert(succeed: Type, response: Response): R? + + companion object DEFAULT : NetConverter { + /** + * 返回结果应当等于泛型对象, 可空 + * @param succeed 请求要求返回的泛型类型 + * @param response 请求响应对象 + */ + override fun onConvert(succeed: Type, response: Response): R? { + return when { + succeed === String::class.java && response.isSuccessful -> response.body?.string() as R + succeed === ByteString::class.java && response.isSuccessful -> response.body?.byteString() as R + succeed is GenericArrayType && succeed.genericComponentType === Byte::class.java && response.isSuccessful -> response.body?.bytes() as R + succeed === File::class.java && response.isSuccessful -> response.file() as R + succeed === Response::class.java -> response as R + else -> throw ConvertException(response, "An exception occurred while converting the NetConverter.DEFAULT") } } } - - /** - * 反序列化JSON - * - * @param succeed JSON对象的类型 - * @receiver 原始字符串 - */ - abstract fun String.parseBody(succeed: Type): R? } - ``` -JSONConvert的核心逻辑 - -1. 判断服务器的错误码 -1. 判断后端自定义的错误码 -1. 如果判断发生错误则抛出一个包含错误信息的异常 -1. 如果都判断成功则开始解析数据并return数据对象 - -在转换器中根据需要你可以在这里加上常见的解密数据, token失效跳转登录, 限制多端登录等逻辑. 日志信息输出请阅读: [日志记录器](log-recorder.md) - -如果是错误信息建议抛出异常, 就可以在全局异常处理器中统一处理, 请阅读:[全局错误处理](error-handle.md) - -
\ No newline at end of file +假设不支持你需要的数据类型, 例如JSON/ProtoBuf/Bitmap等请[自定义转换器](converter-customize.md#_3) \ No newline at end of file diff --git a/docs/cookie.md b/docs/cookie.md index 40483a027..676d4602d 100644 --- a/docs/cookie.md +++ b/docs/cookie.md @@ -1,15 +1,15 @@ -Net使用的是OkHttp的Cookie管理方案(CookieJar), 并且提供持久化存储的Cookie管理实现(PersistentCookieJar) +使用OkHttp的`CookieJar`, Net提供持久化Cookie`PersistentCookieJar` ```kotlin -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { +NetConfig.initialize(Api.HOST, this) { // 添加持久化Cookie cookieJar(PersistentCookieJar(this@App)) } ``` -PersistentCookieJar可以手动增删Cookie +可以手动增删Cookie -| 函数 | 描述 | +| PersistentCookieJar | 描述 | |-|-| | addAll | 添加Cookie | | getAll | 获取某个域名的所有Cookie | @@ -17,10 +17,10 @@ PersistentCookieJar可以手动增删Cookie | clear | 删除客户端全部Cookie | -你可以通过客户端可以获取到已设置的cookieJar +可通过客户端获取到已配置cookieJar ```kotlin (NetConfig.okHttpClient.cookieJar as? PersistentCookieJar)?.clear() ``` -
-PersistentCookieJar使用数据库实现Cookies存储, 你可以指定`dbName`来创建不同的数据库让不同的客户端隔绝Cookie共享 +!!! note "隔绝Cookies共享" + 为`PersistentCookieJar`指定不同`dbName`阻止不同的客户端共享Cookies \ No newline at end of file diff --git a/docs/coroutine-request.md b/docs/coroutine-request.md index befff75b1..43118885b 100644 --- a/docs/coroutine-request.md +++ b/docs/coroutine-request.md @@ -1,12 +1,6 @@ -Net在2.0开始引入协程来支持并发和异步, 虽然很多网络框架支持协程, 但是Net对于协程生命周期的控制算得上是独有. -并且Net不仅仅网络请求, 其也支持创建任何异步任务. +Net的协程作用域会自动处理协程生命周期 -> 这里的`同时/并发/并行`统称为并发(具体是不是并行不需要开发者来考虑) - -
-在上章节已经使用过了网络的并发请求 - -这里再演示同时(并发)请求百度网站`一万次`并且一次取消 +在上章节已经介绍如何发起并发网络请求, 这里演示同时(并发)请求`一万次`, 然后取消全部 ```kotlin val job = scopeNetLife { @@ -28,9 +22,10 @@ thread { } ``` +
-Net主要推荐使用的是协程请求, 但是同时支持其他方式发起请求 +Net主要使用协程请求, 但也支持其他方式发起请求 -- 协程请求 - [同步请求](sync-request.md) -- [回调请求](callback.md) \ No newline at end of file +- [回调请求](callback.md) +- 协程请求 diff --git a/docs/css/extra.css b/docs/css/extra.css index ab80fff67..a3ccf56a6 100644 --- a/docs/css/extra.css +++ b/docs/css/extra.css @@ -1,69 +1,31 @@ -:root > * { - --md-code-fg-color: #A9B7C6; - --md-code-bg-color: #2b2b2b; - --md-code-hl-color: #214283; - --md-code-hl-number-color: #82AAFF; - --md-code-hl-special-color: #A9B7C6; - --md-code-hl-function-color: #FFE64C; - --md-code-hl-constant-color: hsla(250, 70%, 64%, 1); - --md-code-hl-keyword-color: #CC7832; - --md-code-hl-string-color: #6A8759; - --md-code-hl-name-color: var(--md-code-fg-color); - --md-code-hl-operator-color: #A9B7C6; - --md-code-hl-punctuation-color: #A9B7C6; - --md-code-hl-comment-color: #787878; - --md-code-hl-generic-color: #A9B7C6; - --md-code-hl-variable-color: #A9B7C6; - - --md-typeset-color: #333333; - --drake-highlight: #d63200; - --drake-accent: #e95f59; - --drake-highlight-opacity: #d6320022; - --md-admonition-fg-color: #333333; - --drake-font-size: 13px; -} - -[data-md-color-scheme="drake"] { - --md-primary-fg-color: hsla(0, 0%, 100%, 1); - --md-primary-fg-color--light: hsla(0, 0%, 100%, 0.7); - --md-primary-fg-color--dark: hsla(0, 0%, 0%, 0.07); - --md-primary-bg-color: hsla(0, 0%, 0%, 0.87); - --md-primary-bg-color--light: hsla(0, 0%, 0%, 0.54); - --md-accent-fg-color: #d63200; - --md-accent-fg-color--light: #d63200; - --md-accent-fg-color--dark: #d63200; - --md-typeset-a-color: #d63200 !important; -} - -/*字体渲染*/ @font-face{ - font-family: 'JetBrains Mono'; - src: local('JetBrainsMono-Regular'), - url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Regular.woff2') format('woff2'); + font-family: 'Iosevka Curly'; + src: local('Iosevka Curly Medium'), + url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/iosevka-curly/iosevka-curly-medium.woff2') format('woff2'); font-display: swap; font-weight: normal; font-style: normal; } @font-face{ - font-family: 'JetBrains Mono'; - src: local('JetBrainsMono-Bold'), - url('https://raw.githubusercontent.com/JetBrains/JetBrainsMono/master/fonts/webfonts/JetBrainsMono-Bold.woff2') format('woff2'); + font-family: 'Iosevka Curly'; + src: local('Iosevka Curly Bold'), + url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/iosevka-curly/iosevka-curly-bold.woff2') format('woff2'); font-display: swap; font-weight: bold; font-style: normal; } @font-face{ - font-family: 'HYZhengYuan'; - src: local('HYZhengYuan-55W'), - url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYZhengYuan.ttf') format('truetype'); + font-family: 'HYYouYuan'; + src: local('HYYouYuan-55W'), + url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYYouYuan/HYYouYuan-55W.ttf') format('truetype'); font-display: swap; font-weight: normal; font-style: normal; } @font-face{ - font-family: 'HYZhengYuan'; - src: local('HYZhengYuan-75W'), - url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYZhengYuan-75W.ttf') format('truetype'); + font-family: 'HYYouYuan'; + src: local('HYYouYuan-75W'), + url('https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/font/HYYouYuan/HYYouYuan-75W.ttf') format('truetype'); font-display: swap; font-weight: bold; font-style: normal; @@ -74,241 +36,18 @@ -webkit-font-smoothing: subpixel-antialiased; -moz-osx-font-smoothing: auto; text-rendering: optimizeLegibility; - font-family: "JetBrains Mono", HYZhengYuan, monospace !important; -} - -/*布局*/ -.md-content { - max-width: 49.5rem; -} -.md-grid { - max-width: 80rem; -} - -/*表格*/ -.md-typeset__table { - display: block; - padding: 0 .8rem; - margin: 1em 0; -} -table tr:nth-child(2n), thead { - background-color: #fafafa; -} -.md-typeset table:not([class]) { - border-collapse: collapse; - border-spacing: 0px; - width: 100%; - break-inside: auto; - text-align: left; - display: table; - box-shadow:none; - font-size: var(--drake-font-size); -} - -.md-typeset table:not([class]) th { - border: 1px solid #dfe2e5; - background-color: #f2f2f2; - padding: 6px 13px; - font-weight: bold; - color: var(--md-typeset-color); -} -.md-typeset table:not([class]) td { - border: 1px solid #dfe2e5; -} - -/*隐藏搜索框, 因为不支持中文搜索*/ -.md-search__form { - visibility: hidden; + font-family: "Iosevka Curly", HYYouYuan !important; } -/*引用*/ -.md-typeset :is(.admonition,details) { - font-size: 12px; -} -[dir=ltr] .md-typeset blockquote { - border-left:none; -} -.md-typeset blockquote { - color: inherit; - padding: 10px 16px; - background-color: #fdefee; - position: relative; - border-left: none; - margin: 2em 0; -} -.md-typeset blockquote p { - margin: 0 0 !important; -} -.md-typeset blockquote:before { - display: block; - position: absolute; - content: ''; - width: 4px; - left: 0; - top: 0; - height: 100%; - background-color:var(--drake-accent); - border-radius: 4px; -} - -/*字间距*/ +code, +.md-nav, +.md-typeset code, .md-typeset { - line-height: 1.8; - font-size: var(--drake-font-size); -} -.md-typeset pre { - line-height: 1.6; -} - -/*标签*/ -.md-typeset .tabbed-set>label { - border-bottom: 2px solid transparent; - color: var(--md-typeset-color); - line-height: 1.3; - font-size: var(--drake-font-size); - margin-bottom: .8em; - font-weight:normal; -} -.md-typeset .tabbed-set>input:checked+label { - font-weight:500; - line-height: 1.3; - margin-bottom: .8em; -} - -/*侧边导航*/ -.md-nav__item .md-nav__link--active { - color: var(--drake-highlight); - font-weight:500; -} -.md-nav__title[for="__drawer"] { - display: none; -} -div .md-source__fact { - display: none; -} -.md-source__icon+.md-source__repository { - margin-left: -1em; - padding-left: 0; - font-weight: 500; -} -.md-nav__link { - font-size: var(--drake-font-size); - line-height: 1.6; + font-size: 14px !important; } -/*代码块*/ -.md-typeset code { - font-size: inherit; - border-radius: 2px !important; - border: none !important; -} -.md-typeset pre>code { - padding: 0.8em 0.8em; -} -code span::selection { - background: #214283; -} -.highlight code::selection { - background: #214283; -} - -/*代码片段*/ -p code, article > code, li > code, td > code, th > code, a > code { - background-color: transparent !important; - color: var(--drake-highlight) !important; - padding: 0 2px !important; -} - -img { - border-radius: 2px; - margin: 4px 0; -} - -/*链接*/ -.md-content a { - color: var(--drake-highlight) !important; - text-decoration: underline; - margin: 0 2px; -} - -/*编辑按钮*/ -.md-typeset .md-content__button { - color: var(--md-default-fg-color--lighter) !important; -} -.md-icon svg { - width: 14px; -} - -/*标题*/ -h1, h2, h3, h4, h5, h6, .md-header-nav__title { - font-weight: bold !important; - color: #273849; -} -.md-typeset h1 { - text-align: center; - font-size: 1.45em; - color:#273849; -} -.md-typeset h2 { - display: inline-block; - font-size: 1.45em; -} -h2:after { - display: block; - content: ''; - height: 2px; - margin-top: 4px; - background-color:#273849; - border-radius: 2px; - margin-right: 1.1em; -} - -/*清单*/ -.md-typeset [type=checkbox]:checked+.task-list-indicator:before { - background-color: #43A047; -} -.md-typeset .task-list-indicator:before { - background-color: #c7c7c7; -} -.md-typeset .task-list-control { - margin-right: 8px; -} - -/*复制图标*/ -.md-clipboard:after { - background-color: #4d4d4d; -} - -/*头部*/ -.md-ellipsis { - font-weight: bold; -} - -/*折叠块*/ -/*标题展开状态*/ -.md-typeset .admonition-title, .md-typeset summary { - border-left: none; - margin: 0; -} -/*标题背景*/ -.md-typeset .abstract>.admonition-title, .md-typeset .abstract>summary, .md-typeset .summary>.admonition-title, .md-typeset .summary>summary, .md-typeset .tldr>.admonition-title, .md-typeset .tldr>summary { - background-color: #f2f2f2; - border: 1px solid #dfe2e5; - font-weight: bold; -} -/*内容展开状态*/ -.md-typeset .admonition, .md-typeset details { - border-left: none; - box-shadow: none; - padding: 0; - font-size: var(--drake-font-size); -} -/*标题栏左侧图标*/ -.md-typeset .abstract>.admonition-title:before, .md-typeset .abstract>summary:before, .md-typeset .summary>.admonition-title:before, .md-typeset .summary>summary:before, .md-typeset .tldr>.admonition-title:before, .md-typeset .tldr>summary:before { - background-color: var(--md-admonition-fg-color); - top: .5rem; -} -/*箭头图标*/ -.md-typeset summary:after { - top: .5rem; +.highlight span.filename, +.md-typeset .admonition-title, +.md-typeset summary { + font-weight: normal; } \ No newline at end of file diff --git a/docs/debounce.md b/docs/debounce.md index 1dc087fcb..753c74fb1 100644 --- a/docs/debounce.md +++ b/docs/debounce.md @@ -1,14 +1,15 @@ -现在应用的搜索输入框一般情况下都是输入完搜索关键词后自动发起请求开始搜索 +!!! question "节流" + 在一定时间间隔内,只执行最后一次请求, 忽略其他多余的请求 -这个过程涉及到以下需求: +搜索输入框一般都是输入完关键词后自动开始搜索 -1. 不能每次变化都开始搜索请求, 这样会导致多余的网络资源浪费. 所以应该在用户停止输入后的指定时间后(默认800毫秒)开始搜索 -2. 当产生新的搜索请求后取消旧的请求以防止旧数据覆盖新数据 -3. 当输入内容没有变化(例如复制粘贴重复内容到搜索框)不会发起搜索请求 +这个过程涉及到 -
+1. 每次变化都搜索会导致服务器压力, 应在停止输入满足一定时间后自动搜索 +2. 当产生新的搜索请求后应取消旧请求, 以防止旧数据覆盖新数据 +3. 当输入内容没有变化(例复制粘贴重复内容到搜索框)不会发起搜索请求 -截图预览 +
@@ -17,24 +18,17 @@ ```kotlin var scope: CoroutineScope? = null -et_input.debounce().distinctUntilChanged().launchIn(this) { +// distinctUntilChanged 表示过滤掉重复结果 +binding.etInput.debounce().distinctUntilChanged().launchIn(this) { scope?.cancel() // 发起新的请求前取消旧的请求, 避免旧数据覆盖新数据 - scope = scopeNetLife { // 保存旧的请求到一个变量中, scopeNetLife其函数决定网络请求生命周期 - tvFragment.text = "请求中" - val data = Get("http://api.k780.com/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json").await() - tvFragment.text = JSONObject(data).getJSONObject("result").getString("datetime_2") + scope = scopeNetLife { // 保存旧的请求到一个变量中 + binding.tvFragment.text = "请求中" + binding.tvFragment.text = Get(Api.TIME).await() } } ``` -如果想要设置自己的节流阀超时时间请指定参数 +指定参数设置节流阀超时时间 ```kotlin fun EditText.debounce(timeoutMillis: Long = 800) -``` - -过滤掉重复结果使用函数`distinctUntilChanged` - -## 生命周期 -其生命周期依然遵守[网络请求作用域函数scope*](scope.md#_2) - -例如示例中使用的`scopeNetLife`就会在Activity或Fragment关闭时自动取消网络请求 \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/default-response.md b/docs/default-response.md deleted file mode 100644 index f0986701c..000000000 --- a/docs/default-response.md +++ /dev/null @@ -1,50 +0,0 @@ -Net支持请求返回的数据类型取决于你自己的转换器实现(即理论上支持返回任何对象): - -# Get<任何对象>("path").await() - -如果不自定义转换器默认支持返回以下数据类型 - -| 函数 | 描述 | -|-|-| -| String | 字符串 | -| ByteArray | 字节数组 | -| ByteString | 内部定义的一种字符串对象 | -| File | 文件对象, 这种情况其实应当称为[下载文件](download-file.md) | -| Response | 最基础的, 包含全部响应信息的对象(响应体/响应头/请求信息等) | - -使用示例 - -```kotlin -scopeNetLife { - Get("api").await().headers("响应头名称") // 返回响应头 -} -``` - -??? summary "默认使用的是: [NetConverter.DEFAULT](https://github.com/liangjingkanji/Net/blob/master/net/src/main/java/com/drake/net/convert/NetConverter.kt)" - ```kotlin - interface NetConverter { - - @Throws(Throwable::class) - fun onConvert(succeed: Type, response: Response): R? - - companion object DEFAULT : NetConverter { - /** - * 返回结果应当等于泛型对象, 可空 - * @param succeed 请求要求返回的泛型类型 - * @param response 请求响应对象 - */ - override fun onConvert(succeed: Type, response: Response): R? { - return when { - succeed === String::class.java && response.isSuccessful -> response.body?.string() as R - succeed === ByteString::class.java && response.isSuccessful -> response.body?.byteString() as R - succeed is GenericArrayType && succeed.genericComponentType === Byte::class.java && response.isSuccessful -> response.body?.bytes() as R - succeed === File::class.java && response.isSuccessful -> response.file() as R - succeed === Response::class.java -> response as R - else -> throw ConvertException(response, "An exception occurred while converting the NetConverter.DEFAULT") - } - } - } - } - ``` - -假设这里没有你需要的数据类型请[自定义转换器](/converter/#_3)(例如返回Json或Protocol) \ No newline at end of file diff --git a/docs/download-file.md b/docs/download-file.md index 21ef87811..6d88c710b 100644 --- a/docs/download-file.md +++ b/docs/download-file.md @@ -1,6 +1,4 @@ -## 简单下载 - -下载文件和普通的接口请求唯一区别就是泛型不同 +泛型改为`File`即可 ```kotlin scopeNetLife { @@ -8,11 +6,9 @@ scopeNetLife { } ``` -Download函数一调用就会开始执行下载文件请求, 然后`await`则会等待下载文件完成然后返回一个File对象 - ## 下载选项 -支持丰富的下载定制方案, 并且会不断地更新完善 +丰富的下载定制方案, 并且在不断更新 ```kotlin scopeNetLife { @@ -25,24 +21,22 @@ scopeNetLife { } ``` -配置选项 - -| 函数 | 描述 | -|-|-| -| setDownloadFileName | 下载的文件名称 | -| setDownloadDir | 下载保存的目录, 也支持包含文件名称的完整路径, 如果使用完整路径则无视`setDownloadFileName`设置 | -| setDownloadMd5Verify | 下载文件MD5校验, 如果服务器响应头`Content-MD5`值和指定路径已经存在的文件MD5相同, 则跳过下载直接返回该File | -| setDownloadFileNameConflict | 下载文件路径存在同名文件时是创建新文件(添加序号)还是覆盖, 例如`file_name(1).apk` | -| setDownloadFileNameDecode | 文件名称是否使用URL解码, 例如下载的文件名如果是中文, 服务器传输给你的会是被URL编码的字符串. 你使用URL解码后才是可读的中文名称 | -| setDownloadTempFile | 下载是否使用临时文件, 避免下载失败后覆盖同名文件或者无法判别是否已下载完整, 仅在下载完整以后才会显示为原有文件名 | -| addDownloadListener | 下载进度监听器, 具体介绍在[进度监听](progress.md)中 | - -> 不使用`await`函数则下载报错也不会被Net捕捉到, 将会被忽略, 使用await则会触发Net的错误处理, 终止当前作用域(scope)内其他网络请求, 被Net全局错误处理捕获 - -## 缓存文件 - -文件缓存推荐以下三种方式 - -- 文件判断: 这种方式比较自由, 你自己去判断本地磁盘是否有该文件, 没有才发起请求, 比如你根据文件名判断. 无需网络 -- 缓存模式: 占用设备两份空间(因为缓存和下载后的文件都要占空间), 并且读取缓存的时候会本地磁盘复制依旧有耗时. 如果下载地址动态可以自定义缓存Key. 无需网络 -- MD5校验: 这种比较安全, 就是由服务器返回文件的MD5给你, 请查看`BaseRequest.setDownloadMd5Verify`方法. 要求服务器返回指定响应头, 要求联网 \ No newline at end of file +| 配置选项 | 描述 | +| --------------------------- | ------------------------------------------------------------ | +| addDownloadListener | [下载进度监听器](progress.md) | +| setDownloadFileName | 下载文件名 | +| setDownloadDir | 下载目录 | +| setDownloadMd5Verify | 下载文件MD5校验 | +| setDownloadFileNameConflict | 下载文件同名冲突解决 | +| setDownloadFileNameDecode | 文件名Url解码中文 | +| setDownloadTempFile | 临时文件名 | + +## 重复下载 + +防止重复下载有以下方式 + +| 函数 | 描述 | +| -------- | --------------------------------------------------------- | +| 文件判断 | 判断本地是否存在同名文件 | +| 缓存模式 | 开启缓存, 占用设备两份空间(缓存/下载成功文件都占空间) | +| MD5校验 | 服务器返回`Content-MD5`, 客户端开启`setDownloadMd5Verify` | \ No newline at end of file diff --git a/docs/error-default.md b/docs/error-default.md deleted file mode 100644 index 0af61ee20..000000000 --- a/docs/error-default.md +++ /dev/null @@ -1,29 +0,0 @@ -Net具备完善的错误处理机制, 能捕获大部分网络请求或者异步任务导致的崩溃, 减少App崩溃和收集详细的错误信息 - -
-以下场景的抛出的异常会被Net捕获到(不会导致崩溃) - -1. 作用域内部 (scope**等函数大括号`{}`内部) -1. 拦截器中 (Interceptor/RequestInterceptor) -1. 转换器中 (NetConverter) - - -
- -如果捕获到错误默认会执行以下操作 - -- `Logcat`中会输出详细的异常堆栈信息, 如果想要输出更详细内容请阅读[自定义异常](error-exception.md) -- `Toast`吐司错误异常信息, 如果想要自定义或者国际化错误文本请阅读[自定义错误提示](error-tip.md) - -
-要改变以上的默认错误处理请阅读阅读[全局错误处理](error-global.md), 默认全局错误处理器实现源码: [NetErrorHandler](https://github.com/liangjingkanji/Net/blob/97c31dddde7ced5aa75411d2581c858ca494669e/net/src/main/java/com/drake/net/interfaces/NetErrorHandler.kt#L18)
- - -> 建议在全局错误处理器中将捕获到的Exception(除无网络异常意外)上报到崩溃统计平台 - - - - - - - diff --git a/docs/error-global.md b/docs/error-global.md index 5def0f6d8..4040d54d9 100644 --- a/docs/error-global.md +++ b/docs/error-global.md @@ -1,23 +1,30 @@ -Net可以通过实现`NetErrorHandler`接口来监听全局错误处理, 当你通过`setErrorHandler`后Net就不会再执行默认的错误处理了 +可实现`NetErrorHandler`接口来监听全局错误处理 -```kotlin -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { - - setErrorHandler(object : NetErrorHandler() { +=== "创建" + ```kotlin + class NetworkingErrorHandler : NetErrorHandler { override fun onError(e: Throwable) { - super.onError(e) - } - - override fun onStateError(e: Throwable, view: View) { - super.onStateError(e, view) + // .... 其他错误 + if (e is ResponseException && e.tag == 401) { // 判断异常为token失效 + // 打开登录界面或者弹登录失效对话框 + } } - }) -} -``` + } + ``` +=== "配置" + ```kotlin + NetConfig.initialize(Api.HOST, this) { + setErrorHandler(NetworkingErrorHandler)) + } + ``` -|场景|处理函数|处理方式| +|NetErrorHandler|使用场景|触发位置| |-|-|-| -|普通网络请求/自动加载框|`onError`| 默认吐司错误信息 | -|使用自动处理缺省页的作用域|`onStateError`| 仅部分错误信息会吐司, 因为缺省页不需要所有的错误信息都吐司(toast)提示, 因为错误页可能已经展示错误信息, 所以这里两者处理的函数区分. | +|`onError`| 吐司错误信息 | `scopeNetLife/scopeDialog` | +|`onStateError` | 要求错误显示在缺省页 |`PageRefreshLayout.scope/StateLayout.scope`| + + +!!! warning "以下情况全局错误处理无效" -> `scope/scopeLife`不会触发任何全局错误NetErrorHandler, 请使用单例错误处理方式`catch`, 因为其用于处理异步任务,不应当用于网络请求 \ No newline at end of file + 1. 异步任务作用域(`scope/scopeLife`)发生的错误 + 2. 使用[单例错误处理](error-single.md)处理的错误 \ No newline at end of file diff --git a/docs/error-single.md b/docs/error-single.md index 7a6c7a2d2..9c0dd088a 100644 --- a/docs/error-single.md +++ b/docs/error-single.md @@ -1,13 +1,8 @@ -单例捕获即只捕获某个作用域或者接口, 不会影响到全局 - -- 捕获请求 -- 捕获作用域 - -
+捕获当前请求/作用域错误, 将不会再被全局错误处理 ## 捕获请求 -一个作用域内常常有多个请求发生. 默认情况下一个请求发生错误就会取消当前作用域内部所有协程, 这个时候我们可以捕获错误请求来进行其他处理 +一个请求发生错误会取消当前作用域内所有请求, 但单独捕获后不会再影响其他请求 例如 ```kotlin @@ -27,7 +22,7 @@ scopeNetLife { Get("api2").await() // 上面失败, 此处继续执行 } ``` -当然如果你创建不同的作用域分别请求那是互不影响的 +当然如果创建不同的作用域分别请求那是互不影响的 ```kotlin scopeNetLife { Get("api").await() // 失败 @@ -45,15 +40,13 @@ scopeNetLife { scope { val data = Get("http://www.thisiserror.com/").await() }.catch { - // 协程内部发生错误回调, it为异常 + // 协程内发生错误回调, it为异常对象 }.finally { - // 协程内协程全部执行完成, it为异常(如果是正常结束则it为null) + // 协程执行完毕回调(不论成败), it为异常对象 } ``` -以下函数幕后字段`it`为异常对象, 如果正常完成it则为null. 如果属于请求被手动取消则it为`CancellationException` - -| 函数 | 描述 | +| 函数 | 区别 | |-|-| -| catch | 作用域被`catch`则不会被传递到全局异常处理回调中: [全局处理异常](exception-handle.md), 除非使用`handleError`再次传递给全局 | -| finally | 同样可以获取到异常对象, 且不影响全局异常回调处理 | \ No newline at end of file +| catch | 发生错误后回调, 取消不回调
不会再执行全局错误处理, 可使用`handleError`再次传递给全局 | +| finally | 执行完毕回调(不论成败), 取消作用域时`it`为`CancellationException`
执行全局错误处理 | \ No newline at end of file diff --git a/docs/error-exception.md b/docs/error-throws.md similarity index 53% rename from docs/error-exception.md rename to docs/error-throws.md index 8274db285..ceb75213c 100644 --- a/docs/error-exception.md +++ b/docs/error-throws.md @@ -29,55 +29,51 @@ > 转换器中发生的所有异常除非是NetException的子类否则都将被ConvertException包裹(即捕获的是ConvertException, cause才为实际抛出异常). -## 使用异常属性 +## 异常传递字段 -Net自带的一些异常都会有一个类型为Any的属性`tag`. 可以用来传递任何对象来用于判断错误类型. 比如`ResponseException`我常用于作为请求服务器成功但是服务器业务错误. 然后tag为业务错误码 +Net自带异常会有类型为Any的字段`tag`, 可用传递对象用于判断错误处理 + +例如`ResponseException`常用于作为请求服务器成功但后端业务错误, 然后tag为传递的错误码 示例代码 -在转换器中获取401 - -```kotlin -class SerializationConverter( - val success: String = "0", - val code: String = "code", - val message: String = "msg" -) : NetConverter { - - override fun onConvert(succeed: Type, response: Response): R? { - try { - return NetConverter.onConvert(succeed, response) - } catch (e: ConvertException) { - val code = response.code - when { - code in 200..299 -> { // 请求成功 - // ... 假设Token失效. 后端返回业务错误码 srvCode = 401 - throw ResponseException(response, errorMessage, tag = srvCode) // 将业务错误码作为tag传递 +=== "转换器抛出异常" + + ```kotlin + class SerializationConverter( + val success: String = "0", + val code: String = "code", + val message: String = "msg" + ) : NetConverter { + + override fun onConvert(succeed: Type, response: Response): R? { + try { + return NetConverter.onConvert(succeed, response) + } catch (e: ConvertException) { + val code = response.code + when { + code in 200..299 -> { // 请求成功 + // ... 假设Token失效. 后端返回业务错误码 srvCode = 401 + throw ResponseException(response, errorMessage, tag = srvCode) // 将业务错误码作为tag传递 + } + code in 400..499 -> throw RequestParamsException(response, code.toString()) // 请求参数错误 + code >= 500 -> throw ServerResponseException(response, code.toString()) // 服务器异常错误 + else -> throw ConvertException(response) } - code in 400..499 -> throw RequestParamsException(response, code.toString()) // 请求参数错误 - code >= 500 -> throw ServerResponseException(response, code.toString()) // 服务器异常错误 - else -> throw ConvertException(response) } } } -} -``` - -全局错误处理器 - -```kotlin -// 创建错误处理器 -MyErrorHandler : NetErrorHandler { - override fun onError(e: Throwable) { - // .... 其他错误 - if (e is ResponseException && e.tag == 401) { // 判断异常为token失效 - // 打开登录界面或者弹登录失效对话框 + ``` + +=== "全局错误处理异常" + + ```kotlin + class NetworkingErrorHandler : NetErrorHandler { + override fun onError(e: Throwable) { + // .... 其他错误 + if (e is ResponseException && e.tag == 401) { // 判断异常为token失效 + // 打开登录界面或者弹登录失效对话框 + } } } -} - -// 初始化Net的时候设置错误处理器 -NetConfig.initialize("host", this) { - setErrorHandler(MyErrorHandler() -} -``` \ No newline at end of file + ``` \ No newline at end of file diff --git a/docs/error-tip.md b/docs/error-tip.md index 8e4221c29..bd5ddaa83 100644 --- a/docs/error-tip.md +++ b/docs/error-tip.md @@ -1,31 +1,63 @@ -网络请求发生错误一定要提示给用户, 提示语一般情况下是可读的语义句 +!!! question "网络错误提示" + 网络请求发生错误一定要提示给用户, 并且是可读语义句 -如果你是想修改默认吐司的错误文本信息或者做国际化语言可以参考以下方法 +如需修改默认吐司错误文本或者国际化语言可以参考以下方法 ## 创建多语言 -默认错误处理的文本被定义在`strings.xml`中, 我们可以在项目中创建多语言values来创建同名string实现国际化. 比如英语是`values-en`下创建文件`strings.xml` - -```xml - -连接网络失败 -当前网络不可用 -请求资源地址错误 -无法找到指定服务器主机 -连接服务器超时,%s -下载过程发生错误 -读取缓存失败 -解析数据时发生异常 -请求失败 -请求参数错误 -服务响应错误 -发生空异常 -未知网络错误 -未知错误 -无错误信息 -``` +错误提示文本被定义在框架中`strings.xml`, 在自己应用项目中创建同名`name`可复写Net定义的文本 + +或者中创建多语言values实现国际化, 例如英语是`values-en`下创建文件`strings.xml` + +??? example "错误文本" + ```xml + + 连接网络失败 + 请求资源地址错误 + 无法找到指定服务器主机 + 连接服务器超时,%s + 下载过程发生错误 + 读取缓存失败 + 解析数据时发生异常 + 请求失败 + 请求参数错误 + 服务响应错误 + 发生空异常 + 未知网络错误 + 未知错误 + 无错误信息 + + + 加载中 + ``` ## 创建NetErrorHandler -这实际上就是[自定义全局错误处理](error-global.md), 不过你可以复制默认的实现仅修改下文本信息即可 +使用[自定义全局错误处理](error-global.md)完全复写, 可以不提示错误或者上传错误日志 + +??? example "全局错误处理" + ```kotlin + fun onError(e: Throwable) { + val message = when (e) { + is UnknownHostException -> NetConfig.app.getString(R.string.net_host_error) + is URLParseException -> NetConfig.app.getString(R.string.net_url_error) + is NetConnectException -> NetConfig.app.getString(R.string.net_connect_error) + is NetSocketTimeoutException -> NetConfig.app.getString( + R.string.net_connect_timeout_error, + e.message + ) + is DownloadFileException -> NetConfig.app.getString(R.string.net_download_error) + is ConvertException -> NetConfig.app.getString(R.string.net_parse_error) + is RequestParamsException -> NetConfig.app.getString(R.string.net_request_error) + is ServerResponseException -> NetConfig.app.getString(R.string.net_server_error) + is NullPointerException -> NetConfig.app.getString(R.string.net_null_error) + is NoCacheException -> NetConfig.app.getString(R.string.net_no_cache_error) + is ResponseException -> e.message + is HttpFailureException -> NetConfig.app.getString(R.string.request_failure) + is NetException -> NetConfig.app.getString(R.string.net_error) + else -> NetConfig.app.getString(R.string.net_other_error) + } - + Net.debug(e) + TipUtils.toast(message) + } + ``` diff --git a/docs/error.md b/docs/error.md new file mode 100644 index 000000000..47b3e80f9 --- /dev/null +++ b/docs/error.md @@ -0,0 +1,25 @@ +Net有完善的错误处理机制, 具备捕获异常/取消请求/错误提示/追踪链路 + +!!! success "收集网络日志" + 在Net作用域内发生的异常都会被全局错误处理捕获, 可以将其筛选上传日志 + +
+以下位置抛出异常会被捕获 + +| 函数 | 描述 | +|-|-| +| 作用域 | `scopeXX`代码块中 | +| 拦截器 | `Interceptor/RequestInterceptor` | +| 转换器 | `NetConverter` | + +如果捕获到错误默认会执行以下操作 + +1. `Logcat`输出异常堆栈信息, [自定义异常抛出](error-throws.md) +2. `Toast`显示错误文本, [自定义错误提示](error-tip.md) + +
+!!! failure "捕获不到异常" + 如果请求未执行`await()`, 那么即使发生错误也不会被捕获到 + +
+自定义请阅读[全局错误处理](error-global.md) \ No newline at end of file diff --git a/docs/exception-track.md b/docs/exception-track.md deleted file mode 100644 index a9f2fdac5..000000000 --- a/docs/exception-track.md +++ /dev/null @@ -1,28 +0,0 @@ -Net中网络请求导致的异常都会在LogCat中打印, 同时被全局的NetErrorHandler的onError拦截到(除非catch住) - - -演示访问一个不存在的请求路径 -```kotlin -scopeNetLife { - tvFragment.text = Get("https://githuberror.com/liangjingkanji/Net/").await() -} -``` - -查看LogCat可以看到异常堆栈信息, 这属于URL未知异常 - - - -截图可以看到有具体的URL和请求代码位置 - - -### 关闭日志 - -在初始化时候可以关闭日志打印 - -```kotlin -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { - setDebug(false) // 关闭日志, 我们一般使用 BuildConfig.DEBUG -} -``` - -或者设置字段`NetConfig.debug`的值 \ No newline at end of file diff --git a/docs/fastest.md b/docs/fastest.md index 6d38e8418..4705ddc6e 100644 --- a/docs/fastest.md +++ b/docs/fastest.md @@ -1,7 +1,10 @@ -Net支持多个接口请求并发, 仅返回最快的请求结果, 剩余请求将被自动取消, 同样可以用于筛选掉无法响应的域名 -
+多个请求并发执行 -> 接口请求错误被忽略(LogCat依然可以看到异常信息), 但如果所有请求全部异常则抛出最后一个请求的异常作为错误处理 +1. 当某个请求成功时, 剩余请求将被自动取消 +2. 全部错误时则会抛出最后一个请求错误作为结果 + +!!! Note "异常信息" + 先失败的请求错误会被忽略, 但LogCat依然会输出异常 示例 ```kotlin @@ -21,31 +24,24 @@ scopeNetLife { ## 取消剩余 -上面的示例代码实际上不会在获取到最快的结果后自动取消请求, 我们需要`setGroup()`才可以 +上面的示例不会在获取到结果后取消剩余请求, 需设置同一请求分组才可以 ```kotlin scopeNetLife { // 同时发起四个网络请求 - val deferred2 = Get("api") { setGroup("最快") } - val deferred3 = Post("api") { setGroup("最快") } - val deferred = Get("api0") { setGroup("最快") } // 错误接口 - val deferred1 = Get("api1") { setGroup("最快") } // 错误接口 + val deferred2 = Get("api") { setGroup("初始化") } + val deferred3 = Post("api") { setGroup("初始化") } + val deferred = Get("api0") { setGroup("初始化") } // 错误接口 + val deferred1 = Get("api1") { setGroup("初始化") } // 错误接口 // 只返回最快的请求结果 - tvFragment.text = fastest(listOf(deferred, deferred1, deferred2, deferred3), "最快") + tvFragment.text = fastest(listOf(deferred, deferred1, deferred2, deferred3), "初始化") } ``` -网络请求的取消本质上依靠`group`来辨别,如果使用`setGroup`函数设置分组名称就可以在返回最快结果后取消掉其他网络请求, 反之不会取消其他网络请求 -
- -> group可以是任何类型任何值, 只有请求的`setGroup`参数和`fastest`函数的group参数等于即可 - -
- ## 类型不一致 -假设并发的接口返回的数据类型不同 或者想要监听最快请求返回的结果回调请使用`transform`函数 +当需要返回结果, 但多接口返回数据类型不同, 使用`transform`转换为同一类型结果 ```kotlin scopeNetLife { @@ -64,13 +60,9 @@ scopeNetLife { } ``` -有的场景下并发的接口返回的数据类型不同, 但是fastest只能返回一个类型, 我们可以使`transform`的回调函数返回结果都拥有一个共同的接口, 然后去类型判断 - -
- > 只有最快返回结果的网络请求(或异步任务)的`transform`回调才会被执行到 -## 捕获Fastest +## 捕获错误 ```kotlin scopeNetLife { val task = Get("api2") @@ -78,16 +70,12 @@ scopeNetLife { val task2 = Get("api2") val data = try { - fastest(task, task1, task2) // 当 task/task1/task2 全部异常之后再并发执行 backupTask/backupTask1 + fastest(listOf(task, task1, task2)) + // 当task/task1/task2全部错误后才并发执行backupTask/backupTask1 } catch (e: Exception) { val backupTask = Get("api2") val backupTask1 = Get("api") - fastest(backupTask, backupTask1) + fastest(listOf(backupTask, backupTask1)) } } -``` - - -
- -> 不要尝试使用这种方式来取代CDN加速 \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/https.md b/docs/https.md index ff3a0e9f1..f3df3fb80 100644 --- a/docs/https.md +++ b/docs/https.md @@ -1,10 +1,8 @@ -Https访问主要就是证书配置问题, Net可以使用OkHttp一切函数组件, 且简化证书配置流程 - -> OkHttp如何配置证书, Net就可以如何配置证书 +Net可快速配置Https证书, 或者使用OkHttp的方式 ## 使用CA证书 -Https如果是使用的CA颁发的证书, 不需要任何配置Net可以直接访问 +Https如果使用的CA证书, 不需要任何配置可以直接访问 ```kotlin scopeNetLife { @@ -14,12 +12,12 @@ scopeNetLife { ## 信任所有证书 -信任所有证书可以解决无法访问私有证书的Https地址问题, 但是这么做就失去了使用证书的含义, 不建议此做法 +信任所有证书可以解决无法访问私有证书的Https地址问题 === "全局配置" ```kotlin - NetConfig.initialize("https://github.com/liangjingkanji/Net/", this){ + NetConfig.initialize(Api.HOST, this){ trustSSLCertificate() // 信任所有证书 } ``` @@ -37,12 +35,12 @@ scopeNetLife { ## 导入证书 -私有证书可以放到任何位置, 只要能读取到`InputStream`流对象即可 +私有证书可以放到任何位置, 只要读取到`InputStream`流对象即可 === "全局配置" ```kotlin - NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { + NetConfig.initialize(Api.HOST, this) { val privateCertificate = resources.assets.open("https.certificate") setSSLCertificate(privateCertificate) } diff --git a/docs/img/book-open.svg b/docs/img/book-open.svg new file mode 100644 index 000000000..b0cbc997c --- /dev/null +++ b/docs/img/book-open.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/img/code-preview.png b/docs/img/code-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..b6fd23b9930be0d509b7d603214f33ebc409a628 GIT binary patch literal 26782 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�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 literal 0 HcmV?d00001 diff --git a/docs/img/preview.png b/docs/img/preview.png new file mode 100644 index 0000000000000000000000000000000000000000..da466e86e862dc7bc46ea5ee1fb5f4239529075c GIT binary patch literal 189960 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%0D}PFq3wQ-BXS}|O9U#gqMrI>2?5O3w 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 literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md index 01ee9d217..599bea4ef 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,71 +1,70 @@ -本框架使用Android主流的Http框架OkHttp作为请求内核, 遵守不影响OkHttp原有的函数组件使用原则开发 +Net是基于[OkHttp](https://github.com/square/okhttp)/协程的非侵入式框架(可使用所有Api), 可升级OkHttp版本保持网络安全
-

STAR/分享可以让更多人参与到本开源项目, 点击文档右上角小铅笔可直接修订文档 ↗

+

STAR/分享可以让更多人参与到本开源项目


-## 前言 - -1. 任何本文档没有提到的功能都可以通过搜索`"OkHttp如何**"`来解决, 因为本框架支持OkHttp所有功能/组件 -1. 建议创建一个Api.kt的`object`单例类存储所有请求路径常量 -1. `Post/Get等`函数属于请求动作. `scope**`等函数属于作用域, 假设你有某个请求需要重复使用建议封装`请求动作`而不是作用域 -1. 如果你觉得文档看不懂或者有歧义那肯定是作者问题, 请反馈给作者或者自我修订 +!!! note "前言" + - 未实现功能可以搜索`"OkHttp如何XX"`来扩展 + - 阅读示例/源码来学习如何封装 + - 如果觉得文档看不懂那肯定是作者问题, 请反馈给作者或者自我修订 ## 使用
-这里演示发起网络`请求百度网站`内容的三个步骤 +发起网络请求的三个步骤 1. 创建作用域 -1. 发起请求 -1. 接收数据 +1. 发起请求动作 +1. 等待数据返回 -=== "单个请求" +=== "简单请求" ```kotlin - scopeNetLife { // 创建作用域 + scopeNetLife { 创建作用域 // 这个大括号内就属于作用域内部 val data = Get("https://github.com/liangjingkanji/Net/").await() // 发起GET请求并返回`String`类型数据 } ``` -=== "串行请求" +=== "同步请求" ```kotlin scopeNetLife { - val data = Get("http://www.baidu.com/").await() // 请求A 发起GET请求并返回数据 - val data = Get("https://github.com/liangjingkanji/Net/").await() // 请求B 将等待A请求完毕后发起GET请求并返回数据 + val userInfo = Get("https://github.com/liangjingkanji/BRV/").await() // 立即请求 + val config = Get("https://github.com/liangjingkanji/Net/"){ + param("userId", userInfo.id) // 使用上个请求的数据作为参数 + }.await() // 请求B 将等待A请求完毕后发起GET请求并返回数据 } ``` === "并发请求" ```kotlin scopeNetLife { // 以下两个网络请求属于同时进行中 - val aDeferred = Get("https://github.com/liangjingkanji/Net/") // 发起GET请求并返回一个对象(Deferred)表示"任务A" - val bDeferred = Get("https://github.com/liangjingkanji/Net/") // 发起请求并返回"任务B" + val getUserInfoAsync = Get("https://github.com/liangjingkanji/Net/") // 立即请求 + val getConfigAsync = Get("https://github.com/liangjingkanji/BRV/") // 立即请求 - // 随任务同时进行, 但是数据依然可以按序返回 - val aData = aDeferred.await() // 等待任务A返回数据 - val bData = bDeferred.await() // 等待任务B返回数据 + val userInfo = getUserInfoAsync.await() // 等待数据返回 + val config = getConfigAsync.await() } ``` -多个网络请求放在同一个作用域内就可以统一控制, 如果你的多个网络请求毫无关联, 你可以创建多个作用域. +多个网络请求在同一个作用域内可以统一管理 -> 多进程或Xposed项目要求[初始化](config.md/#_1) +如果多个网络请求之间毫无关联, 可以创建多个作用域来请求 -
+!!! failure "强制初始化" + 多进程或Xposed项目要求先[初始化](config.md#_1) -> 当`Get`或`Post`等函数调用就会开始发起网络请求, `await`只是等待其请求成功返回结果, 所以如果你在`await`后执行的网络请求,这不属于并发(属于串行) +并发请求错误示例 -并发的错误示例 ```kotlin hl_lines="3" scopeNetLife { // 请求A - val aDeferred = Get("https://github.com/liangjingkanji/Net/").await() - // 请求B, 由于上面使用`await()`函数, 所以必须等待A请求返回结果后才会执行B - val bDeferred = Get("https://github.com/liangjingkanji/Net/") + val userInfo = Get("https://github.com/liangjingkanji/Net/").await() + // 由于上面使用`await()`函数, 所以必须等待A请求返回结果后才会执行B + val getConfigAsync = Post("https://github.com/liangjingkanji/Net/") - val bData = bDeferred.await() // 等待任务B返回数据 + val config = getConfigAsync.await() // 等待任务B返回数据 } ``` @@ -81,38 +80,16 @@ scopeNetLife { | Response | 最基础的响应 | | File | 文件对象, 这种情况其实应当称为[下载文件](download-file.md) | -非以上类型要求[自定义转换器](converter.md) - -> 转换器的返回值决定你的网络请求的返回结果类型, 你甚至可以返回null, 前提是泛型为可空类型 - - -## RestFul -Net支持RestFul设计风格 - ```kotlin scopeNetLife { - tvFragment.text = Get("https://github.com/liangjingkanji/Net/").await() - tvFragment.text = Post("https://github.com/liangjingkanji/Net/").await() - tvFragment.text = Head("https://github.com/liangjingkanji/Net/").await() - tvFragment.text = Put("https://github.com/liangjingkanji/Net/").await() - tvFragment.text = Patch("https://github.com/liangjingkanji/Net/").await() - tvFragment.text = Delete("https://github.com/liangjingkanji/Net/").await() - tvFragment.text = Trace("https://github.com/liangjingkanji/Net/").await() - tvFragment.text = Options("https://github.com/liangjingkanji/Net/").await() + val file = Get(Api.FILE).await() } ``` -## 函数 +详细查看[转换器](converter.md), 非以上类型要求[自定义转换器](converter-customize.md) -默认在IO线程执行网络请求(通过作用域参数可以控制Dispatch调度器), 要求在协程作用域内执行. - -|请求函数|描述| -|-|-| -| [Get](api/-net/com.drake.net/-get.html)|标准Http请求方法| -| [Post](api/-net/com.drake.net/-post.html)|标准Http请求方法| -| [Head](api/-net/com.drake.net/-head.html)|标准Http请求方法| -| [Options](api/-net/com.drake.net/-options.html)|标准Http请求方法| -| [Trace](api/-net/com.drake.net/-trace.html)|标准Http请求方法| -| [Delete](api/-net/com.drake.net/-delete.html)|标准Http请求方法| -| [Put](api/-net/com.drake.net/-put.html)|标准Http请求方法| -| [Patch](api/-net/com.drake.net/-patch.html)|标准Http请求方法| \ No newline at end of file +--- +[下载Apk](https://github.com/liangjingkanji/Net/releases/latest/download/net-sample.apk){ .md-button } +[下载源码](https://github.com/liangjingkanji/Net.git){ .md-button } +[示例代码](https://github1s.com/liangjingkanji/Net/blob/HEAD/sample/src/main/java/com/drake/net/sample/ui/fragment/RequestMethodFragment.kt){ .md-button } +[界面演示](https://github.com/liangjingkanji/BRV){ .md-button } diff --git a/docs/interceptor.md b/docs/interceptor.md index f489654ff..286193bc1 100644 --- a/docs/interceptor.md +++ b/docs/interceptor.md @@ -1,11 +1,12 @@ -总共支持两种拦截器 +根据使用场景选择 -1. `Interceptor`, 支持市面上的所有OkHttp拦截器组件库, 更方便修改请求/响应信息, 可以转发请求 -2. `RequestInterceptor`, 部分场景更简单易用的轻量拦截器, 更方便添加全局请求头/参数, 无法转发请求 +1. `Interceptor`: 支持三方OkHttp拦截器组件, 允许修改请求/响应信息, 可以转发请求 +2. `RequestInterceptor`: Net独有的轻量级拦截器, 允许修改全局请求头/请求参数, 无法转发请求
-> 实际项目中可能存在需求加密请求/解密响应, 非常不建议封装Post/Get等请求动作(低扩展性/增加学习成本), 任何项目需求都可以通过自定义拦截器和转换器实现
-> [常见拦截器示例](https://github.com/liangjingkanji/Net/tree/master/sample/src/main/java/com/drake/net/sample/interceptor) +!!! Failure "禁止随意封装" + 不应为全局参数/加密等封装请求方法, 应自定义拦截器/转换器来实现, [常见拦截器示例](https://github.com/liangjingkanji/Net/tree/master/sample/src/main/java/com/drake/net/sample/interceptor) + ## 拦截器 @@ -13,18 +14,12 @@ 添加拦截器 ```kotlin -class App : Application() { - override fun onCreate() { - super.onCreate() - - NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { - addInterceptor(RefreshTokenInterceptor()) - } - } +NetConfig.initialize(Api.HOST, this) { + addInterceptor(RefreshTokenInterceptor()) } ``` -以下为简单演示客户端自动刷新token拦截器 +演示客户端自动刷新token的拦截器 ```kotlin /** @@ -50,12 +45,10 @@ class RefreshTokenInterceptor : Interceptor { ## 请求拦截器 -RequestInterceptor属于轻量级的请求拦截器, 在每次请求的时候该拦截器都会被触发(无法修改响应信息), 方便添加全局请求头/参数 - -示例 +轻量级拦截器(`RequestInterceptor`), 其Api更适合添加全局请求头/参数 ```kotlin -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { +NetConfig.initialize(Api.HOST, this) { setRequestInterceptor(object : RequestInterceptor { override fun interceptor(request: BaseRequest) { request.param("client", "Net") @@ -65,4 +58,4 @@ NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { } ``` -可以看到`setRequestInterceptor`是set开头. 仅支持一个请求拦截器, 不像`addInterceptor`支持多个请求拦截器 \ No newline at end of file +可以看出`setRequestInterceptor`仅支持一个, `addInterceptor`支持多个拦截器 \ No newline at end of file diff --git a/docs/interval.md b/docs/interval.md index 094c54a4a..b3e68ad8a 100644 --- a/docs/interval.md +++ b/docs/interval.md @@ -1,4 +1,4 @@ -轮询器(计时器)属于项目中常见需求, 而本框架拥有非常强大的轮询器工具. 支持以下特性 +Net自带轮询器(计时器), 包含以下特性 - 正计时 - 倒计时 @@ -8,11 +8,11 @@ - 页面销毁自动取消
-=== "指定轮循次数/间隔" +=== "限制次数/间隔" ```kotlin interval = Interval(100, 1, TimeUnit.SECONDS).life(this) // 自定义计数器个数的轮询器 ``` -=== "仅轮循间隔" +=== "无限执行" ```kotlin interval = Interval(1, TimeUnit.SECONDS) // 每秒回调一次, 不会自动结束 ``` @@ -31,7 +31,7 @@ interval.subscribe { }.start() ``` -| Interval函数 | 描述 | +| Interval | 描述 | |-|-| | start | 开始轮询器 | | stop | 停止 | @@ -45,4 +45,5 @@ interval.subscribe { | life | 指定生命周期自动取消轮询器 | | onlyResumed | 当界面不可见时暂停, 当界面可见时继续 | -[完整源码](https://github.com/liangjingkanji/Net/blob/master/sample/src/main/java/com/drake/net/sample/ui/fragment/SuperIntervalFragment.kt) \ No newline at end of file +!!! failure "后台运行" + 由于系统限制, 本工具无法保证后台运行 \ No newline at end of file diff --git a/docs/issues.md b/docs/issues.md index 5c6bd24ce..eacaf6d72 100644 --- a/docs/issues.md +++ b/docs/issues.md @@ -1,8 +1,19 @@ -## 常见问题 - -- [networkSecurityConfig导致无法打包](https://github.com/liangjingkanji/Net/issues/57) -- [没有我需要的请求参数类型](https://github.com/liangjingkanji/Net/issues/56) -- [没有我需要的功能](https://github.com/liangjingkanji/Net/issues/58) -- [使用inline reified封装请求函数导致崩溃](https://github.com/liangjingkanji/Net/issues/54) -- [错误提示 toast 内存泄漏](https://github.com/liangjingkanji/Net/issues/65) -- [如何使用Cookie](https://github.com/liangjingkanji/Net/issues/51) +Net最大的特点在于完美支持OkHttp的所有功能组件, 而OkHttp是Android主流网络解决方案 + +所以如果存在Net没有实现的功能可以谷歌搜索`"OkHttp如何实现XX"`, 然后可以很容易在Net中使用 + +## 低版本兼容 + +如果你是在 Android 5 (API level 21) +以上开发建议使用最新版本: [Net](https://github.com/liangjingkanji/Net)
+如果要求低至 Android 4.4 (API level 19) +请使用兼容版本: [Net-okhttp3](https://github.com/liangjingkanji/Net-okhttp3) + +如果需更低版本支持建议拉取仓库修改`minSdkVersion`后编译成aar使用 + +## 开发者交流 + +- [反馈问题](https://github.com/liangjingkanji/Net/issues) +- [其他开发者提及问题](https://github.com/liangjingkanji/Net/issues) +- [交流社区](https://github.com/liangjingkanji/Net/discussions) +-
diff --git a/docs/kotlin-serialization.md b/docs/kotlin-serialization.md index 8bfbba6c1..0a2bc6678 100644 --- a/docs/kotlin-serialization.md +++ b/docs/kotlin-serialization.md @@ -1,25 +1,30 @@ -1. 从Net3开始支持使用[kotlin-serialization](https://github.com/Kotlin/kotlinx.serialization)(以下简称ks) +1. 从Net3开始支持使用 [Kotlin-Serialization](https://github.com/Kotlin/kotlinx.serialization) (以下简称ks) +2. 更多了解请阅读: [Kotlin最强解析库 - kotlin-serialization](https://juejin.cn/post/6963676982651387935) -1. 更多使用教程请阅读: [Kotlin最强解析库 - kotlin-serialization](https://juejin.cn/post/6963676982651387935) +## 特点 -
-## kotlin-serialization 特点 - -- kotlin官方库 +- 官方库 - 动态数据类型解析 - 自定义序列化器 - 支持ProtoBuf/JSON等数据结构序列化 -- 非空覆盖(即返回的Json字段为null则使用数据类默认值) -- 启用宽松模式, Json和数据类字段无需一致 +- 非空覆盖(即JSON字段为null则使用变量默认值) +- 启用宽松模式, JSON和数据类结构无需一致 - 解析任何类型(Map/List/Pair...) -> ks的数据模型类都要求使用注解`@Serializable`(除非自定义解析过程), 父类和子类都需要使用
-> 一般开发中都是使用[插件生成数据模型](model-generate.md), 所以这并不会增加工作量. 即使手写也只是一个注解, 但是可以带来默认值支持和更安全的数据解析 +!!! Failure "强制注解" + ks的数据类都要求使用注解`@Serializable`(除非自定义解析), 父类和子类都需要 + + + +!!! Success "生成默认值" + 使用[插件生成数据Model](model-generate.md), 支持自动生成默认值和注解 + + 生成默认值可避免后端返回异常数据导致解析崩溃, 以及反复编写判空代码 ## 依赖 -项目 build.gradle +Project build.gradle ```kotlin @@ -27,7 +32,7 @@ classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" // 和Kotlin插件同一个版本号即可 ``` -module build.gradle +Model build.gradle ```kotlin apply plugin: "kotlin-kapt" @@ -37,12 +42,11 @@ implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0" ## 配置转换器 -这里使用Demo中的[SerializationConvert](https://github.com/liangjingkanji/Net/blob/master/sample/src/main/java/com/drake/net/sample/converter/SerializationConverter.kt)作演示. -如果你业务有特殊需要可以复制Demo中的转换器代码稍加修改 +这里使用示例代码中 [SerializationConvert](https://github.com/liangjingkanji/Net/blob/master/sample/src/main/java/com/drake/net/sample/converter/SerializationConverter.kt) 作为演示 === "全局配置" ```kotlin - NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { + NetConfig.initialize(Api.HOST, this) { setConverter(SerializationConvert()) // ... 其他配置 } @@ -58,7 +62,6 @@ implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.0" ```kotlin scopeNetLife { - // 这里后端直接返回的Json数组 val userList = Get>("list") { converter = SerializationConvert() }.await() @@ -72,9 +75,6 @@ scopeNetLife { data class UserModel(var name: String, var age: Int, var height: Int) ``` -> 具体解析返回的JSON中的某个字段请在转换器里面自定, 其注意如果存在父类, 父类和子类都需要使用`@Serializable`注解修饰
-如果想详细了解KS, 请阅读文章: [Kotlin最强解析库 - kotlin-serialization](https://juejin.cn/post/6963676982651387935) - ## 常用配置 以下为反序列化Json常用配置 @@ -91,7 +91,6 @@ val jsonDecoder = Json { @Serializable data class Data(var name:String = "", var age:Int = 0) ``` -> 建议为数据类所有字段设置默认值, 避免后端数据缺失导致解析异常, 也减少频繁判空操作 ### 启用默认值 diff --git a/docs/log-notice.md b/docs/log-notice.md index 1d66f83dc..1212ef99f 100644 --- a/docs/log-notice.md +++ b/docs/log-notice.md @@ -1,10 +1,11 @@ -前面介绍过如何使用AndroidStudio来抓取网络日志. 但是我们可能需要在App使用过程或者让测试人员查看日志 +使用三方库 [Chucker](https://github.com/ChuckerTeam/chucker) 在通知栏记录网络请求日志 -这里介绍一个第三方日志拦截器[chucker](https://github.com/ChuckerTeam/chucker), 他会在Net发起请求后自动在设备通知栏显示网络请求记录, 点击通知可以跳转详情 +
+ ![](https://github.com/ChuckerTeam/chucker/raw/main/assets/chucker-http.gif){ width="300" } +
Chucker
+
- - -添加依赖 +依赖 ```groovy implementation "com.github.chuckerteam.chucker:library:3.5.2" @@ -13,25 +14,19 @@ implementation "com.github.chuckerteam.chucker:library:3.5.2" 添加拦截器 ```kotlin -class App : Application() { - -override fun onCreate() { - super.onCreate() - - NetConfig.initialize("https://github.com/liangjingkanji/", this) { - // ... - if (BuildConfig.DEBUG) { - addInterceptor( - ChuckerInterceptor.Builder(this@App) - .collector(ChuckerCollector(this@App)) - .maxContentLength(250000L) - .redactHeaders(emptySet()) - .alwaysReadResponseBody(false) - .build() - ) - } +NetConfig.initialize("https://github.com/liangjingkanji/", this) { + // ... + if (BuildConfig.DEBUG) { + addInterceptor( + ChuckerInterceptor.Builder(this@App) + .collector(ChuckerCollector(this@App)) + .maxContentLength(250000L) + .redactHeaders(emptySet()) + .alwaysReadResponseBody(false) + .build() + ) } } ``` -更多自定义功能请查看[chucker](https://github.com/ChuckerTeam/chucker)主页 +自定义功能浏览 [Chucker](https://github.com/ChuckerTeam/chucker) 官网 diff --git a/docs/log-recorder.md b/docs/log-recorder.md index 78639be5d..4e221270d 100644 --- a/docs/log-recorder.md +++ b/docs/log-recorder.md @@ -1,23 +1,17 @@ -由于LogCat日志可读性差, 所以Net支持以下两种方案 +两种日志插件 +| [Okhttp Profiler](https://github.com/itkacher/OkHttpProfiler) | [Profiler](https://developer.android.com/studio/profile/network-profiler?hl=zh-cn) | +| ------------------------------------------------------------ | ------------------------------------------------------------ | +| 列表显示 | 动态曲线图 | +| 要求添加`LogRecordInterceptor` | 可查看所有OkHttp的请求 | +| 原理是插件捕获LogCat日志, 线上环境请关闭 | 无法捕获启动一瞬间的请求 | +## LogRecordInterceptor -1. 使用AndroidStudio的[Profiler](https://developer.android.com/studio/profile/network-profiler?hl=zh-cn)监听网络 - - 可以查看项目所有OkHttp框架发起的网络请求 - - 网络请求是动态曲线图, 查不太方便 - - 启动应用时立刻触发的请求无法捕捉 - -
- -2. 安装[Okhttp Profiler](https://github.com/itkacher/OkHttpProfiler)插件 (推荐) - - 列表显示请求 - - 要求添加Net的`LogRecordInterceptor`拦截器 - - 实际上是插件捕获logCat生成的日志, 线上环境需要关闭 - -## 添加日志拦截器 +添加日志拦截器 ```kotlin hl_lines="2" -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { +NetConfig.initialize(Api.HOST, this) { addInterceptor(LogRecordInterceptor(BuildConfig.DEBUG)) } ``` @@ -28,7 +22,7 @@ NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { | requestByteCount | 请求日志信息最大字节数, 默认1MB | | responseByteCount | 响应日志信息最大字节数, 默认4MB | -这样会可以在LogCat看到日志输出, 但是我们要使用插件预览就需要第 2 步 +此时仅LogCat输出日志, 要预览请安装插件 ## 安装插件 @@ -43,7 +37,10 @@ NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { -> 请在每次使用前都先打开插件窗口, 如果有延迟或者不显示就反复打开下窗口 +!!! warning "不显示日志" + 请在请求前确保有打开过插件窗口, 如果依然不显示可以反复打开/关闭窗口 + + 每次AS更新都需要该插件作者适配, 可能存在beta版本作者没有适配情况 @@ -60,35 +57,36 @@ NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { | 清空 | 清空记录 | -## 自定义日志(解密) +## 自定义日志 -通过继承`LogRecordInterceptor`可以覆写函数自定义自己的日志输出逻辑 +继承`LogRecordInterceptor`复写函数实现自定义 -1. 如果你的请求体是被加密的内容, 你可以通过覆写`requestString`函数返回解密后的请求信息 -2. 如果你的响应体是被加密的内容, 你可以通过覆写`responseString`函数返回解密后的响应信息 +1. 复写`getRequestLog`返回解密请求参数 +2. 复写`getResponseLog`返回解密响应参数 -然后初始化时添加自己实现拦截器即可 +初始化时添加自己的拦截器 ```kotlin -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { +NetConfig.initialize(Api.HOST, this) { addInterceptor(MyLogRecordInterceptor(BuildConfig.DEBUG)) } ``` -## LogCat过滤 -实际上Net的网络日志还是会被打印到LogCat, 然后通过插件捕捉显示. +## 日志过滤 -如果不想LogCat的冗余日志影响查看其它日志, 可以通过AndroidStudio的功能折叠隐藏, 添加一个`OKPREL_`过滤字段即可 +不想网络日志影响其他日志查看, 可以添加`OKPREL_`为日志折叠规则 ## 其他网络框架 -可能你项目中还残留其他网络框架, 也可以使用Net的日志记录器`LogRecorder`来为其他框架打印日志信息. 如果是基于OkHttp的框架那可以直接使用LogRecordInterceptor +`LogRecordInterceptor`属于OkHttp拦截器, 其他网络请求框架也可以使用 + +甚至可以直接使用`LogRecorder`输出日志 -| 函数 | 描述 | +| LogRecorder | 描述 | |-|-| | generateId | 产生一个唯一标识符, 用于判断为同一网络请求 | | recordRequest | 记录请求信息 | diff --git a/docs/model-generate.md b/docs/model-generate.md index 2be5e70a3..5fbdf56fd 100644 --- a/docs/model-generate.md +++ b/docs/model-generate.md @@ -1,6 +1,4 @@ -在请求网络数据过程中会需要编写大量的数据模型对象, 映射后端返回的数据创建数据对象. 因为我们业务逻辑中一般是直接数据对象更为方便 - -创建数据模型我不推荐手写, 错误率高且不方便. 我推荐使用`JSON To Kotlin Class` 插件自动生成 +应该没人会完全手写数据模型吧? 建议使用`JSON To Kotlin Class` 插件完成 ## 安装插件 @@ -18,9 +16,10 @@ -然后就会在你选中的分包下生成一个数据模型类了 +然后就会在选中的分包下生成一个数据模型类了 -> 不要输入json数组去生成, 输入数组里面的对象即可(否则生成的类会自动继承ArrayList). 然后请求的时候使用List泛型即可, 例如`Get>` +!!! Warning "不要生成数组" + 不要输入JSON数组生成(否则生成的类会继承ArrayList), 输入JSON对象, 请求使用`Get>` ## 高级设置 @@ -38,7 +37,7 @@ ### 使用的框架 -生成数据模型时会兼容你使用的框架, 例如Moshi和Ks可能需要注解, 然后会自动生成SerialName这些名称注解 +生成数据模型时会兼容你使用的框架, 例如Moshi和ks可能需要注解, 然后会自动生成SerialName这些名称注解 diff --git a/docs/okhttp-client.md b/docs/okhttp-client.md index 8b7937ff7..43d34d34f 100644 --- a/docs/okhttp-client.md +++ b/docs/okhttp-client.md @@ -1,6 +1,4 @@ -每个请求都会存在一个客户端对象, 既OkHttpClient - -Net在全局维护了一个OkHttpClient对象, 在NetConfig.okHttpClient的字段 +Net全局持有一个`OkHttpClient`对象发起请求 ```kotlin object NetConfig { @@ -8,40 +6,38 @@ object NetConfig { } ``` -> 当然也支持创建一个新的客户端来发起请求(配置区别于全局客户端) - -## 全局OkHttpClient +## 全局 ```kotlin -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { +NetConfig.initialize(Api.HOST, this) { // 此处this即为OkHttpClient.Builder } ``` -## 单例OkHttpClient - -每个请求可能存在独立的OkHttpClient配置, 我们可以单例配置客户端选项 - -1. 在全局的OkHttpClient配置基础下修改 - -```kotlin -scopeNetLife { - tv_response.text = Get("https://github.com/liangjingkanji/Net/") { - setClient { - // 此处this即为OkHttpClient.Builder - trustCertificate() - } - }.await() -} -``` - -2. 完全重新创建一个OkHttpClient, 一般情况不推荐重新创建一个OkHttpClient, 因为一个新的OkHttpClient会重新创建线程池/连接池等造成内存消耗等 - -```kotlin -scopeNetLife { - tv_response.text = Get("https://github.com/liangjingkanji/Net/") { - okHttpClient = OkHttpClient.Builder().build() - }.await() -} -``` \ No newline at end of file +## 单例 + +可以单独指定当前请求客户端 + +=== "修改全局客户端" + ```kotlin + scopeNetLife { + tv_response.text = Get(Api.PATH) { + setClient { + // 此处this即为OkHttpClient.Builder + trustCertificate() + } + }.await() + } + ``` + 在全局的OkHttpClient配置基础下修改 + +=== "创建新客户端" + ```kotlin + scopeNetLife { + tv_response.text = Get(Api.PATH) { + okHttpClient = OkHttpClient.Builder().build() + }.await() + } + ``` + 创建新的OkHttpClient, 一般不使用, 因为新OkHttpClient会重新创建线程池/连接池等造成内存消耗 \ No newline at end of file diff --git a/docs/practice.md b/docs/practice.md deleted file mode 100644 index 5cadd8326..000000000 --- a/docs/practice.md +++ /dev/null @@ -1,11 +0,0 @@ -本章节毫无意义, 仅仅是有些人完全没有下载过本仓库, 且不知道项目中存在sample模块 - -## 使用场景 -实际上本框架使用也就两三行代码而已, 并且常见使用场景在本项目的sample模块中也进行了全面的讲解 - -

- -## 结合案例 -但有些人可能完全是新手, 那么推荐结合BRV的sample, 里面有常见的界面数据加载示例 - -

\ No newline at end of file diff --git a/docs/progress.md b/docs/progress.md index 719ec29c6..ec4ecf5ae 100644 --- a/docs/progress.md +++ b/docs/progress.md @@ -49,16 +49,17 @@ scopeNetLife { } ``` -> 无论是上传还是下载的进度监听不仅仅是泛型为File对象才有效, 任何请求/响应都会被监听到进度 +!!! success "监听任何进度" + 不仅是泛型为File才有效, 任何请求/响应都可以监听进度 ## 监听器 -通过继承`ProgressListener`抽象类实现监听进度信息. 进度信息为`Progress`对象 +实现`ProgressListener`监听进度信息. 进度信息为`Progress` ### 进度间隔时间 -ProgressListener的构造参数`interval`控制触发进度监听器的间隔时间, 默认是500毫秒. 毕竟进度监听不需要太频繁的调用影响性能. +ProgressListener的构造参数`interval`控制触发进度监听器的间隔时间 ### 进度信息 @@ -75,13 +76,13 @@ ProgressListener的构造参数`interval`控制触发进度监听器的间隔时 | useTimeSeconds | 已经使用时间, 单位秒 | | remainTimeSeconds | 估算的剩余时间, 单位秒 | -同时提供了一系列字符串格式函数, 都是返回已经添加好单位 +### 格式化字符串 | 函数 | 描述 | |-|-| | currentSize | 已完成大小, 例如: `120kB` 或者 `1.5MB` | -| totalSize | 全部大小, 同上 | -| remainSize | 剩余大小, 同上 | -| speedSize | 每秒下载大小, 同上 | +| totalSize | 全部大小 | +| remainSize | 剩余大小 | +| speedSize | 每秒下载大小 | | useTime | 已使用时间, 格式为: `01:23:04` | | remainTime | 估算的剩余时间, 格式为: `01:23:04` | diff --git a/docs/read-cache.md b/docs/read-cache.md deleted file mode 100644 index a937006ef..000000000 --- a/docs/read-cache.md +++ /dev/null @@ -1,59 +0,0 @@ -Net_v2基于[Kalle](https://github.com/yanzhenjie/Kalle)开发, 支持Kalle的9种缓存模式 - -缓存模式要求在初始化的时候开启 - -```kotlin -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { - cacheEnabled() // 开启缓存 -} -``` - -=== "请求缓存或网络" - ```kotlin - scopeNetLife { - // 先读取缓存, 如果缓存不存在再请求网络 - tvFragment.text = Get("api", cache = CacheMode.READ_CACHE_NO_THEN_NETWORK).await() - Log.d("日志", "读取缓存") - } - ``` -=== "读取缓存然后请求网络" - - ```kotlin - scopeNetLife { - // 然后执行这里(网络请求) - tvFragment.text = Post("api", cache = CacheMode.NETWORK_YES_THEN_WRITE_CACHE).await() - Log.d("日志", "网络请求") - }.preview { - // 先执行这里(仅读缓存), 任何异常都视为读取缓存失败 - tvFragment.text = Get("api", cache = CacheMode.READ_CACHE).await() - Log.d("日志", "读取缓存") - } - ``` - -预读模式本质上就是创建一个`preview`附加作用域, 里面的所有异常崩溃都会被静默捕捉(算作缓存失败), 会优先于`scope*`执行, 然后再执行scope本身, -而且一旦缓存读取成功(`preview`内部无异常)即使网络请求失败也可以不提醒用户任何错误信息(可配置) - -
- -> `preview`并没有说只能用在网络缓存上, 也可以用于其他的处理场景 - -
- -## 缓存模式 - -缓存模式属于`CacheMod`枚举, 建议开发者浏览缓存模式的源码和注释,有助于理解和更好的使用缓存模式。 - -| 常量 | 描述 | -| ------------------------------ | ------------------------------------------------------------ | -| `HTTP` | 发起请求前如果本地已经有缓存,如果缓存未过期则返回缓存数据,
如果过期则带上缓存头去服务器做校验。如果服务器响应304则返回缓存数据,否则读取服务器数据,
根据服务器响应头来决定是否写入缓存数据 | -| `HTTP_YES_THEN_WRITE_CACHE` | 发起请求前如果本地已经有缓存则带缓存头,服务器响应304才返回缓存数据,否则读取服务器数据,
并写入缓存数据 | -| `NETWORK` | 发起请求前不管本地是否有缓存,都不会带上缓存头,
不论服务器响应头如何,绝不写入缓存数据 | -| `NETWORK_YES_THEN_HTTP` | 发起请求前不管本地是否有缓存,都不会带上缓存头,
根据服务器响应头来决定是否写入缓存数据 | -| `NETWORK_YES_THEN_WRITE_CACHE` | 发起请求前不管本地是否有缓存,都不会带上缓存头,
请求成功后写入缓存数据 | -| `NETWORK_NO_THEN_READ_CACHE` | 发起请求前不管本地是否有缓存,都不会带上缓存头,请求失败后尝试读取缓存,
根据服务器响应头来决定是否写入缓存数据 | -| `READ_CACHE ` | 仅读取缓存 | -| `READ_CACHE_NO_THEN_NETWORK` | 读取缓存,如果缓存不存在就请求网络,
不写入缓存数据 | -| `READ_CACHE_NO_THEN_HTTP` | 先读取缓存,如果缓存不存在就请求网络,
根据服务器响应头来决定是否写入缓存数据 | -| `READ_CACHE_NO_THEN_NETWORK_THEN_WRITE_CACHE` | 先本地有缓存则读取缓存,如果没有缓存则读取网络并且写入缓存,
该模式请求成功后会永久使用缓存, 但你可以指定动态的cacheKey来让缓存失效
例如一天后失效, 可以做到客户端完全控制缓存 | - -
\ No newline at end of file diff --git a/docs/repeat-request.md b/docs/repeat-request.md index 99f260f94..a14219524 100644 --- a/docs/repeat-request.md +++ b/docs/repeat-request.md @@ -1,25 +1,20 @@ -某个重复发起的请求, 在发起的时候自动取消旧的网络请求.
+常用于筛选列表请求, 选择新的筛选条件时应将上次未完成的取消后再发起请求 -这种应用场景常见于筛选菜单, 每次点击菜单都会发起网络请求返回筛选后的列表, 但是请求未完成时, 用户又点击了新的筛选条件, 这个时候应该取消上次请求, 重新发起新的请求
+在Net禁止重复请求仅2行代码 -这个需求在Net中非常好实现, 保存一个变量即可 - -```kotlin +```kotlin hl_lines="4" var scope: AndroidScope? = null -btn_request.setOnClickListener { - tv_result.text = "请求中" - scope?.cancel() // 如果存在则取消 +btnFilter.setOnClickListener { + scope?.cancel() scope = scopeNetLife { val result = Post("api").await() - Log.d("日志", "请求到结果") // 你一直重复点击"发起请求"按钮会发现永远无法拿到请求结果, 因为每次发起新的请求会取消未完成的 - tv_result.text = result } } ``` -当`scope`不为空时即表示存在上个请求, 我们无论上个请求是否完成都调用`cancel`函数保证取消即可 +当`scope`不为空时表示存在旧请求, 无论旧请求是否完成都可以调用`cancel()`保证取消即可 -> 详细的关于取消网络请求的操作查看: [取消请求](cancel.md)
-> 可以限制部分重复请求一定时间内读取缓存: [缓存有效期](/cache/#_3) \ No newline at end of file +1. [取消请求](cancel.md)
+2. 限制时间强制使用缓存, [配置缓存有效期](cache.md#_3) \ No newline at end of file diff --git a/docs/request.md b/docs/request.md index 1577fafbb..e312d494b 100644 --- a/docs/request.md +++ b/docs/request.md @@ -1,67 +1,38 @@ -Net中关于请求的类只有两个类和他们共同的抽象父类 -```kotlin -BaseRequest - |- UrlRequest - |- BodyRequest -``` +!!! question "请求参数" + 根据请求方式不同请求参数分为两类 + `UrlRequest`: GET, HEAD, OPTIONS, TRACE // Query(请求参数位于Url中)
+ `BodyRequest`: POST, DELETE, PUT, PATCH // Body(请求体为流) -根据请求方法不同使用的Request也不同 - -```kotlin -GET, HEAD, OPTIONS, TRACE, // Url request -POST, DELETE, PUT, PATCH // Body request -``` - -代码示例 +使用示例 ```kotlin scopeNetLife { - Get("api") { - // this 即为 UrlRequest + val userInfo = Post(Api.LOGIN) { + param("username", "drake") + param("password", "6f2961eb44b12123393fff7e449e50b9de2499c6") }.await() - - Post("api") { - // this 即为 BodyRequest - }.await() -} -``` - -## 请求参数 - -```kotlin -scopeNetLife { // 创建作用域 - // 这个大括号内就属于作用域内部 - val data = Get("https://github.com/liangjingkanji/Net/"){ - param("u_name", "drake") - param("pwd", "123456") - }.await() // 发起GET请求并返回`String`类型数据 } ``` -|请求函数|描述| +|函数|描述| |-|-| -|`param`|支持基础类型/文件/RequestBody/Part| -|`json`|请求参数为JSONObject/JsonArray/String| -|`setQuery/addQuery`|设置/添加Url参数, 如果当前请求为Url请求则该函数等效于`param`函数| +|`param`| Url请求时为Query, Body请求时为表单/文件| +|`json`|JSON字符串| +|`setQuery/addQuery`|Url中的Query参数, 如果当为Url请求则该函数等效`param`| |`setHeader/addHeader`|设置/添加请求头| -如果没有添加文件/流那么就是通过BodyRequest内部的`FormBody`发起请求. 反之就是通过`MultipartBody`发起请求. +## JSON -> 当然你可以完全自定义Body来请求, 譬如以下的Json请求 +三种参数类型上传JSON示例, 更多请阅读方法注释 - -## Json请求 - -这里提供三种创建Json请求的示例代码. 酌情选用 - -=== "JSON键值对(推荐)" +=== "键值对" ```kotlin val name = "金城武" val age = 29 val measurements = listOf(100, 100, 100) - + scopeNetLife { tvFragment.text = Post("api") { // 只支持基础类型的值, 如果值为对象或者包含对象的集合/数组会导致其值为null @@ -87,29 +58,29 @@ scopeNetLife { // 创建作用域 } ``` -=== "自定义的body" +=== "自定义RequestBody" ```kotlin val name = "金城武" val age = 29 val measurements = listOf(100, 100, 100) - + scopeNetLife { tvFragment.text = Post("api") { - body = MyJsonBody(name, age, measurements) + body = CustomizerJSONBody(name, age, measurements) }.await() } ``` -对于某些可能JSON请求参数存在固定值: +如果JSON需要全局参数 -1. 可以考虑继承RequestBody来扩展出自己的新的Body对象, 然后赋值给`body`字段 -2. 添加请求拦截器[RequestInterceptor](/interceptor/#_1) +1. 自定义`RequestBody`添加全局参数 +2. 使用请求拦截器来添加全局参数 [RequestInterceptor](interceptor.md#_1) -## 自定义请求函数 +## 自定义扩展函数 -前面提到`json(Pair)`函数不支持对象值, 因为框架内部使用的`org.json.JSONObject`其不支持映射对象字段 +由于`json()`不能传对象, 因为使用的`org.json.JSONObject`其不支持映射对象字段 -这里可以创建扩展函数来支持你想要的Json解析框架, 比如以下常用的Json解析框架示例 +但可创建扩展函数来使用射对象序列化框架来解析, 如下 === "Gson" ```kotlin @@ -135,23 +106,25 @@ scopeNetLife { } ``` -- 举一反三可以创建其他功能自定义的请求函数 -- 扩展函数要求为顶层函数, 即直接在文件中 (kotlin基础语法) - ## 全局请求参数 -对于动态生成的全局请求头或参数都可以通过实现`RequestInterceptor`来设置全局的请求拦截器来添加, 如果RequestInterceptor不满足你的需求可以使用拦截器(Interceptor)来实现 +使用`RequestInterceptor`请求拦截器添加全局参数/请求头, 更复杂请实现`Interceptor` ```kotlin -NetConfig.initialize("https://github.com/liangjingkanji/Net/", this) { - // 添加请求拦截器, 每次请求都会触发的, 可配置全局/动态参数 - setRequestInterceptor(MyRequestInterceptor()) +class GlobalHeaderInterceptor : RequestInterceptor { + + // 每次请求都会回调, 可以是动态参数 + override fun interceptor(request: BaseRequest) { + request.setHeader("client", "Android") + request.setHeader("token", UserConfig.token) + } } ``` -## 请求函数 - -关于全部的请求配置选项推荐阅读函数文档或者阅读源码. Net提供清晰的函数结构浏览方便直接阅读源码 - - +```kotlin +NetConfig.initialize(Api.HOST, this) { + setRequestInterceptor(GlobalHeaderInterceptor()) +} +``` +更多请求参数相关请阅读Api文档/函数列表 \ No newline at end of file diff --git a/docs/scope.md b/docs/scope.md index 85d631e39..6dc1b3a39 100644 --- a/docs/scope.md +++ b/docs/scope.md @@ -1,28 +1,26 @@ -协程请求要求在协程的作用域中调用, 这里介绍如何创建不同的作用域获取不同的功能 +创建不同协程作用域可以实现不同的功能 -本质上Net的请求动作函数返回的是一个Deferred对象. 可以在任何协程作用域内执行. 但是考虑到完整的生命周期和错误处理等推荐使用Net内部定义的作用域. - -> 发生在Net作用域内的任何异常都会被捕获, 有效减少应用崩溃率. 如果配合[kotlin-serialization](kotlin-serialization.md)还可以解决因服务器返回null字段导致的崩溃 +!!! Success "减少崩溃" + Net所有作用域内抛出异常都会被捕获到, 可以防止应用崩溃
## 异步任务的作用域 -创建可以捕捉异常的协程作用域, 但是不会触发`NetErrorHandler`(全局错误处理者). 该作用域于一般用于普通的异步任务 +可以捕捉异常的协程作用域, 用于构建普通异步任务 |函数|描述| |-|-| -|`scope`|创建最基础的作用域, 所有作用域都包含异常捕捉| -|`scopeLife`|创建跟随生命周期取消的作用域| +|`scope`|创建最基础的作用域| +|`scopeLife`|创建跟随生命周期自动取消的作用域| |`ViewModel.scopeLife`|创建跟随ViewModel生命周期的作用域, [如何在ViewModel创建作用域](view-model.md)| ## 网络请求的作用域 -网络请求的作用域可以根据生命周期自动取消网络请求, 发生错误也会自动弹出吐司(可以自定义或者取消), 并且具备一些场景的特殊功能(例如加载对话框, 缺省页, 下拉刷新等) - -网络请求的作用域比上面提到的异步任务的作用域多的区别就是 +除异步任务外还适用于网络请求场景的作用域, 对比上面`异步任务的作用域`区别: -1. 发生错误会触发全局错误处理`NetErrorHandler` -2. 具备一些特殊场景功能, 比如自动下拉刷新, 自动显示加载库等 +1. 发生错误自动吐司(可以自定义或者取消) +2. 发生错误会触发全局错误处理`NetErrorHandler` +3. 具备一些特殊场景功能, 比如根据网络请求结果自动处理下拉刷新/上拉加载/缺省页/加载框的状态 | 函数 | 描述 | |-|-| @@ -33,23 +31,25 @@ |`PageRefreshLayout.scope`|创建跟随[PageRefreshLayout](https://github.com/liangjingkanji/BRV)生命周期的作用域| |`StateLayout.scope`|创建跟随[StateLayout](https://github.com/liangjingkanji/BRV)生命周期的作用域| -
-> PageRefreshLayout/StateLayout 属于[BRV](https://github.com/liangjingkanji/BRV)框架中的布局, 用于支持[自动化缺省页/下拉刷新](auto-state.md) -
+!!! Failure "区分函数接受者" + 注意`StateLayout.scope`等存在`函数接受者`的方法和`scope`属于两个方法, 严禁混用 +!!! quote "第三方库支持" + PageRefreshLayout/StateLayout 属于第三方开源项目 [BRV](https://github.com/liangjingkanji/BRV) + 框架中的布局, 可用于支持[自动化缺省页/下拉刷新](auto-state.md)
-> 如果想了解详细的协程使用方式, 可以查看一篇文章: [最全面的Kotlin协程: Coroutine/Channel/Flow 以及实际应用](https://juejin.im/post/6844904037586829320) +如果想更了解协程使用方式, +可以阅读一篇文章: [最全面的Kotlin协程: Coroutine/Channel/Flow 以及实际应用](https://juejin.im/post/6844904037586829320) -有时候可能面临嵌套的`scope*`函数或者作用域内有子作用域情况, 这个时候的生命周期是如何 +## 嵌套作用域 - -## 嵌套Scope +有时候可能面临内嵌`scopeXX`函数(嵌套作用域), 这时候生命周期如下 ```kotlin hl_lines="5" -scopeNet { +scopeNetLife { val task = Post("api0").await() - scopeNet { + scopeNetLife { val task = Post("api0").await() // 此时发生请求错误 }.catch { // A @@ -59,9 +59,9 @@ scopeNet { } ``` -- 以下嵌套作用域错误将会仅发生在`A`处, 并被捕获, 同时不影响外部`scopeNet`的请求和异常捕获 -- 两个`scopeNet`的异常抛出和捕获互不影响 -- `scopeNet/scopeDialog/scope`等函数同理 +- 错误将在`A`处可以获取到, 且不影响外部`scopeNetLife`的请求 +- 两个`scopeNetLife`的异常抛出和捕获互不影响 +- `scopeXX()`等函数同理 ## 子作用域 diff --git a/docs/switch-thread.md b/docs/switch-thread.md deleted file mode 100644 index 8a223c5e5..000000000 --- a/docs/switch-thread.md +++ /dev/null @@ -1,50 +0,0 @@ -所有`scope*`前缀函数的创建的作用域都默认情况下为主线程, 即可以在作用域内直接操作UI - -既然是默认当然也可以直接修改scope内的作用域线程也可以在里面进行多次切换 - -> 在协程中切换线程只需要切换调度器即可 - -## 切换作用域调度器 - -```kotlin -scopeNetLife(dispatcher = Dispatchers.IO) { - binding.tvFragment.text = Get("api").await() -} -``` - -## 在作用域内部切换 - -可能某些情况只是作用域内部分阻塞任务需要在其他线程执行. 我们可以直接在作用域内部进行多次的线程切换 - -=== "主线程作用域内切换子线程" - ```kotlin hl_lines="2" - scopeNetLife { - binding.tvFragment.text = withIO { - // 假设此处是一个IO读写阻塞任务 - return "读出结果" - } - } - ``` - -=== "子线程作用域内切换主线程" - ```kotlin hl_lines="2" - scopeNetLife(dispatcher = Dispatchers.IO) { - binding.tvFragment.text = withMain { - // 假设此处是一个IO读写阻塞任务 - return "读出结果" - } - } - ``` - -|函数|描述| -|-|-| -|[withMain](api/-net/com.drake.net.utils/with-main.html)|切换到主线程| -|[withIO](api/-net/com.drake.net.utils/with-i-o.html)|切换到IO线程| -|[withDefault](api/-net/com.drake.net.utils/with-default.html)|切换到默认线程(属于子线程)| -|[withUnconfined](api/-net/com.drake.net.utils/with-unconfined.html)|切换到无限制调度器, 其取决于上一个执行的线程切换| -|launch|无返回值的协程挂起函数, 可指定线程| -|async|有返回值的协程挂起函数, 但得通过`await()`返回值. 可指定线程| -|[runMain](api/-net/com.drake.net.utils/with-unconfined.html)|切换到主线程, 该函数可以在任何地方调用不限于协程作用域| - -- `with*`函数属于调用就立即执行, 在作用域内会阻塞(不会阻塞主线程) -- `launch/async` 属于执行并发任务, 两者区分就是有无返回值 diff --git a/docs/sync-request.md b/docs/sync-request.md index 569781cd1..6dcfd61e6 100644 --- a/docs/sync-request.md +++ b/docs/sync-request.md @@ -1,8 +1,13 @@ -Net支持在当前线程执行, 会阻塞当前线程的同步请求 -- `execute` +Net支持在当前线程执行阻塞线程的同步请求 -这里介绍的是不使用协程的同步请求. 由于Android主线程不允许发起网络请求, 这里我们得随便创建一个子线程才可以开发起同步请求 +!!! question "什么是同步请求" + 即上个请求结束才会发起下个请求, 实际上协程也可以实现但是他不会阻塞线程 -=== "同步请求" + 同步请求应用场景一般是在拦截器(执行在子线程)中使用 + +因为Android主线程不允许发起网络请求, 这里我们创建一个子线程来演示 + +=== "返回数据" ```kotlin thread { @@ -12,31 +17,22 @@ Net支持在当前线程执行, 会阻塞当前线程的同步请求 -- `execute } } ``` -=== "toResult" + +=== "返回Result" ```kotlin thread { - val result = Net.post("api").toResult().getOrDefault("请求发生错误, 我这是默认值") + val result = Net.post("api").toResult().getOrDefault("请求发生错误, 我是默认值") tvFragment?.post { tvFragment?.text = result // view要求在主线程更新 } } ``` -1. `execute`在请求发生错误时会抛出异常 -2. `toResult`不会抛出异常, 通过`exception*`函数来获取异常信息, 且支持默认值等特性 +1. `execute`在请求错误时会直接抛出异常 +2. `toResult`不会抛出异常, 可`getOrThrow/exceptionOrNull`等返回异常对象 + + -> 同步请求应用场景一般是在拦截器中使用, 拦截器默认是子线程 -作用域具体介绍可以看[创建作用域](scope.md) -|请求函数|描述| -|-|-| -|Net.get|标准Http请求方法| -|Net.post|标准Http请求方法| -|Net.head|标准Http请求方法| -|Net.options|标准Http请求方法| -|Net.trace|标准Http请求方法| -|Net.delete|标准Http请求方法| -|Net.put|标准Http请求方法| -|Net.patch|标准Http请求方法| diff --git a/docs/tag.md b/docs/tag.md index 4a0866287..237960bf2 100644 --- a/docs/tag.md +++ b/docs/tag.md @@ -1,13 +1,13 @@ -Net支持两种类型数据贯穿整个请求流程(请求 -> 拦截器 -> 转换器) +Net支持两种方式携带数据, 贯穿整个请求流程(请求/拦截器/转换器) - tag: `HashMap, Any?>` 标签 - extra: `HashMap` 额外数据 -> 他们的区别是key是Class还是String类型, 具体使用哪一种请根据自己方便来 +两者区别为存储时key是`Class`还是`String`, 自由选择 ## 标签使用 -### 1) 写入标签 +### 1. 写入标签 ```kotlin hl_lines="2" scopeNetLife { @@ -18,10 +18,7 @@ scopeNetLife { } ``` -> `tagOf(Person())` 等效 `tag(Person::class.java, Person())`, 只是使用泛型推断区别
-> 但`tag(Person())` 等效 `tag(Any::class.java, Person())`, 可以查看方法实现 - -### 2) 拦截器中读取标签 +### 2. 拦截器中读取标签 ```kotlin hl_lines="4" class MyInterceptor : Interceptor { override fun intercept(chain: Chain): Response { @@ -37,7 +34,7 @@ class MyInterceptor : Interceptor { } ``` -### 3) 转换器中读取标签 +### 3. 转换器中读取标签 ```kotlin hl_lines="4" class MyConvert : NetConvert { @@ -52,7 +49,7 @@ class MyConvert : NetConvert {
-我们通过Request的函数可以读取/写入标签/额外数据 +通过`Request`可以读写数据 | 方法 | 描述 | |-|-| @@ -60,7 +57,7 @@ class MyConvert : NetConvert { | extra | 读取额外数据 | | extras | 全部额外数据 | | tag | 读取/写入标签 | -| tagOf | 读取/写入标签, 和上面函数不同的时本函数使用的泛型而非`Class<*>` | +| tagOf | 读取/写入标签, 为`tag()`的泛型替代 | | tags | 全部标签 | ## 多个标签 diff --git a/docs/thread.md b/docs/thread.md new file mode 100644 index 000000000..eaf79fcb1 --- /dev/null +++ b/docs/thread.md @@ -0,0 +1,49 @@ +所有`scopeXX`作用域内为主线程, 可直接更新视图 + +!!! question "调度器" + 协程中的调度器实际上为线程池, 通过切换调度器可以切换到不同线程上 + +## 切换调度器 + +```kotlin +scopeNetLife(dispatcher = Dispatchers.IO) { + binding.tvFragment.text = Get("api").await() +} +``` + +## 作用域内部切换 + +有时需开启新的线程处理耗时任务 + +=== "主线程作用域内切换子线程" + ```kotlin hl_lines="2" + scopeNetLife { + binding.tvFragment.text = withIO { + // 假设此处是一个IO读写阻塞任务 + return "读出结果" + } + } + ``` + +=== "子线程作用域内切换主线程" + ```kotlin hl_lines="2" + scopeNetLife(dispatcher = Dispatchers.IO) { + binding.tvFragment.text = withMain { + // 假设此处是一个IO读写阻塞任务 + return "读出结果" + } + } + ``` + +| 函数 | 描述 | +| ------------------------------------------------------------ | ----------------------------------------------------------- | +| [withMain](api/-net/com.drake.net.utils/with-main.html) | 切换到主线程 | +| [withIO](api/-net/com.drake.net.utils/with-i-o.html) | 切换到IO线程 | +| [withDefault](api/-net/com.drake.net.utils/with-default.html) | 切换到子线程 | +| [withUnconfined](api/-net/com.drake.net.utils/with-unconfined.html) | 切换到无限制调度器, 其取决于上一个执行的线程切换 | +| launch | 无返回值的挂起函数, 可指定线程 | +| async | 有返回值的挂起函数, 通过`await()`返回值, 可指定线程 | +| [runMain](api/-net/com.drake.net.utils/with-unconfined.html) | 切换到主线程, 该函数不属于协程可以在任何地方调用 | + +- `withXX()` 协程阻塞挂起 +- `launch()/async()` 非阻塞执行, 两者区别是有无返回值 diff --git a/docs/timing.md b/docs/timing.md index bfb0f0b28..349ecae96 100644 --- a/docs/timing.md +++ b/docs/timing.md @@ -1,8 +1,10 @@ -以下仅提供一些如何实现定时/限时请求功能的思路, 并不限定只有以下描述的方式可以实现 +以下为提供实现定时/限时请求思路, 并不限定只有以下方式 ## 限时请求 +即超过指定时间后立即取消请求 + ```kotlin scopeDialog { // 当接口请求在100毫秒内没有完成会抛出异常TimeoutCancellationException @@ -41,7 +43,6 @@ scopeNetLife { ```kotlin scopeNetLife { - // 每两秒请求一次, 总共执行10次 while (true) { delay(1.toDuration(DurationUnit.SECONDS)) val data = @@ -51,6 +52,4 @@ scopeNetLife { // 通过break可以终止循环 } } -``` - -建议下载源码查看 \ No newline at end of file +``` \ No newline at end of file diff --git a/docs/track.md b/docs/track.md new file mode 100644 index 000000000..15ff2d3e5 --- /dev/null +++ b/docs/track.md @@ -0,0 +1,24 @@ +Net中网络请求异常会LogCat输出, 除非开发者修改全局错误处理 + + +演示访问一个不存在的请求路径 +```kotlin +scopeNetLife { + tvFragment.text = Get("https://githuberror.com/liangjingkanji/Net/").await() +} +``` + +LogCat可以看到异常堆栈信息, 包含具体Url和代码位置 + + + + +### 关闭日志 + +可以关闭日志打印 + +```kotlin +NetConfig.initialize(Api.HOST, this) { + setDebug(false) // 关闭日志, 一般使用 BuildConfig.DEBUG +} +``` diff --git a/docs/updates.md b/docs/updates.md index 009156d46..ad79871f2 100644 --- a/docs/updates.md +++ b/docs/updates.md @@ -241,7 +241,7 @@ - 修复未知的TypeToken访问权限问题 ## 3.0.6 -- 所有Json解析框架都可以解析`List`等嵌套泛型数据结构: [特殊结构解析](convert-special.md) +- 所有Json解析框架都可以解析`List`等嵌套泛型数据结构 ## 3.0.5 - 修复Path编码问题 diff --git a/docs/upload-file.md b/docs/upload-file.md index 95f6665a5..fc9982e18 100644 --- a/docs/upload-file.md +++ b/docs/upload-file.md @@ -1,5 +1,3 @@ -上传文件和普通接口请求区别不大 - ```kotlin scopeNetLife { Post(Api.UPLOAD) { @@ -8,11 +6,11 @@ scopeNetLife { } ``` -使用`addUploadListener`添加上传进度监听器, 监听上传进度具体介绍在[进度监听](progress.md)中 +使用`addUploadListener`添加上传进度监听器, 阅读[进度监听](progress.md)章节 ## 指定类型 -默认会根据文件的后缀名产生MediaType. 但是如果你想自定义MediaType可以直接创建RequestBody参数 +默认根据文件后缀名生成`MediaType`, 如果想自定义MediaType可以直接创建`RequestBody` ```kotlin scopeNetLife { diff --git a/docs/view-model.md b/docs/view-model.md index 0a26d8c37..8477991ea 100644 --- a/docs/view-model.md +++ b/docs/view-model.md @@ -1,30 +1,44 @@ -Net支持在ViewModel中创建网络请求/异步任务, 并且在ViewModel被销毁时自动取消 +Net支持在ViewModel中创建网络请求/异步任务 +!!! Warning "不推荐" + 1. 网络请求不一定要写在ViewModel
+ 2. 网络请求不要写接口回调 + 3. 可以在Activity中直接返回请求结果 -## 使用 -和一般网络请求没有区别, 在ViewModel中使用函数`scopeLife/scopeNetLife`这两个函数创建作用域即可, 具体介绍看[作用域](scope.md) +## 自动生命周期 + +- [示例代码](https://github.com/liangjingkanji/Net/blob/c4d7c4cde6a34b9fa97a75cb357276b75432f8d1/sample/src/main/java/com/drake/net/sample/ui/fragment/ViewModelRequestFragment.kt) + +使用`scopeXXLife()`创建作用域, 在ViewModel被销毁时自动取消请求 ```kotlin class UserViewModel : ViewModel() { // 用户信息 - var userInfo: MutableLiveData = MutableLiveData() + private var userInfo: MutableLiveData = MutableLiveData() /** - * 拉取用户信息, 会自动通知页面更新, 同时页面销毁会自动取消网络请求 + * 使用LiveData接受请求结果, 将该liveData直接使用DataBinding绑定到页面上, 会在请求成功自动更新视图 */ fun fetchUserInfo() = scopeNetLife { - userInfo.value = Get("api").await() + userInfo.value = Get(Api.GAME).await() } -} -``` - -1. 不建议使用LiveData实现MVVM. 应当使用DataBinding. 但LiveData像ObservableField一样使用 -1. 简单地业务直接在Activity/Fragment中进行请求会更加方便 -1. ViewModel这个类本质是属于防止数据意外销毁或者桥接VM, 但不是每个页面都有这种需求 + /** + * 开始非阻塞异步任务 + * 返回Deferred, 调用await()才会返回结果 + */ + fun fetchList(scope: CoroutineScope) = scope.Get(Api.TEST) -
+ /** + * 开始阻塞异步任务 + * 直接返回结果 + */ + suspend fun fetchPrecessData() = coroutineScope { + val response = Get(Api.TEST).await() + response + "处理数据" + } +} +``` - diff --git a/mkdocs.yml b/mkdocs.yml index 6cdb21437..40f00d2f8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,37 +1,76 @@ site_name: Net -site_author: drake site_description: Net document -copyright: Copyright © 2018 - 2020 劉強東 - -repo_name: GitHub repo_url: https://github.com/liangjingkanji/Net +extra: + social: + - icon: fontawesome/brands/github + link: https://github.com/liangjingkanji + - icon: fontawesome/brands/qq + link: https://raw.githubusercontent.com/liangjingkanji/liangjingkanji/master/img/group-qrcode.png + - icon: fontawesome/brands/twitch + link: https://github.com/liangjingkanji/Net/discussions +site_author: 劉強東 +copyright: Copyright © 2018 - 2023 劉強東 +repo_name: GitHub +docs_dir: 'docs' extra_css: - css/extra.css - -docs_dir: docs - theme: name: material custom_dir: docs/material + favicon: img/book-open.svg + logo: img/book-open.svg palette: - scheme: drake - primary: white + - media: "(prefers-color-scheme: light)" + scheme: default + primary: white font: false - language: 'zh' + language: zh features: + - navigation.top + - navigation.prune + - navigation.footer + - navigation.instant - search.highlight - + - search.suggest + - search.share + - content.code.copy + - content.code.annotate +plugins: + - offline + - search: + separator: '[\s\-,:!=\[\]()"/]+|(?!\b)(?=[A-Z][a-z])|\.(?!\d)|&[lg]t;' + lang: + - en + - zh markdown_extensions: - toc: permalink: true - pymdownx.tasklist: custom_checkbox: true + - pymdownx.tabbed: + alternate_style: true + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - attr_list + - def_list + - md_in_html - admonition + - pymdownx.highlight - pymdownx.details - - pymdownx.superfences - - pymdownx.inlinehilite - - pymdownx.tabbed + - pymdownx.caret + - pymdownx.keys + - pymdownx.mark + - pymdownx.tilde + - pymdownx.emoji: + emoji_index: !!python/name:materialx.emoji.twemoji + emoji_generator: !!python/name:materialx.emoji.to_svg nav: - 使用: index.md @@ -41,45 +80,44 @@ nav: - 请求参数: request.md - 全局配置: config.md - 请求结果: - - 默认结果: default-response.md - - 自定义转换器: converter.md - - 自定义结构解析: convert-special.md + - 转换器: converter.md + - 自定义转换器: converter-customize.md + - 自定义结构解析: converter-struct.md - Kotlin-Serialization: kotlin-serialization.md - 数据类生成插件: model-generate.md - 自动化: - 自动加载框: auto-dialog.md - 自动缺省页: auto-state.md - 自动下拉刷新: auto-refresh.md - - 自动分页加载: auto-page.md - - 切换线程: switch-thread.md + - 自动分页加载: auto-pull.md + - 切换线程: thread.md - ViewModel: view-model.md - - 异常追踪: exception-track.md + - 异常追踪: track.md - 错误处理: - - 默认错误处理: error-default.md - - 单例错误捕获: error-single.md - - 全局错误捕获: error-global.md + - 错误处理: error.md + - 单例捕获: error-single.md + - 全局捕获: error-global.md - 自定义错误提示: error-tip.md - - 自定义异常抛出: error-exception.md + - 自定义异常抛出: error-throws.md - OkHttpClient: okhttp-client.md - 拦截器: interceptor.md - Https证书: https.md - Cookie: cookie.md - 缓存: cache.md - - 标签/额外数据: tag.md + - 标签/额外: tag.md - 上传文件: upload-file.md - 下载文件: download-file.md - 进度监听: progress.md - 取消请求: cancel.md - 重复请求: repeat-request.md - - 自动搜索: debounce.md + - 搜索节流: debounce.md - 最快请求结果: fastest.md - 日志插件: log-recorder.md - - 通知栏日志: log-notice.md + - 日志通知: log-notice.md - 限时/定时请求: timing.md - Callback: callback.md - 轮询器/倒计时: interval.md - 社区讨论: https://github.com/liangjingkanji/Net/discussions - - 常见问题: https://github.com/liangjingkanji/Net/blob/master/docs/issues.md - - 项目实践: practice.md + - 常见问题: issues.md - 更新日志: updates.md - 3.x文档: api/index.html diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro index bc5d812e1..481bb4348 100644 --- a/sample/proguard-rules.pro +++ b/sample/proguard-rules.pro @@ -1,5 +1,5 @@ # Add project specific ProGuard rules here. -# You can control the setKalle of applied configuration files using the +# You can control the set of applied configuration files using the # proguardFiles setting in build.gradle. # # For more details, see diff --git a/sample/src/main/java/com/drake/net/sample/constants/Api.kt b/sample/src/main/java/com/drake/net/sample/constants/Api.kt index a8439cc30..a9f92a617 100644 --- a/sample/src/main/java/com/drake/net/sample/constants/Api.kt +++ b/sample/src/main/java/com/drake/net/sample/constants/Api.kt @@ -15,4 +15,5 @@ object Api { const val ARRAY = "/array" const val CONFIG = "/config" const val USER_INFO = "/userInfo" + const val TIME = "/time" } \ No newline at end of file diff --git a/sample/src/main/java/com/drake/net/sample/mock/MockDispatcher.kt b/sample/src/main/java/com/drake/net/sample/mock/MockDispatcher.kt index 024739f40..633207606 100644 --- a/sample/src/main/java/com/drake/net/sample/mock/MockDispatcher.kt +++ b/sample/src/main/java/com/drake/net/sample/mock/MockDispatcher.kt @@ -13,6 +13,8 @@ import okio.buffer import okio.sink import okio.source import java.io.File +import java.text.SimpleDateFormat +import java.util.Date import java.util.concurrent.TimeUnit import kotlin.concurrent.thread @@ -33,23 +35,24 @@ class MockDispatcher : Dispatcher() { } override fun dispatch(request: RecordedRequest): MockResponse { - var path = request.path - if (path != null) { - path = path.substringBefore("?") // 剔除URL参数 - } - return when (path) { - Api.TEST -> MockResponse().setHeader("Content-Type", "text/plain").setBody("Request Success : ${request.method}") - Api.DELAY -> MockResponse().setBodyDelay(2, TimeUnit.SECONDS).setHeader("Content-Type", "text/plain").setBody("Request Success : ${request.method}") + return when (request.requestUrl?.encodedPath ?: "") { + Api.TEST -> getString("Request Success : ${request.method}") + Api.DELAY -> getString("Request Success : ${request.method}").setBodyDelay(2, TimeUnit.SECONDS) Api.UPLOAD -> uploadFile(request) Api.GAME -> getRawResponse(R.raw.game) Api.DATA -> getRawResponse(R.raw.data) Api.ARRAY -> getRawResponse(R.raw.array) Api.USER_INFO -> getRawResponse(R.raw.user) Api.CONFIG -> getRawResponse(R.raw.user) + Api.TIME -> getString(SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(Date())).setBodyDelay(1, TimeUnit.SECONDS) else -> MockResponse().setResponseCode(404) } } + private fun getString(text: String): MockResponse { + return MockResponse().setHeader("Content-Type", "text/plain").setBody(text) + } + // 将接口上传的文件复制到应用缓存目录 private fun uploadFile(req: RecordedRequest): MockResponse { val file = File(app.cacheDir.absolutePath, "uploadFile.apk") diff --git a/sample/src/main/java/com/drake/net/sample/ui/fragment/EditDebounceFragment.kt b/sample/src/main/java/com/drake/net/sample/ui/fragment/EditDebounceFragment.kt index 56588fe71..b6ec41497 100644 --- a/sample/src/main/java/com/drake/net/sample/ui/fragment/EditDebounceFragment.kt +++ b/sample/src/main/java/com/drake/net/sample/ui/fragment/EditDebounceFragment.kt @@ -3,6 +3,7 @@ package com.drake.net.sample.ui.fragment import com.drake.engine.base.EngineFragment import com.drake.net.Get import com.drake.net.sample.R +import com.drake.net.sample.constants.Api import com.drake.net.sample.databinding.FragmentEditDebounceBinding import com.drake.net.utils.debounce import com.drake.net.utils.launchIn @@ -10,7 +11,6 @@ import com.drake.net.utils.scopeNetLife import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.distinctUntilChanged -import org.json.JSONObject class EditDebounceFragment : EngineFragment(R.layout.fragment_edit_debounce) { @@ -26,10 +26,7 @@ class EditDebounceFragment : scope?.cancel() // 发起新的请求前取消旧的请求, 避免旧数据覆盖新数据 scope = scopeNetLife { // 保存旧的请求到一个变量中 binding.tvFragment.text = "请求中" - val data = - Get("http://api.k780.com/?app=life.time&appkey=10003&sign=b59bc3ef6191eb9f747dd4e83c99f2a4&format=json").await() - binding.tvFragment.text = - JSONObject(data).getJSONObject("result").getString("datetime_2") + binding.tvFragment.text = Get(Api.TIME).await() } } } diff --git a/sample/src/main/java/com/drake/net/sample/vm/UserViewModel.kt b/sample/src/main/java/com/drake/net/sample/vm/UserViewModel.kt index aa51e4254..2372f561f 100644 --- a/sample/src/main/java/com/drake/net/sample/vm/UserViewModel.kt +++ b/sample/src/main/java/com/drake/net/sample/vm/UserViewModel.kt @@ -10,37 +10,28 @@ import kotlinx.coroutines.coroutineScope /** - * 强烈建议不要封装作用域, 封装异步任务即可, 作用域灵活随用随写(Activity/Fragment都可以写作用域) - * - * 不要企图把所有逻辑代码都写在ViewModel中就觉得自己会写MVVM了, 给代码换个位置不叫架构设计, 特别是还增加一堆无效代码情况下 + * 不要将请求结果抛来抛去, 增加代码复杂度 */ class UserViewModel : ViewModel() { // 用户信息 var userInfo: MutableLiveData = MutableLiveData() - var updateTime: Long = 0 - /** - * 拉取用户信息, 只能监听返回结果, 仅当外部调用对象不在乎返回结果时使用 - * 会自动通知页面更新: 因为使用LiveData将请求结果回调出去, 建议将该liveData对象直接使用DataBinding绑定到页面上, 就会自动触发UI - * 同时页面销毁会自动取消网络请求: 因为他使用`scopeNetLife`. 生命周期跟随当前viewModel - * - * 本质上我并不推荐将Scope定义在ViewModel中(仅仅换个位置要多写很多代码), 特别是妄图在ViewModel中请求网络却要求更新UI + * 使用LiveData接受请求结果, 将该liveData直接使用DataBinding绑定到页面上, 会在请求成功自动更新视图 */ fun fetchUserInfo() = scopeNetLife { userInfo.value = Get(Api.GAME).await() - updateTime = System.currentTimeMillis() } /** - * 非阻塞异步任务 - * 返回Deferred, 调用await()才会返回结果. 调用即执行任务 + * 开始非阻塞异步任务 + * 返回Deferred, 调用await()才会返回结果 */ fun fetchList(scope: CoroutineScope) = scope.Get(Api.TEST) /** - * 阻塞异步任务 + * 开始阻塞异步任务 * 直接返回结果 */ suspend fun fetchPrecessData() = coroutineScope { diff --git a/sample/src/main/res/menu/menu_main.xml b/sample/src/main/res/menu/menu_main.xml index b72a6ca1b..162b242d3 100644 --- a/sample/src/main/res/menu/menu_main.xml +++ b/sample/src/main/res/menu/menu_main.xml @@ -78,7 +78,7 @@ android:icon="@drawable/ic_unique" android:title="重复请求" />