lookup(String hostname) throws UnknownHostException {
+ if (DnsPrefetcher.getDnsPrefetcher().getInetAddressByHost(hostname) != null) {
+ return DnsPrefetcher.getDnsPrefetcher().getInetAddressByHost(hostname);
}
- });
- }
+ return okhttp3.Dns.SYSTEM.lookup(hostname);
+ }
+ });
+
builder.networkInterceptors().add(new Interceptor() {
@Override
public okhttp3.Response intercept(Chain chain) throws IOException {
diff --git a/library/src/main/java/com/qiniu/android/http/DnsPrefetcher.java b/library/src/main/java/com/qiniu/android/http/DnsPrefetcher.java
new file mode 100644
index 000000000..f8e0294e1
--- /dev/null
+++ b/library/src/main/java/com/qiniu/android/http/DnsPrefetcher.java
@@ -0,0 +1,345 @@
+package com.qiniu.android.http;
+
+import com.qiniu.android.collect.Config;
+import com.qiniu.android.common.Constants;
+import com.qiniu.android.common.FixedZone;
+import com.qiniu.android.common.ZoneInfo;
+import com.qiniu.android.http.custom.DnsCacheKey;
+import com.qiniu.android.storage.Configuration;
+import com.qiniu.android.storage.Recorder;
+import com.qiniu.android.storage.persistent.DnsCacheFile;
+import com.qiniu.android.utils.AndroidNetwork;
+import com.qiniu.android.utils.StringUtils;
+import com.qiniu.android.utils.UrlSafeBase64;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ *
+ * Created by jemy on 2019/8/20.
+ */
+
+public class DnsPrefetcher {
+
+ public static DnsPrefetcher dnsPrefetcher = null;
+ private static String token;
+
+ private static ConcurrentHashMap> mConcurrentHashMap = new ConcurrentHashMap>();
+ private static List mHosts = new ArrayList();
+
+ private DnsPrefetcher() {
+
+ }
+
+ public static DnsPrefetcher getDnsPrefetcher() {
+ if (dnsPrefetcher == null) {
+ synchronized (DnsPrefetcher.class) {
+ if (dnsPrefetcher == null) {
+ dnsPrefetcher = new DnsPrefetcher();
+ }
+ }
+ }
+ return dnsPrefetcher;
+ }
+
+ public DnsPrefetcher init(String token) throws UnknownHostException {
+ this.token = token;
+ preHosts();
+ preFetch();
+ return this;
+ }
+
+ public void setConcurrentHashMap(ConcurrentHashMap> mConcurrentHashMap) {
+ this.mConcurrentHashMap = mConcurrentHashMap;
+ }
+
+ //use for test
+ public List getHosts() {
+ return this.mHosts;
+ }
+
+ public void setHosts(List mHosts) {
+ this.mHosts = mHosts;
+ }
+
+ //use for test
+ public ConcurrentHashMap> getConcurrentHashMap() {
+ return this.mConcurrentHashMap;
+ }
+
+ //use for test
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+ public List getInetAddressByHost(String host) {
+ return mConcurrentHashMap.get(host);
+ }
+
+ private void preHosts() {
+ HashSet set = new HashSet();
+
+ //preQuery sync
+ ZoneInfo zoneInfo = getPreQueryZone();
+ if (zoneInfo != null) {
+ for (String host : zoneInfo.upDomainsList) {
+ if (set.add(host))
+ mHosts.add(host);
+ }
+ }
+ //local
+ List listZoneinfo = getLocalZone();
+ for (ZoneInfo zone : listZoneinfo) {
+ for (String host : zone.upDomainsList) {
+ if (set.add(host))
+ mHosts.add(host);
+ }
+ }
+ if (set.add(Config.preQueryHost))
+ mHosts.add(Config.preQueryHost);
+ }
+
+
+ private void preFetch() {
+ List rePreHosts = new ArrayList();
+ for (String host : mHosts) {
+ List inetAddresses = null;
+ try {
+ inetAddresses = okhttp3.Dns.SYSTEM.lookup(host);
+ mConcurrentHashMap.put(host, inetAddresses);
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ rePreHosts.add(host);
+ }
+ }
+ rePreFetch(rePreHosts, null);
+ }
+
+ /**
+ * 对hosts预取失败对进行重新预取,deafult retryNum = 2
+ *
+ * @param rePreHosts 用于重试的hosts
+ * @param customeDns 是否自定义dns
+ */
+ private void rePreFetch(List rePreHosts, Dns customeDns) {
+ for (String host : rePreHosts) {
+ int rePreNum = 0;
+ while (rePreNum < Config.rePreHost) {
+ rePreNum += 1;
+ if (rePreFetch(host, customeDns))
+ break;
+ }
+ }
+ }
+
+ private boolean rePreFetch(String host, Dns customeDns) {
+ List inetAddresses = null;
+ try {
+ if (customeDns == null) {
+ inetAddresses = okhttp3.Dns.SYSTEM.lookup(host);
+ } else {
+ inetAddresses = customeDns.lookup(host);
+ }
+ mConcurrentHashMap.put(host, inetAddresses);
+ return true;
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * 自定义dns预取
+ *
+ * @param dns
+ * @return
+ * @throws UnknownHostException
+ */
+ public void dnsPreByCustom(Dns dns) {
+ List rePreHosts = new ArrayList();
+ for (String host : mHosts) {
+ List inetAddresses = null;
+ try {
+ inetAddresses = dns.lookup(host);
+ mConcurrentHashMap.put(host, inetAddresses);
+ } catch (UnknownHostException e) {
+ e.printStackTrace();
+ rePreHosts.add(host);
+ }
+ }
+ rePreFetch(rePreHosts, dns);
+ }
+
+ /**
+ * look local host
+ */
+ public List getLocalZone() {
+ List listZoneInfo = FixedZone.getZoneInfos();
+ return listZoneInfo;
+ }
+
+
+ /**
+ * query host sync
+ */
+ public ZoneInfo getPreQueryZone() {
+ DnsPrefetcher.ZoneIndex index = DnsPrefetcher.ZoneIndex.getFromToken(token);
+ ZoneInfo zoneInfo = preQueryIndex(index);
+ return zoneInfo;
+ }
+
+ ZoneInfo preQueryIndex(DnsPrefetcher.ZoneIndex index) {
+ ZoneInfo info = null;
+ try {
+ ResponseInfo responseInfo = getZoneJsonSync(index);
+ info = ZoneInfo.buildFromJson(responseInfo.response);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ return null;
+ }
+ return info;
+ }
+
+ ResponseInfo getZoneJsonSync(DnsPrefetcher.ZoneIndex index) {
+ Client client = new Client();
+ String address = "http://" + Config.preQueryHost + "/v2/query?ak=" + index.accessKey + "&bucket=" + index.bucket;
+ return client.syncGet(address, null);
+ }
+
+
+ static class ZoneIndex {
+ final String accessKey;
+ final String bucket;
+
+ ZoneIndex(String accessKey, String bucket) {
+ this.accessKey = accessKey;
+ this.bucket = bucket;
+ }
+
+ static DnsPrefetcher.ZoneIndex getFromToken(String token) {
+ String[] strings = token.split(":");
+ String ak = strings[0];
+ String policy = null;
+ try {
+ policy = new String(UrlSafeBase64.decode(strings[2]), Constants.UTF_8);
+ JSONObject obj = new JSONObject(policy);
+ String scope = obj.getString("scope");
+ String bkt = scope.split(":")[0];
+ return new DnsPrefetcher.ZoneIndex(ak, bkt);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public int hashCode() {
+ return accessKey.hashCode() * 37 + bucket.hashCode();
+ }
+
+ public boolean equals(Object obj) {
+ return obj == this || !(obj == null || !(obj instanceof DnsPrefetcher.ZoneIndex))
+ && ((DnsPrefetcher.ZoneIndex) obj).accessKey.equals(accessKey) && ((DnsPrefetcher.ZoneIndex) obj).bucket.equals(bucket);
+ }
+ }
+
+ /**
+ *
+ * ip changed, the network has changed
+ * ak:scope变化,prequery(v2)自动获取域名接口发生变化,存储区域可能变化
+ * cacheTime>config.cacheTime(默认24H)
+ *
+ *
+ * @return true:重新预期并缓存, false:不需要重新预取和缓存
+ */
+ public static boolean checkRePrefetchDns(String token, Configuration config) {
+ Recorder recorder = null;
+ try {
+ recorder = new DnsCacheFile(Config.dnscacheDir);
+ } catch (IOException e) {
+ e.printStackTrace();
+ return true;
+ }
+ String dnscache = recorder.getFileName();
+ if (dnscache == null)
+ return true;
+
+ byte[] data = recorder.get(dnscache);
+ if (data == null)
+ return true;
+
+ DnsCacheKey cacheKey = DnsCacheKey.toCacheKey(dnscache);
+ if (cacheKey == null)
+ return true;
+
+ String currentTime = String.valueOf(System.currentTimeMillis());
+ String localip = AndroidNetwork.getHostIP();
+ String akScope = StringUtils.getAkAndScope(token);
+
+ long cacheTime = (Long.parseLong(currentTime) - Long.parseLong(cacheKey.getCurrentTime())) / 1000;
+ if (!localip.equals(cacheKey.getLocalIp()) || cacheTime > config.dnsCacheTimeMs || !akScope.equals(cacheKey.getAkScope())) {
+ return true;
+ }
+
+ return recoverDnsCache(data);
+ }
+
+ /**
+ * start preFetchDns: Time-consuming operation, in a thread
+ *
+ * @param token
+ */
+ public static void startPrefetchDns(String token, Configuration config) {
+ String currentTime = String.valueOf(System.currentTimeMillis());
+ String localip = AndroidNetwork.getHostIP();
+ String akScope = StringUtils.getAkAndScope(token);
+ String cacheKey = new DnsCacheKey(currentTime, localip, akScope).toString();
+ Recorder recorder = null;
+ DnsPrefetcher dnsPrefetcher = null;
+ try {
+ recorder = new DnsCacheFile(Config.dnscacheDir);
+ dnsPrefetcher = DnsPrefetcher.getDnsPrefetcher().init(token);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (config.dns != null) {
+ DnsPrefetcher.getDnsPrefetcher().dnsPreByCustom(config.dns);
+ }
+ ConcurrentHashMap> concurrentHashMap = dnsPrefetcher.getConcurrentHashMap();
+ byte[] dnscache = StringUtils.toByteArray(concurrentHashMap);
+
+ recorder.set(cacheKey, dnscache);
+ }
+
+ /**
+ * @param data
+ * @return
+ */
+ public static boolean recoverDnsCache(byte[] data) {
+ ConcurrentHashMap> concurrentHashMap = (ConcurrentHashMap>) StringUtils.toObject(data);
+ if (concurrentHashMap == null) {
+ return true;
+ }
+ DnsPrefetcher.getDnsPrefetcher().setConcurrentHashMap(concurrentHashMap);
+
+ ArrayList list = new ArrayList();
+ Iterator iter = concurrentHashMap.keySet().iterator();
+ while (iter.hasNext()) {
+ String tmpkey = (String) iter.next();
+ if (tmpkey == null || tmpkey.length() == 0)
+ continue;
+ list.add(tmpkey);
+ }
+ DnsPrefetcher.getDnsPrefetcher().setHosts(list);
+ return false;
+ }
+}
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 47d738ff8..aad0e258d 100644
--- a/library/src/main/java/com/qiniu/android/http/ResponseInfo.java
+++ b/library/src/main/java/com/qiniu/android/http/ResponseInfo.java
@@ -200,7 +200,8 @@ public static ResponseInfo fileError(Exception e, final UpToken upToken) {
}
public static ResponseInfo networkError(int code, UpToken upToken) {
- return create(null, code, "", "", "", "", "", "", 80, 0, 0, "Network error during preQuery", upToken, 0);
+ return create(null, code, "", "", "", "", "", "", 80, 0, 0, "Network error during preQuery, Please check your network or " +
+ "use http try again", upToken, 0);
}
public static boolean isStatusCodeForBrokenNetwork(int code) {
diff --git a/library/src/main/java/com/qiniu/android/http/custom/DnsCacheKey.java b/library/src/main/java/com/qiniu/android/http/custom/DnsCacheKey.java
new file mode 100644
index 000000000..b148a462a
--- /dev/null
+++ b/library/src/main/java/com/qiniu/android/http/custom/DnsCacheKey.java
@@ -0,0 +1,65 @@
+package com.qiniu.android.http.custom;
+
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * Created by jemy on 2019/9/23.
+ */
+
+public class DnsCacheKey {
+ public String currentTime;
+ public String localIp;
+ public String akScope;
+
+ public DnsCacheKey() {
+
+ }
+
+ public DnsCacheKey(String currentTime, String localIp, String akScope) {
+ this.currentTime = currentTime;
+ this.localIp = localIp;
+ this.akScope = akScope;
+ }
+
+ public String getCurrentTime() {
+ return currentTime;
+ }
+
+ public String getAkScope() {
+ return akScope;
+ }
+
+ public String getLocalIp() {
+ return localIp;
+ }
+
+ public void setAkScope(String akScope) {
+ this.akScope = akScope;
+ }
+
+ public void setCurrentTime(String currentTime) {
+ this.currentTime = currentTime;
+ }
+
+ public void setLocalIp(String localIp) {
+ this.localIp = localIp;
+ }
+
+ public static DnsCacheKey toCacheKey(String key) {
+ try {
+ JSONObject object = new JSONObject(key);
+ return new DnsCacheKey(object.getString("currentTime"), object.getString("localIp"), object.getString("akScope"));
+ } catch (JSONException e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "{\"currentTime\":\"" + currentTime + "\", \"localIp\":\"" + localIp + "\", \"akScope\":\"" + akScope + "\"}";
+
+ }
+}
diff --git a/library/src/main/java/com/qiniu/android/storage/Configuration.java b/library/src/main/java/com/qiniu/android/storage/Configuration.java
index 0e8e3734b..1c65c2496 100644
--- a/library/src/main/java/com/qiniu/android/storage/Configuration.java
+++ b/library/src/main/java/com/qiniu/android/storage/Configuration.java
@@ -65,6 +65,11 @@ public final class Configuration {
*/
public boolean useHttps;
+ /**
+ * dns预取缓存时间
+ */
+ public long dnsCacheTimeMs;
+
private Configuration(Builder builder) {
useHttps = builder.useHttps;
@@ -82,9 +87,14 @@ private Configuration(Builder builder) {
proxy = builder.proxy;
+ dnsCacheTimeMs = builder.dnsCacheTimeMs;
+
urlConverter = builder.urlConverter;
+ AutoZone autoZone = null;
- zone = builder.zone == null ? AutoZone.autoZone : builder.zone;
+ autoZone = new AutoZone(builder.useHttps);
+
+ zone = builder.zone == null ? autoZone : builder.zone;
dns = builder.dns;
}
@@ -114,6 +124,7 @@ public static class Builder {
private int retryMax = 3;
private UrlConverter urlConverter = null;
private Dns dns = null;
+ private long dnsCacheTimeMs = 60 * 60 * 24 * 1000;
public Builder zone(Zone zone) {
this.zone = zone;
@@ -176,6 +187,11 @@ public Builder useHttps(boolean useHttps) {
return this;
}
+ public Builder dnsCacheTimeMs(long dnsCacheTimeMs) {
+ this.dnsCacheTimeMs = dnsCacheTimeMs;
+ return this;
+ }
+
public Configuration build() {
return new Configuration(this);
}
diff --git a/library/src/main/java/com/qiniu/android/storage/Recorder.java b/library/src/main/java/com/qiniu/android/storage/Recorder.java
index 493a708cb..9bd218cb2 100644
--- a/library/src/main/java/com/qiniu/android/storage/Recorder.java
+++ b/library/src/main/java/com/qiniu/android/storage/Recorder.java
@@ -27,4 +27,6 @@ public interface Recorder {
* @param key 持久化的键
*/
void del(String key);
+
+ String getFileName();
}
diff --git a/library/src/main/java/com/qiniu/android/storage/ResumeUploaderFast.java b/library/src/main/java/com/qiniu/android/storage/ResumeUploaderFast.java
new file mode 100644
index 000000000..40108f04c
--- /dev/null
+++ b/library/src/main/java/com/qiniu/android/storage/ResumeUploaderFast.java
@@ -0,0 +1,564 @@
+package com.qiniu.android.storage;
+
+import com.qiniu.android.http.Client;
+import com.qiniu.android.http.CompletionHandler;
+import com.qiniu.android.http.ProgressHandler;
+import com.qiniu.android.http.ResponseInfo;
+import com.qiniu.android.utils.AndroidNetwork;
+import com.qiniu.android.utils.Crc32;
+import com.qiniu.android.utils.StringMap;
+import com.qiniu.android.utils.StringUtils;
+import com.qiniu.android.utils.UrlSafeBase64;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static java.lang.String.format;
+
+/**
+ * Created by jemy on 2019/7/9.
+ */
+
+public class ResumeUploaderFast implements Runnable {
+
+ /**
+ * 文件总大小
+ */
+ private final long totalSize;
+
+ private final String key;
+
+ private final UpCompletionHandler completionHandler;
+
+ private final UploadOptions options;
+
+ private final Client client;
+
+ private final Configuration config;
+ /**
+ * 保存每一块上传返回的ctx
+ */
+ private final String[] contexts;
+ /**
+ * headers
+ */
+ private final StringMap headers;
+ /**
+ * 修改时间
+ */
+ private final long modifyTime;
+ /**
+ * 断点续传记录文件
+ */
+ private final String recorderKey;
+ /**
+ * 文件对象
+ */
+ private RandomAccessFile file;
+ /**
+ * 待上传文件
+ */
+ private File f;
+ /**
+ * 上传token
+ */
+ private UpToken token;
+ /**
+ * 分块数据
+ */
+ private Map blockInfo;
+ /**
+ * 上传域名
+ */
+ AtomicReference upHost = new AtomicReference();
+ /**
+ * 总块数
+ */
+ AtomicInteger tblock;
+ /**
+ * 重传域名数
+ */
+ AtomicInteger retried = new AtomicInteger(0);
+ /**
+ * 单域名检测次数
+ */
+ AtomicInteger singleDomainRetry = new AtomicInteger(0);
+ /**
+ * 线程数量
+ */
+ private int multithread;
+ /**
+ * 第一个任务
+ */
+ private boolean isFirstTask = true;
+ /**
+ * 每块偏移位子
+ * use 断点续传
+ */
+ private Long[] offsets;
+ /**
+ * 已上传块
+ */
+ private int upBlock = 0;
+ private final int domainRetry = 3;
+ /**
+ * 避免多个任务同时回调
+ */
+ private boolean isInterrupted = false;
+
+ ResumeUploaderFast(Client client, Configuration config, File f, String key, UpToken token,
+ final UpCompletionHandler completionHandler, UploadOptions options, String recorderKey, int multithread) {
+ this.client = client;
+ this.config = config;
+ this.f = f;
+ this.recorderKey = recorderKey;
+ this.totalSize = f.length();
+ this.key = key;
+ this.headers = new StringMap().put("Authorization", "UpToken " + token.token);
+ this.file = null;
+ this.multithread = multithread;
+ this.completionHandler = new UpCompletionHandler() {
+ @Override
+ public void complete(String key, ResponseInfo info, JSONObject response) {
+ if (file != null) {
+ try {
+ file.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ //第一个回调出来之后通知其他线程停止这个事件的回调
+ synchronized (this) {
+ if (!isInterrupted) {
+ isInterrupted = true;
+ completionHandler.complete(key, info, response);
+ } else {
+ return;
+ }
+ }
+ }
+ };
+ this.options = options != null ? options : UploadOptions.defaultOptions();
+ tblock = new AtomicInteger((int) (totalSize + Configuration.BLOCK_SIZE - 1) / Configuration.BLOCK_SIZE);
+ this.offsets = new Long[tblock.get()];
+ contexts = new String[tblock.get()];
+ modifyTime = f.lastModified();
+ this.token = token;
+ this.blockInfo = new LinkedHashMap<>();
+ }
+
+ @Override
+ public void run() {
+ try {
+ file = new RandomAccessFile(f, "r");
+ } catch (FileNotFoundException e) {
+ e.printStackTrace();
+ completionHandler.complete(key, ResponseInfo.fileError(e, token), null);
+ return;
+ }
+ putBlockInfo();
+
+ upHost.set(config.zone.upHost(token.token, config.useHttps, null));
+ if (blockInfo.size() < multithread) {
+ multithread = blockInfo.size();
+ }
+ for (int i = 0; i < multithread; i++) {
+ BlockElement mblock = getBlockInfo();
+ new UploadThread(mblock.getOffset(), mblock.getBlocksize(), upHost.get().toString()).start();
+ }
+ }
+
+
+ /**
+ * cut file to blockInfo
+ */
+ private void putBlockInfo() {
+ Long[] offs = recoveryFromRecord();
+ int lastBlock = tblock.get() - 1;
+ if (offs == null) {
+ for (int i = 0; i < lastBlock; i++) {
+ blockInfo.put((long) i * Configuration.BLOCK_SIZE, Configuration.BLOCK_SIZE);
+ }
+ blockInfo.put((long) lastBlock * Configuration.BLOCK_SIZE, (int) (totalSize - lastBlock * Configuration.BLOCK_SIZE));
+ } else {
+ HashSet set = new HashSet(Arrays.asList(offs));
+ for (int i = 0; i < lastBlock; i++) {
+ Long offset = (long) i * Configuration.BLOCK_SIZE;
+ if (!set.contains(offset)) {
+ blockInfo.put(offset, Configuration.BLOCK_SIZE);
+ } else {
+ offsets[i] = offset;
+ upBlock += 1;
+ }
+ }
+ Long offset = (long) lastBlock * Configuration.BLOCK_SIZE;
+ if (!set.contains(offset)) {
+ blockInfo.put(offset, (int) (totalSize - lastBlock * Configuration.BLOCK_SIZE));
+ } else {
+ offsets[lastBlock] = offset;
+ upBlock += 1;
+ }
+ }
+ }
+
+ /**
+ * get next block use to upload
+ *
+ * @return BlockElement
+ */
+ private synchronized BlockElement getBlockInfo() {
+ Iterator> it = blockInfo.entrySet().iterator();
+ long offset = 0;
+ int blockSize = 0;
+ if (it.hasNext()) {
+ Map.Entry entry = it.next();
+ offset = entry.getKey();
+ blockSize = entry.getValue();
+ blockInfo.remove(offset);
+ }
+ return new BlockElement(offset, blockSize);
+ }
+
+ class UploadThread extends Thread {
+ private long offset;
+ private int blockSize;
+ private String upHost;
+
+ UploadThread(long offset, int blockSize, String upHost) {
+ this.offset = offset;
+ this.blockSize = blockSize;
+ this.upHost = upHost;
+ }
+
+ @Override
+ public void run() {
+ super.run();
+ mkblk(offset, blockSize, upHost);
+ }
+ }
+
+ /**
+ * 创建块,并上传内容
+ *
+ * @param upHost 上传主机
+ * @param offset 本地文件偏移量
+ * @param blockSize 分块的块大小
+ */
+ private void mkblk(long offset, int blockSize, String upHost) {
+ String path = format(Locale.ENGLISH, "/mkblk/%d", blockSize);
+ byte[] chunkBuffer = new byte[blockSize];
+ synchronized (this) {
+ try {
+ //多线程时file.read可能会在seek时被篡改,导致上传buffer紊乱
+ file.seek(offset);
+ file.read(chunkBuffer, 0, blockSize);
+ } catch (IOException e) {
+ completionHandler.complete(key, ResponseInfo.fileError(e, token), null);
+ return;
+ }
+ }
+
+ long crc32 = Crc32.bytes(chunkBuffer, 0, blockSize);
+ String postUrl = String.format("%s%s", upHost, path);
+
+ post(postUrl, chunkBuffer, 0, blockSize, getProgressHandler(),
+ getCompletionHandler(offset, blockSize, crc32), options.cancellationSignal);
+ }
+
+ private void makeFile(String upHost, CompletionHandler _completionHandler, UpCancellationSignal c) {
+ String mime = format(Locale.ENGLISH, "/mimeType/%s/fname/%s",
+ UrlSafeBase64.encodeToString(options.mimeType), UrlSafeBase64.encodeToString(f.getName()));
+
+ String keyStr = "";
+ if (key != null) {
+ keyStr = format("/key/%s", UrlSafeBase64.encodeToString(key));
+ }
+
+ String paramStr = "";
+ if (options.params.size() != 0) {
+ String str[] = new String[options.params.size()];
+ int j = 0;
+ for (Map.Entry i : options.params.entrySet()) {
+ str[j++] = format(Locale.ENGLISH, "%s/%s", i.getKey(), UrlSafeBase64.encodeToString(i.getValue()));
+ }
+ paramStr = "/" + StringUtils.join(str, "/");
+ }
+ String path = format(Locale.ENGLISH, "/mkfile/%d%s%s%s", totalSize, mime, keyStr, paramStr);
+ String bodyStr = StringUtils.join(contexts, ",");
+ byte[] data = bodyStr.getBytes();
+ String postUrl = String.format("%s%s", upHost, path);
+ post(postUrl, data, 0, data.length, null, _completionHandler, c);
+ }
+
+ private void post(String upHost, byte[] data, int offset, int dataSize, ProgressHandler progress,
+ CompletionHandler completion, UpCancellationSignal c) {
+ client.asyncPost(upHost, data, offset, dataSize, headers, token, totalSize, progress, completion, c);
+ }
+
+ private ProgressHandler getProgressHandler() {
+ return new ProgressHandler() {
+ @Override
+ public void onProgress(long bytesWritten, long totalSize) {
+ long size = 0;
+ for (Long offset : offsets) {
+ if (offset != null && offset > 0) {
+ size += 1;
+ }
+ }
+ double percent = (double) size * Configuration.BLOCK_SIZE / totalSize;
+ if (percent > 0.95) {
+ percent = 0.95;
+ }
+ options.progressHandler.progress(key, percent);
+ }
+ };
+
+ }
+
+ private CompletionHandler getMkfileCompletionHandler() {
+ return new CompletionHandler() {
+ @Override
+ public void complete(ResponseInfo info, JSONObject response) {
+ if (info.isNetworkBroken() && !AndroidNetwork.isNetWorkReady()) {
+ options.netReadyHandler.waitReady();
+ if (!AndroidNetwork.isNetWorkReady()) {
+ completionHandler.complete(key, info, response);
+ return;
+ }
+ }
+
+ if (info.isOK()) {
+ removeRecord();
+ options.progressHandler.progress(key, 1.0);
+ completionHandler.complete(key, info, response);
+ return;
+ }
+
+ // mkfile ,允许多重试一次,这里不需要重试时,成功与否都complete回调给客户端
+ if (info.needRetry() && retried.get() < config.retryMax + 1) {
+ makeFile(upHost.get().toString(), getMkfileCompletionHandler(), options.cancellationSignal);
+ retried.addAndGet(1);
+ return;
+ }
+ completionHandler.complete(key, info, response);
+ }
+ };
+ }
+
+
+ private CompletionHandler getCompletionHandler(final long offset, final int blockSize, final long crc32) {
+ return new CompletionHandler() {
+ @Override
+ public void complete(ResponseInfo info, JSONObject response) {
+ //网络断开或者无状态,检查3s,直接返回
+ if (info.isNetworkBroken() && !AndroidNetwork.isNetWorkReady()) {
+ options.netReadyHandler.waitReady();
+ if (!AndroidNetwork.isNetWorkReady()) {
+ completionHandler.complete(key, info, response);
+ return;
+ }
+ }
+
+ //取消
+ if (info.isCancelled()) {
+ completionHandler.complete(key, info, response);
+ return;
+ }
+
+ //上传失败:重试
+ //701: ctx不正确或者已经过期
+ //checkRetried之后应该立即updateRetried,否则每个线程进来checkRetried判定都是跟第一个一样
+ if (!isChunkOK(info, response)) {
+ if (info.statusCode == 701 && checkRetried()) {
+ updateRetried();
+ mkblk(offset, blockSize, upHost.get().toString());
+ return;
+ }
+ if (upHost != null
+ && ((isNotChunkToQiniu(info, response) || info.needRetry())
+ && checkRetried())) {
+ updateRetried();
+ mkblk(offset, blockSize, upHost.get().toString());
+ return;
+ }
+
+ completionHandler.complete(key, info, response);
+ return;
+ }
+
+ //上传成功(伪成功):检查response
+ String context = null;
+ if (response == null && checkRetried()) {
+ updateRetried();
+ mkblk(offset, blockSize, upHost.get().toString());
+ return;
+ }
+ long crc = 0;
+ Exception tempE = null;
+ try {
+ context = response.getString("ctx");
+ crc = response.getLong("crc32");
+ } catch (Exception e) {
+ tempE = e;
+ e.printStackTrace();
+ }
+ if ((context == null || crc != crc32) && checkRetried()) {
+ updateRetried();
+ mkblk(offset, blockSize, upHost.get().toString());
+ return;
+ }
+ if (context == null) {
+ String error = "get context failed.";
+ if (tempE != null) {
+ error += "\n";
+ error += tempE.getMessage();
+ }
+ ResponseInfo info2 = ResponseInfo.errorInfo(info, ResponseInfo.UnknownError, error);
+ completionHandler.complete(key, info2, response);
+ return;
+ }
+ if (crc != crc32) {
+ String error = "block's crc32 is not match. local: " + crc32 + ", remote: " + crc;
+ ResponseInfo info2 = ResponseInfo.errorInfo(info, ResponseInfo.Crc32NotMatch, error);
+ completionHandler.complete(key, info2, response);
+ return;
+ }
+
+ synchronized (this) {
+ contexts[(int) (offset / Configuration.BLOCK_SIZE)] = context;
+ offsets[(int) (offset / Configuration.BLOCK_SIZE)] = offset;
+ record(offsets);
+ upBlock += 1;
+ if (upBlock == tblock.get()) {
+ makeFile(upHost.get().toString(), getMkfileCompletionHandler(), options.cancellationSignal);
+ return;
+ }
+ }
+
+ if (blockInfo.size() > 0) {
+ BlockElement mblock = getBlockInfo();
+ if (mblock.getOffset() != 0 && mblock.getBlocksize() != 0)
+ new UploadThread(mblock.getOffset(), mblock.getBlocksize(), upHost.get().toString()).start();
+ }
+ }
+ };
+ }
+
+ private synchronized void updateRetried() {
+ if (singleDomainRetry.get() < config.retryMax) {
+ singleDomainRetry.getAndAdd(1);
+ } else if (retried.get() < domainRetry) {
+ singleDomainRetry.getAndSet(1);
+ retried.getAndAdd(1);
+ upHost.getAndSet(config.zone.upHost(token.token, config.useHttps, upHost.get().toString()));
+ }
+
+ }
+
+ private boolean checkRetried() {
+ return retried.get() < domainRetry;
+ }
+
+ private boolean isChunkOK(ResponseInfo info, JSONObject response) {
+ return info.statusCode == 200 && info.error == null && (info.hasReqId() || isChunkResOK(response));
+ }
+
+ private boolean isChunkResOK(JSONObject response) {
+ try {
+ // getXxxx 若获取不到值,会抛出异常
+ response.getString("ctx");
+ response.getLong("crc32");
+ } catch (Exception e) {
+ return false;
+ }
+ return true;
+ }
+
+ private boolean isNotChunkToQiniu(ResponseInfo info, JSONObject response) {
+ return info.statusCode < 500 && info.statusCode >= 200 && (!info.hasReqId() && !isChunkResOK(response));
+ }
+
+ private Long[] recoveryFromRecord() {
+
+ if (config.recorder == null) {
+ return null;
+ }
+ byte[] data = config.recorder.get(recorderKey);
+ if (data == null) {
+ return null;
+ }
+ String jsonStr = new String(data);
+ JSONObject obj;
+ try {
+ obj = new JSONObject(jsonStr);
+ } catch (JSONException e) {
+ e.printStackTrace();
+ return null;
+ }
+ JSONArray offsetsArray = obj.optJSONArray("offsets");
+ long modify = obj.optLong("modify_time", 0);
+ long fSize = obj.optLong("size", 0);
+ JSONArray array = obj.optJSONArray("contexts");
+ if (offsetsArray.length() == 0 || modify != modifyTime || fSize != totalSize || array == null || array.length() == 0) {
+ return null;
+ }
+ for (int i = 0; i < array.length(); i++) {
+ contexts[i] = array.optString(i);
+ }
+ for (int i = 0; i < array.length(); i++) {
+ String offset = offsetsArray.optString(i);
+ if (offset != null && !offset.equals("null")) {
+ offsets[i] = Long.parseLong(offset);
+ }
+ }
+ return offsets;
+ }
+
+ private void removeRecord() {
+ if (config.recorder != null) {
+ config.recorder.del(recorderKey);
+ }
+ }
+
+ private void record(Long[] offsets) {
+ if (config.recorder == null || offsets.length == 0) {
+ return;
+ }
+ String data = format(Locale.ENGLISH, "{\"size\":%d,\"offsets\":[%s], \"modify_time\":%d, \"contexts\":[%s]}",
+ totalSize, StringUtils.jsonJoin(offsets), modifyTime, StringUtils.jsonJoin(contexts));
+ config.recorder.set(recorderKey, data.getBytes());
+ }
+
+ class BlockElement {
+ private long offset;
+ private int blocksize;
+
+ BlockElement(long offset, int blocksize) {
+ this.offset = offset;
+ this.blocksize = blocksize;
+ }
+
+ public long getOffset() {
+ return offset;
+ }
+
+ public int getBlocksize() {
+ return blocksize;
+ }
+ }
+
+}
diff --git a/library/src/main/java/com/qiniu/android/storage/UploadManager.java b/library/src/main/java/com/qiniu/android/storage/UploadManager.java
index 517b4c678..78b6f0da9 100644
--- a/library/src/main/java/com/qiniu/android/storage/UploadManager.java
+++ b/library/src/main/java/com/qiniu/android/storage/UploadManager.java
@@ -4,6 +4,7 @@
import com.qiniu.android.collect.UploadInfoCollector;
import com.qiniu.android.common.Zone;
import com.qiniu.android.http.Client;
+import com.qiniu.android.http.DnsPrefetcher;
import com.qiniu.android.http.ResponseInfo;
import com.qiniu.android.utils.AsyncRun;
import com.qiniu.android.utils.StringUtils;
@@ -11,6 +12,7 @@
import org.json.JSONObject;
import java.io.File;
+import java.util.concurrent.atomic.AtomicBoolean;
/**
* 七牛文件上传管理器
@@ -21,25 +23,52 @@
public final class UploadManager {
private final Configuration config;
private final Client client;
+ private int multithreads = 1;
+ private static int DEF_THREAD_NUM = 3;
+ /**
+ * 保证代码只执行一次,防止多个uploadManager同时开始预取dns
+ */
+ static AtomicBoolean atomicStruct = new AtomicBoolean(false);
+ /**
+ * default 3 Threads
+ */
public UploadManager() {
- this(new Configuration.Builder().build());
+ this(new Configuration.Builder().build(), DEF_THREAD_NUM);
}
+ /**
+ * @param config Configuration, default 1 Thread
+ */
public UploadManager(Configuration config) {
this.config = config;
this.client = new Client(config.proxy, config.connectTimeout, config.responseTimeout,
config.urlConverter, config.dns);
}
- public UploadManager(Recorder recorder, KeyGenerator keyGen) {
- this(new Configuration.Builder().recorder(recorder, keyGen).build());
+ public UploadManager(Configuration config, int multitread) {
+ this.config = config;
+ this.multithreads = multitread >= 1 ? multitread : DEF_THREAD_NUM;
+ this.client = new Client(config.proxy, config.connectTimeout, config.responseTimeout,
+ config.urlConverter, config.dns);
}
public UploadManager(Recorder recorder) {
this(recorder, null);
}
+ public UploadManager(Recorder recorder, KeyGenerator keyGen) {
+ this(new Configuration.Builder().recorder(recorder, keyGen).build());
+ }
+
+ public UploadManager(Recorder recorder, int multitread) {
+ this(recorder, null, multitread);
+ }
+
+ public UploadManager(Recorder recorder, KeyGenerator keyGen, int multitread) {
+ this(new Configuration.Builder().recorder(recorder, keyGen).build(), multitread);
+ }
+
private static boolean areInvalidArg(final String key, byte[] data, File f, String token,
UpToken decodedToken, final UpCompletionHandler complete) {
if (complete == null) {
@@ -113,6 +142,16 @@ public void put(final byte[] data, final String key, final String token,
return;
}
+ if (atomicStruct.compareAndSet(false, true)) {
+ if (DnsPrefetcher.checkRePrefetchDns(token, config)) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ DnsPrefetcher.startPrefetchDns(token, config);
+ }
+ }).start();
+ }
+ }
Zone z = config.zone;
z.preQuery(token, new Zone.QueryHandler() {
@Override
@@ -145,6 +184,7 @@ public void put(String filePath, String key, String token, UpCompletionHandler c
put(new File(filePath), key, token, completionHandler, options);
}
+
/**
* 上传文件
*
@@ -154,13 +194,23 @@ public void put(String filePath, String key, String token, UpCompletionHandler c
* @param complete 上传完成的后续处理动作
* @param options 上传数据的可选参数
*/
- public void put(final File file, final String key, String token, final UpCompletionHandler complete,
+ public void put(final File file, final String key, final String token, final UpCompletionHandler complete,
final UploadOptions options) {
final UpToken decodedToken = UpToken.parse(token);
if (areInvalidArg(key, null, file, token, decodedToken, complete)) {
return;
}
+ if (atomicStruct.compareAndSet(false, true)) {
+ if (DnsPrefetcher.checkRePrefetchDns(token, config)) {
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ DnsPrefetcher.startPrefetchDns(token, config);
+ }
+ }).start();
+ }
+ }
Zone z = config.zone;
z.preQuery(token, new Zone.QueryHandler() {
@Override
@@ -172,10 +222,15 @@ public void onSuccess() {
}
String recorderKey = config.keyGen.gen(key, file);
final WarpHandler completionHandler = warpHandler(complete, file != null ? file.length() : 0);
- ResumeUploader uploader = new ResumeUploader(client, config, file, key,
- decodedToken, completionHandler, options, recorderKey);
-
- AsyncRun.runInMain(uploader);
+ if (multithreads == 1) {
+ ResumeUploader uploader = new ResumeUploader(client, config, file, key,
+ decodedToken, completionHandler, options, recorderKey);
+ AsyncRun.runInMain(uploader);
+ } else {
+ ResumeUploaderFast uploader = new ResumeUploaderFast(client, config, file, key,
+ decodedToken, completionHandler, options, recorderKey, multithreads);
+ AsyncRun.runInMain(uploader);
+ }
}
@Override
@@ -279,4 +334,5 @@ public void run() {
}
}
+
}
diff --git a/library/src/main/java/com/qiniu/android/storage/persistent/DnsCacheFile.java b/library/src/main/java/com/qiniu/android/storage/persistent/DnsCacheFile.java
new file mode 100644
index 000000000..710f4a308
--- /dev/null
+++ b/library/src/main/java/com/qiniu/android/storage/persistent/DnsCacheFile.java
@@ -0,0 +1,127 @@
+package com.qiniu.android.storage.persistent;
+
+import com.qiniu.android.storage.Recorder;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+/**
+ * Created by jemy on 2019/9/17.
+ */
+
+public class DnsCacheFile implements Recorder {
+
+ public String directory;
+
+ public DnsCacheFile(String directory) throws IOException {
+ this.directory = directory;
+ File f = new File(directory);
+ if (!f.exists()) {
+ boolean r = f.mkdirs();
+ if (!r) {
+ throw new IOException("mkdir failed");
+ }
+ return;
+ }
+ if (!f.isDirectory()) {
+ throw new IOException("does not mkdir");
+ }
+ }
+
+ /**
+ * 设置DNS缓存
+ *
+ * @param key 缓存文件明
+ * @param data 缓存数据
+ */
+ @Override
+ public void set(String key, byte[] data) {
+ File file = new File(directory);
+ File[] fs = file.listFiles();
+ if (fs.length > 0) {
+ for (int i = 0; i < fs.length; i++) {
+ del(fs[i].getName());
+ }
+ }
+
+ File f = new File(directory, key);
+ FileOutputStream fo = null;
+ try {
+ fo = new FileOutputStream(f);
+ fo.write(data);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (fo != null) {
+ try {
+ fo.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * 获取缓存
+ *
+ * @param key 缓存文件名
+ */
+ @Override
+ public byte[] get(String key) {
+ File f = new File(directory, key);
+ FileInputStream fi = null;
+ byte[] data = null;
+ int read = 0;
+ try {
+ data = new byte[(int) f.length()];
+ fi = new FileInputStream(f);
+ read = fi.read(data);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ if (fi != null) {
+ try {
+ fi.close();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ if (read == 0) {
+ return null;
+ }
+ return data;
+ }
+
+ //f.delete()=false时才会有fs.length>1的情况
+ public String getFileName() {
+ File file = new File(directory);
+ File[] fs = file.listFiles();
+ if (fs.length == 1) {
+ return fs[0].getName();
+ } else if (fs.length > 1) {
+ String fileName = null;
+ long cachetime = 0;
+ for (int i = 1; i < fs.length; i++) {
+ String key = fs[i].getName();
+ long time = Long.parseLong(key.split(":")[0]);
+ if (time > cachetime) {
+ del(fileName);
+ cachetime = time;
+ fileName = key;
+ }
+ }
+ return fileName;
+ }
+ return null;
+ }
+
+ @Override
+ public void del(String key) {
+ if (key != null) {
+ File f = new File(directory, key);
+ f.delete();
+ }
+ }
+}
diff --git a/library/src/main/java/com/qiniu/android/storage/persistent/FileRecorder.java b/library/src/main/java/com/qiniu/android/storage/persistent/FileRecorder.java
index 8cc99dfde..6d91e8ae3 100644
--- a/library/src/main/java/com/qiniu/android/storage/persistent/FileRecorder.java
+++ b/library/src/main/java/com/qiniu/android/storage/persistent/FileRecorder.java
@@ -121,4 +121,9 @@ public void del(String key) {
File f = new File(directory, hash(key));
f.delete();
}
-}
+
+ @Override
+ public String getFileName() {
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/library/src/main/java/com/qiniu/android/utils/AndroidNetwork.java b/library/src/main/java/com/qiniu/android/utils/AndroidNetwork.java
index c920070ae..511669877 100644
--- a/library/src/main/java/com/qiniu/android/utils/AndroidNetwork.java
+++ b/library/src/main/java/com/qiniu/android/utils/AndroidNetwork.java
@@ -4,6 +4,12 @@
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
/**
* Created by bailong on 16/9/7.
*/
@@ -22,4 +28,35 @@ public static boolean isNetWorkReady() {
return true;
}
}
+
+ /**
+ * 获取ip地址
+ * 使用DNS解析某地址时,可能会同时返回IPv4和IPv6的地址。
+ * 如果同时拥有IPv4和IPv6的地址,是会默认优先上报IPv6的地址
+ *
+ * @return
+ */
+ public static String getHostIP() {
+ String hostIp = null;
+ try {
+ Enumeration nis = NetworkInterface.getNetworkInterfaces();
+ InetAddress ia = null;
+ while (nis.hasMoreElements()) {
+ NetworkInterface ni = (NetworkInterface) nis.nextElement();
+ Enumeration ias = ni.getInetAddresses();
+ while (ias.hasMoreElements()) {
+ ia = ias.nextElement();
+ if (!ia.isLinkLocalAddress() && !ia.isLoopbackAddress()) {
+ hostIp = ia.getHostAddress();
+ break;
+ }
+ continue;
+ }
+ }
+ } catch (SocketException e) {
+ e.printStackTrace();
+ }
+ return hostIp;
+ }
+
}
diff --git a/library/src/main/java/com/qiniu/android/utils/StringUtils.java b/library/src/main/java/com/qiniu/android/utils/StringUtils.java
index 2b4f57ba7..1441e3e45 100644
--- a/library/src/main/java/com/qiniu/android/utils/StringUtils.java
+++ b/library/src/main/java/com/qiniu/android/utils/StringUtils.java
@@ -1,7 +1,17 @@
package com.qiniu.android.utils;
+import android.util.Log;
+
import com.qiniu.android.common.Constants;
+import com.qiniu.android.http.DnsPrefetcher;
+
+import org.json.JSONObject;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
import java.io.UnsupportedEncodingException;
/**
@@ -69,6 +79,9 @@ public static String join(String[] array, String sep) {
*/
public static String jsonJoin(String[] array) {
int arraySize = array.length;
+ if (array[0] == null) {
+ array[0] = "";
+ }
int bufSize = arraySize * (array[0].length() + 3);
StringBuilder buf = new StringBuilder(bufSize);
for (int i = 0; i < arraySize; i++) {
@@ -83,6 +96,25 @@ public static String jsonJoin(String[] array) {
return buf.toString();
}
+
+ public static String jsonJoin(Long[] array) {
+ return jsonJoin(longToString(array));
+ }
+
+ public static String[] longToString(Long longArray[]) {
+ String stringArray[] = new String[longArray.length];
+ for (int i = 0; i < stringArray.length; i++) {
+ try {
+ stringArray[i] = String.valueOf(longArray[i]);
+ } catch (NumberFormatException e) {
+ stringArray[i] = "null";
+ continue;
+ }
+ }
+ return stringArray;
+
+ }
+
public static byte[] utf8Bytes(String data) {
try {
return data.getBytes(Constants.UTF_8);
@@ -109,4 +141,65 @@ public static String strip(String s) {
}
return b.toString();
}
+
+ /**
+ * 对象转数组
+ *
+ * @param obj
+ * @return
+ */
+ public static byte[] toByteArray(Object obj) {
+ byte[] bytes = null;
+ ByteArrayOutputStream bos = new ByteArrayOutputStream();
+ try {
+ ObjectOutputStream oos = new ObjectOutputStream(bos);
+ oos.writeObject(obj);
+ oos.flush();
+ bytes = bos.toByteArray();
+ oos.close();
+ bos.close();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ }
+ return bytes;
+ }
+
+ /**
+ * 数组转对象
+ *
+ * @param bytes
+ * @return
+ */
+ public static Object toObject(byte[] bytes) {
+ Object obj = null;
+ try {
+ ByteArrayInputStream bis = new ByteArrayInputStream(bytes);
+ ObjectInputStream ois = new ObjectInputStream(bis);
+ obj = ois.readObject();
+ ois.close();
+ bis.close();
+ } catch (IOException ex) {
+ ex.printStackTrace();
+ } catch (ClassNotFoundException ex) {
+ ex.printStackTrace();
+ }
+ return obj;
+ }
+
+ public static String getAkAndScope(String token) {
+ String[] strings = token.split(":");
+ String ak = strings[0];
+ String policy = null;
+ try {
+ policy = new String(UrlSafeBase64.decode(strings[2]), Constants.UTF_8);
+ JSONObject obj = new JSONObject(policy);
+ String scope = obj.getString("scope");
+ String bkt = scope.split(":")[0];
+ return ak + bkt;
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
}