Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#Changelog
## 8.1.2(2021-01-18)
* 区域查询采用SingleFlight模式
* 增加网络链接状态检测

## 8.1.1 (2021-01-06)
* 优化日志统计

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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时生效)
```
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
}
188 changes: 188 additions & 0 deletions library/src/androidTest/java/com/qiniu/android/SingleFlightTest.java
Original file line number Diff line number Diff line change
@@ -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;
}

}
71 changes: 54 additions & 17 deletions library/src/main/java/com/qiniu/android/common/AutoZone.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -23,6 +24,8 @@ public final class AutoZone extends Zone {
private Map<String, ZonesInfo> zonesInfoMap = new ConcurrentHashMap<>();
private ArrayList<RequestTransaction> transactions = new ArrayList<>();

private static final SingleFlight SingleFlight = new SingleFlight();

//私有云可能改变ucServer
public void setUcServer(String ucServer) {
this.ucServer = ucServer;
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading