Преглед изворни кода

+ 协程替代RxJava实现网络请求
+ 支持并发网络
+ 重构轮循器

drake пре 5 година
родитељ
комит
98e0d4a9ce
33 измењених фајлова са 1152 додато и 1691 уклоњено
  1. 173 223
      README.md
  2. 2 2
      build.gradle
  3. 5 2
      net/build.gradle
  4. 124 214
      net/src/main/java/com/drake/net/Net.kt
  5. 4 21
      net/src/main/java/com/drake/net/NetConfig.kt
  6. 0 50
      net/src/main/java/com/drake/net/observable/FromObservable.java
  7. 0 162
      net/src/main/java/com/drake/net/observable/Interval.kt
  8. 0 289
      net/src/main/java/com/drake/net/observable/ShootObservable.java
  9. 0 87
      net/src/main/java/com/drake/net/observable/TimeInterval.kt
  10. 0 40
      net/src/main/java/com/drake/net/observer/NetObserver.kt
  11. 0 129
      net/src/main/java/com/drake/net/observer/ObservableUtils.kt
  12. 0 182
      net/src/main/java/com/drake/net/observer/ObserverUtils.kt
  13. 0 79
      net/src/main/java/com/drake/net/observer/StateObserver.kt
  14. 0 110
      net/src/main/java/com/drake/net/observer/TryObserver.kt
  15. 98 0
      net/src/main/java/com/drake/net/scope/AndroidScope.kt
  16. 20 43
      net/src/main/java/com/drake/net/scope/DialogCoroutineScope.kt
  17. 39 0
      net/src/main/java/com/drake/net/scope/NetCoroutineScope.kt
  18. 28 32
      net/src/main/java/com/drake/net/scope/PageCoroutineScope.kt
  19. 19 17
      net/src/main/java/com/drake/net/scope/RefreshCoroutineScope.kt
  20. 64 0
      net/src/main/java/com/drake/net/scope/StateCoroutineScope.kt
  21. 206 0
      net/src/main/java/com/drake/net/utils/Interval.kt
  22. 199 0
      net/src/main/java/com/drake/net/utils/ScopeUtils.kt
  23. 24 0
      net/src/main/java/com/drake/net/utils/SuspendUtils.kt
  24. 1 1
      net/src/main/res/values/strings.xml
  25. 7 3
      sample/build.gradle
  26. 9 0
      sample/src/main/java/com/drake/net/sample/App.kt
  27. 25 1
      sample/src/main/java/com/drake/net/sample/MainActivity.kt
  28. BIN
      sample/src/main/res/drawable/bg_empty.webp
  29. BIN
      sample/src/main/res/drawable/bg_error.webp
  30. 12 4
      sample/src/main/res/layout/activity_main.xml
  31. 35 0
      sample/src/main/res/layout/layout_empty.xml
  32. 39 0
      sample/src/main/res/layout/layout_error.xml
  33. 19 0
      sample/src/main/res/layout/layout_loading.xml

+ 173 - 223
README.md

@@ -1,30 +1,42 @@
 # Net
 
