Browse Source

| 修复无法完全取消请求的问题

drake 4 years ago
parent
commit
82728cf7ad
31 changed files with 722 additions and 424 deletions
  1. 3 3
      README.md
  2. 1 1
      build.gradle
  3. 57 0
      kalle/src/main/java/com/yanzhenjie/kalle/Canceler.kt
  4. 0 69
      kalle/src/main/java/com/yanzhenjie/kalle/CancelerManager.java
  5. 0 10
      kalle/src/main/java/com/yanzhenjie/kalle/Kalle.java
  6. 15 0
      kalle/src/main/java/com/yanzhenjie/kalle/Request.java
  7. 3 2
      kalle/src/main/java/com/yanzhenjie/kalle/download/BasicWorker.java
  8. 1 1
      kalle/src/main/java/com/yanzhenjie/kalle/download/BodyDownload.java
  9. 5 0
      kalle/src/main/java/com/yanzhenjie/kalle/download/BodyWorker.java
  10. 12 18
      kalle/src/main/java/com/yanzhenjie/kalle/download/DownloadManager.java
  11. 2 1
      kalle/src/main/java/com/yanzhenjie/kalle/download/UrlDownload.java
  12. 5 0
      kalle/src/main/java/com/yanzhenjie/kalle/download/UrlWorker.java
  13. 2 1
      kalle/src/main/java/com/yanzhenjie/kalle/simple/BasicWorker.java
  14. 5 0
      kalle/src/main/java/com/yanzhenjie/kalle/simple/BodyWorker.java
  15. 12 18
      kalle/src/main/java/com/yanzhenjie/kalle/simple/RequestManager.java
  16. 5 0
      kalle/src/main/java/com/yanzhenjie/kalle/simple/UrlWorker.java
  17. 0 3
      kalle/src/main/java/com/yanzhenjie/kalle/simple/Work.java
  18. 423 198
      net/src/main/java/com/drake/net/Net.kt
  19. 29 0
      net/src/main/java/com/drake/net/error/NetCancellationException.kt
  20. 14 4
      net/src/main/java/com/drake/net/scope/AndroidScope.kt
  21. 11 10
      net/src/main/java/com/drake/net/scope/NetCoroutineScope.kt
  22. 23 34
      net/src/main/java/com/drake/net/utils/ScopeUtils.kt
  23. 1 0
      sample/build.gradle
  24. 1 1
      sample/src/main/java/com/drake/net/sample/base/App.kt
  25. 19 0
      sample/src/main/java/com/drake/net/sample/mod/ListModel.kt
  26. 7 32
      sample/src/main/java/com/drake/net/sample/ui/fragment/DownloadFileFragment.kt
  27. 20 1
      sample/src/main/java/com/drake/net/sample/ui/fragment/PullRefreshFragment.kt
  28. 20 3
      sample/src/main/java/com/drake/net/sample/ui/fragment/PushRefreshFragment.kt
  29. 6 7
      sample/src/main/res/layout/fragment_pull_refresh.xml
  30. 8 7
      sample/src/main/res/layout/fragment_push_refresh.xml
  31. 12 0
      sample/src/main/res/layout/item_list.xml

+ 3 - 3
README.md

@@ -80,10 +80,10 @@ module 的 build.gradle
 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
 implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'
 
-// 支持自动下拉刷新和缺省页的, 可选, 刷新头和上拉加载参考SmartRefreshLayout
-implementation 'com.github.liangjingkanji:BRV:1.2.1'
+// 支持自动下拉刷新和缺省页的, 可选, 刷新头和上拉加载参考SmartRefreshLayout (可选)
+implementation 'com.github.liangjingkanji:BRV:1.3.3'
 
