diff --git a/CHANGELOG.md b/CHANGELOG.md
index f7821cfe6..6cfea34c2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,8 @@
#Changelog
+## 8.1.2(2021-01-18)
+* 区域查询采用SingleFlight模式
+* 增加网络链接状态检测
+
## 8.1.1 (2021-01-06)
* 优化日志统计
diff --git a/README.md b/README.md
index b967950d1..66725c7fb 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ https://github.com/qiniudemo/qiniu-lab-android
| 7.0.7 | Android 2.2+ | android-async-http 1.4.8 |
### 注意
-* 推荐使用最新版:8.1.1
+* 推荐使用最新版:8.1.2
* AndroidNetwork.getMobileDbm()可以获取手机信号强度,需要如下权限(API>=18时生效)
```
diff --git a/library/src/androidTest/java/com/qiniu/android/ConnectCheckTest.java b/library/src/androidTest/java/com/qiniu/android/ConnectCheckTest.java
new file mode 100644
index 000000000..bc77e6698
--- /dev/null
+++ b/library/src/androidTest/java/com/qiniu/android/ConnectCheckTest.java
@@ -0,0 +1,46 @@
+package com.qiniu.android;
+
+import com.qiniu.android.http.connectCheck.ConnectChecker;
+import com.qiniu.android.storage.GlobalConfiguration;
+
+public class ConnectCheckTest extends BaseTest {
+
+ public void testCheck() {
+
+ int maxCount = 100;
+ int successCount = 0;
+ for (int i = 0; i < maxCount; i++) {
+ if (ConnectChecker.check()) {
+ successCount += 1;
+ }
+ }
+
+ assertEquals("maxCount:" + maxCount + " successCount:" + successCount, maxCount, successCount);
+ }
+
+ public void testCustomCheckHosts() {
+ GlobalConfiguration.getInstance().connectCheckURLStrings = new String[]{"https://www.baidu.com"};
+ int maxCount = 100;
+ int successCount = 0;
+ for (int i = 0; i < maxCount; i++) {
+ if (ConnectChecker.check()) {
+ successCount += 1;
+ }
+ }
+
+ assertEquals("maxCount:" + maxCount + " successCount:" + successCount, maxCount, successCount);
+ }
+
+ public void testNotConnected() {
+ GlobalConfiguration.getInstance().connectCheckURLStrings = new String[]{"https://www.test1.com", "https://www.test2.com"};
+ int maxCount = 100;
+ int successCount = 0;
+ for (int i = 0; i < maxCount; i++) {
+ if (ConnectChecker.check()) {
+ successCount += 1;
+ }
+ }
+
+ assertEquals("maxCount:" + maxCount + " successCount:" + successCount, 0, successCount);
+ }
+}
diff --git a/library/src/androidTest/java/com/qiniu/android/SingleFlightTest.java b/library/src/androidTest/java/com/qiniu/android/SingleFlightTest.java
new file mode 100644
index 000000000..cea74bb71
--- /dev/null
+++ b/library/src/androidTest/java/com/qiniu/android/SingleFlightTest.java
@@ -0,0 +1,188 @@
+package com.qiniu.android;
+
+import com.qiniu.android.utils.LogUtil;
+import com.qiniu.android.utils.SingleFlight;
+
+public class SingleFlightTest extends BaseTest {
+
+ private static final int RetryCount = 5;
+
+ public void testSync() {
+ final TestStatus testStatus = new TestStatus();
+ testStatus.maxCount = 1000;
+ testStatus.completeCount = 0;
+
+ SingleFlight singleFlight = new SingleFlight();
+ for (int i = 0; i < testStatus.maxCount; i++) {
+ singleFlightPerform(singleFlight, i, RetryCount, false, new CompleteHandler() {
+ @Override
+ public void complete() throws Exception {
+ testStatus.completeCount += 1;
+ LogUtil.d("== sync completeCount:" + testStatus.completeCount);
+ }
+ });
+ }
+
+ wait(new WaitConditional() {
+ @Override
+ public boolean shouldWait() {
+ return testStatus.maxCount != testStatus.completeCount;
+ }
+ }, 60);
+ }
+
+ public void testSyncRetry() {
+ final TestStatus testStatus = new TestStatus();
+ testStatus.maxCount = 1000;
+ testStatus.completeCount = 0;
+
+ SingleFlight singleFlight = new SingleFlight();
+ for (int i = 0; i < testStatus.maxCount; i++) {
+ singleFlightPerform(singleFlight, i, 0, false, new CompleteHandler() {
+ @Override
+ public void complete() throws Exception {
+ testStatus.completeCount += 1;
+ LogUtil.d("== sync completeCount:" + testStatus.completeCount);
+ }
+ });
+ }
+
+ wait(new WaitConditional() {
+ @Override
+ public boolean shouldWait() {
+ return testStatus.maxCount != testStatus.completeCount;
+ }
+ }, 60);
+ }
+
+ public void testAsync() {
+ final TestStatus testStatus = new TestStatus();
+ testStatus.maxCount = 1000;
+ testStatus.completeCount = 0;
+
+ SingleFlight singleFlight = new SingleFlight();
+ for (int i = 0; i < testStatus.maxCount; i++) {
+ singleFlightPerform(singleFlight, i, RetryCount, true, new CompleteHandler() {
+ @Override
+ public void complete() throws Exception {
+ synchronized (testStatus) {
+ testStatus.completeCount += 1;
+ }
+ LogUtil.d("== async complete Count:" + testStatus.completeCount);
+ }
+ });
+ }
+
+ wait(new WaitConditional() {
+ @Override
+ public boolean shouldWait() {
+ return testStatus.maxCount != testStatus.completeCount;
+ }
+ }, 60);
+
+ assertTrue("== async" + "max Count:" + testStatus.maxCount + " complete Count:" + testStatus.completeCount, testStatus.maxCount == testStatus.completeCount);
+ LogUtil.d("== async" + "max Count:" + testStatus.maxCount + " complete Count:" + testStatus.completeCount);
+ }
+
+ public void testAsyncRetry() {
+ final TestStatus testStatus = new TestStatus();
+ testStatus.maxCount = 1000;
+ testStatus.completeCount = 0;
+
+ SingleFlight singleFlight = new SingleFlight();
+ for (int i = 0; i < testStatus.maxCount; i++) {
+ singleFlightPerform(singleFlight, i, 0, true, new CompleteHandler() {
+ @Override
+ public void complete() throws Exception {
+ synchronized (testStatus) {
+ testStatus.completeCount += 1;
+ }
+ LogUtil.d("== async completeCount:" + testStatus.completeCount);
+ }
+ });
+ }
+
+ wait(new WaitConditional() {
+ @Override
+ public boolean shouldWait() {
+ return testStatus.maxCount != testStatus.completeCount;
+ }
+ }, 60);
+
+ LogUtil.d("== async completeCount:" + testStatus.completeCount + " end");
+ }
+
+ private void singleFlightPerform(final SingleFlight singleFlight,
+ final int index,
+ final int retryCount,
+ final boolean isAsync,
+ final CompleteHandler completeHandler) {
+
+ try {
+ singleFlight.perform("key", new SingleFlight.ActionHandler() {
+ @Override
+ public void action(final SingleFlight.CompleteHandler singleFlightCompleteHandler) throws Exception {
+
+ final CompleteHandler completeHandlerP = new CompleteHandler() {
+ @Override
+ public void complete() throws Exception {
+ if (retryCount < RetryCount) {
+ LogUtil.d("== " + (isAsync ? "async" : "sync") + " action retryCount:" + retryCount + " index:" + index + " error");
+ throw new Exception("== 123 ==");
+ } else {
+ LogUtil.d("== " + (isAsync ? "async" : "sync") + " action retryCount:" + retryCount + " index:" + index + " value");
+ singleFlightCompleteHandler.complete(index + "");
+ }
+ }
+ };
+
+ if (isAsync) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ completeHandlerP.complete();
+ } catch (Exception e) {
+ singleFlightCompleteHandler.complete(null);
+ }
+ }
+ }).start();
+ } else {
+ completeHandlerP.complete();
+ }
+ }
+ }, new SingleFlight.CompleteHandler() {
+ @Override
+ public void complete(Object value) {
+ if (retryCount < RetryCount) {
+ singleFlightPerform(singleFlight, index, retryCount + 1, isAsync, completeHandler);
+ } else {
+ LogUtil.d("== " + (isAsync ? "async" : "sync") + " action complete retryCount:" + retryCount + " value:" + value + " index:" + index);
+ if (!isAsync) {
+ assertTrue("index:" + index + "value error",(value + "").equals(index + ""));
+ }
+ try {
+ completeHandler.complete();
+ } catch (Exception e) {
+ }
+ }
+ }
+ });
+ } catch (Exception e) {
+ singleFlightPerform(singleFlight, index, retryCount + 1, isAsync, completeHandler);
+ }
+ }
+
+
+
+ private interface CompleteHandler {
+ void complete() throws Exception;
+ }
+
+
+ protected static class TestStatus {
+ int maxCount;
+ int completeCount;
+ }
+
+}
diff --git a/library/src/main/java/com/qiniu/android/common/AutoZone.java b/library/src/main/java/com/qiniu/android/common/AutoZone.java
index d11ceb487..020e40a11 100644
--- a/library/src/main/java/com/qiniu/android/common/AutoZone.java
+++ b/library/src/main/java/com/qiniu/android/common/AutoZone.java
@@ -4,6 +4,7 @@
import com.qiniu.android.http.request.RequestTransaction;
import com.qiniu.android.http.metrics.UploadRegionRequestMetrics;
import com.qiniu.android.storage.UpToken;
+import com.qiniu.android.utils.SingleFlight;
import org.json.JSONObject;
@@ -23,6 +24,8 @@ public final class AutoZone extends Zone {
private Map zonesInfoMap = new ConcurrentHashMap<>();
private ArrayList transactions = new ArrayList<>();
+ private static final SingleFlight SingleFlight = new SingleFlight();
+
//私有云可能改变ucServer
public void setUcServer(String ucServer) {
this.ucServer = ucServer;
@@ -72,27 +75,56 @@ public void preQuery(final UpToken token, final QueryHandler completeHandler) {
return;
}
- final RequestTransaction transaction = createUploadRequestTransaction(token);
- transaction.queryUploadHosts(true, new RequestTransaction.RequestCompleteHandler() {
- @Override
- public void complete(ResponseInfo responseInfo, UploadRegionRequestMetrics requestMetrics, JSONObject response) {
- if (responseInfo != null && responseInfo.isOK() && response != null) {
- ZonesInfo zonesInfoP = ZonesInfo.createZonesInfo(response);
- zonesInfoMap.put(cacheKey, zonesInfoP);
- GlobalCache.getInstance().cache(response, cacheKey);
- completeHandler.complete(0, responseInfo, requestMetrics);
- } else {
- if (responseInfo.isNetworkBroken()) {
- completeHandler.complete(ResponseInfo.NetworkError, responseInfo, requestMetrics);
- } else {
- ZonesInfo zonesInfoP = FixedZone.localsZoneInfo().getZonesInfo(token);
+
+ try {
+ SingleFlight.perform(cacheKey, new SingleFlight.ActionHandler() {
+ @Override
+ public void action(final com.qiniu.android.utils.SingleFlight.CompleteHandler completeHandler) throws Exception {
+
+ final RequestTransaction transaction = createUploadRequestTransaction(token);
+ transaction.queryUploadHosts(true, new RequestTransaction.RequestCompleteHandler() {
+ @Override
+ public void complete(ResponseInfo responseInfo, UploadRegionRequestMetrics requestMetrics, JSONObject response) {
+ destroyUploadRequestTransaction(transaction);
+
+ SingleFlightValue value = new SingleFlightValue();
+ value.responseInfo = responseInfo;
+ value.response = response;
+ value.metrics = requestMetrics;
+ completeHandler.complete(value);
+ }
+ });
+ }
+
+ }, new SingleFlight.CompleteHandler() {
+ @Override
+ public void complete(Object value) {
+ SingleFlightValue singleFlightValue = (SingleFlightValue)value;
+ ResponseInfo responseInfo = singleFlightValue.responseInfo;
+ UploadRegionRequestMetrics requestMetrics = singleFlightValue.metrics;
+ JSONObject response = singleFlightValue.response;
+
+ if (responseInfo != null && responseInfo.isOK() && response != null) {
+ ZonesInfo zonesInfoP = ZonesInfo.createZonesInfo(response);
zonesInfoMap.put(cacheKey, zonesInfoP);
+ GlobalCache.getInstance().cache(response, cacheKey);
completeHandler.complete(0, responseInfo, requestMetrics);
+ } else {
+ if (responseInfo.isNetworkBroken()) {
+ completeHandler.complete(ResponseInfo.NetworkError, responseInfo, requestMetrics);
+ } else {
+ ZonesInfo zonesInfoP = FixedZone.localsZoneInfo().getZonesInfo(token);
+ zonesInfoMap.put(cacheKey, zonesInfoP);
+ completeHandler.complete(0, responseInfo, requestMetrics);
+ }
}
}
- destroyUploadRequestTransaction(transaction);
- }
- });
+ });
+
+ } catch (Exception e) {
+ /// 此处永远不会执行,回调只为占位
+ completeHandler.complete(ResponseInfo.NetworkError, ResponseInfo.localIOError("uc query"), null);
+ }
}
private RequestTransaction createUploadRequestTransaction(UpToken token) {
@@ -107,6 +139,11 @@ private void destroyUploadRequestTransaction(RequestTransaction transaction) {
transactions.remove(transaction);
}
+ private static class SingleFlightValue {
+ private ResponseInfo responseInfo;
+ private JSONObject response;
+ private UploadRegionRequestMetrics metrics;
+ }
private static class GlobalCache {
private static GlobalCache globalCache = new GlobalCache();
diff --git a/library/src/main/java/com/qiniu/android/common/Constants.java b/library/src/main/java/com/qiniu/android/common/Constants.java
index 51be5db93..a76e38c45 100644
--- a/library/src/main/java/com/qiniu/android/common/Constants.java
+++ b/library/src/main/java/com/qiniu/android/common/Constants.java
@@ -2,7 +2,7 @@
public final class Constants {
- public static final String VERSION = "8.1.1";
+ public static final String VERSION = "8.1.2";
public static final String UTF_8 = "utf-8";
}
diff --git a/library/src/main/java/com/qiniu/android/http/ResponseInfo.java b/library/src/main/java/com/qiniu/android/http/ResponseInfo.java
index f32eb08f2..1e53e5207 100644
--- a/library/src/main/java/com/qiniu/android/http/ResponseInfo.java
+++ b/library/src/main/java/com/qiniu/android/http/ResponseInfo.java
@@ -279,7 +279,7 @@ public boolean canConnectToHost(){
public boolean isHostUnavailable(){
// 基本不可恢复,注:会影响下次请求,范围太大可能会造成大量的timeout
- if (isTlsError() || statusCode == 502 || statusCode == 503 || statusCode == 504 || statusCode == 599) {
+ if (statusCode == 502 || statusCode == 503 || statusCode == 504 || statusCode == 599) {
return true;
} else {
return false;
diff --git a/library/src/main/java/com/qiniu/android/http/connectCheck/ConnectChecker.java b/library/src/main/java/com/qiniu/android/http/connectCheck/ConnectChecker.java
new file mode 100644
index 000000000..9c8acd995
--- /dev/null
+++ b/library/src/main/java/com/qiniu/android/http/connectCheck/ConnectChecker.java
@@ -0,0 +1,138 @@
+package com.qiniu.android.http.connectCheck;
+
+import com.qiniu.android.http.ResponseInfo;
+import com.qiniu.android.http.metrics.UploadSingleRequestMetrics;
+import com.qiniu.android.http.request.IRequestClient;
+import com.qiniu.android.http.request.Request;
+import com.qiniu.android.http.request.httpclient.SystemHttpClient;
+import com.qiniu.android.storage.GlobalConfiguration;
+import com.qiniu.android.utils.LogUtil;
+import com.qiniu.android.utils.SingleFlight;
+import com.qiniu.android.utils.Wait;
+
+import org.json.JSONObject;
+
+public class ConnectChecker {
+
+ private static SingleFlight singleFlight = new SingleFlight<>();
+
+ public static boolean check() {
+
+ final CheckResult result = new CheckResult();
+
+ final Wait wait = new Wait();
+ check(new CheckCompleteHandler() {
+ @Override
+ public void complete(boolean isConnected) {
+ result.isConnected = isConnected;
+ wait.stopWait();
+ }
+ });
+ wait.startWait();
+
+ return result.isConnected;
+ }
+
+ private static void check(final CheckCompleteHandler completeHandler) {
+
+ try {
+ singleFlight.perform("connect_check", new SingleFlight.ActionHandler() {
+ @Override
+ public void action(final SingleFlight.CompleteHandler singleFlightComplete) throws Exception {
+ checkAllHosts(new CheckCompleteHandler() {
+ @Override
+ public void complete(boolean isConnected) {
+ singleFlightComplete.complete(isConnected);
+ }
+ });
+ }
+ }, new SingleFlight.CompleteHandler() {
+ @Override
+ public void complete(Boolean value) {
+ completeHandler.complete(value);
+ }
+ });
+ } catch (Exception e) {
+ completeHandler.complete(true);
+ }
+ }
+
+ private static void checkAllHosts(final CheckCompleteHandler completeHandler) {
+ String[] allHosts = GlobalConfiguration.getInstance().connectCheckURLStrings;
+ if (allHosts == null) {
+ completeHandler.complete(true);
+ return;
+ }
+
+ allHosts = allHosts.clone();
+ final CheckStatus checkStatus = new CheckStatus();
+ checkStatus.totalCount = allHosts.length;
+ checkStatus.completeCount = 0;
+ checkStatus.isCompleted = false;
+ for (String host : allHosts) {
+ checkHost(host, new CheckCompleteHandler() {
+ @Override
+ public void complete(boolean isHostConnected) {
+
+ synchronized (checkStatus) {
+ checkStatus.completeCount += 1;
+ }
+ if (isHostConnected) {
+ checkStatus.isConnected = true;
+ }
+ if (isHostConnected || checkStatus.completeCount == checkStatus.totalCount) {
+ synchronized (checkStatus) {
+ if (checkStatus.isCompleted) {
+ LogUtil.i("== check all hosts has completed totalCount:" + checkStatus.totalCount + " completeCount:" + checkStatus.completeCount);
+ return;
+ } else {
+ LogUtil.i("== check all hosts completed totalCount:" + checkStatus.totalCount + " completeCount:" + checkStatus.completeCount);
+ checkStatus.isCompleted = true;
+ }
+ }
+ completeHandler.complete(checkStatus.isConnected);
+ } else {
+ LogUtil.i("== check all hosts not completed totalCount:" + checkStatus.totalCount + " completeCount:" + checkStatus.completeCount);
+ }
+ }
+ });
+ }
+
+ }
+
+ private static void checkHost(final String host, final CheckCompleteHandler completeHandler) {
+
+ Request request = new Request(host, Request.HttpMethodHEAD, null, null, GlobalConfiguration.getInstance().connectCheckTimeout);
+ SystemHttpClient client = new SystemHttpClient();
+
+ LogUtil.i("== checkHost:" + host);
+ client.request(request, true, null, null, new IRequestClient.RequestClientCompleteHandler() {
+ @Override
+ public void complete(ResponseInfo responseInfo, UploadSingleRequestMetrics metrics, JSONObject response) {
+ if (responseInfo.statusCode > 99) {
+ LogUtil.i("== checkHost:" + host + " result: true");
+ completeHandler.complete(true);
+ } else {
+ LogUtil.i("== checkHost:" + host + " result: false");
+ completeHandler.complete(false);
+ }
+ }
+ });
+ }
+
+
+ private interface CheckCompleteHandler {
+ void complete(boolean isConnected);
+ }
+
+ private static class CheckStatus {
+ private int totalCount = 0;
+ private int completeCount = 0;
+ private boolean isCompleted = false;
+ private boolean isConnected = false;
+ }
+
+ private static class CheckResult {
+ private boolean isConnected = false;
+ }
+}
diff --git a/library/src/main/java/com/qiniu/android/http/metrics/UploadTaskMetrics.java b/library/src/main/java/com/qiniu/android/http/metrics/UploadTaskMetrics.java
index a273c6af7..8cf029664 100644
--- a/library/src/main/java/com/qiniu/android/http/metrics/UploadTaskMetrics.java
+++ b/library/src/main/java/com/qiniu/android/http/metrics/UploadTaskMetrics.java
@@ -4,16 +4,17 @@
import com.qiniu.android.http.request.IUploadRegion;
import java.util.ArrayList;
-import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
public class UploadTaskMetrics {
public ArrayList regions;
- private HashMap metricsInfo;
+ private Map metricsInfo;
public UploadTaskMetrics(ArrayList regions) {
this.regions = regions;
- this.metricsInfo = new HashMap();
+ this.metricsInfo = new ConcurrentHashMap<>();
}
diff --git a/library/src/main/java/com/qiniu/android/http/request/HttpSingleRequest.java b/library/src/main/java/com/qiniu/android/http/request/HttpSingleRequest.java
index 90388a417..d970d6a67 100644
--- a/library/src/main/java/com/qiniu/android/http/request/HttpSingleRequest.java
+++ b/library/src/main/java/com/qiniu/android/http/request/HttpSingleRequest.java
@@ -4,6 +4,7 @@
import com.qiniu.android.collect.ReportItem;
import com.qiniu.android.collect.UploadInfoReporter;
import com.qiniu.android.http.ResponseInfo;
+import com.qiniu.android.http.connectCheck.ConnectChecker;
import com.qiniu.android.http.dns.DnsPrefetcher;
import com.qiniu.android.http.request.httpclient.SystemHttpClient;
import com.qiniu.android.http.request.handler.CheckCancelHandler;
@@ -106,6 +107,12 @@ public void complete(ResponseInfo responseInfo, UploadSingleRequestMetrics metri
if (metrics != null){
requestMetricsList.add(metrics);
}
+
+ if (shouldCheckConnect(responseInfo) && !ConnectChecker.check()) {
+ String message = "check origin statusCode:" + responseInfo.statusCode + " error:" + responseInfo.error;
+ responseInfo = ResponseInfo.errorInfo(ResponseInfo.NetworkSlow, message);
+ }
+
LogUtil.i("key:" + StringUtils.toNonnullString(requestInfo.key) +
" response:" + StringUtils.toNonnullString(responseInfo));
if (shouldRetryHandler != null && shouldRetryHandler.shouldRetry(responseInfo, response)
@@ -126,6 +133,15 @@ public void complete(ResponseInfo responseInfo, UploadSingleRequestMetrics metri
}
+ private boolean shouldCheckConnect(ResponseInfo responseInfo) {
+ return responseInfo != null && (
+ responseInfo.statusCode == -1001 || /* timeout */
+ responseInfo.statusCode == -1003 || /* unknown host */
+ responseInfo.statusCode == -1004 || /* cannot connect to host */
+ responseInfo.statusCode == -1005 || /* connection lost */
+ responseInfo.statusCode == -1009 || /* not connected to host */
+ responseInfo.isTlsError());
+ }
private synchronized void completeAction(Request request,
ResponseInfo responseInfo,
diff --git a/library/src/main/java/com/qiniu/android/http/request/Request.java b/library/src/main/java/com/qiniu/android/http/request/Request.java
index 5c0dc5a20..943d31470 100644
--- a/library/src/main/java/com/qiniu/android/http/request/Request.java
+++ b/library/src/main/java/com/qiniu/android/http/request/Request.java
@@ -1,14 +1,12 @@
package com.qiniu.android.http.request;
-import java.net.Inet4Address;
-import java.net.Inet6Address;
import java.net.InetAddress;
-import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Map;
public class Request {
+ public static final String HttpMethodHEAD = "HEAD";
public static final String HttpMethodGet = "GET";
public static final String HttpMethodPOST = "POST";
public static final String HttpMethodPUT = "PUT";
diff --git a/library/src/main/java/com/qiniu/android/http/request/httpclient/SystemHttpClient.java b/library/src/main/java/com/qiniu/android/http/request/httpclient/SystemHttpClient.java
index 7cd09531d..d8a8b78fa 100644
--- a/library/src/main/java/com/qiniu/android/http/request/httpclient/SystemHttpClient.java
+++ b/library/src/main/java/com/qiniu/android/http/request/httpclient/SystemHttpClient.java
@@ -214,7 +214,8 @@ private okhttp3.Request.Builder createRequestBuilder(final RequestClientProgress
Headers allHeaders = Headers.of(currentRequest.allHeaders);
okhttp3.Request.Builder requestBuilder = null;
- if (currentRequest.httpMethod.equals(Request.HttpMethodGet)){
+ if (currentRequest.httpMethod.equals(Request.HttpMethodHEAD) ||
+ currentRequest.httpMethod.equals(Request.HttpMethodGet)){
requestBuilder = new okhttp3.Request.Builder().get().url(currentRequest.urlString);
for (String key : currentRequest.allHeaders.keySet()){
String value = currentRequest.allHeaders.get(key);
diff --git a/library/src/main/java/com/qiniu/android/storage/GlobalConfiguration.java b/library/src/main/java/com/qiniu/android/storage/GlobalConfiguration.java
index a5ad0a42d..2e73b8d89 100644
--- a/library/src/main/java/com/qiniu/android/storage/GlobalConfiguration.java
+++ b/library/src/main/java/com/qiniu/android/storage/GlobalConfiguration.java
@@ -3,6 +3,8 @@
import com.qiniu.android.http.dns.Dns;
import com.qiniu.android.utils.Utils;
+import java.util.List;
+
public class GlobalConfiguration {
/**
@@ -11,44 +13,58 @@ public class GlobalConfiguration {
public boolean isDnsOpen = true;
/**
- * dns 预取失败后 会进行重新预取 dnsRepreHostNum为最多尝试次数
+ * dns 预取失败后 会进行重新预取 dnsRepreHostNum为最多尝试次数
*/
public int dnsRepreHostNum = 2;
/**
- * dns预取缓存时间 单位:秒
+ * dns预取缓存时间 单位:秒
*/
public int dnsCacheTime = 120;
/**
- * 自定义DNS解析客户端host
+ * 自定义DNS解析客户端host
*/
public Dns dns = null;
/**
- * 自定义DNS解析客户端host
+ * 自定义DNS解析客户端host
*/
public String dnsCacheDir = Utils.sdkDirectory() + "/dnsCache/";
/**
- * Host全局冻结时间 单位:秒 默认:30 推荐范围:[10 ~ 60]
- * 当某个Host的上传失败后并且可能短时间无法恢复,会冻结该Host,globalHostFrozenTime为全局冻结时间
- * Host全局冻结时间 单位:秒 默认:10 推荐范围:[5 ~ 30]
- * 当某个Host的上传失败后并且可能短时间无法恢复,会冻结该Host
+ * Host全局冻结时间 单位:秒 默认:30 推荐范围:[10 ~ 60]
+ * 当某个Host的上传失败后并且可能短时间无法恢复,会冻结该Host,globalHostFrozenTime为全局冻结时间
+ * Host全局冻结时间 单位:秒 默认:10 推荐范围:[5 ~ 30]
+ * 当某个Host的上传失败后并且可能短时间无法恢复,会冻结该Host
*/
public int globalHostFrozenTime = 10;
/**
- * Host局部冻结时间,只会影响当前上传操作 单位:秒 默认:5*60 推荐范围:[60 ~ 10*60]
- * 当某个Host的上传失败后并且短时间可能会恢复,会局部冻结该Host
+ * Host局部冻结时间,只会影响当前上传操作 单位:秒 默认:5*60 推荐范围:[60 ~ 10*60]
+ * 当某个Host的上传失败后并且短时间可能会恢复,会局部冻结该Host
*/
- public int partialHostFrozenTime = 5*60;
+ public int partialHostFrozenTime = 5 * 60;
+ /**
+ * 网络连接状态检测使用的connectCheckURLStrings,网络链接状态检测可能会影响重试机制,启动网络连接状态检测有助于提高上传可用性。
+ * 当请求的 Response 为网络异常时,并发对 connectCheckURLStrings 中 URLString 进行 HEAD 请求,以此检测当前网络状态的链接状态,其中任意一个 URLString 链接成功则认为当前网络状态链接良好;
+ * 当 connectCheckURLStrings 为 nil 或者 空数组时则弃用检测功能。
+ */
+ public String[] connectCheckURLStrings = new String[]{"http://www.qiniu.com", "http://www.baidu.com", "http://www.google.com"};
+
+ /**
+ * 网络连接状态检测HEAD请求超时,默认:3s
+ */
+ public int connectCheckTimeout = 3;
+
private static GlobalConfiguration configuration = new GlobalConfiguration();
- private GlobalConfiguration(){
+
+ private GlobalConfiguration() {
}
- public static GlobalConfiguration getInstance(){
+
+ public static GlobalConfiguration getInstance() {
return configuration;
}
}
diff --git a/library/src/main/java/com/qiniu/android/utils/SingleFlight.java b/library/src/main/java/com/qiniu/android/utils/SingleFlight.java
new file mode 100644
index 000000000..db6e5a632
--- /dev/null
+++ b/library/src/main/java/com/qiniu/android/utils/SingleFlight.java
@@ -0,0 +1,134 @@
+package com.qiniu.android.utils;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class SingleFlight {
+
+ private Map> callInfo = new HashMap<>();
+
+ /**
+ * 异步 SingleFlight 执行函数
+ *
+ * @param key actionHandler 对应的 key,同一时刻同一个 key 最多只有一个对应的 actionHandler 在执行
+ * @param actionHandler 执行函数,注意:actionHandler 中,【完成回调】和【抛出异常】二者有且有一个,且只能出现一次
+ * @param completeHandler single flight 执行 actionHandler 后的完成回调
+ */
+ public void perform(String key, ActionHandler actionHandler, CompleteHandler completeHandler) throws Exception {
+ if (actionHandler == null) {
+ return;
+ }
+
+ boolean isFirstTask = false;
+ boolean shouldComplete = false;
+ SingleFlightCall call = null;
+ synchronized (this) {
+
+ if (key != null) {
+ call = callInfo.get(key);
+ }
+
+ if (call == null) {
+ call = new SingleFlightCall<>();
+ if (key != null) {
+ callInfo.put(key, call);
+ }
+ isFirstTask = true;
+ }
+
+ synchronized (call) {
+ shouldComplete = call.isComplete;
+ if (!shouldComplete) {
+ SingleFlightTask task = new SingleFlightTask<>();
+ task.completeHandler = completeHandler;
+ call.tasks.add(task);
+ }
+ }
+ }
+
+ if (shouldComplete) {
+ if (call.exception != null) {
+ throw call.exception;
+ } else {
+ if (completeHandler != null) {
+ completeHandler.complete(call.value);
+ }
+ }
+ return;
+ }
+
+ if (!isFirstTask) {
+ return;
+ }
+
+ final String finalKey = key;
+ final SingleFlightCall finalCall = call;
+ try {
+ actionHandler.action(new CompleteHandler() {
+ @Override
+ public void complete(T value) {
+ List> currentTasks = null;
+ synchronized (finalCall) {
+ if (finalCall.isComplete) {
+ return;
+ }
+ finalCall.isComplete = true;
+ finalCall.value = value;
+ currentTasks = new ArrayList<>(finalCall.tasks);
+ }
+ if (finalKey != null) {
+ synchronized (this) {
+ callInfo.remove(finalKey);
+ }
+ }
+ for (SingleFlightTask task : currentTasks) {
+ if (task != null && task.completeHandler != null) {
+ task.completeHandler.complete(finalCall.value);
+ }
+ }
+ }
+ });
+ } catch (Exception e) {
+ List> currentTasks = null;
+ synchronized (finalCall) {
+ if (finalCall.isComplete) {
+ return;
+ }
+ finalCall.isComplete = true;
+ finalCall.exception = e;
+ currentTasks = new ArrayList<>(call.tasks);
+ }
+ if (key != null) {
+ synchronized (this) {
+ callInfo.remove(key);
+ }
+ }
+ for (SingleFlightTask task : currentTasks) {
+ if (task != null && task.completeHandler != null) {
+ throw call.exception;
+ }
+ }
+ }
+ }
+
+ private static class SingleFlightTask {
+ private CompleteHandler completeHandler;
+ }
+
+ private static class SingleFlightCall {
+ private boolean isComplete = false;
+ private List> tasks = new ArrayList<>();
+ private T value;
+ private Exception exception;
+ }
+
+ public interface CompleteHandler {
+ void complete(T value);
+ }
+
+ public interface ActionHandler {
+ void action(CompleteHandler completeHandler) throws Exception;
+ }
+}