-针对[Kalle](https://github.com/yanzhenjie/Kalle)网络请求框架进行扩展
+异步任务库, Android 创新式的网络请求库(针对[Kalle](https://github.com/yanzhenjie/Kalle)网络请求框架进行扩展), 支持协程高并发网络请求
 
 
 
-Android 创新式的网络请求库, 完美扩展RxJava|列表|缺省页
+本项目为Android项目中的所有的异步任务和网络请求而生
+
+
+
+1.0+ 版本为RxKotlin实现
+2.0+ 版本开始引入Kotlin协程特性, 开发者无需掌握协程也可以使用, 两个版本存在Api冲突需要手动迁移
 
 
 
 主要新增特性
 
-- Kotlin DSL
-- RxJava
+- 协程
+- 并发网络请求
+- 串行网络请求
+- 切换线程
+- DSL编程
 - 自动错误信息吐司
+- 自动异常捕获
+- 自动日志打印异常
 - 自动JSON解析
 - 自动处理下拉刷新和上拉加载
 - 自动处理分页加载
 - 自动缺省页
-- 自动生命周期
+- 自动处理生命周期
 - 自动处理加载对话框
+- 协程作用域支持错误和结束回调
 
 
 
 同时完全不影响[Kalle](https://github.com/yanzhenjie/Kalle)的特性
 
-- 九种缓存模式, 缓存加密
+- 九种缓存模式
+- 缓存加密
 - 上传进度监听
 - 下载进度监听
 - 断点续传
@@ -32,7 +44,7 @@ Android 创新式的网络请求库, 完美扩展RxJava|列表|缺省页
 - 网络连接判断
 - 自定义数据转换器
 - 网络拦截器
-- 连接重试
+- 重定向
 - 自定义请求体
 - 全局配置
 - Cookie
@@ -58,7 +70,14 @@ allprojects {
 module 的 build.gradle
 
 ```groovy
-implementation 'com.github.liangjingkanji:Net:1.3.7'
+// 协程库
+implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
+implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
+
+// 支持自动下拉刷新和缺省页的, 可选
+implementation 'com.github.liangjingkanji:BRV:1.1.7'
+
+implementation 'com.github.liangjingkanji:Net:2.0.0'
 ```
 
 
@@ -67,13 +86,21 @@ implementation 'com.github.liangjingkanji:Net:1.3.7'
 
 ## 请求方式
 
+请求方式支持同步和异步, 异步只允许在作用域内执行. 详情请看`Net.kt`文件
+
+![image-20191223150901891](https://tva1.sinaimg.cn/large/006tNbRwgy1ga6o9s47lsj30dg0ca0tz.jpg)
+
 ### Post
 
 ```kotlin
-post<Model>(""){
-  param("key", "value")
-}.net { 
+scopeLife {
 
+  val data = post<String>(
+    "https://raw.githubusercontent.com/liangjingkanji/BRV/master/README.md",
+    absolutePath = true
+  )
+
+  textView.text = data.await()
 }
 ```
 
@@ -83,11 +110,15 @@ post<Model>(""){
 
 ### Get
 
-```
-get<Model>(""){
-  param("key", "value")
-}.net { 
+```kotlin
+scopeLife {
+
+  val data = get<String>(
+    "https://raw.githubusercontent.com/liangjingkanji/BRV/master/README.md",
+    absolutePath = true
+  )
 
+  textView.text = data.await()
 }
 ```
 
@@ -98,10 +129,16 @@ get<Model>(""){
 ### 文件上传
 
 ```kotlin
-post<Model>(""){
-  file("file", File("path"))
-}.net {
+scopeLife {
 
+  val data = post<String>(
+    "https://raw.githubusercontent.com/liangjingkanji/BRV/master/README.md",
+    absolutePath = true
+  ){
+    file("file", File())
+  }
+
+  textView.text = data.await()
 }
 ```
 
@@ -112,15 +149,15 @@ post<Model>(""){
 ### 文件下载
 
 ```kotlin
-download("/path", "下载目录"){
-
-  // 进度监听
-  onProgress { progress, byteCount, speed ->
+scopeLife {
+  download("/path", "下载目录"){
 
-             }
+    // 进度监听
+    onProgress { progress, byteCount, speed ->
 
-}.dialog(this){
+               }
 
+  }
 }
 ```
 
@@ -139,14 +176,20 @@ Context.downloadImg(url: String, with: Int = -1, height: Int = -1)
 示例
 
 ```kotlin
-val netObserver = downloadImg(
-  "https://cdn.sspai.com/article/ebe361e4-c891-3afd-8680-e4bad609723e.jpg?imageMogr2/quality/95/thumbnail/!2880x620r/gravity/Center/crop/2880x620/interlace/1".
-  200,200
-).net(this) {
-  it // File对象
+scopeLife {
+
+  val data = downImage(
+    "https://cdn.sspai.com/article/ebe361e4-c891-3afd-8680-e4bad609723e.jpg?imageMogr2/quality/95/thumbnail/!2880x620r/gravity/Center/crop/2880x620/interlace/1".
+    200,200
+  )
+
 }
 ```
 
+
+
+
+
 ## 初始化
 
 ```kotlin
@@ -273,209 +316,185 @@ initNet("http://192.168.2.1") {
 }
 ```
 
-
-
 ## 
 
-## 生命周期
+## 作用域
 
-将Fragment或者Activity作为参数传递即可在页面关闭时自动取消订阅, 避免操作已销毁视图.
+上面的网络请求方式全部使用的`scopeLife`, 该作用域属于跟随生命周期自动销毁作用域
 
-```kotlin
-post<Model>(""){
-  param("key", "value")
-}.net(activity) { 
 
-}
-```
 
-其他的对话框或者缺省页和下拉刷新等自动支持生命周期管理
+所有作用域都会自动打印异常信息到LogCat, 作用域都是异步执行在主线程
 
 
 
-如果是`net`跟随生命周期
+### 异步作用域
 
-```kotlin
-post<Model>(""){
-  param("key", "value")
-}.net(activity, Lifecycle.Event.ON_DESTROY) { 
+该作用域的生命周期跟随整个应用, 不会自动取消, 需要你自己手动取消`cancel()`
 
+```
+scope {
+	
 }
 ```
 
-返回值 Disposable 可以用于完全手动任何位置取消
 
-## 对话框
 
-将会在网络请求开始时弹出对话框, 结束时关闭对话框.
+### 生命周期作用域
 
-```kotlin
-post<Model>(""){
-  file("file", File("path"))
-}.dialog(this) {
+该作用域默认在销毁`OnDestroy`时被销毁, 内部所有网络请求都会取消
 
+```
+scopeLife {
+	
 }
 ```
 
-
-
-自定义对话框
+函数
 
 ```kotlin
-fun <M> Observable<M>.dialog(
-    activity: FragmentActivity,
-    dialog: Dialog = ProgressDialog(activity),
-    cancelable: Boolean = true,
-    block: (DialogObserver<M>.(M) -> Unit)? = null
-)
+fun LifecycleOwner.scopeLife(
+    lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_DESTROY, // 自定义销毁作用域的生命周期
+    block: suspend CoroutineScope.() -> Unit
+): AndroidScope
 ```
 
-- `cancelable` 决定对话框是否可以点击用户关闭
-- `dialog` 传入自定义对话框
 
 
+以上两种作用域不会自动吐司网络请求的异常信息
 
-对话框关闭会导致网络请求被取消订阅
+### 网络请求作用域
 
-## 分页加载
+和异步作用域的区别就是会自动吐司网络请求的异常信息
 
-需要引入第三方库: [BRV](https://github.com/liangjingkanji/BRV)
+```
+scopeNet {
 
-```kotlin
-pageRefreshLayout.onRefresh { 
-    post<Model>("/path"){
-        param("key", "value")
-        param("page", index) // 页面索引使用pageRefreshLayout的属性index
-    }.page(page) {
-        if (it.data.isEmpty()){
-            showEmpty()
-            return
-        }
-        addData(it.data){
-            index < it.data.totalPage // 判断是否存在下一页
-        }
-    } 
 }
 ```
 
-此时下拉和上拉都会调用该回调`onRefresh`中的接口请求. 
 
-如果下拉刷新和上拉加载的接口不一致可以再实现`onLoadMore`回调
 
-```kotlin
-pageRefreshLayout.onLoadMore {
-	// 上拉加载网络请求
+跟随生命周期的网络请求
+
+```
+scopeNetLife {
+	
 }
 ```
 
 
 
-
-
-某些情况存在一些页面仅仅需要下拉刷新, 不需要分页/缺省页/上拉加载, 例如用户中心的刷新. 这个时候我们应该使用`refresh`函数而不是`page`.
+### 自动加载对话框
 
 ```
-post<Model>(""){
-  param("key", "value")
-}.refresh(smartRefreshLayout) {
+scopeDialog {
 
 }
 ```
 
+该作用域会在开始执行时显示对话框, 在作用域内全部任务执行完毕后取消对话框(异常或正常结束都会取消).
 
+并且该对话框支持自定义全局或者通过函数参数传入单例对话框(仅该作用域对象使用的加载框)
 
-refresh函数
+
+
+> 自定义全局对话框
+
+全局对话框设置通过NetConfig.onDialog设置
 
 ```kotlin
-/**
- * 自动结束下拉加载
- * @receiver Observable<M>
- * @param pageRefreshLayout SmartRefreshLayout
- * @param loadMore 是否启用上拉加载
- * @param block (M) -> UnitUtils
- */
-fun <M> Observable<M>.refresh(
-    pageRefreshLayout: PageRefreshLayout,
-    loadMore: Boolean = false,
-    block: RefreshObserver<M>.(M) -> Unit
-) {
-    subscribe(object : RefreshObserver<M>(pageRefreshLayout, loadMore) {
-        override fun onNext(t: M) {
-            block(t)
-        }
-    })
+initNet("http://localhost.com") {
+  onDialog {
+    ProgressDialog(it).apply { setMessage("正在加载中") } // 返回一个Dialog
+  }
 }
 ```
 
 
 
-## 缺省页
+### 自动缺省页
+
 
-需要引入第三方库: [StateLayout](https://github.com/liangjingkanji/StateLayout) (如果已经引入BRV可以不再引入)
 
 ```
-post<Model>(""){
-  param("key", "value")
-}.state(stateLayout) { 
+scopeState {
 
 }
 ```
 
 
 
-关于`state`函数支持参数类型有如下: 
+缺省页支持局部(使用控件对象)和全局页面(Activity或者Fragment对象)开启作用域
+
+Fragment|Activity|View都支持直接通过`scopeState`函数开启作用域
+
+StateLayout使用`scope`函数开启作用域
+
+
+
+关于支持接收者类型有如下: 
 
 - stateLayout
 - activity
 - fragment
 - view
 
+### 自动下拉刷新|上拉加载|分页|缺省页
+
+需要引入第三方库: [BRV](https://github.com/liangjingkanji/BRV)
+
+```kotlin
+pageRefreshLayout.onRefresh { 
 
+  pageRefreshLayout.scope {
 
-会根据参数的不同而给不同的对象添加缺省页状态
+    val result = post<Model>("/path"){
+      param("key", "value")
+      param("page", index) // 页面索引使用pageRefreshLayout的属性index
+    }
 
-##Observer
+    val data = result.await().data
 
-无论是`page/refresh/net/dialog`这些函数本身都是快速创建Observer的扩展函数而已, 如果你需要拿到Observer的onError/onCompleted等回调请自己创建匿名类或者继承.
+    if (data.isEmpty()){
+      it.showEmpty()
+      return
+    }
+    
+    it.addData(data){
+      index < data.totalPage // 判断是否存在下一页
+    }
+  }
+}.showLoading
+```
+
+此时下拉和上拉都会调用该回调`onRefresh`中的接口请求. 
 
 
 
-例如查看page源码即可看到只是创建一个Observer订阅而已
+如果下拉刷新和上拉加载的接口不一致可以再实现`onLoadMore`回调
 
 ```kotlin
-fun <M> Observable<M>.page(
-    pageRefreshLayout: PageRefreshLayout,
-    block: PageObserver<M>.(M) -> Unit
-) {
-    subscribe(object : PageObserver<M>(pageRefreshLayout) {
-        override fun onSucceed(t: M) {
-            block(t)
-        }
-    })
+pageRefreshLayout.onLoadMore {
+	// 上拉加载网络请求
 }
 ```
 
-所有扩展订阅函数的都在`ObserverUtils`类中
 
 
+showLoading属于现实缺省页中的加载页, 你也可以使用`autoRefresh()`显示下拉刷新的动画而不是缺省页
 
-扩展函数都会返回对应的Observer对象可以进行手动取消订阅等操作
 
-```kotlin
-val netObserver = download(
-    "https://cdn.sspai.com/article/ebe361e4-c891-3afd-8680-e4bad609723e.jpg?imageMogr2/quality/95/thumbnail/!2880x620r/gravity/Center/crop/2880x620/interlace/1",
-    isAbsolutePath = true
-).net(this) {
-
-}.error {
-    // 自定义自己的错误处理
-    handleError(it) // 该函数每个Observer都存在, 属于默认的错误处理操作
-}
+
+如果仅仅是自动完成下拉加载. 例如一般用户中心页面只需要自动处理下拉刷新的状态
+
 ```
+pageRefreshLayout.scopeRefresh{
 
+}
+```
 
 
-本网络请求的所有的Observer都不仅仅是网络请求可以用, 可以配合任何其他的被观察者使用, 主要解决视图更新以及生命周期问题
 
 ## 请求和响应规范
 
@@ -540,96 +559,27 @@ abstract class DefaultConverter(
 
 
 
-函数
-
-```
-subscribe() // 即开启定时器, 订阅多个也会监听同一个计数器
-stop() // 结束
-pause() // 暂停
-resume() // 继续
-reset // 重置轮循器(包含计数器count和计时period)
-```
-
-
-
-### 事件间隔
-
-可以在观察者中接收到相邻两次事件之间的事件间隔, 第一次事件的间隔(`time()`)是: -1
-
-```
-Observabl().interval()
-```
-
-
-
-常见示例: 返回两次退出应用
-
-```kotlin
-class MainActivity : AppCompatActivity() {
-
-     // 1.5秒内两次返回退出应用
-    private val exitOb by lazy {
-        val temp = PublishSubject.create<Boolean>()
-        temp.interval(TimeUnit.SECONDS)
-                .observeMain()
-                .auto(this)
-                .subscribe {
-                    val time = it.time()
-                    if (time <= 1.5 && time != -1L) super.onBackPressed() else toast("再按一次退出")
-                }
-        temp
-    }
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        setContentView(R.layout.activity_main)
-    }
-
-    override fun onBackPressed() {
-        exitOb.onNext(true)
-    }
-}
-```
-
-
-
-## 快速创建被观察者
-
-使用本框架定义的创建被观察者函数可以避免异步阻塞时发射事件导致的空指针
-
-
+示例
 
 ```kotlin
-from { 
-
-// 返回值为事件
-  8
-}.subscribe { 
+Interval(1, TimeUnit.SECONDS).subscribe { 
 	
-}
-
-shoot<Int> { 
-	it.onNext(2)
-}.subscribe { 
+}.finish { 
 	
-}
+}.start()
 ```
 
-这里提到`from`可以快速创建一个被观察者. 函数参数返回值即发送的事件
-
-`auto`函数可以跟随生命周期自动取消订阅
-
 
 
-快速切换线程
+函数
 
-```kotlin
-from { 
-  // 这里属于IO线程 
-  8
-}.io().observeMain.subscribe { 
-  // 这里属于主线程
-  // 这里会受到 事件 8
-}
 ```
+subscribe() // 即开启定时器, 订阅多个也会监听同一个计数器
+stop() // 结束
+pause() // 暂停
+resume() // 继续
+reset // 重置轮循器 (包含计数器count和计时period) 不会停止轮循器
+switch //  切换轮循器开关
 
+state // 轮循器的状态
+```

+ 2 - 2
build.gradle

@@ -1,14 +1,14 @@
 // Top-level build file where you can add configuration options common to all sub-projects/modules.
 
 buildscript {
-    ext.kotlin_version = '1.3.50'
+    ext.kotlin_version = '1.3.61'
     repositories {
         google()
         jcenter()
 
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.4.2'
+        classpath 'com.android.tools.build:gradle:3.5.3'
         classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
         classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1'
         // NOTE: Do not place your application dependencies here; they belong

+ 5 - 2
net/build.gradle

@@ -43,6 +43,9 @@ dependencies {
     compileOnly 'com.github.liangjingkanji:BRV:1.1.7'
     compileOnly 'com.github.bumptech.glide:glide:4.9.0'
 
-    compileOnly 'io.reactivex.rxjava2:rxkotlin:2.3.0'
-    compileOnly 'io.reactivex.rxjava2:rxandroid:2.1.1'
+    implementation 'com.github.liangjingkanji:Tooltip:1.0.3'
+
+    compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
+    compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
+
 }

+ 124 - 214
net/src/main/java/com/drake/net/Net.kt

@@ -16,9 +16,7 @@ import com.yanzhenjie.kalle.Kalle
 import com.yanzhenjie.kalle.download.UrlDownload
 import com.yanzhenjie.kalle.simple.SimpleBodyRequest
 import com.yanzhenjie.kalle.simple.SimpleUrlRequest
-import io.reactivex.Observable
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.schedulers.Schedulers
+import kotlinx.coroutines.*
 import java.io.File
 
 // <editor-fold desc="异步请求">
@@ -27,84 +25,62 @@ import java.io.File
  * Get请求
  * @param path String 网络路径, 非绝对路径会加上HOST为前缀
  * @see NetConfig.host
- * @param isAbsolutePath Boolean Path是否是绝对路径
+ * @param absolutePath Boolean Path是否是绝对路径
  * @param tag 可以传递对象给Request请求
  * @param block SimpleUrlRequest.Api.() -> UnitUtils
  * @return Observable<M> 结果会在主线程
  */
-inline fun <reified M> get(
+
+inline fun <reified M> CoroutineScope.get(
     path: String,
     tag: Any? = null,
-    isAbsolutePath: Boolean = false,
+    absolutePath: Boolean = false,
     noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): Observable<M> {
-
-    return Observable.create<M> {
-        try {
-            val get = Kalle.get(if (isAbsolutePath) path else (NetConfig.host + path)).tag(tag)
-            val response = if (block == null) {
-                get.perform(M::class.java, String::class.java)
-            } else {
-                get.apply(block).perform<M, String>(M::class.java, String::class.java)
-            }
-
-            if (!it.isDisposed) {
-                if (response.isSucceed) {
-                    it.onNext(response.succeed())
-                    it.onComplete()
-                } else {
-                    it.onError(ResponseException(response.failed(), response.code()))
-                }
-            }
-        } catch (e: java.lang.Exception) {
-            if (!it.isDisposed) {
-                it.onError(e)
-            }
+): Deferred<M> = async(Dispatchers.IO) {
+
+    val get = Kalle.get(if (absolutePath) path else (NetConfig.host + path)).tag(tag)
+
+    val response = if (block == null) {
+        get.perform(M::class.java, String::class.java)
+    } else {
+        get.apply(block).perform<M, String>(M::class.java, String::class.java)
+    }
+
+    if (isActive) {
+        if (response.isSucceed) {
+            response.succeed()
+        } else {
+            throw ResponseException(response.failed(), response.code())
         }
-    }.onTerminateDetach().subscribeOn(Schedulers.io())
-        .observeOn(AndroidSchedulers.mainThread())
+    } else {
+        throw CancellationException()
+    }
 }
 
-/**
- * Post提交
- * @param path String 网络路径, 非绝对路径会加上HOST为前缀
- * @see NetConfig.host
- * @param tag 可以传递对象给Request请求
- * @param isAbsolutePath Boolean 是否是绝对路径
- * @param block SimpleBodyRequest.Api.() -> UnitUtils
- * @return Observable<M> 结果会在主线程
- */
-inline fun <reified M> post(
+
+inline fun <reified M> CoroutineScope.post(
     path: String,
     tag: Any? = null,
-    isAbsolutePath: Boolean = false,
+    absolutePath: Boolean = false,
     noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): Observable<M> {
-
-    return Observable.create<M> {
-        try {
-            val post = Kalle.post(if (isAbsolutePath) path else (NetConfig.host + path)).tag(tag)
-            val response = if (block == null) {
-                post.perform<M, String>(M::class.java, String::class.java)
-            } else {
-                post.apply(block).perform<M, String>(M::class.java, String::class.java)
-            }
-
-            if (!it.isDisposed) {
-                if (response.isSucceed) {
-                    it.onNext(response.succeed())
-                    it.onComplete()
-                } else {
-                    it.onError(ResponseException(response.failed(), response.code()))
-                }
-            }
-        } catch (e: java.lang.Exception) {
-            if (!it.isDisposed) {
-                it.onError(e)
-            }
+): Deferred<M> = async(Dispatchers.IO) {
+
+    val post = Kalle.post(if (absolutePath) path else (NetConfig.host + path)).tag(tag)
+    val response = if (block == null) {
+        post.perform<M, String>(M::class.java, String::class.java)
+    } else {
+        post.apply(block).perform<M, String>(M::class.java, String::class.java)
+    }
+
+    if (isActive) {
+        if (response.isSucceed) {
+            response.succeed()
+        } else {
+            throw ResponseException(response.failed(), response.code())
         }
-    }.onTerminateDetach().subscribeOn(Schedulers.io())
-        .observeOn(AndroidSchedulers.mainThread())
+    } else {
+        throw CancellationException()
+    }
 }
 
 
@@ -115,43 +91,33 @@ inline fun <reified M> post(
  * @see NetConfig.host
  * @param directory String 下载文件存放目录 {默认存在android/data/packageName/cache目录}
  * @param tag 可以传递对象给Request请求
- * @param isAbsolutePath Boolean 下载链接是否是绝对路径
+ * @param absolutePath Boolean 下载链接是否是绝对路径
  * @param block 请求参数
  * @return Observable<String> 结果会在主线程
  */
-fun download(
+fun CoroutineScope.download(
     path: String,
     directory: String = NetConfig.app.externalCacheDir!!.absolutePath,
     tag: Any? = null,
-    isAbsolutePath: Boolean = false,
+    absolutePath: Boolean = false,
     block: (UrlDownload.Api.() -> Unit)? = null
-): Observable<String> {
-
-    return Observable.create<String> {
-        try {
-            val realPath = if (isAbsolutePath) path else (NetConfig.host + path)
-
-            val download = Kalle.Download.get(realPath).directory(directory).tag(tag)
-
-            val filePath = if (block == null) {
-                download.perform()
-            } else {
-                download.apply(block).perform()
-            }
-
-            if (!it.isDisposed) {
-                it.onNext(filePath)
-                it.onComplete()
-            }
-        } catch (e: Exception) {
-            if (!it.isDisposed) {
-                it.onError(e)
-            }
+): Deferred<String> = async(Dispatchers.IO) {
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val download = Kalle.Download.get(realPath).directory(directory).tag(tag)
+
+    if (isActive) {
+        if (block == null) {
+            download.perform()
+        } else {
+            download.apply(block).perform()
         }
-    }.onTerminateDetach().subscribeOn(Schedulers.io())
-        .observeOn(AndroidSchedulers.mainThread())
+    } else {
+        throw CancellationException()
+    }
 }
 
+
 /**
  * 下载图片, 图片宽高要求要么同时指定要么同时不指定
  *
@@ -161,31 +127,24 @@ fun download(
  * @param height Int 图片高度
  * @return Observable<File>
  */
-fun Context.downloadImg(url: String, with: Int = -1, height: Int = -1): Observable<File> {
-
-    return Observable.create<File> {
+fun CoroutineScope.downImage(
+    context: Context,
+    url: String,
+    with: Int = -1,
+    height: Int = -1
+): Deferred<File> = async(Dispatchers.IO) {
 
-        Glide.with(this).downloadOnly()
+    Glide.with(context).downloadOnly()
 
-        val download = Glide.with(this).download(url)
-
-        val futureTarget = if (with == -1 && height == -1) {
-            download.submit()
-        } else {
-            download.submit(with, height)
-        }
+    val download = Glide.with(context).download(url)
 
-        try {
-            val file = futureTarget.get()
-            if (!it.isDisposed) {
-                it.onNext(file)
-                it.onComplete()
-            }
-        } catch (e: Exception) {
-            if (!it.isDisposed) it.onError(e)
-        }
+    val futureTarget = if (with == -1 && height == -1) {
+        download.submit()
+    } else {
+        download.submit(with, height)
+    }
 
-    }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
+    futureTarget.get()
 }
 
 // </editor-fold>
@@ -195,126 +154,77 @@ fun Context.downloadImg(url: String, with: Int = -1, height: Int = -1): Observab
 inline fun <reified M> syncGet(
     path: String,
     tag: Any? = null,
-    isAbsolutePath: Boolean = false,
+    absolutePath: Boolean = false,
     noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): Observable<M> {
-
-    return Observable.create<M> {
-        try {
-            val get = Kalle.get(if (isAbsolutePath) path else (NetConfig.host + path)).tag(tag)
-            val response = if (block == null) {
-                get.perform(M::class.java, String::class.java)
-            } else {
-                get.apply(block).perform<M, String>(M::class.java, String::class.java)
-            }
-
-            if (!it.isDisposed) {
-                if (response.isSucceed) {
-                    it.onNext(response.succeed())
-                    it.onComplete()
-                } else {
-                    it.onError(ResponseException(response.failed(), response.code()))
-                }
-            }
-        } catch (e: java.lang.Exception) {
-            if (!it.isDisposed) {
-                it.onError(e)
-            }
-        }
-    }.onTerminateDetach()
+): M {
+
+    val get = Kalle.get(if (absolutePath) path else (NetConfig.host + path)).tag(tag)
+    val response = if (block == null) {
+        get.perform(M::class.java, String::class.java)
+    } else {
+        get.apply(block).perform<M, String>(M::class.java, String::class.java)
+    }
+
+    return if (response.isSucceed) {
+        response.succeed()
+    } else {
+        throw ResponseException(response.failed(), response.code())
+    }
 }
 
 inline fun <reified M> syncPost(
     path: String,
     tag: Any? = null,
-    isAbsolutePath: Boolean = false,
+    absolutePath: Boolean = false,
     noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): Observable<M> {
-
-    return Observable.create<M> {
-        try {
-            val post = Kalle.post(if (isAbsolutePath) path else (NetConfig.host + path)).tag(tag)
-            val response = if (block == null) {
-                post.perform<M, String>(M::class.java, String::class.java)
-            } else {
-                post.apply(block).perform<M, String>(M::class.java, String::class.java)
-            }
-
-            if (!it.isDisposed) {
-                if (response.isSucceed) {
-                    it.onNext(response.succeed())
-                    it.onComplete()
-                } else {
-                    it.onError(ResponseException(response.failed(), response.code()))
-                }
-            }
-        } catch (e: java.lang.Exception) {
-            if (!it.isDisposed) {
-                it.onError(e)
-            }
-        }
-    }.onTerminateDetach()
+): M {
+
+    val post = Kalle.post(if (absolutePath) path else (NetConfig.host + path)).tag(tag)
+    val response = if (block == null) {
+        post.perform<M, String>(M::class.java, String::class.java)
+    } else {
+        post.apply(block).perform<M, String>(M::class.java, String::class.java)
+    }
+
+    return if (response.isSucceed) {
+        response.succeed()
+    } else {
+        throw ResponseException(response.failed(), response.code())
+    }
 }
 
 fun syncDownload(
     path: String,
     directory: String = NetConfig.app.externalCacheDir!!.absolutePath,
     tag: Any? = null,
-    isAbsolutePath: Boolean = false,
+    absolutePath: Boolean = false,
     block: (UrlDownload.Api.() -> Unit)? = null
-): Observable<String> {
-
-    return Observable.create<String> {
-        try {
-            val realPath = if (isAbsolutePath) path else (NetConfig.host + path)
-
-            val download = Kalle.Download.get(realPath).directory(directory).tag(tag)
-
-            val filePath = if (block == null) {
-                download.perform()
-            } else {
-                download.apply(block).perform()
-            }
-
-            if (!it.isDisposed) {
-                it.onNext(filePath)
-                it.onComplete()
-            }
-        } catch (e: Exception) {
-            if (!it.isDisposed) {
-                it.onError(e)
-            }
-        }
-    }.onTerminateDetach()
-}
+): String {
 
-fun Context.syncDownloadImg(url: String, with: Int = 0, height: Int = 0): Observable<File> {
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
 
-    return Observable.create<File> {
+    val download = Kalle.Download.get(realPath).directory(directory).tag(tag)
 
-        Glide.with(this).downloadOnly()
+    return if (block == null) {
+        download.perform()
+    } else {
+        download.apply(block).perform()
+    }
+}
 
-        val download = Glide.with(this).download(url)
+fun Context.syncDownImage(url: String, with: Int = 0, height: Int = 0): File {
 
-        val futureTarget = if (with == 0 && height == 0) {
-            download.submit()
-        } else {
-            download.submit(with, height)
-        }
+    Glide.with(this).downloadOnly()
 
-        try {
-            val file = futureTarget.get()
-            if (!it.isDisposed) {
-                it.onNext(file)
-                it.onComplete()
-            }
-        } catch (e: Exception) {
-            if (!it.isDisposed) {
-                it.onError(e)
-            }
-        }
+    val download = Glide.with(this).download(url)
+
+    val futureTarget = if (with == 0 && height == 0) {
+        download.submit()
+    } else {
+        download.submit(with, height)
+    }
 
-    }.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
+    return futureTarget.get()
 }
 
 // </editor-fold>

+ 4 - 21
net/src/main/java/com/drake/net/NetConfig.kt

@@ -9,15 +9,14 @@ package com.drake.net
 
 import android.app.Application
 import android.app.Dialog
-import android.content.Context
 import android.view.View
 import android.widget.Toast
 import androidx.fragment.app.FragmentActivity
 import com.drake.net.error.RequestParamsException
 import com.drake.net.error.ResponseException
 import com.drake.net.error.ServerResponseException
-import com.drake.net.observer.DialogObserver
-import com.drake.net.observer.runMain
+import com.drake.net.scope.DialogCoroutineScope
+import com.drake.tooltip.toast
 import com.yanzhenjie.kalle.Kalle
 import com.yanzhenjie.kalle.KalleConfig
 import com.yanzhenjie.kalle.exception.*
@@ -30,7 +29,7 @@ object NetConfig {
     lateinit var app: Application
 
     internal var defaultToast: Toast? = null
-    internal var defaultDialog: (DialogObserver<*>.(FragmentActivity) -> Dialog)? = null
+    internal var defaultDialog: (DialogCoroutineScope.(FragmentActivity) -> Dialog)? = null
     internal var onError: Throwable.() -> Unit = {
 
         val message = when (this) {
@@ -133,23 +132,7 @@ fun KalleConfig.Builder.onStateError(block: Throwable.(view: View) -> Unit) {
  * 设置使用DialogObserver默认弹出的加载对话框
  * 默认使用系统自带的ProgressDialog
  */
-fun KalleConfig.Builder.onDialog(block: (DialogObserver<*>.(context: FragmentActivity) -> Dialog)) {
+fun KalleConfig.Builder.onDialog(block: (DialogCoroutineScope.(context: FragmentActivity) -> Dialog)) {
     NetConfig.defaultDialog = block
 }
 
-/**
- * 系统消息吐司
- * 允许异步线程显示
- * 不会覆盖显示
- */
-internal fun Context.toast(message: CharSequence, config: Toast.() -> Unit = {}) {
-    NetConfig.defaultToast?.cancel()
-
-    runMain {
-        NetConfig.defaultToast =
-            Toast.makeText(this, message, Toast.LENGTH_SHORT).apply { config() }
-        NetConfig.defaultToast?.show()
-    }
-}
-
-

+ 0 - 50
net/src/main/java/com/drake/net/observable/FromObservable.java

@@ -1,50 +0,0 @@
-/*
- * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
- * Project:Net
- * Author:Drake
- * Date:12/7/19 1:08 PM
- */
-
-package com.drake.net.observable;
-
-import java.util.concurrent.Callable;
-
-import io.reactivex.Observable;
-import io.reactivex.Observer;
-import io.reactivex.exceptions.Exceptions;
-import io.reactivex.internal.functions.ObjectHelper;
-import io.reactivex.internal.observers.DeferredScalarDisposable;
-
-
-public final class FromObservable<T> extends Observable<T> implements Callable<T> {
-    final Callable<? extends T> callable;
-
-    public FromObservable(Callable<? extends T> callable) {
-        this.callable = callable;
-    }
-
-    @Override
-    public void subscribeActual(Observer<? super T> observer) {
-
-        DeferredScalarDisposable<T> d = new DeferredScalarDisposable<T>(observer);
-        observer.onSubscribe(d);
-        if (d.isDisposed()) {
-            return;
-        }
-        T value;
-        try {
-            value = ObjectHelper.requireNonNull(callable.call(), "Callable returned null");
-        } catch (Throwable e) {
-            e.printStackTrace();
-            Exceptions.throwIfFatal(e);
-            return;
-        }
-
-        d.complete(value);
-    }
-
-    @Override
-    public T call() throws Exception {
-        return ObjectHelper.requireNonNull(callable.call(), "The callable returned a null value");
-    }
-}

+ 0 - 162
net/src/main/java/com/drake/net/observable/Interval.kt

@@ -1,162 +0,0 @@
-/*
- * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
- * Project:Net
- * Author:Drake
- * Date:12/7/19 1:08 PM
- */
-package com.drake.net.observable
-
-import io.reactivex.Observable
-import io.reactivex.Observer
-import io.reactivex.Scheduler
-import io.reactivex.disposables.Disposable
-import io.reactivex.internal.disposables.DisposableHelper
-import io.reactivex.internal.schedulers.TrampolineScheduler
-import io.reactivex.schedulers.Schedulers
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.atomic.AtomicReference
-
-
-/**
- * 非常强大的轮循器
- *
- * 多个观察者可观察同一个计时器
- *
- * 支持 开始 | 暂停 | 继续 | 结束 | 重置 | 中途修改计数器
- */
-class Interval(
-    var end: Long, // -1 表示永远不结束, 可以修改
-    private val period: Long,
-    private val unit: TimeUnit,
-    private val initialDelay: Long = 0,
-    private val start: Long = 0,
-    private val scheduler: Scheduler = Schedulers.computation()
-) : Observable<Long>() {
-
-    /**
-     * 不会自动结束的轮循器
-     */
-    constructor(
-        period: Long,
-        unit: TimeUnit,
-        initialDelay: Long = 0,
-        scheduler: Scheduler = Schedulers.computation()
-    ) : this(-1, period, unit, initialDelay, 0, scheduler)
-
-    private var observerList = ArrayList<IntervalRangeObserver>()
-    private var pause = false
-    private var stop = false
-    private var dispose: Disposable? = null
-    private val iterator = {
-
-        if (!pause) {
-            synchronized(this) {
-                count += 1
-            }
-            for (i in 0 until observerList.size) {
-                observerList[i].run(count, end, stop)
-            }
-        }
-    }
-
-    public override fun subscribeActual(observer: Observer<in Long?>) {
-
-        val agentObserver = IntervalRangeObserver(observer)
-        observerList.add(agentObserver)
-
-        observer.onSubscribe(agentObserver)
-
-        if (dispose == null) init()
-        agentObserver.setResource(dispose)
-    }
-
-    private fun init() {
-        if (observerList.isEmpty()) return
-
-        dispose = if (scheduler is TrampolineScheduler) {
-            val worker = scheduler.createWorker()
-            worker.schedulePeriodically(iterator, initialDelay, period, unit)
-            worker
-        } else {
-            scheduler.schedulePeriodicallyDirect(iterator, initialDelay, period, unit)
-        }
-    }
-
-    // <editor-fold desc="操作">
-
-
-    /**
-     * 计数器
-     */
-    var count = start
-
-    /**
-     * 停止轮循器
-     * 如果开启轮循器请订阅观察者[subscribe], 多个观察者观察的同一轮循计数器
-     */
-    fun stop() {
-        stop = true
-    }
-
-    /**
-     * 重置轮循器
-     * count = start , 计时器重置
-     */
-    fun reset() {
-        count = start
-        dispose?.dispose()
-        init()
-    }
-
-    /**
-     * 暂停轮循
-     */
-    fun pause() {
-        pause = true
-    }
-
-    /**
-     * 如果轮循器被暂停的情况下继续轮循器
-     */
-    fun resume() {
-        pause = false
-    }
-
-    // </editor-fold>
-
-    private class IntervalRangeObserver(
-        private val downstream: Observer<in Long?>
-    ) : AtomicReference<Disposable?>(), Disposable {
-
-        override fun dispose() {
-            DisposableHelper.dispose(this)
-        }
-
-        override fun isDisposed(): Boolean {
-            return get() === DisposableHelper.DISPOSED
-        }
-
-        fun run(count: Long, end: Long, stop: Boolean) {
-            if (!isDisposed) {
-
-                downstream.onNext(count)
-
-                if (end != -1L && count == end || stop) {
-                    DisposableHelper.dispose(this)
-                    downstream.onComplete()
-                    return
-                }
-            }
-        }
-
-        fun setResource(d: Disposable?) {
-            DisposableHelper.setOnce(this, d)
-        }
-
-        companion object {
-            private const val serialVersionUID = 1891866368734007884L
-        }
-
-    }
-
-}

+ 0 - 289
net/src/main/java/com/drake/net/observable/ShootObservable.java

@@ -1,289 +0,0 @@
-/*
- * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
- * Project:Net
- * Author:Drake
- * Date:12/7/19 1:30 PM
- */
-package com.drake.net.observable;
-
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-
-import io.reactivex.Observable;
-import io.reactivex.ObservableOnSubscribe;
-import io.reactivex.Observer;
-import io.reactivex.disposables.Disposable;
-import io.reactivex.exceptions.Exceptions;
-import io.reactivex.functions.Cancellable;
-import io.reactivex.internal.disposables.CancellableDisposable;
-import io.reactivex.internal.disposables.DisposableHelper;
-import io.reactivex.internal.fuseable.SimpleQueue;
-import io.reactivex.internal.queue.SpscLinkedArrayQueue;
-import io.reactivex.internal.util.AtomicThrowable;
-import io.reactivex.plugins.RxJavaPlugins;
-
-public final class ShootObservable<T> extends Observable<T> {
-    final ObservableOnSubscribe<T> source;
-
-    public ShootObservable(ObservableOnSubscribe<T> source) {
-        this.source = source;
-    }
-
-    @Override
-    protected void subscribeActual(Observer<? super T> observer) {
-        CreateEmitter<T> parent = new CreateEmitter<T>(observer);
-        observer.onSubscribe(parent);
-
-        try {
-            source.subscribe(parent);
-        } catch (Throwable ex) {
-            ex.printStackTrace();
-            Exceptions.throwIfFatal(ex);
-        }
-    }
-
-    static final class CreateEmitter<T>
-            extends AtomicReference<Disposable>
-            implements io.reactivex.ObservableEmitter<T>, Disposable {
-
-        private static final long serialVersionUID = -3434801548987643227L;
-
-        final Observer<? super T> observer;
-
-        CreateEmitter(Observer<? super T> observer) {
-            this.observer = observer;
-        }
-
-        @Override
-        public void onNext(T t) {
-            if (t == null) {
-                onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
-                return;
-            }
-            if (!isDisposed()) {
-                observer.onNext(t);
-            }
-        }
-
-        @Override
-        public void onError(Throwable t) {
-            tryOnError(t);
-        }
-
-        @Override
-        public boolean tryOnError(Throwable t) {
-            if (t == null) {
-                t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources.");
-            }
-            if (!isDisposed()) {
-                try {
-                    observer.onError(t);
-                } finally {
-                    dispose();
-                }
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public void onComplete() {
-            if (!isDisposed()) {
-                try {
-                    observer.onComplete();
-                } finally {
-                    dispose();
-                }
-            }
-        }
-
-        @Override
-        public void setDisposable(Disposable d) {
-            DisposableHelper.set(this, d);
-        }
-
-        @Override
-        public void setCancellable(Cancellable c) {
-            setDisposable(new CancellableDisposable(c));
-        }
-
-        @Override
-        public io.reactivex.ObservableEmitter<T> serialize() {
-            return new SerializedEmitter<T>(this);
-        }
-
-        @Override
-        public void dispose() {
-            DisposableHelper.dispose(this);
-        }
-
-        @Override
-        public boolean isDisposed() {
-            return DisposableHelper.isDisposed(get());
-        }
-
-        @Override
-        public String toString() {
-            return String.format("%s{%s}", getClass().getSimpleName(), super.toString());
-        }
-    }
-
-    /**
-     * Serializes calls to onNext, onError and onComplete.
-     *
-     * @param <T> the value type
-     */
-    static final class SerializedEmitter<T>
-            extends AtomicInteger
-            implements io.reactivex.ObservableEmitter<T> {
-
-        private static final long serialVersionUID = 4883307006032401862L;
-
-        final io.reactivex.ObservableEmitter<T> emitter;
-
-        final AtomicThrowable error;
-
-        final SpscLinkedArrayQueue<T> queue;
-
-        volatile boolean done;
-
-        SerializedEmitter(io.reactivex.ObservableEmitter<T> emitter) {
-            this.emitter = emitter;
-            this.error = new AtomicThrowable();
-            this.queue = new SpscLinkedArrayQueue<T>(16);
-        }
-
-        @Override
-        public void onNext(T t) {
-            if (emitter.isDisposed() || done) {
-                return;
-            }
-            if (t == null) {
-                onError(new NullPointerException("onNext called with null. Null values are generally not allowed in 2.x operators and sources."));
-                return;
-            }
-            if (get() == 0 && compareAndSet(0, 1)) {
-                emitter.onNext(t);
-                if (decrementAndGet() == 0) {
-                    return;
-                }
-            } else {
-                SimpleQueue<T> q = queue;
-                synchronized (q) {
-                    q.offer(t);
-                }
-                if (getAndIncrement() != 0) {
-                    return;
-                }
-            }
-            drainLoop();
-        }
-
-        @Override
-        public void onError(Throwable t) {
-            if (!tryOnError(t)) {
-                RxJavaPlugins.onError(t);
-            }
-        }
-
-        @Override
-        public boolean tryOnError(Throwable t) {
-            if (emitter.isDisposed() || done) {
-                return false;
-            }
-            if (t == null) {
-                t = new NullPointerException("onError called with null. Null values are generally not allowed in 2.x operators and sources.");
-            }
-            if (error.addThrowable(t)) {
-                done = true;
-                drain();
-                return true;
-            }
-            return false;
-        }
-
-        @Override
-        public void onComplete() {
-            if (emitter.isDisposed() || done) {
-                return;
-            }
-            done = true;
-            drain();
-        }
-
-        void drain() {
-            if (getAndIncrement() == 0) {
-                drainLoop();
-            }
-        }
-
-        void drainLoop() {
-            io.reactivex.ObservableEmitter<T> e = emitter;
-            SpscLinkedArrayQueue<T> q = queue;
-            AtomicThrowable error = this.error;
-            int missed = 1;
-            for (; ; ) {
-
-                for (; ; ) {
-                    if (e.isDisposed()) {
-                        q.clear();
-                        return;
-                    }
-
-                    if (error.get() != null) {
-                        q.clear();
-                        e.onError(error.terminate());
-                        return;
-                    }
-
-                    boolean d = done;
-                    T v = q.poll();
-
-                    boolean empty = v == null;
-
-                    if (d && empty) {
-                        e.onComplete();
-                        return;
-                    }
-
-                    if (empty) {
-                        break;
-                    }
-
-                    e.onNext(v);
-                }
-
-                missed = addAndGet(-missed);
-                if (missed == 0) {
-                    break;
-                }
-            }
-        }
-
-        @Override
-        public void setDisposable(Disposable d) {
-            emitter.setDisposable(d);
-        }
-
-        @Override
-        public void setCancellable(Cancellable c) {
-            emitter.setCancellable(c);
-        }
-
-        @Override
-        public boolean isDisposed() {
-            return emitter.isDisposed();
-        }
-
-        @Override
-        public io.reactivex.ObservableEmitter<T> serialize() {
-            return this;
-        }
-
-        @Override
-        public String toString() {
-            return emitter.toString();
-        }
-    }
-
-}

+ 0 - 87
net/src/main/java/com/drake/net/observable/TimeInterval.kt

@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
- * Project:Net
- * Author:Drake
- * Date:12/7/19 7:08 PM
- */
-package com.drake.net.observable
-
-import io.reactivex.Observable
-import io.reactivex.ObservableSource
-import io.reactivex.Observer
-import io.reactivex.Scheduler
-import io.reactivex.disposables.Disposable
-import io.reactivex.internal.disposables.DisposableHelper
-import io.reactivex.schedulers.Schedulers
-import io.reactivex.schedulers.Timed
-import java.util.concurrent.TimeUnit
-
-/**
- * 第一次事件的时间间隔[Timed.time]为 -1
- */
-fun <T> Observable<T>.interval(
-    unit: TimeUnit,
-    scheduler: Scheduler = Schedulers.computation()
-): TimeInterval<T> {
-    return TimeInterval(this, unit, scheduler)
-}
-
-class TimeInterval<T>(
-    private val source: ObservableSource<T>,
-    private val unit: TimeUnit,
-    private val scheduler: Scheduler
-) : Observable<Timed<T>>() {
-
-    override fun subscribeActual(observer: Observer<in Timed<T>>) {
-        val timeIntervalObserver = TimeIntervalObserver(observer, unit, scheduler)
-        source.subscribe(timeIntervalObserver)
-    }
-
-    internal class TimeIntervalObserver<T>(
-        private val downstream: Observer<in Timed<T>>,
-        private val unit: TimeUnit,
-        private val scheduler: Scheduler
-    ) : Observer<T>, Disposable {
-
-        private var lastTime = 0L
-        private var first = true
-        private var upstream: Disposable? = null
-
-        override fun onSubscribe(d: Disposable) {
-            if (DisposableHelper.validate(upstream, d)) {
-                upstream = d
-
-                downstream.onSubscribe(this)
-            }
-        }
-
-        override fun dispose() {
-            upstream!!.dispose()
-        }
-
-        override fun isDisposed(): Boolean {
-            return upstream!!.isDisposed
-        }
-
-        override fun onNext(t: T) {
-            val now = scheduler.now(unit)
-
-            val delta = if (first) {
-                first = false; -1
-            } else now - lastTime
-
-            downstream.onNext(Timed(t, delta, unit))
-            lastTime = now
-        }
-
-        override fun onError(t: Throwable) {
-            downstream.onError(t)
-        }
-
-        override fun onComplete() {
-            downstream.onComplete()
-        }
-
-    }
-
-}

+ 0 - 40
net/src/main/java/com/drake/net/observer/NetObserver.kt

@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
- * Project:Net
- * Author:Drake
- * Date:9/16/19 12:54 AM
- */
-
-package com.drake.net.observer
-
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
-import androidx.lifecycle.LifecycleObserver
-import androidx.lifecycle.LifecycleOwner
-import com.drake.net.NetConfig
-
-
-/**
- * 自动显示错误信息
- */
-abstract class NetObserver<M>() : TryObserver<NetObserver<M>, M>(), LifecycleObserver {
-
-    /**
-     * 跟随生命周期
-     * @param lifecycle 默认是销毁时取消订阅
-     */
-    constructor(
-        lifecycleOwner: LifecycleOwner?,
-        lifecycle: Lifecycle.Event = Lifecycle.Event.ON_DESTROY
-    ) : this() {
-        lifecycleOwner?.lifecycle?.addObserver(object : LifecycleEventObserver {
-            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
-                if (event == lifecycle) dispose()
-            }
-        })
-    }
-
-    override fun handleError(e: Throwable) {
-        NetConfig.onError(e)
-    }
-}

+ 0 - 129
net/src/main/java/com/drake/net/observer/ObservableUtils.kt

@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
- * Project:Net
- * Author:Drake
- * Date:11/25/19 1:03 PM
- */
-
-package com.drake.net.observer
-
-import android.os.Handler
-import android.os.Looper
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
-import androidx.lifecycle.LifecycleOwner
-import com.drake.net.observable.FromObservable
-import com.drake.net.observable.ShootObservable
-import io.reactivex.Observable
-import io.reactivex.ObservableEmitter
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.schedulers.Schedulers
-
-// <editor-fold desc="被观察者线程">
-
-
-fun <M> Observable<M>.computation(): Observable<M> {
-    return subscribeOn(Schedulers.computation())
-}
-
-fun <M> Observable<M>.io(): Observable<M> {
-    return subscribeOn(Schedulers.io())
-}
-
-fun <M> Observable<M>.single(): Observable<M> {
-    return subscribeOn(Schedulers.single())
-}
-
-fun <M> Observable<M>.newThread(): Observable<M> {
-    return subscribeOn(Schedulers.newThread())
-}
-
-fun <M> Observable<M>.main(): Observable<M> {
-    return subscribeOn(AndroidSchedulers.mainThread())
-}
-
-
-// </editor-fold>
-
-
-// <editor-fold desc="观察者线程">
-
-fun <M> Observable<M>.observeMain(): Observable<M> {
-    return observeOn(AndroidSchedulers.mainThread())
-}
-
-fun <M> Observable<M>.observeSingle(): Observable<M> {
-    return observeOn(Schedulers.single())
-}
-
-fun <M> Observable<M>.observeIO(): Observable<M> {
-    return observeOn(Schedulers.io())
-}
-
-fun <M> Observable<M>.observeThread(): Observable<M> {
-    return observeOn(Schedulers.newThread())
-}
-
-fun <M> Observable<M>.observeComputation(): Observable<M> {
-    return observeOn(Schedulers.computation())
-}
-
-// </editor-fold>
-
-/**
- * 在主线程运行
- */
-fun runMain(block: () -> Unit) {
-    if (Looper.myLooper() == Looper.getMainLooper()) {
-        block()
-    } else {
-        Handler(Looper.getMainLooper()).post { block() }
-    }
-}
-
-
-/**
- * io线程 -> main线程
- */
-fun <M> Observable<M>.async(): Observable<M> {
-    return subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())
-}
-
-// <editor-fold desc="创建被观察者">
-
-/**
- * 快速创建一个被观察者
- * @param M 返回值定义被观察者发射事件类型
- */
-fun <M> from(block: () -> M): Observable<M> {
-    return FromObservable(block)
-}
-
-fun <M> shoot(block: (ObservableEmitter<in M>) -> Unit): Observable<M> {
-    return ShootObservable(block)
-}
-
-// </editor-fold>
-
-
-/**
- * 自动跟随生命周期取消观察者订阅
- */
-fun <T> Observable<T>.auto(
-    lifecycleOwner: LifecycleOwner,
-    event: Lifecycle.Event = Lifecycle.Event.ON_DESTROY
-): Observable<T> {
-
-    return doOnLifecycle({ dispose ->
-
-        lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
-
-            override fun onStateChanged(source: LifecycleOwner, actualEvent: Lifecycle.Event) {
-                if (event == actualEvent) {
-                    dispose.dispose()
-                }
-            }
-
-        })
-    }, {})
-}

+ 0 - 182
net/src/main/java/com/drake/net/observer/ObserverUtils.kt

@@ -1,182 +0,0 @@
-/*
- * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
- * Project:Net
- * Author:Drake
- * Date:11/20/19 10:37 PM
- */
-
-package com.drake.net.observer
-
-import android.app.Dialog
-import android.view.View
-import androidx.fragment.app.Fragment
-import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.LifecycleOwner
-import com.drake.brv.PageRefreshLayout
-import com.drake.statelayout.StateLayout
-import io.reactivex.Observable
-
-
-/**
- * 自动打印错误信息
- * @receiver Observable<M>
- * @param lifecycleOwner FragmentActivity? 如果不为null 则会根据当前activity销毁而取消订阅
- * @param block (M) -> UnitUtils
- */
-fun <M> Observable<M>.net(
-    lifecycleOwner: LifecycleOwner? = null,
-    block: (NetObserver<M>.(M) -> Unit) = {}
-): NetObserver<M> {
-    val observer = object : NetObserver<M>(lifecycleOwner) {
-        override fun onSucceed(it: M) {
-            block(it)
-        }
-    }
-    subscribe(observer)
-    return observer
-}
-
-// <editor-fold desc="缺省页">
-
-/**
- * 自动处理多状态页面
- * @receiver Observable<M>
- * @param stateLayout StateLayout
- * @param block (M) -> UnitUtils
- */
-fun <M> Observable<M>.state(
-    stateLayout: StateLayout,
-    block: StateObserver<M>.(M) -> Unit = {}
-): StateObserver<M> {
-    val observer = object : StateObserver<M>(stateLayout) {
-        override fun onSucceed(it: M) {
-            block(it)
-        }
-    }
-    subscribe(observer)
-    return observer
-}
-
-fun <M> Observable<M>.state(
-    view: View,
-    block: StateObserver<M>.(M) -> Unit = {}
-): StateObserver<M> {
-    val observer = object : StateObserver<M>(view) {
-        override fun onSucceed(it: M) {
-            block(it)
-        }
-    }
-    subscribe(observer)
-    return observer
-}
-
-fun <M> Observable<M>.state(
-    activity: FragmentActivity,
-    block: StateObserver<M>.(M) -> Unit
-): StateObserver<M> {
-    val observer = object : StateObserver<M>(activity) {
-        override fun onSucceed(it: M) {
-            block(it)
-        }
-    }
-    subscribe(observer)
-    return observer
-}
-
-fun <M> Observable<M>.state(fragment: Fragment, block: StateObserver<M>.(M) -> Unit = {}) {
-
-    subscribe(object : StateObserver<M>(fragment) {
-        override fun onSucceed(it: M) {
-            block(it)
-        }
-    })
-}
-
-// </editor-fold>
-
-
-// <editor-fold desc="加载框">
-
-/**
- * 请求网络自动开启和关闭对话框
- * @receiver Observable<M>
- * @param activity FragmentActivity
- * @param cancelable Boolean 对话框是否可以被用户取消
- * @param block (M) -> Unit
- */
-fun <M> Observable<M>.dialog(
-    activity: FragmentActivity?,
-    dialog: Dialog? = null,
-    cancelable: Boolean = true,
-    block: (DialogObserver<M>.(M) -> Unit) = {}
-): DialogObserver<M> {
-    val observer = object : DialogObserver<M>(activity, dialog, cancelable) {
-        override fun onSucceed(it: M) {
-            block(it)
-        }
-    }
-    subscribe(observer)
-    return observer
-}
-
-
-fun <M> Observable<M>.dialog(
-    fragment: Fragment,
-    dialog: Dialog? = null,
-    cancelable: Boolean = true,
-    block: (DialogObserver<M>.(M) -> Unit) = {}
-): DialogObserver<M> {
-    val observer = object : DialogObserver<M>(fragment, dialog, cancelable) {
-        override fun onSucceed(it: M) {
-            block(it)
-        }
-    }
-    subscribe(observer)
-    return observer
-}
-
-// </editor-fold>
-
-/**
- * 自动结束下拉加载
- * @receiver Observable<M>
- * @param refreshLayout SmartRefreshLayout
- * @param loadMore 是否启用上拉加载
- * @param block (M) -> UnitUtils
- */
-fun <M> Observable<M>.refresh(
-    refreshLayout: PageRefreshLayout,
-    loadMore: Boolean = false,
-    block: RefreshObserver<M>.(M) -> Unit = {}
-): RefreshObserver<M> {
-    val observer = object : RefreshObserver<M>(refreshLayout, loadMore) {
-        override fun onSucceed(it: M) {
-            block(it)
-        }
-    }
-    subscribe(observer)
-    return observer
-}
-/*
-
-*/
-/**
- * 自动处理分页加载更多和下拉加载
- *
- * @receiver Observable<M>
- * @param pageRefreshLayout PageRefreshLayout
- * @param block (M) -> UnitUtils
- */
-
-fun <M> Observable<M>.page(
-    pageRefreshLayout: PageRefreshLayout,
-    block: PageObserver<M>.(M) -> Unit = {}
-): PageObserver<M> {
-    val observer = object : PageObserver<M>(pageRefreshLayout) {
-        override fun onSucceed(it: M) {
-            block(it)
-        }
-    }
-    subscribe(observer)
-    return observer
-}

+ 0 - 79
net/src/main/java/com/drake/net/observer/StateObserver.kt

@@ -1,79 +0,0 @@
-/*
- * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
- * Project:Net
- * Author:Drake
- * Date:9/16/19 12:54 AM
- */
-
-package com.drake.net.observer
-
-import android.app.Activity
-import android.view.View
-import android.view.View.OnAttachStateChangeListener
-import androidx.fragment.app.Fragment
-import com.drake.net.NetConfig
-import com.drake.statelayout.StateLayout
-import com.drake.statelayout.state
-import io.reactivex.disposables.Disposable
-
-/**
- * 自动显示多状态布局
- */
-abstract class StateObserver<M> : TryObserver<StateObserver<M>, M> {
-
-    val state: StateLayout
-
-    private var error: (StateObserver<M>.(e: Throwable) -> Unit)? = null
-
-    constructor(view: View) {
-        state = view.state()
-    }
-
-    constructor(activity: Activity) {
-        state = activity.state()
-    }
-
-    constructor(fragment: Fragment) {
-        state = fragment.state()
-    }
-
-    constructor(stateLayout: StateLayout) {
-        this.state = stateLayout
-    }
-
-    override fun onStart(d: Disposable) {
-        state.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
-            override fun onViewAttachedToWindow(v: View?) {
-            }
-
-            override fun onViewDetachedFromWindow(v: View) {
-                dispose()
-            }
-        })
-    }
-
-
-    override fun onFailed(e: Throwable) {
-        super.onFailed(e)
-        state.showError()
-        error?.invoke(this, e) ?: handleError(e)
-    }
-
-    override fun handleError(e: Throwable) {
-        NetConfig.onStateError(e, state)
-    }
-
-
-    override fun onFinish() {
-        state.showContent()
-    }
-
-    /**
-     * 显示空缺省页
-     * 此操作会导致观察者取消订阅
-     */
-    fun showEmpty() {
-        state.showEmpty()
-        dispose()
-    }
-}

+ 0 - 110
net/src/main/java/com/drake/net/observer/TryObserver.kt

@@ -1,110 +0,0 @@
-/*
- * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
- * Project:Net
- * Author:Drake
- * Date:12/6/19 5:38 PM
- */
-package com.drake.net.observer
-
-import io.reactivex.Observer
-import io.reactivex.disposables.Disposable
-import io.reactivex.exceptions.Exceptions
-import io.reactivex.internal.disposables.DisposableHelper
-import java.util.concurrent.atomic.AtomicReference
-
-/**
- * 捕捉所有异常
- */
-
-@Suppress("UNCHECKED_CAST")
-abstract class TryObserver<T : Observer<M>, M> : AtomicReference<Disposable>(), Observer<M>,
-    Disposable {
-
-    private var error: (T.(e: Throwable) -> Unit)? = null
-
-    /**
-     * 错误处理回调
-     *
-     * 只在观察者和被观察者事件发送时存在不同步的情况下生效(一般用于异步处理)
-     */
-    fun error(block: (T.(e: Throwable) -> Unit)?): T {
-        error = block
-        return this as T
-    }
-
-    // <editor-fold desc="异常捕捉">
-
-    protected open fun onStart(d: Disposable) {}
-
-    protected open fun onFailed(e: Throwable) = error?.invoke(this as T, e) ?: handleError(e)
-
-    protected abstract fun onSucceed(it: M)
-
-    protected open fun onFinish() {}
-
-    // </editor-fold>
-
-    open fun handleError(e: Throwable) {}
-
-    final override fun onSubscribe(d: Disposable) {
-        if (DisposableHelper.setOnce(this, d)) {
-            try {
-                onStart(d)
-            } catch (ex: Throwable) {
-                Exceptions.throwIfFatal(ex)
-                d.dispose()
-                onError(ex)
-            }
-        }
-    }
-
-    final override fun onNext(t: M) {
-        if (!isDisposed) {
-            try {
-                onSucceed(t)
-            } catch (e: Throwable) {
-                Exceptions.throwIfFatal(e)
-                get().dispose()
-                onError(e)
-            }
-        }
-    }
-
-
-    final override fun onError(t: Throwable) {
-        if (!isDisposed) {
-            lazySet(DisposableHelper.DISPOSED)
-            try {
-                onFailed(t)
-            } catch (e: Throwable) {
-                e.printStackTrace()
-                Exceptions.throwIfFatal(e)
-            }
-        }
-    }
-
-    final override fun onComplete() {
-        if (!isDisposed) {
-            lazySet(DisposableHelper.DISPOSED)
-            try {
-                onFinish()
-            } catch (e: Throwable) {
-                e.printStackTrace()
-                Exceptions.throwIfFatal(e)
-            }
-        }
-    }
-
-    override fun dispose() {
-        DisposableHelper.dispose(this)
-    }
-
-    override fun isDisposed(): Boolean {
-        return get() === DisposableHelper.DISPOSED
-    }
-
-    companion object {
-        private const val serialVersionUID = -7251123623727029452L
-    }
-
-}

+ 98 - 0
net/src/main/java/com/drake/net/scope/AndroidScope.kt

@@ -0,0 +1,98 @@
+/*
+ * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+ * Project:Net
+ * Author:Drake
+ * Date:12/20/19 11:51 AM
+ */
+
+package com.drake.net.scope
+
+import androidx.annotation.CallSuper
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import kotlinx.coroutines.*
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+
+/**
+ * 异步协程作用域
+ */
+open class AndroidScope() : CoroutineScope {
+
+    /**
+     * 使用该构造函数创建对象可以跟随[LifecycleOwner]的生命周期自动取消作用域
+     */
+    constructor(
+        lifecycleOwner: LifecycleOwner,
+        lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_DESTROY
+    ) : this() {
+        lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
+            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+                if (lifeEvent == event) {
+                    cancel()
+                }
+            }
+        })
+    }
+
+    private var catch: (AndroidScope.(Throwable) -> Unit)? = null
+    private var finally: (AndroidScope.(Throwable?) -> Unit)? = null
+    private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
+        catch(throwable)
+    }
+
+    final override val coroutineContext: CoroutineContext =
+        Dispatchers.Main.immediate + exceptionHandler + Job()
+
+    /**
+     * 开启一个作用域
+     */
+    fun launch(
+        block: suspend CoroutineScope.() -> Unit
+    ): AndroidScope {
+        val job: Job = launch(EmptyCoroutineContext, CoroutineStart.DEFAULT, block)
+        job.invokeOnCompletion { finally(it) }
+        return this
+    }
+
+    @CallSuper
+    protected open fun catch(e: Throwable) {
+        catch?.invoke(this, e) ?: handleError(e)
+    }
+
+    @CallSuper
+    protected open fun finally(e: Throwable?) {
+        finally?.invoke(this, e)
+    }
+
+    /**
+     * 当作用域内发生异常时回调
+     */
+    fun catch(block: AndroidScope.(Throwable) -> Unit): AndroidScope {
+        this.catch = block
+        return this
+    }
+
+    /**
+     * 无论正常或者异常结束都将最终执行
+     * @param block 如果是发生异常[Throwable]为该异常对象, 正常结束为NULL
+     * @see launch 只有通过该函数开启的作用域才有效
+     */
+    fun finally(block: AndroidScope.(Throwable?) -> Unit): AndroidScope {
+        this.finally = block
+        return this
+    }
+
+
+    /**
+     * 错误处理
+     * 一般用于被子类重写, 当前默认为打印异常信息到LogCat
+     */
+    open fun handleError(e: Throwable) {
+        e.printStackTrace()
+    }
+
+}
+

+ 20 - 43
net/src/main/java/com/drake/net/observer/DialogObserver.kt → net/src/main/java/com/drake/net/scope/DialogCoroutineScope.kt

@@ -2,24 +2,20 @@
  * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
  * Project:Net
  * Author:Drake
- * Date:9/16/19 12:54 AM
+ * Date:12/20/19 9:20 PM
  */
 
-@file:Suppress("DEPRECATION")
-
-package com.drake.net.observer
+package com.drake.net.scope
 
 import android.app.Dialog
 import android.app.ProgressDialog
-import androidx.fragment.app.Fragment
 import androidx.fragment.app.FragmentActivity
-import androidx.lifecycle.Lifecycle.Event
+import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleObserver
 import androidx.lifecycle.OnLifecycleEvent
 import com.drake.net.NetConfig
 import com.drake.net.NetConfig.defaultDialog
 import com.drake.net.R
-import io.reactivex.disposables.Disposable
 
 /**
  * 自动加载对话框网络请求
@@ -33,23 +29,14 @@ import io.reactivex.disposables.Disposable
  * @param dialog 不使用默认的加载对话框而指定对话框
  * @param cancelable 是否允许用户取消对话框
  */
-abstract class DialogObserver<M>(
-    val activity: FragmentActivity?,
+@Suppress("DEPRECATION")
+class DialogCoroutineScope(
+    val activity: FragmentActivity,
     var dialog: Dialog? = null,
-    val cancelable: Boolean = true
-) : TryObserver<DialogObserver<M>, M>(), LifecycleObserver {
-
-    private var error: (DialogObserver<M>.(e: Throwable) -> Unit)? = null
-
-    constructor(
-        fragment: Fragment?,
-        dialog: Dialog? = null,
-        cancelable: Boolean = true
-    ) : this(fragment?.activity, dialog, cancelable)
+    cancelable: Boolean = true
+) : AndroidScope(), LifecycleObserver {
 
-
-    override fun onStart(d: Disposable) {
-        activity ?: return
+    init {
         activity.lifecycle.addObserver(this)
 
         dialog = when {
@@ -62,35 +49,25 @@ abstract class DialogObserver<M>(
             }
         }
 
-        dialog?.setOnDismissListener { dispose() }
+        dialog?.setOnDismissListener { }
         dialog?.setCancelable(cancelable)
         dialog?.show()
     }
 
-    @OnLifecycleEvent(Event.ON_DESTROY)
-    fun dismiss() {
-        if (dialog != null && dialog!!.isShowing) {
-            dialog?.dismiss()
-        }
-    }
-
-
-    /**
-     * 关闭进度对话框并提醒错误信息
-     *
-     * @param e 包括错误信息
-     */
-    override fun onFailed(e: Throwable) {
-        super.onFailed(e)
-        dismiss()
-    }
-
     override fun handleError(e: Throwable) {
         NetConfig.onError(e)
     }
 
-    override fun onFinish() {
+    override fun finally(e: Throwable?) {
+        super.finally(e)
         dismiss()
     }
 
-}
+    @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
+    fun dismiss() {
+        if (dialog != null && dialog!!.isShowing) {
+            dialog?.dismiss()
+        }
+    }
+
+}

+ 39 - 0
net/src/main/java/com/drake/net/scope/NetCoroutineScope.kt

@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+ * Project:Net
+ * Author:Drake
+ * Date:12/20/19 9:26 PM
+ */
+
+package com.drake.net.scope
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import com.drake.net.NetConfig
+import kotlinx.coroutines.cancel
+
+
+/**
+ * 自动显示网络错误信息协程作用域
+ */
+class NetCoroutineScope() : AndroidScope() {
+
+    constructor(
+        lifecycleOwner: LifecycleOwner,
+        lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_DESTROY
+    ) : this() {
+        lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
+            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+                if (lifeEvent == event) {
+                    cancel()
+                }
+            }
+        })
+    }
+
+    override fun handleError(e: Throwable) {
+        NetConfig.onError(e)
+    }
+
+}

+ 28 - 32
net/src/main/java/com/drake/net/observer/PageObserver.kt → net/src/main/java/com/drake/net/scope/PageCoroutineScope.kt

@@ -2,59 +2,60 @@
  * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
  * Project:Net
  * Author:Drake
- * Date:9/16/19 12:54 AM
+ * Date:12/20/19 2:19 PM
  */
 
-package com.drake.net.observer
+package com.drake.net.scope
 
 import android.view.View
-import android.view.View.OnAttachStateChangeListener
 import com.drake.brv.BindingAdapter
 import com.drake.brv.PageRefreshLayout
-import com.drake.net.NetConfig.onStateError
+import com.drake.net.NetConfig
 import com.scwang.smart.refresh.layout.constant.RefreshState
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.cancel
 
-/**
- * 自动结束下拉刷新和上拉加载状态
- * 自动展示缺省页
- * 自动分页加载
- */
-@Suppress("unused", "MemberVisibilityCanBePrivate")
-abstract class PageObserver<M>(val page: PageRefreshLayout) :
-    TryObserver<PageObserver<M>, M>() {
-
+@Suppress("unused", "MemberVisibilityCanBePrivate", "NAME_SHADOWING")
+class PageCoroutineScope(
+    val page: PageRefreshLayout,
+    val block: suspend CoroutineScope.(PageCoroutineScope) -> Unit
+) : AndroidScope() {
 
     val index get() = page.index
 
     init {
-        page.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
+        page.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
             override fun onViewAttachedToWindow(v: View) {
 
             }
 
             override fun onViewDetachedFromWindow(v: View) {
-                dispose()
+                cancel()
             }
         })
     }
 
-    /**
-     * 关闭进度对话框并提醒错误信息
-     */
-    override fun onFailed(e: Throwable) {
-        super.onFailed(e)
+    override fun catch(e: Throwable) {
+        super.catch(e)
         if (page.state == RefreshState.Refreshing) {
             page.showError()
         } else page.finish(false)
     }
 
+    override fun finally(e: Throwable?) {
+        super.finally(e)
+        if (e == null) {
+            if (page.stateEnabled) page.showContent() else page.finish()
+        }
+    }
+
     override fun handleError(e: Throwable) {
-        onStateError.invoke(e, page)
+        NetConfig.onStateError.invoke(e, page)
     }
 
     /**
      * 自动判断是添加数据还是覆盖数据, 以及数据为空或者NULL时[showEmpty]
-     * @param hasMore 如果不穿数据, 默认已加载完全部(建议此时可以关闭[PageRefreshLayout]的加载更多功能)
+     * @param hasMore 如果不数据, 默认已加载完全部(建议此时可以关闭[PageRefreshLayout]的加载更多功能)
      */
     fun addData(
         data: List<Any>,
@@ -62,21 +63,15 @@ abstract class PageObserver<M>(val page: PageRefreshLayout) :
         hasMore: BindingAdapter.() -> Boolean = { false }
     ) {
         page.addData(data, bindingAdapter, hasMore)
-        dispose()
     }
 
-
     /**
      * 显示空缺省页
      * 此操作会导致观察者取消订阅
      */
     fun showEmpty() {
         page.showEmpty()
-        dispose()
-    }
-
-    override fun onFinish() {
-        if (page.stateEnabled) page.showContent() else page.finish()
+        cancel()
     }
 
     /**
@@ -85,7 +80,7 @@ abstract class PageObserver<M>(val page: PageRefreshLayout) :
      */
     fun showContent() {
         page.showContent()
-        dispose()
+        cancel()
     }
 
     /**
@@ -95,6 +90,7 @@ abstract class PageObserver<M>(val page: PageRefreshLayout) :
      */
     fun finish(success: Boolean = true) {
         page.finish(success)
-        dispose()
+        cancel()
     }
-}
+
+}

+ 19 - 17
net/src/main/java/com/drake/net/observer/RefreshObserver.kt → net/src/main/java/com/drake/net/scope/RefreshCoroutineScope.kt

@@ -2,48 +2,50 @@
  * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
  * Project:Net
  * Author:Drake
- * Date:9/16/19 12:54 AM
+ * Date:12/20/19 2:20 PM
  */
 
-package com.drake.net.observer
+package com.drake.net.scope
 
 import android.view.View
-import android.view.View.OnAttachStateChangeListener
 import com.drake.net.NetConfig
 import com.scwang.smart.refresh.layout.SmartRefreshLayout
+import kotlinx.coroutines.cancel
 
 /**
- * 自动结束下拉刷新
+ * 自动结束下拉刷新 协程作用域
  */
-abstract class RefreshObserver<M>(
+class RefreshCoroutineScope(
     val refresh: SmartRefreshLayout,
-    val loadMore: Boolean = false
-) : TryObserver<RefreshObserver<M>, M>() {
-
+    loadMore: Boolean = false
+) : AndroidScope() {
 
     init {
         refresh.setEnableLoadMore(loadMore)
-        refresh.addOnAttachStateChangeListener(object : OnAttachStateChangeListener {
+        refresh.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
             override fun onViewAttachedToWindow(v: View) {
 
             }
 
             override fun onViewDetachedFromWindow(v: View) {
-                dispose()
+                cancel()
             }
         })
     }
 
-    override fun onFailed(e: Throwable) {
-        super.onFailed(e)
+    override fun catch(e: Throwable) {
+        super.catch(e)
         refresh.finishRefresh(false)
     }
 
-    override fun handleError(e: Throwable) {
-        NetConfig.onError(e)
+    override fun finally(e: Throwable?) {
+        super.finally(e)
+        if (e == null) {
+            refresh.finishRefresh(true)
+        }
     }
 
-    override fun onFinish() {
-        refresh.finishRefresh(true)
+    override fun handleError(e: Throwable) {
+        NetConfig.onError(e)
     }
-}
+}

+ 64 - 0
net/src/main/java/com/drake/net/scope/StateCoroutineScope.kt

@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+ * Project:Net
+ * Author:Drake
+ * Date:12/20/19 2:20 PM
+ */
+
+package com.drake.net.scope
+
+import android.app.Activity
+import android.view.View
+import androidx.fragment.app.Fragment
+import com.drake.net.NetConfig
+import com.drake.statelayout.StateLayout
+import com.drake.statelayout.state
+import kotlinx.coroutines.cancel
+
+/**
+ * 缺省页协程作用域
+ */
+class StateCoroutineScope(val state: StateLayout) : AndroidScope() {
+
+
+    constructor(view: View) : this(view.state())
+
+    constructor(activity: Activity) : this(activity.state())
+
+    constructor(fragment: Fragment) : this(fragment.state())
+
+    init {
+        state.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener {
+            override fun onViewAttachedToWindow(v: View?) {
+            }
+
+            override fun onViewDetachedFromWindow(v: View) {
+                cancel()
+            }
+        })
+
+
+    }
+
+    override fun catch(e: Throwable) {
+        super.catch(e)
+        state.showError()
+        NetConfig.onStateError(e, state)
+    }
+
+    override fun finally(e: Throwable?) {
+        if (e == null) {
+            state.showContent()
+        }
+        super.finally(e)
+    }
+
+    /**
+     * 显示空缺省页
+     */
+    fun showEmpty() {
+        state.showEmpty()
+        cancel()
+    }
+
+}

+ 206 - 0
net/src/main/java/com/drake/net/utils/Interval.kt

@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+ * Project:Net
+ * Author:Drake
+ * Date:12/22/19 3:59 PM
+ */
+
+@file:UseExperimental(ObsoleteCoroutinesApi::class)
+
+package com.drake.net.utils
+
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
+import com.drake.net.scope.AndroidScope
+import kotlinx.coroutines.ObsoleteCoroutinesApi
+import kotlinx.coroutines.cancel
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.TickerMode
+import kotlinx.coroutines.channels.ticker
+import java.io.Serializable
+import java.util.concurrent.TimeUnit
+
+/**
+ * 轮循器
+ *
+ * 操作
+ * 1. 开启: 只有在闲置状态下才可以开始
+ * 2. 停止
+ * 3. 暂停
+ * 4. 继续
+ * 5. 重置: 重置不会导致轮循器停止
+ * 6. 开关: 开启|暂停切换
+ * 7. 生命周期
+ *
+ * 回调: 允许多次订阅同一个轮循器
+ * 1. 每个事件
+ * 2. 停止或者结束
+ *
+ * @param end 结束值
+ * @param period 事件间隔
+ * @param unit 事件单位
+ * @param initialDelay 第一次事件的间隔时间
+ * @param start 开始值
+ */
+class Interval(
+    var end: Long, // -1 表示永远不结束, 可以修改
+    private val period: Long,
+    private val unit: TimeUnit,
+    private val initialDelay: Long = period,
+    private val start: Long = 0
+) : Serializable {
+
+    constructor(
+        period: Long,
+        unit: TimeUnit,
+        initialDelay: Long = period
+    ) : this(-1, period, unit, initialDelay, 0)
+
+    private val receiveList: MutableList<(Long) -> Unit> = mutableListOf()
+    private val finishList: MutableList<(Long) -> Unit> = mutableListOf()
+    private var _state = TickerState.STATE_IDLE
+    private var countTime = 0L
+    private var delay = 0L
+    private var scope: AndroidScope? = null
+    private lateinit var ticker: ReceiveChannel<Unit>
+
+    var count = start
+    val state get() = _state
+
+    // <editor-fold desc="回调">
+
+    /**
+     * 订阅轮循器
+     */
+    fun subscribe(block: (Long) -> Unit): Interval {
+        receiveList.add(block)
+        return this
+    }
+
+    /**
+     * 轮循器完成
+     */
+    fun finish(block: (Long) -> Unit): Interval {
+        finishList.add(block)
+        return this
+    }
+
+    // </editor-fold>
+
+    // <editor-fold desc="操作">
+
+    /**
+     * 开始
+     */
+    fun start() {
+        if (_state == TickerState.STATE_ACTIVE || _state == TickerState.STATE_PAUSE) {
+            return
+        }
+        _state = TickerState.STATE_ACTIVE
+        launch()
+    }
+
+
+    /**
+     * 停止
+     */
+    fun stop() {
+        if (_state == TickerState.STATE_IDLE) return
+        _state = TickerState.STATE_IDLE
+        scope?.cancel()
+        finishList.forEach {
+            it.invoke(count)
+        }
+        count = start
+    }
+
+    /**
+     * 开关
+     */
+    fun switch() {
+        when (state) {
+            TickerState.STATE_ACTIVE -> stop()
+            TickerState.STATE_IDLE -> start()
+            else -> return
+        }
+    }
+
+    /**
+     * 继续
+     */
+    fun resume() {
+        if (_state != TickerState.STATE_PAUSE) return
+        _state = TickerState.STATE_ACTIVE
+        launch(delay)
+    }
+
+    /**
+     * 暂停
+     */
+    fun pause() {
+        if (_state != TickerState.STATE_ACTIVE) return
+        _state = TickerState.STATE_PAUSE
+        delay = System.currentTimeMillis() - countTime
+        scope?.cancel()
+    }
+
+    /**
+     * 重置
+     */
+    fun reset() {
+        if (_state == TickerState.STATE_IDLE) return
+        count = start
+        scope?.cancel()
+        launch()
+    }
+
+    /**
+     * 生命周期
+     * @param lifecycleOwner 默认在销毁时取消轮循器
+     */
+    fun life(
+        lifecycleOwner: LifecycleOwner,
+        lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_DESTROY
+    ): Interval {
+        lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
+            override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+                if (lifeEvent == event) scope?.cancel()
+            }
+        })
+        return this
+    }
+
+    // </editor-fold>
+
+    private fun launch(delay: Long = unit.toMillis(initialDelay)) {
+        scope = scope {
+
+            ticker = ticker(unit.toMillis(period), delay, mode = TickerMode.FIXED_DELAY)
+
+            for (unit in ticker) {
+
+                count++
+                countTime = System.currentTimeMillis()
+
+                receiveList.forEach {
+                    it.invoke(count)
+                }
+
+                if (end != -1L && count == end) {
+                    scope?.cancel()
+                    finishList.forEach {
+                        it.invoke(count)
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * 计时器的状态
+     */
+    enum class TickerState {
+        STATE_ACTIVE, STATE_IDLE, STATE_PAUSE
+    }
+}

+ 199 - 0
net/src/main/java/com/drake/net/utils/ScopeUtils.kt

@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+ * Project:Net
+ * Author:Drake
+ * Date:12/20/19 7:16 PM
+ */
+
+package com.drake.net.utils
+
+import android.app.Activity
+import android.app.Dialog
+import android.os.Handler
+import android.os.Looper
+import android.view.View
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
+import com.drake.brv.PageRefreshLayout
+import com.drake.net.scope.*
+import com.drake.statelayout.StateLayout
+import com.drake.statelayout.state
+import com.scwang.smart.refresh.layout.SmartRefreshLayout
+import kotlinx.coroutines.CoroutineScope
+
+/**
+ * 作用域内部全在主线程
+ * 作用域全部属于异步
+ * 作用域内部异常全部被捕获, 不会引起应用崩溃
+ */
+
+// <editor-fold desc="加载对话框">
+
+/**
+ * 作用域开始时自动显示加载对话框, 结束时自动关闭加载对话框
+ * 可以设置全局对话框 [NetConfig.onDialog]
+ * @param dialog 仅该作用域使用的对话框
+ *
+ * 对话框被取消或者界面关闭作用域被取消
+ */
+fun FragmentActivity.scopeDialog(
+    dialog: Dialog? = null,
+    cancelable: Boolean = true, block: suspend CoroutineScope.() -> Unit
+): AndroidScope {
+    return DialogCoroutineScope(this, dialog, cancelable).launch(block = block)
+}
+
+fun Fragment.scopeDialog(
+    dialog: Dialog? = null,
+    cancelable: Boolean = true, block: suspend CoroutineScope.() -> Unit
+): AndroidScope {
+    return DialogCoroutineScope(activity!!, dialog, cancelable).launch(block = block)
+}
+
+// </editor-fold>
+
+// <editor-fold desc="缺省页">
+
+
+/**
+ * 自动处理缺省页的异步作用域
+ * 作用域开始执行时显示加载中缺省页
+ * 作用域正常结束时显示成功缺省页
+ * 作用域抛出异常时显示错误缺省页
+ * 并且自动吐司错误信息, 可配置 [NetConfig.onStateError]
+ * 自动打印异常日志
+ * @receiver 当前视图会被缺省页包裹
+ *
+ * 布局被销毁或者界面关闭作用域被取消
+ */
+fun View.scopeState(block: suspend CoroutineScope.(StateCoroutineScope) -> Unit): AndroidScope {
+    val stateLayout = state()
+    val scope = StateCoroutineScope(stateLayout)
+    stateLayout.onRefresh {
+        scope.launch { block(scope) }
+    }.showLoading()
+    return scope
+}
+
+fun Fragment.scopeState(block: suspend CoroutineScope.(StateCoroutineScope) -> Unit): AndroidScope {
+    val stateLayout = state()
+    val scope = StateCoroutineScope(stateLayout)
+    stateLayout.onRefresh {
+        scope.launch { block(scope) }
+    }.showLoading()
+    return scope
+}
+
+fun Activity.scopeState(block: suspend CoroutineScope.(StateCoroutineScope) -> Unit): AndroidScope {
+    val stateLayout = state()
+    val scope = StateCoroutineScope(stateLayout)
+    stateLayout.onRefresh {
+        scope.launch { block(scope) }
+    }.showLoading()
+    return scope
+}
+
+fun StateLayout.scope(block: suspend CoroutineScope.(StateCoroutineScope) -> Unit): AndroidScope {
+    val scope = StateCoroutineScope(this)
+    onRefresh {
+        scope.launch { block(scope) }
+    }.showLoading()
+    return scope
+}
+
+// </editor-fold>
+
+/**
+ * SmartRefreshLayout的异步作用域
+ * 自动结束下拉刷新
+ *
+ * 布局被销毁或者界面关闭作用域被取消
+ */
+fun SmartRefreshLayout.scopeRefresh(
+    loadMore: Boolean = false,
+    block: suspend CoroutineScope.() -> Unit
+): AndroidScope {
+    val scope = RefreshCoroutineScope(this, loadMore)
+    scope.launch(block = block)
+    return scope
+}
+
+/**
+ * PageRefreshLayout的异步作用域
+ * 1. 下拉刷新自动结束
+ * 2. 上拉加载自动结束
+ * 3. 捕获异常
+ * 4. 打印异常日志
+ * 5. 吐司部分异常[NetConfig.onStateError]
+ * 6. 判断添加还是覆盖数据
+ * 7. 自动显示缺省页
+ *
+ * 布局被销毁或者界面关闭作用域被取消
+ */
+fun PageRefreshLayout.scope(block: suspend CoroutineScope.() -> Unit): AndroidScope {
+    val scope = StateCoroutineScope(this)
+    scope.launch(block = block)
+    return scope
+}
+
+/**
+ * 在主线程运行
+ */
+fun runMain(block: () -> Unit) {
+    if (Looper.myLooper() == Looper.getMainLooper()) {
+        block()
+    } else {
+        Handler(Looper.getMainLooper()).post { block() }
+    }
+}
+
+/**
+ * 异步作用域
+ *
+ * 该作用域生命周期跟随整个应用, 注意内存泄漏
+ */
+fun scope(block: suspend CoroutineScope.() -> Unit): AndroidScope {
+    return AndroidScope().launch(block = block)
+}
+
+/**
+ * 跟随当前页面[LifecycleOwner]生命周期的异步作用域
+ *
+ * @param lifeEvent 自定义取消作用域的生命周期
+ */
+fun LifecycleOwner.scopeLife(
+    lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_DESTROY,
+    block: suspend CoroutineScope.() -> Unit
+): AndroidScope {
+    return AndroidScope(this).launch(block = block)
+}
+
+/**
+ * 网络请求的异步作用域
+ * 自动显示错误信息吐司
+ *
+ * 该作用域生命周期跟随整个应用, 注意内存泄漏
+ */
+fun scopeNet(block: suspend CoroutineScope.() -> Unit): AndroidScope {
+    return NetCoroutineScope().launch(block = block)
+}
+
+/**
+ * 网络请求的异步作用域
+ * 自动显示错误信息吐司
+ *
+ */
+fun LifecycleOwner.scopeNetLife(
+    lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_DESTROY,
+    block: suspend CoroutineScope.() -> Unit
+): AndroidScope {
+    return NetCoroutineScope(this, lifeEvent).launch(block = block)
+}
+
+
+
+
+
+

+ 24 - 0
net/src/main/java/com/drake/net/utils/SuspendUtils.kt

@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+ * Project:Net
+ * Author:Drake
+ * Date:12/19/19 6:20 PM
+ */
+
+package com.drake.net.utils
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+
+suspend fun <T> withMain(block: suspend CoroutineScope.() -> T) =
+    withContext(Dispatchers.Main, block)
+
+suspend fun <T> withIO(block: suspend CoroutineScope.() -> T) =
+    withContext(Dispatchers.IO, block)
+
+suspend fun <T> withDefault(block: suspend CoroutineScope.() -> T) =
+    withContext(Dispatchers.Default, block)
+
+suspend fun <T> withUnconfined(block: suspend CoroutineScope.() -> T) =
+    withContext(Dispatchers.Unconfined, block)

+ 1 - 1
net/src/main/res/values/strings.xml

@@ -18,7 +18,7 @@
     <string name="net_server_error">服务响应错误</string>
     <string name="net_image_error">图片下载错误</string>
     <string name="net_null_error">数据为空</string>
-    <string name="net_other_error">服务器未响应</string>
+    <string name="net_other_error">未知错误</string>
 
     <!--对话框-->
     <string name="net_dialog_msg">加载中</string>

+ 7 - 3
sample/build.gradle

@@ -39,13 +39,17 @@ dependencies {
 
     implementation 'com.squareup.moshi:moshi-kotlin:1.8.0'
     kapt 'com.squareup.moshi:moshi-kotlin-codegen:1.8.0'
-    implementation 'com.github.liangjingkanji:BRV:1.1.6'
+    implementation 'com.github.liangjingkanji:BRV:1.1.7'
     implementation 'com.scwang.smart:refresh-header-classics:2.0.0-alpha-1'
     implementation 'com.scwang.smart:refresh-footer-classics:2.0.0-alpha-1'
-    implementation 'io.reactivex.rxjava2:rxkotlin:2.3.0'
-    implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
 
     implementation 'com.github.liangjingkanji:debugkit:1.2.9'
 
     implementation 'com.github.bumptech.glide:glide:4.9.0'
+
+    implementation 'com.github.liangjingkanji:LogCat:1.0'
+
+    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
+    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
+
 }

+ 9 - 0
sample/src/main/java/com/drake/net/sample/App.kt

@@ -2,12 +2,20 @@ package com.drake.net.sample
 
 import android.app.Application
 import com.drake.net.initNet
+import com.drake.statelayout.StateConfig
 
 class App : Application() {
 
     override fun onCreate() {
         super.onCreate()
 
+        // 缺省页初始化
+        StateConfig.apply {
+            emptyLayout = R.layout.layout_empty
+            loadingLayout = R.layout.layout_loading
+            errorLayout = R.layout.layout_error
+        }
+
         initNet("http://localhost.com") {
             /*converter(object : JsonConverter() {
                 override fun <S> convert(succeed: Type, body: String): S? {
@@ -16,5 +24,6 @@ class App : Application() {
             })*/
         }
 
+
     }
 }

+ 25 - 1
sample/src/main/java/com/drake/net/sample/MainActivity.kt

@@ -2,15 +2,39 @@ package com.drake.net.sample
 
 import android.os.Bundle
 import androidx.appcompat.app.AppCompatActivity
+import com.drake.net.get
+import com.drake.net.utils.scopeDialog
+import kotlinx.android.synthetic.main.activity_main.*
 
 class MainActivity : AppCompatActivity() {
 
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
+
         setContentView(R.layout.activity_main)
 
+        scopeDialog {
+
+            val data = get<String>(
+                "https://raw.githubusercontent.com/liangjingkanji/BRV/master/README.md",
+                absolutePath = true
+            ) {
+                cacheKey("wu")
+            }
 
+            textView.text = data.await()
+        }
     }
 
-}
+}
+
+
+
+
+
+
+
+
+
+

BIN
sample/src/main/res/drawable/bg_empty.webp


BIN
sample/src/main/res/drawable/bg_error.webp


+ 12 - 4
sample/src/main/res/layout/activity_main.xml

@@ -8,10 +8,18 @@
     tools:context=".MainActivity">
 
 
-    <TextView
-        android:id="@+id/textView"
+    <ScrollView
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="TextView" />
+        android:layout_height="match_parent"
+        android:fillViewport="true">
+
+        <TextView
+            android:id="@+id/textView"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:gravity="center"
+            android:text="主页" />
+
+    </ScrollView>
 
 </LinearLayout>

+ 35 - 0
sample/src/main/res/layout/layout_empty.xml

@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+  ~ Project:Net
+  ~ Author:Drake
+  ~ Date:12/21/19 6:41 PM
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/iv"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        android:src="@drawable/bg_empty"
+        app:layout_constraintBottom_toTopOf="@+id/msg"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_chainStyle="packed" />
+
+    <TextView
+        android:id="@+id/msg"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:text="暂无数据"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/iv" />
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 39 - 0
sample/src/main/res/layout/layout_error.xml

@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+  ~ Project:Net
+  ~ Author:Drake
+  ~ Date:12/21/19 6:41 PM
+  -->
+
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+
+    <ImageView
+        android:id="@+id/iv"
+        android:layout_width="200dp"
+        android:layout_height="200dp"
+        android:src="@drawable/bg_error"
+        app:layout_constraintBottom_toTopOf="@+id/msg"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toTopOf="parent"
+        app:layout_constraintVertical_chainStyle="packed" />
+
+    <TextView
+        android:id="@+id/msg"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="24dp"
+        android:text="网络异常"
+        app:layout_constraintBottom_toBottomOf="parent"
+        app:layout_constraintLeft_toLeftOf="parent"
+        app:layout_constraintRight_toRightOf="parent"
+        app:layout_constraintTop_toBottomOf="@+id/iv"
+        tools:layout_editor_absoluteX="177dp" />
+
+
+</androidx.constraintlayout.widget.ConstraintLayout>

+ 19 - 0
sample/src/main/res/layout/layout_loading.xml

@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2018,天下网络科技有限公司 All rights reserved.
+  ~ Project:TX2
+  ~ Author:Nathan
+  ~ Date:5/26/19 10:41 AM
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <ProgressBar
+        android:id="@+id/progress"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center" />
+
+</FrameLayout>