-implementation 'com.github.liangjingkanji:Net:2.1.6'
+implementation 'com.github.liangjingkanji:Net:2.2.0'
 ```
 
 

+ 1 - 1
build.gradle

@@ -3,7 +3,7 @@
 buildscript {
     ext {
         kotlin_version = '1.3.72'
-        brv_version = '1.3.1'
+        brv_version = '1.3.3'
         coroutine_version = '1.3.0'
         glide_version = '4.9.0'
         room_version = "2.2.5"

+ 57 - 0
kalle/src/main/java/com/yanzhenjie/kalle/Canceler.kt

@@ -0,0 +1,57 @@
+/*
+ * Copyright © 2018 Zhenjie Yan.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.yanzhenjie.kalle
+
+
+object Canceler {
+
+    private val map = mutableMapOf<Any, Canceller>()
+
+    /**
+     * Add a task to cancel.
+     *
+     * @param uid   target request.
+     * @param canceller canceller.
+     */
+    fun addCancel(uid: Any, canceller: Canceller) {
+        map[uid] = canceller
+    }
+
+    /**
+     * Remove a task.
+     *
+     * @param uid target request.
+     */
+    fun removeCancel(uid: Any?) {
+        uid ?: return
+        map.remove(uid)
+    }
+
+    /**
+     * According to the tag to cancel a task.
+     *
+     */
+    fun cancel(uid: Any?) {
+        val iterator = map.iterator()
+        while (iterator.hasNext()) {
+            val next = iterator.next()
+            if (uid == next.key) {
+                iterator.remove()
+                next.value.cancel()
+            }
+        }
+    }
+}

+ 0 - 69
kalle/src/main/java/com/yanzhenjie/kalle/CancelerManager.java

@@ -1,69 +0,0 @@
-/*
- * Copyright © 2018 Zhenjie Yan.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.yanzhenjie.kalle;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-
-import static android.util.Log.d;
-
-/**
- * Created by Zhenjie Yan on 2018/2/27.
- */
-public class CancelerManager {
-
-    private final Map<Request, Canceller> mCancelMap;
-
-    public CancelerManager() {
-        this.mCancelMap = new ConcurrentHashMap<>();
-    }
-
-    /**
-     * Add a task to cancel.
-     *
-     * @param request   target request.
-     * @param canceller canceller.
-     */
-    public void addCancel(Request request, Canceller canceller) {
-        mCancelMap.put(request, canceller);
-    }
-
-    /**
-     * Remove a task.
-     *
-     * @param request target request.
-     */
-    public void removeCancel(Request request) {
-        mCancelMap.remove(request);
-    }
-
-    /**
-     * According to the tag to cancel a task.
-     *
-     * @param tag tag.
-     */
-    public void cancel(Object tag) {
-        for (Map.Entry<Request, Canceller> entry : mCancelMap.entrySet()) {
-            Request request = entry.getKey();
-            Object oldTag = request.tag();
-            d("日志", "(CancelerManager.java:62)    ");
-            if ((tag == oldTag) || (tag != null && tag.equals(oldTag))) {
-                d("日志", "(CancelerManager.java:61)    ");
-                entry.getValue().cancel();
-            }
-        }
-    }
-}

+ 0 - 10
kalle/src/main/java/com/yanzhenjie/kalle/Kalle.java

@@ -18,9 +18,7 @@ package com.yanzhenjie.kalle;
 import android.util.Log;
 
 import com.yanzhenjie.kalle.download.BodyDownload;
-import com.yanzhenjie.kalle.download.DownloadManager;
 import com.yanzhenjie.kalle.download.UrlDownload;
-import com.yanzhenjie.kalle.simple.RequestManager;
 import com.yanzhenjie.kalle.simple.SimpleBodyRequest;
 import com.yanzhenjie.kalle.simple.SimpleUrlRequest;
 
@@ -116,10 +114,6 @@ public final class Kalle {
         return SimpleBodyRequest.newApi(url, RequestMethod.DELETE);
     }
 
-    public static void cancel(Object tag) {
-        RequestManager.getInstance().cancel(tag);
-    }
-
     public static class Download {
 
         public static UrlDownload.Api get(String url) {
@@ -185,9 +179,5 @@ public final class Kalle {
         public static BodyDownload.Api delete(Url url) {
             return BodyDownload.newApi(url, RequestMethod.DELETE);
         }
-
-        public static void cancel(Object tag) {
-            DownloadManager.getInstance().cancel(tag);
-        }
     }
 }

+ 15 - 0
kalle/src/main/java/com/yanzhenjie/kalle/Request.java

@@ -36,6 +36,7 @@ public abstract class Request {
     private final int mConnectTimeout;
     private final int mReadTimeout;
     private final Object mTag;
+    private final Object uid;
 
     private String location;
 
@@ -59,6 +60,7 @@ public abstract class Request {
         this.mConnectTimeout = api.mConnectTimeout;
         this.mReadTimeout = api.mReadTimeout;
         this.mTag = api.mTag;
+        this.uid = api.uid;
     }
 
     /**
@@ -132,6 +134,10 @@ public abstract class Request {
         return mTag;
     }
 
+    public Object uid() {
+        return uid;
+    }
+
     public static abstract class Api<T extends Api<T>> {
 
         private final RequestMethod mMethod;
@@ -142,6 +148,7 @@ public abstract class Request {
         private int mConnectTimeout = Kalle.getConfig().getConnectTimeout();
         private int mReadTimeout = Kalle.getConfig().getReadTimeout();
         private Object mTag;
+        private Object uid;
 
         protected Api(RequestMethod method) {
             this.mMethod = method;
@@ -327,5 +334,13 @@ public abstract class Request {
             this.mTag = tag;
             return (T) this;
         }
+
+        /**
+         * 唯一标记
+         */
+        public T uid(Object uid) {
+            this.uid = uid;
+            return (T) this;
+        }
     }
 }

+ 3 - 2
kalle/src/main/java/com/yanzhenjie/kalle/download/BasicWorker.java

@@ -17,6 +17,7 @@ package com.yanzhenjie.kalle.download;
 
 import android.text.TextUtils;
 
+import com.yanzhenjie.kalle.Canceller;
 import com.yanzhenjie.kalle.Headers;
 import com.yanzhenjie.kalle.Kalle;
 import com.yanzhenjie.kalle.Response;
@@ -37,7 +38,7 @@ import static com.yanzhenjie.kalle.Headers.KEY_RANGE;
 /**
  * Created by Zhenjie Yan on 2018/3/18.
  */
-public abstract class BasicWorker<T extends Download> implements Callable<String> {
+public abstract class BasicWorker<T extends Download> implements Callable<String>, Canceller {
 
     private final T mDownload;
 
@@ -129,7 +130,7 @@ public abstract class BasicWorker<T extends Download> implements Callable<String
 
             InputStream stream = response.body().stream();
 
-            while (((len = stream.read(buffer)) != -1)) {
+            while ((len = stream.read(buffer)) != -1) {
                 randomFile.write(buffer, 0, len);
 
                 oldCount += len;

+ 1 - 1
kalle/src/main/java/com/yanzhenjie/kalle/download/BodyDownload.java

@@ -100,7 +100,7 @@ public class BodyDownload extends BodyRequest implements Download {
         }
 
         public String perform() throws Exception {
-            return new BodyWorker(new BodyDownload(this)).call();
+            return DownloadManager.getInstance().perform(new BodyDownload(this));
         }
 
         public Canceller perform(Callback callback) {

+ 5 - 0
kalle/src/main/java/com/yanzhenjie/kalle/download/BodyWorker.java

@@ -43,4 +43,9 @@ public class BodyWorker extends BasicWorker<BodyDownload> {
             mCall.asyncCancel();
         }
     }
+
+    @Override
+    public boolean isCancelled() {
+        return mCall.isCanceled();
+    }
 }

+ 12 - 18
kalle/src/main/java/com/yanzhenjie/kalle/download/DownloadManager.java

@@ -15,7 +15,7 @@
  */
 package com.yanzhenjie.kalle.download;
 
-import com.yanzhenjie.kalle.CancelerManager;
+import com.yanzhenjie.kalle.Canceler;
 import com.yanzhenjie.kalle.Canceller;
 import com.yanzhenjie.kalle.Kalle;
 
@@ -28,10 +28,9 @@ public class DownloadManager {
 
     private static DownloadManager sInstance;
     private final Executor mExecutor;
-    private final CancelerManager mCancelManager;
+
     private DownloadManager() {
         this.mExecutor = Kalle.getConfig().getWorkExecutor();
-        this.mCancelManager = new CancelerManager();
     }
 
     public static DownloadManager getInstance() {
@@ -57,10 +56,10 @@ public class DownloadManager {
             @Override
             public void onEnd() {
                 super.onEnd();
-                mCancelManager.removeCancel(download);
+                Canceler.INSTANCE.removeCancel(download.uid());
             }
         });
-        mCancelManager.addCancel(download, work);
+        Canceler.INSTANCE.addCancel(download.uid(), work);
         mExecutor.execute(work);
         return work;
     }
@@ -72,7 +71,9 @@ public class DownloadManager {
      * @return download the completed file path.
      */
     public String perform(UrlDownload download) throws Exception {
-        return new UrlWorker(download).call();
+        UrlWorker worker = new UrlWorker(download);
+        Canceler.INSTANCE.addCancel(download.uid(), worker);
+        return worker.call();
     }
 
     /**
@@ -87,10 +88,10 @@ public class DownloadManager {
             @Override
             public void onEnd() {
                 super.onEnd();
-                mCancelManager.removeCancel(download);
+                Canceler.INSTANCE.removeCancel(download.uid());
             }
         });
-        mCancelManager.addCancel(download, work);
+        Canceler.INSTANCE.addCancel(download.uid(), work);
         mExecutor.execute(work);
         return work;
     }
@@ -102,16 +103,9 @@ public class DownloadManager {
      * @return download the completed file path.
      */
     public String perform(BodyDownload download) throws Exception {
-        return new BodyWorker(download).call();
-    }
-
-    /**
-     * Cancel multiple requests based on tag.
-     *
-     * @param tag Specified tag.
-     */
-    public void cancel(Object tag) {
-        mCancelManager.cancel(tag);
+        BodyWorker worker = new BodyWorker(download);
+        Canceler.INSTANCE.addCancel(download.uid(), worker);
+        return worker.call();
     }
 
     private static class AsyncCallback implements Callback {

+ 2 - 1
kalle/src/main/java/com/yanzhenjie/kalle/download/UrlDownload.java

@@ -30,6 +30,7 @@ public class UrlDownload extends UrlRequest implements Download {
     private final String mFileName;
     private final ProgressBar mProgressBar;
     private final Policy mPolicy;
+
     private UrlDownload(Api api) {
         super(api);
         this.mDirectory = api.mDirectory;
@@ -100,7 +101,7 @@ public class UrlDownload extends UrlRequest implements Download {
         }
 
         public String perform() throws Exception {
-            return new UrlWorker(new UrlDownload(this)).call();
+            return DownloadManager.getInstance().perform(new UrlDownload(this));
         }
 
         public Canceller perform(Callback callback) {

+ 5 - 0
kalle/src/main/java/com/yanzhenjie/kalle/download/UrlWorker.java

@@ -43,4 +43,9 @@ public class UrlWorker extends BasicWorker<UrlDownload> {
             mCall.asyncCancel();
         }
     }
+
+    @Override
+    public boolean isCancelled() {
+        return mCall.isCanceled();
+    }
 }

+ 2 - 1
kalle/src/main/java/com/yanzhenjie/kalle/simple/BasicWorker.java

@@ -15,6 +15,7 @@
  */
 package com.yanzhenjie.kalle.simple;
 
+import com.yanzhenjie.kalle.Canceller;
 import com.yanzhenjie.kalle.Headers;
 import com.yanzhenjie.kalle.Kalle;
 import com.yanzhenjie.kalle.Response;
@@ -37,7 +38,7 @@ import static com.yanzhenjie.kalle.Headers.KEY_IF_NONE_MATCH;
  * Created by Zhenjie Yan on 2018/2/18.
  */
 abstract class BasicWorker<T extends SimpleRequest, Succeed, Failed>
-        implements Callable<Result<Succeed, Failed>> {
+        implements Callable<Result<Succeed, Failed>>, Canceller {
 
     private static final long MAX_EXPIRES = System.currentTimeMillis() + 100L * 365L * 24L * 60L * 60L * 1000L;
 

+ 5 - 0
kalle/src/main/java/com/yanzhenjie/kalle/simple/BodyWorker.java

@@ -44,4 +44,9 @@ final class BodyWorker<S, F> extends BasicWorker<SimpleBodyRequest, S, F> {
             mCall.asyncCancel();
         }
     }
+
+    @Override
+    public boolean isCancelled() {
+        return mCall.isCanceled();
+    }
 }

+ 12 - 18
kalle/src/main/java/com/yanzhenjie/kalle/simple/RequestManager.java

@@ -15,7 +15,7 @@
  */
 package com.yanzhenjie.kalle.simple;
 
-import com.yanzhenjie.kalle.CancelerManager;
+import com.yanzhenjie.kalle.Canceler;
 import com.yanzhenjie.kalle.Canceller;
 import com.yanzhenjie.kalle.Kalle;
 
@@ -29,10 +29,9 @@ public class RequestManager {
 
     private static RequestManager sInstance;
     private final Executor mExecutor;
-    private final CancelerManager mCancelManager;
+
     private RequestManager() {
         this.mExecutor = Kalle.getConfig().getWorkExecutor();
-        this.mCancelManager = new CancelerManager();
     }
 
     public static RequestManager getInstance() {
@@ -60,10 +59,10 @@ public class RequestManager {
             @Override
             public void onEnd() {
                 super.onEnd();
-                mCancelManager.removeCancel(request);
+                Canceler.INSTANCE.removeCancel(request.uid());
             }
         });
-        mCancelManager.addCancel(request, work);
+        Canceler.INSTANCE.addCancel(request.uid(), work);
         mExecutor.execute(work);
         return work;
     }
@@ -79,7 +78,9 @@ public class RequestManager {
      * @return the response to this request.
      */
     public <S, F> Result<S, F> perform(SimpleUrlRequest request, Type succeed, Type failed) throws Exception {
-        return new UrlWorker<S, F>(request, succeed, failed).call();
+        UrlWorker<S, F> worker = new UrlWorker<>(request, succeed, failed);
+        Canceler.INSTANCE.addCancel(request.uid(), worker);
+        return worker.call();
     }
 
     /**
@@ -96,10 +97,10 @@ public class RequestManager {
             @Override
             public void onEnd() {
                 super.onEnd();
-                mCancelManager.removeCancel(request);
+                Canceler.INSTANCE.removeCancel(request.uid());
             }
         });
-        mCancelManager.addCancel(request, work);
+        Canceler.INSTANCE.addCancel(request.uid(), work);
         mExecutor.execute(work);
         return work;
     }
@@ -115,16 +116,9 @@ public class RequestManager {
      * @return the response to this request.
      */
     public <S, F> Result<S, F> perform(SimpleBodyRequest request, Type succeed, Type failed) throws Exception {
-        return new BodyWorker<S, F>(request, succeed, failed).call();
-    }
-
-    /**
-     * Cancel multiple requests based on tag.
-     *
-     * @param tag specified tag.
-     */
-    public void cancel(Object tag) {
-        mCancelManager.cancel(tag);
+        BodyWorker<S, F> worker = new BodyWorker<>(request, succeed, failed);
+        Canceler.INSTANCE.addCancel(request.uid(), worker);
+        return worker.call();
     }
 
     private static class AsyncCallback<S, F> extends Callback<S, F> {

+ 5 - 0
kalle/src/main/java/com/yanzhenjie/kalle/simple/UrlWorker.java

@@ -44,4 +44,9 @@ final class UrlWorker<S, F> extends BasicWorker<SimpleUrlRequest, S, F> {
             mCall.asyncCancel();
         }
     }
+
+    @Override
+    public boolean isCancelled() {
+        return mCall.isCanceled();
+    }
 }

+ 0 - 3
kalle/src/main/java/com/yanzhenjie/kalle/simple/Work.java

@@ -21,8 +21,6 @@ import java.util.concurrent.CancellationException;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.FutureTask;
 
-import static android.util.Log.d;
-
 /**
  * Created by Zhenjie Yan on 2018/2/13.
  */
@@ -71,7 +69,6 @@ final class Work<T extends SimpleRequest, S, F> extends FutureTask<Result<S, F>>
     @Override
     public void cancel() {
         cancel(true);
-        d("日志", "(Work.java:72)    取消");
         mWorker.cancel();
     }
 }

+ 423 - 198
net/src/main/java/com/drake/net/Net.kt

@@ -12,6 +12,7 @@ package com.drake.net
 import android.content.Context
 import com.bumptech.glide.Glide
 import com.drake.net.error.ResponseException
+import com.yanzhenjie.kalle.Canceler
 import com.yanzhenjie.kalle.Kalle
 import com.yanzhenjie.kalle.RequestMethod
 import com.yanzhenjie.kalle.Url
@@ -21,6 +22,7 @@ import com.yanzhenjie.kalle.simple.SimpleUrlRequest
 import com.yanzhenjie.kalle.simple.cache.CacheMode
 import kotlinx.coroutines.*
 import java.io.File
+import java.net.SocketException
 
 // <editor-fold desc="异步请求">
 
@@ -35,86 +37,260 @@ import java.io.File
  */
 
 inline fun <reified M> CoroutineScope.Get(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): Deferred<M> = async(Dispatchers.IO) {
-    _submitUrl<M>(RequestMethod.GET, path, tag, cache, absolutePath, block)
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleUrlRequest.Api.() -> Unit = {}): Deferred<M> = async(Dispatchers.IO) {
+    if (!isActive) throw CancellationException()
+
+    val uid = coroutineContext[CoroutineExceptionHandler]
+    coroutineContext[Job]?.invokeOnCompletion {
+        if (it != null) Canceler.cancel(uid) else Canceler.removeCancel(uid)
+    }
+
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val request = SimpleUrlRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.GET)
+            .tag(tag)
+            .uid(uid)
+            .cacheKey(path)
+            .cacheMode(cache)
+
+    val response = try {
+        request.apply(block)
+                .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+    } catch (e: SocketException) {
+        throw CancellationException()
+    }
+
+    if (response.isSucceed) response.success!! else throw response.failure!!
 }
 
 
 inline fun <reified M> CoroutineScope.Post(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): Deferred<M> = async(Dispatchers.IO) {
-    _submitBody<M>(RequestMethod.POST, path, tag, cache, absolutePath, block)
-}
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleBodyRequest.Api.() -> Unit = {}): Deferred<M> =
+        async(Dispatchers.IO) {
+            if (!isActive) throw CancellationException()
+
+            val uid = coroutineContext[CoroutineExceptionHandler]
+            coroutineContext[Job]?.invokeOnCompletion {
+                if (it != null) Canceler.cancel(uid) else Canceler.removeCancel(uid)
+            }
+
+            val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+            val request = SimpleBodyRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.POST)
+                    .tag(tag)
+                    .uid(uid)
+                    .cacheKey(path)
+                    .cacheMode(cache)
+
+            val response = try {
+                request.apply(block)
+                        .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+            } catch (e: SocketException) {
+                throw CancellationException()
+            }
+
+            if (response.isSucceed) response.success!! else throw response.failure!!
+        }
 
 inline fun <reified M> CoroutineScope.Head(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): Deferred<M> = async(Dispatchers.IO) {
-    _submitUrl<M>(RequestMethod.HEAD, path, tag, cache, absolutePath, block)
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleUrlRequest.Api.() -> Unit = {}): Deferred<M> = async(Dispatchers.IO) {
+    if (!isActive) throw CancellationException()
+
+    val uid = coroutineContext[CoroutineExceptionHandler]
+    coroutineContext[Job]?.invokeOnCompletion {
+        if (it != null) Canceler.cancel(uid) else Canceler.removeCancel(uid)
+    }
+
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val request = SimpleUrlRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.HEAD)
+            .tag(tag)
+            .uid(uid)
+            .cacheKey(path)
+            .cacheMode(cache)
+
+    val response = try {
+        request.apply(block)
+                .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+    } catch (e: SocketException) {
+        throw CancellationException()
+    }
+
+    if (response.isSucceed) response.success!! else throw response.failure!!
 }
 
 inline fun <reified M> CoroutineScope.Options(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): Deferred<M> = async(Dispatchers.IO) {
-    _submitUrl<M>(RequestMethod.OPTIONS, path, tag, cache, absolutePath, block)
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleUrlRequest.Api.() -> Unit = {}): Deferred<M> = async(Dispatchers.IO) {
+    if (!isActive) throw CancellationException()
+
+    val uid = coroutineContext[CoroutineExceptionHandler]
+    coroutineContext[Job]?.invokeOnCompletion {
+        if (it != null) Canceler.cancel(uid) else Canceler.removeCancel(uid)
+    }
+
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val request = SimpleUrlRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.OPTIONS)
+            .tag(tag)
+            .uid(uid)
+            .cacheKey(path)
+            .cacheMode(cache)
+
+    val response = try {
+        request.apply(block)
+                .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+    } catch (e: SocketException) {
+        throw CancellationException()
+    }
+
+    if (response.isSucceed) response.success!! else throw response.failure!!
 }
 
 
 inline fun <reified M> CoroutineScope.Trace(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): Deferred<M> = async(Dispatchers.IO) {
-    _submitUrl<M>(RequestMethod.TRACE, path, tag, cache, absolutePath, block)
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleUrlRequest.Api.() -> Unit = {}): Deferred<M> = async(Dispatchers.IO) {
+    if (!isActive) throw CancellationException()
+
+    val uid = coroutineContext[CoroutineExceptionHandler]
+    coroutineContext[Job]?.invokeOnCompletion {
+        if (it != null) Canceler.cancel(uid) else Canceler.removeCancel(uid)
+    }
+
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val request = SimpleUrlRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.TRACE)
+            .tag(tag)
+            .uid(uid)
+            .cacheKey(path)
+            .cacheMode(cache)
+
+    val response = try {
+        request.apply(block)
+                .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+    } catch (e: SocketException) {
+        throw CancellationException()
+    }
+
+    if (response.isSucceed) response.success!! else throw response.failure!!
 }
 
 inline fun <reified M> CoroutineScope.Delete(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): Deferred<M> = async(Dispatchers.IO) {
-    _submitBody<M>(RequestMethod.DELETE, path, tag, cache, absolutePath, block)
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleBodyRequest.Api.() -> Unit = {}
+                                            ): Deferred<M> = async(Dispatchers.IO) {
+    if (!isActive) throw CancellationException()
+
+    val uid = coroutineContext[CoroutineExceptionHandler]
+    coroutineContext[Job]?.invokeOnCompletion {
+        if (it != null) Canceler.cancel(uid) else Canceler.removeCancel(uid)
+    }
+
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val request = SimpleBodyRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.DELETE)
+            .tag(tag)
+            .uid(uid)
+            .cacheKey(path)
+            .cacheMode(cache)
+
+    val response = try {
+        request.apply(block)
+                .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+    } catch (e: SocketException) {
+        throw CancellationException()
+    }
+
+    if (response.isSucceed) response.success!! else throw response.failure!!
 }
 
 inline fun <reified M> CoroutineScope.Put(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): Deferred<M> = async(Dispatchers.IO) {
-    _submitBody<M>(RequestMethod.PUT, path, tag, cache, absolutePath, block)
-}
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleBodyRequest.Api.() -> Unit = {}): Deferred<M> =
+        async(Dispatchers.IO) {
+            if (!isActive) throw CancellationException()
+
+            val uid = coroutineContext[CoroutineExceptionHandler]
+            coroutineContext[Job]?.invokeOnCompletion {
+                if (it != null) Canceler.cancel(uid) else Canceler.removeCancel(uid)
+            }
+
+            val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+            val request = SimpleBodyRequest.newApi(Url.newBuilder(realPath).build(),
+                                                   RequestMethod.PUT)
+                    .tag(tag)
+                    .uid(uid)
+                    .cacheKey(path)
+                    .cacheMode(cache)
+
+            val response = try {
+                request.apply(block)
+                        .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+            } catch (e: SocketException) {
+                throw CancellationException()
+            }
+
+            if (response.isSucceed) response.success!! else throw response.failure!!
+        }
 
 inline fun <reified M> CoroutineScope.Patch(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): Deferred<M> = async(Dispatchers.IO) {
-    _submitBody<M>(RequestMethod.PATCH, path, tag, cache, absolutePath, block)
-}
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleBodyRequest.Api.() -> Unit = {}): Deferred<M> =
+        async(Dispatchers.IO) {
+            if (!isActive) throw CancellationException()
+
+            val uid = coroutineContext[CoroutineExceptionHandler]
+            coroutineContext[Job]?.invokeOnCompletion {
+                if (it != null) Canceler.cancel(uid) else Canceler.removeCancel(uid)
+            }
+
+            val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+            val request =
+                    SimpleBodyRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.PATCH)
+                            .tag(tag)
+                            .uid(uid)
+                            .cacheKey(path)
+                            .cacheMode(cache)
+
+            val response = try {
+                request.apply(block)
+                        .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+            } catch (e: SocketException) {
+                throw CancellationException()
+            }
+
+            if (response.isSucceed) response.success!! else throw response.failure!!
+        }
 
 
 /**
@@ -128,32 +304,34 @@ inline fun <reified M> CoroutineScope.Patch(
  * @param block 请求参数
  */
 fun CoroutineScope.Download(
-    path: String,
-    method: RequestMethod = RequestMethod.GET,
-    dir: String = NetConfig.app.externalCacheDir!!.absolutePath,
-    tag: Any? = null,
-    absolutePath: Boolean = false,
-    block: (UrlDownload.Api.() -> Unit)? = null
-): Deferred<String> = async(Dispatchers.IO) {
+        path: String,
+        method: RequestMethod = RequestMethod.GET,
+        dir: String = NetConfig.app.externalCacheDir!!.absolutePath,
+        tag: Any? = null,
+        absolutePath: Boolean = false,
+        block: UrlDownload.Api.() -> Unit = {}): Deferred<String> = async(Dispatchers.IO) {
+
+    if (!isActive) throw CancellationException()
+
+    val uid = coroutineContext[CoroutineExceptionHandler]
+    coroutineContext[Job]?.invokeOnCompletion {
+        if (it != null && it !is CancellationException) Canceler.cancel(uid) else Canceler.removeCancel(uid)
+    }
 
     val realPath = if (absolutePath) path else (NetConfig.host + path)
 
-    val download =
-        UrlDownload.newApi(Url.newBuilder(realPath).build(), method).directory(dir).tag(tag)
+    val download = UrlDownload.newApi(Url.newBuilder(realPath).build(), method).directory(dir).tag(tag).uid(uid)
 
-    if (isActive) {
-        if (block == null) {
-            download.perform()
-        } else {
-            download.apply(block).perform()
-        }
-    } else {
+    try {
+        download.apply(block).perform()
+    } catch (e: SocketException) {
         throw CancellationException()
     }
 }
 
 /**
  * 下载图片, 图片宽高要求要么同时指定要么同时不指定
+ * 要求依赖 Glide
  *
  * @receiver Context
  * @param url String
@@ -161,183 +339,228 @@ fun CoroutineScope.Download(
  * @param height Int 图片高度
  * @return Observable<File>
  */
-fun CoroutineScope.DownloadImg(
-    url: String,
-    with: Int = -1,
-    height: Int = -1
-): Deferred<File> = async(Dispatchers.IO) {
+fun CoroutineScope.DownloadImg(url: String, with: Int = -1, height: Int = -1): Deferred<File> =
+        async(Dispatchers.IO) {
 
-    val download = Glide.with(NetConfig.app).download(url)
+            val download = Glide.with(NetConfig.app).download(url)
 
-    val futureTarget = if (with == -1 && height == -1) {
-        download.submit()
-    } else {
-        download.submit(with, height)
-    }
+            val futureTarget = if (with == -1 && height == -1) {
+                download.submit()
+            } else {
+                download.submit(with, height)
+            }
 
-    futureTarget.get()
-}
+            futureTarget.get()
+        }
 
+// </editor-fold>
+
+// <editor-fold desc="同步请求">
 
-inline fun <reified M> _submitBody(
-    method: RequestMethod,
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): M {
+inline fun <reified M> syncGet(
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleUrlRequest.Api.() -> Unit = {}): M {
 
     val realPath = if (absolutePath) path else (NetConfig.host + path)
 
-    val request = SimpleBodyRequest.newApi(Url.newBuilder(realPath).build(), method)
-        .tag(tag)
-        .cacheKey(path)
-        .cacheMode(cache)
+    val request = SimpleUrlRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.GET)
+            .tag(tag)
+            .cacheKey(path)
+            .cacheMode(cache)
 
-    val response = if (block == null) {
-        request.perform(M::class.java, ResponseException::class.java)
-    } else {
-        request.apply(block).perform<M, String>(M::class.java, ResponseException::class.java)
-    }
+    val response = request.apply(block)
+            .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
 
     return if (response.isSucceed) {
         response.success!!
     } else {
-        throw response.failure as ResponseException
+        throw response.failure!!
     }
 }
 
-inline fun <reified M> _submitUrl(
-    method: RequestMethod,
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): M {
+inline fun <reified M> syncPost(
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleBodyRequest.Api.() -> Unit = {}): M {
 
     val realPath = if (absolutePath) path else (NetConfig.host + path)
 
-    val request = SimpleUrlRequest.newApi(Url.newBuilder(realPath).build(), method)
-        .tag(tag)
-        .cacheKey(path)
-        .cacheMode(cache)
+    val request = SimpleBodyRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.POST)
+            .tag(tag)
+            .cacheKey(path)
+            .cacheMode(cache)
 
-    val response = if (block == null) {
-        request.perform(M::class.java, ResponseException::class.java)
-    } else {
-        request.apply(block).perform<M, String>(M::class.java, ResponseException::class.java)
-    }
+    val response = request.apply(block)
+            .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
 
     return if (response.isSucceed) {
         response.success!!
     } else {
-        throw response.failure as ResponseException
+        throw response.failure!!
     }
 }
 
-// </editor-fold>
+inline fun <reified M> syncHead(
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleUrlRequest.Api.() -> Unit = {}): M {
 
-// <editor-fold desc="同步请求">
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
 
-inline fun <reified M> syncGet(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): M {
-    return _submitUrl<M>(RequestMethod.GET, path, tag, cache, absolutePath, block)
-}
+    val request = SimpleUrlRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.HEAD)
+            .tag(tag)
+            .cacheKey(path)
+            .cacheMode(cache)
 
-inline fun <reified M> syncPost(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): M {
-    return _submitBody<M>(RequestMethod.POST, path, tag, cache, absolutePath, block)
-}
+    val response = request.apply(block)
+            .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
 
-inline fun <reified M> syncHead(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): M {
-    return _submitUrl<M>(RequestMethod.HEAD, path, tag, cache, absolutePath, block)
+    return if (response.isSucceed) {
+        response.success!!
+    } else {
+        throw response.failure!!
+    }
 }
 
 inline fun <reified M> syncOptions(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): M {
-    return _submitUrl<M>(RequestMethod.OPTIONS, path, tag, cache, absolutePath, block)
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleUrlRequest.Api.() -> Unit = {}): M {
+
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val request = SimpleUrlRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.OPTIONS)
+            .tag(tag)
+            .cacheKey(path)
+            .cacheMode(cache)
+
+    val response = request.apply(block)
+            .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+
+    return if (response.isSucceed) {
+        response.success!!
+    } else {
+        throw response.failure!!
+    }
 }
 
 inline fun <reified M> syncTrace(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleUrlRequest.Api.() -> Unit)? = null
-): M {
-    return _submitUrl<M>(RequestMethod.TRACE, path, tag, cache, absolutePath, block)
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleUrlRequest.Api.() -> Unit = {}): M {
+
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val request = SimpleUrlRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.TRACE)
+            .tag(tag)
+            .cacheKey(path)
+            .cacheMode(cache)
+
+    val response = request.apply(block)
+            .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+
+    return if (response.isSucceed) {
+        response.success!!
+    } else {
+        throw response.failure!!
+    }
 }
 
 inline fun <reified M> syncDelete(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): M {
-    return _submitBody<M>(RequestMethod.DELETE, path, tag, cache, absolutePath, block)
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleBodyRequest.Api.() -> Unit = {}): M {
+
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val request = SimpleBodyRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.DELETE)
+            .tag(tag)
+            .cacheKey(path)
+            .cacheMode(cache)
+
+    val response = request.apply(block)
+            .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+
+    return if (response.isSucceed) {
+        response.success!!
+    } else {
+        throw response.failure!!
+    }
 }
 
 inline fun <reified M> syncPut(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): M {
-    return _submitBody<M>(RequestMethod.PUT, path, tag, cache, absolutePath, block)
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleBodyRequest.Api.() -> Unit = {}): M {
+
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val request = SimpleBodyRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.PUT)
+            .tag(tag)
+            .cacheKey(path)
+            .cacheMode(cache)
+
+    val response = request.apply(block)
+            .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+
+    return if (response.isSucceed) {
+        response.success!!
+    } else {
+        throw response.failure!!
+    }
 }
 
 inline fun <reified M> syncPatch(
-    path: String,
-    tag: Any? = null,
-    cache: CacheMode = CacheMode.HTTP,
-    absolutePath: Boolean = false,
-    noinline block: (SimpleBodyRequest.Api.() -> Unit)? = null
-): M {
-    return _submitBody<M>(RequestMethod.PATCH, path, tag, cache, absolutePath, block)
+        path: String,
+        tag: Any? = null,
+        cache: CacheMode = CacheMode.HTTP,
+        absolutePath: Boolean = false,
+        noinline block: SimpleBodyRequest.Api.() -> Unit = {}): M {
+
+    val realPath = if (absolutePath) path else (NetConfig.host + path)
+
+    val request = SimpleBodyRequest.newApi(Url.newBuilder(realPath).build(), RequestMethod.PATCH)
+            .tag(tag)
+            .cacheKey(path)
+            .cacheMode(cache)
+
+    val response = request.apply(block)
+            .perform<M, ResponseException>(M::class.java, ResponseException::class.java)
+
+    return if (response.isSucceed) {
+        response.success!!
+    } else {
+        throw response.failure!!
+    }
 }
 
 fun syncDownload(
-    path: String,
-    directory: String = NetConfig.app.externalCacheDir!!.absolutePath,
-    tag: Any? = null,
-    absolutePath: Boolean = false,
-    block: (UrlDownload.Api.() -> Unit)? = null
-): String {
+        path: String,
+        directory: String = NetConfig.app.externalCacheDir!!.absolutePath,
+        tag: Any? = null,
+        absolutePath: Boolean = false,
+        block: UrlDownload.Api.() -> Unit = {}): String {
 
     val realPath = if (absolutePath) path else (NetConfig.host + path)
 
     val download = Kalle.Download.get(realPath).directory(directory).tag(tag)
 
-    return if (block == null) {
-        download.perform()
-    } else {
-        download.apply(block).perform()
-    }
+    return download.apply(block).perform()
 }
 
 fun Context.syncDownloadImg(url: String, with: Int = 0, height: Int = 0): File {
@@ -358,3 +581,5 @@ fun Context.syncDownloadImg(url: String, with: Int = 0, height: Int = 0): File {
 // </editor-fold>
 
 
+
+

+ 29 - 0
net/src/main/java/com/drake/net/error/NetCancellationException.kt

@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+ * Project:Net
+ * Author:Drake
+ * Date:6/24/20 1:35 AM
+ */
+
+package com.drake.net.error
+
+import com.yanzhenjie.kalle.Canceler
+import kotlinx.coroutines.CoroutineExceptionHandler
+import kotlinx.coroutines.CoroutineScope
+import java.util.concurrent.CancellationException
+
+class NetCancellationException(coroutineScope: CoroutineScope, message: String? = null) :
+        CancellationException(message) {
+    init {
+        Canceler.cancel(coroutineScope.coroutineContext[CoroutineExceptionHandler])
+    }
+}
+
+
+/**
+ * 抛出该异常将取消作用域内所有的网络请求
+ */
+@Suppress("FunctionName")
+fun CoroutineScope.NetCancellationException(message: String? = null): NetCancellationException {
+    return NetCancellationException(this, message)
+}

+ 14 - 4
net/src/main/java/com/drake/net/scope/AndroidScope.kt

@@ -36,18 +36,19 @@ open class AndroidScope(
 
     protected var catch: (AndroidScope.(Throwable) -> Unit)? = null
     protected var finally: (AndroidScope.(Throwable?) -> Unit)? = null
-
     private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
         catch(throwable)
     }
 
+    val uid = exceptionHandler
+
     override val coroutineContext: CoroutineContext =
-        Dispatchers.Main + exceptionHandler + SupervisorJob()
+            Dispatchers.Main + exceptionHandler + SupervisorJob()
 
 
     open fun launch(
-        block: suspend CoroutineScope.() -> Unit
-    ): AndroidScope {
+            block: suspend CoroutineScope.() -> Unit
+                   ): AndroidScope {
         start()
         launch(EmptyCoroutineContext, block = block).invokeOnCompletion { finally(it) }
         return this
@@ -89,5 +90,14 @@ open class AndroidScope(
         e.printStackTrace()
     }
 
+    open fun cancel(cause: CancellationException? = null) {
+        val job = coroutineContext[Job]
+                  ?: error("Scope cannot be cancelled because it does not have a job: $this")
+        job.cancel(cause)
+    }
+
+    open fun cancel(message: String, cause: Throwable? = null) =
+            cancel(CancellationException(message, cause))
+
 }
 

+ 11 - 10
net/src/main/java/com/drake/net/scope/NetCoroutineScope.kt

@@ -11,8 +11,9 @@ import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
 import androidx.lifecycle.LifecycleOwner
 import com.drake.net.NetConfig
+import com.yanzhenjie.kalle.Canceler
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.cancel
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.supervisorScope
 import kotlin.coroutines.EmptyCoroutineContext
@@ -41,9 +42,7 @@ open class NetCoroutineScope() : AndroidScope() {
     ) : this() {
         lifecycleOwner.lifecycle.addObserver(object : LifecycleEventObserver {
             override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
-                if (lifeEvent == event) {
-                    cancel()
-                }
+                if (lifeEvent == event) cancel()
             }
         })
     }
@@ -68,7 +67,6 @@ open class NetCoroutineScope() : AndroidScope() {
         }.invokeOnCompletion {
             finally(it)
         }
-
         return this
     }
 
@@ -86,7 +84,6 @@ open class NetCoroutineScope() : AndroidScope() {
         catch?.invoke(this, e) ?: if (error) handleError(e)
     }
 
-
     /**
      * 该函数一般用于缓存读取
      * 只在第一次启动作用域时回调
@@ -98,14 +95,18 @@ open class NetCoroutineScope() : AndroidScope() {
      * @param onCache 该作用域内的所有异常都算缓存读取失败, 不会吐司和打印任何错误
      */
     fun cache(
-        error: Boolean = false,
-        animate: Boolean = false,
-        onCache: suspend CoroutineScope.() -> Unit
-    ): AndroidScope {
+            error: Boolean = false,
+            animate: Boolean = false,
+            onCache: suspend CoroutineScope.() -> Unit
+             ): AndroidScope {
         this.animate = animate
         this.error = error
         this.onCache = onCache
         return this
     }
 
+    override fun cancel(cause: CancellationException?) {
+        Canceler.cancel(uid)
+        super.cancel(cause)
+    }
 }

+ 23 - 34
net/src/main/java/com/drake/net/utils/ScopeUtils.kt

@@ -35,19 +35,13 @@ import kotlinx.coroutines.flow.FlowCollector
  *
  * 对话框被取消或者界面关闭作用域被取消
  */
-fun FragmentActivity.scopeDialog(
-    dialog: Dialog? = null,
-    cancelable: Boolean = true, block: suspend CoroutineScope.() -> Unit
-): NetCoroutineScope {
-    return DialogCoroutineScope(this, dialog, cancelable).launch(block)
-}
+fun FragmentActivity.scopeDialog(dialog: Dialog? = null,
+                                 cancelable: Boolean = true,
+                                 block: suspend CoroutineScope.() -> Unit) = DialogCoroutineScope(this, dialog, cancelable).launch(block)
 
-fun Fragment.scopeDialog(
-    dialog: Dialog? = null,
-    cancelable: Boolean = true, block: suspend CoroutineScope.() -> Unit
-): NetCoroutineScope {
-    return DialogCoroutineScope(activity!!, dialog, cancelable).launch(block)
-}
+fun Fragment.scopeDialog(dialog: Dialog? = null,
+                         cancelable: Boolean = true,
+                         block: suspend CoroutineScope.() -> Unit) = DialogCoroutineScope(requireActivity(), dialog, cancelable).launch(block)
 
 // </editor-fold>
 
@@ -97,12 +91,11 @@ fun scope(block: suspend CoroutineScope.() -> Unit): AndroidScope {
     return AndroidScope().launch(block)
 }
 
-fun LifecycleOwner.scopeLife(
-    lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_DESTROY,
-    block: suspend CoroutineScope.() -> Unit
-): AndroidScope {
-    return AndroidScope(this, lifeEvent).launch(block)
-}
+fun LifecycleOwner.scopeLife(lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_DESTROY,
+                             block: suspend CoroutineScope.() -> Unit) = AndroidScope(this, lifeEvent).launch(block)
+
+fun Fragment.scopeLife(lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_STOP,
+                       block: suspend CoroutineScope.() -> Unit) = AndroidScope(this, lifeEvent).launch(block)
 
 /**
  * 网络请求的异步作用域
@@ -110,25 +103,23 @@ fun LifecycleOwner.scopeLife(
  *
  * 该作用域生命周期跟随整个应用, 注意内存泄漏
  */
-fun scopeNet(block: suspend CoroutineScope.() -> Unit): NetCoroutineScope {
-    return NetCoroutineScope().launch(block)
-}
+fun scopeNet(block: suspend CoroutineScope.() -> Unit) = NetCoroutineScope().launch(block)
 
 
-fun LifecycleOwner.scopeNetLife(
-    lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_DESTROY,
-    block: suspend CoroutineScope.() -> Unit
-): NetCoroutineScope {
-    return NetCoroutineScope(this, lifeEvent).launch(block)
-}
+fun LifecycleOwner.scopeNetLife(lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_DESTROY,
+                                block: suspend CoroutineScope.() -> Unit) = NetCoroutineScope(this, lifeEvent).launch(block)
+
+/**
+ * Fragment应当在[Lifecycle.Event.ON_STOP]时就取消作用域, 避免[Fragment.onDestroyView]导致引用空视图
+ */
+fun Fragment.scopeNetLife(lifeEvent: Lifecycle.Event = Lifecycle.Event.ON_STOP,
+                          block: suspend CoroutineScope.() -> Unit) = NetCoroutineScope(this, lifeEvent).launch(block)
 
 
 @UseExperimental(InternalCoroutinesApi::class)
-inline fun <T> Flow<T>.scope(
-    owner: LifecycleOwner? = null,
-    event: Lifecycle.Event = Lifecycle.Event.ON_DESTROY,
-    crossinline action: suspend (value: T) -> Unit
-): CoroutineScope = AndroidScope(owner, event).launch {
+inline fun <T> Flow<T>.scope(owner: LifecycleOwner? = null,
+                             event: Lifecycle.Event = Lifecycle.Event.ON_DESTROY,
+                             crossinline action: suspend (value: T) -> Unit): CoroutineScope = AndroidScope(owner, event).launch {
     this@scope.collect(object : FlowCollector<T> {
         override suspend fun emit(value: T) = action(value)
     })
@@ -136,5 +127,3 @@ inline fun <T> Flow<T>.scope(
 
 
 
-
-

+ 1 - 0
sample/build.gradle

@@ -58,6 +58,7 @@ dependencies {
     // ------------------------------我的其他库-------------------------------------
     implementation 'com.github.liangjingkanji:StatusBar:1.0.3' // 透明状态栏
     implementation "com.github.liangjingkanji:LogCat:1.0" // 日志输出工具
+    implementation 'com.github.liangjingkanji:debugkit:1.2.9'
 
     // ------------------------------Google数据库-------------------------------------
     implementation "androidx.room:room-runtime:$room_version"

+ 1 - 1
sample/src/main/java/com/drake/net/sample/base/App.kt

@@ -30,7 +30,7 @@ class App : Application() {
         }
 
         // Net 网络初始化信息
-        initNet("http://192.168.0.107:80/") {
+        initNet("http://192.168.1.222:80/") {
             converter(JsonConvert()) // 自动解析JSON映射到实体类中, 转换器分为全局和单例, 覆盖生效(拦截器允许多个)
             cacheEnabled()
         }

+ 19 - 0
sample/src/main/java/com/drake/net/sample/mod/ListModel.kt

@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+ * Project:Net
+ * Author:Drake
+ * Date:6/23/20 4:32 PM
+ */
+
+package com.drake.net.sample.mod
+
+data class ListModel(
+        var code: Int,
+        var msg: String,
+        var data: Data
+                    ) {
+    data class Data(
+            var totalPage: Int,
+            var list: List<String>
+                   )
+}

+ 7 - 32
sample/src/main/java/com/drake/net/sample/ui/fragment/DownloadFileFragment.kt

@@ -9,7 +9,6 @@ package com.drake.net.sample.ui.fragment
 
 import android.os.Bundle
 import android.text.format.Formatter
-import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
@@ -17,16 +16,14 @@ import androidx.fragment.app.Fragment
 import com.drake.net.Download
 import com.drake.net.sample.R
 import com.drake.net.utils.scopeNetLife
-import com.yanzhenjie.kalle.Kalle
 import kotlinx.android.synthetic.main.fragment_download_file.*
 
 
 class DownloadFileFragment : Fragment() {
 
-    override fun onCreateView(
-        inflater: LayoutInflater, container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
+    override fun onCreateView(inflater: LayoutInflater,
+                              container: ViewGroup?,
+                              savedInstanceState: Bundle?): View? {
 
         return inflater.inflate(R.layout.fragment_download_file, container, false)
     }
@@ -35,21 +32,12 @@ class DownloadFileFragment : Fragment() {
         super.onActivityCreated(savedInstanceState)
 
         scopeNetLife {
-            val fileDir = requireContext().cacheDir.path
-
-            Download("download/img", dir = fileDir, tag = "drake") {
-
-                // 下载进度回调 (普通接口或者上传进度也可以监听)
+            Download("download", dir = requireContext().filesDir.path) {
+                // 下载进度回调
                 onProgress { progress, byteCount, speed ->
+                    // 进度条
+                    seek.progress = progress
 
-                    Log.d(
-                        "日志",
-                        "(DownloadFileFragment.kt:52)    progress = $progress"
-                    )
-
-                    seek ?: return@onProgress
-
-                    seek.progress = progress // 进度条
                     // 格式化显示单位
                     val downloadSize = Formatter.formatFileSize(requireContext(), byteCount)
                     val downloadSpeed = Formatter.formatFileSize(requireContext(), speed)
@@ -60,17 +48,4 @@ class DownloadFileFragment : Fragment() {
             }.await()
         }
     }
-
-    override fun onDestroyView() {
-        super.onDestroyView()
-        Kalle.Download.cancel("drake")
-        Log.d("日志", "(DownloadFileFragment.kt:68) -> onDestroyView    ")
-    }
-
-    override fun onDestroy() {
-        super.onDestroy()
-        Log.d("日志", "(DownloadFileFragment.kt:67) -> onDestroy    ")
-
-    }
-
 }

+ 20 - 1
sample/src/main/java/com/drake/net/sample/ui/fragment/PullRefreshFragment.kt

@@ -12,7 +12,13 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.setup
+import com.drake.net.Get
 import com.drake.net.sample.R
+import com.drake.net.sample.mod.ListModel
+import com.drake.net.utils.scope
+import kotlinx.android.synthetic.main.fragment_pull_refresh.*
 
 
 class PullRefreshFragment : Fragment() {
@@ -28,7 +34,20 @@ class PullRefreshFragment : Fragment() {
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
 
-
+        rv_pull.linear().setup {
+            addType<ListModel.Data>(R.layout.item_list)
+        }
+
+        page.onRefresh {
+            scope {
+                val data = Get<ListModel>("list") {
+                    param("page", index)
+                }.await().data
+                addData(data.list) {
+                    index < data.totalPage
+                }
+            }
+        }.autoRefresh()
     }
 
 }

+ 20 - 3
sample/src/main/java/com/drake/net/sample/ui/fragment/PushRefreshFragment.kt

@@ -12,21 +12,38 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
+import com.drake.brv.utils.linear
+import com.drake.brv.utils.models
+import com.drake.brv.utils.setup
+import com.drake.net.Get
 import com.drake.net.sample.R
+import com.drake.net.sample.mod.ListModel
+import com.drake.net.utils.scope
+import kotlinx.android.synthetic.main.fragment_push_refresh.*
 
 
 class PushRefreshFragment : Fragment() {
 
     override fun onCreateView(
-        inflater: LayoutInflater, container: ViewGroup?,
-        savedInstanceState: Bundle?
-    ): View? {
+            inflater: LayoutInflater, container: ViewGroup?,
+            savedInstanceState: Bundle?
+                             ): View? {
 
         return inflater.inflate(R.layout.fragment_push_refresh, container, false)
     }
 
     override fun onActivityCreated(savedInstanceState: Bundle?) {
         super.onActivityCreated(savedInstanceState)
+
+        rv_push.linear().setup {
+            addType<ListModel.Data>(R.layout.item_list)
+        }
+
+        page.onRefresh {
+            scope {
+                rv_push.models = Get<ListModel>("list").await().data.list
+            }
+        }.autoRefresh()
     }
 
 }

+ 6 - 7
sample/src/main/res/layout/fragment_pull_refresh.xml

@@ -5,17 +5,16 @@
   ~ Date:4/16/20 4:58 PM
   -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.drake.brv.PageRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/page"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     tools:context=".ui.fragment.PullRefreshFragment">
 
-    <TextView
-        android:id="@+id/tv_fragment"
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_pull"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:text="AutoPagingFragment" />
+        android:layout_height="match_parent" />
 
-</FrameLayout>
+</com.drake.brv.PageRefreshLayout>

+ 8 - 7
sample/src/main/res/layout/fragment_push_refresh.xml

@@ -5,17 +5,18 @@
   ~ Date:4/16/20 3:24 PM
   -->
 
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<com.drake.brv.PageRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/page"
     android:layout_width="match_parent"
+    app:srlEnableLoadMore="false"
     android:layout_height="match_parent"
     tools:context=".ui.fragment.PushRefreshFragment">
 
-    <TextView
-        android:id="@+id/tv_fragment"
+    <androidx.recyclerview.widget.RecyclerView
+        android:id="@+id/rv_push"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:text="PushSwipeFrgment" />
+        android:layout_height="match_parent" />
 
-</FrameLayout>
+</com.drake.brv.PageRefreshLayout>

+ 12 - 0
sample/src/main/res/layout/item_list.xml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  ~ Copyright (C) 2018, Umbrella CompanyLimited All rights reserved.
+  ~ Project:Net
+  ~ Author:Drake
+  ~ Date:6/23/20 4:40 PM
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+</LinearLayout>