From 7df84b13c87686a6fe54e0d3e3115a27f2c28570 Mon Sep 17 00:00:00 2001 From: Cheney Date: Tue, 12 Nov 2013 10:46:54 +0800 Subject: [PATCH 01/34] fix ResumableIO: ignore other exception when upload failure --- src/com/qiniu/io/IO.java | 10 ++++++- .../qiniu/resumableio/ResumableClient.java | 14 +++++++-- src/com/qiniu/resumableio/ResumableIO.java | 6 +++- src/com/qiniu/utils/InputStreamAt.java | 29 ++++++++----------- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/src/com/qiniu/io/IO.java b/src/com/qiniu/io/IO.java index 134fe694c..631df9011 100644 --- a/src/com/qiniu/io/IO.java +++ b/src/com/qiniu/io/IO.java @@ -12,6 +12,7 @@ import org.json.JSONObject; import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.util.Map; @@ -44,7 +45,14 @@ private static Client defaultClient() { public void put(String key, InputStreamAt isa, PutExtra extra, JSONObjectRet ret) { MultipartEntity m = new MultipartEntity(); if (key != null) m.addField("key", key); - if (extra.checkCrc == PutExtra.AUTO_CRC32) extra.crc32 = isa.crc32(); + if (extra.checkCrc == PutExtra.AUTO_CRC32) { + try { + extra.crc32 = isa.crc32(); + } catch (IOException e) { + ret.onFailure(e); + return; + } + } if (extra.checkCrc != PutExtra.UNUSE_CRC32) m.addField("crc32", extra.crc32 + ""); for (Map.Entry i: extra.params.entrySet()) m.addField(i.getKey(), i.getValue()); diff --git a/src/com/qiniu/resumableio/ResumableClient.java b/src/com/qiniu/resumableio/ResumableClient.java index 20cad3c77..688f9d0d2 100644 --- a/src/com/qiniu/resumableio/ResumableClient.java +++ b/src/com/qiniu/resumableio/ResumableClient.java @@ -47,14 +47,24 @@ public void onInit(int flag) { public void putInit() { int chunkSize = Math.min(writeNeed, CHUNK_SIZE); - crc32 = input.getCrc32(offset, chunkSize); + try { + crc32 = input.getCrc32(offset, chunkSize); + } catch (IOException e) { + onFailure(e); + return; + } canceler[0] = mkblk(input, offset, writeNeed, chunkSize, this); } public void putNext() { wrote = putRet.offset; int remainLength = Math.min((int) (input.length() - offset - putRet.offset), CHUNK_SIZE); - crc32 = input.getCrc32(offset+putRet.offset, remainLength); + try { + crc32 = input.getCrc32(offset+putRet.offset, remainLength); + } catch (IOException e) { + onFailure(e); + return; + } canceler[0] = bput(putRet.host, input, putRet.ctx, offset, putRet.offset, remainLength, this); } diff --git a/src/com/qiniu/resumableio/ResumableIO.java b/src/com/qiniu/resumableio/ResumableIO.java index 67ca5eec8..e9564e840 100644 --- a/src/com/qiniu/resumableio/ResumableIO.java +++ b/src/com/qiniu/resumableio/ResumableIO.java @@ -36,7 +36,7 @@ private synchronized void removeTask(Integer id) { idCancels.remove(id); } - public int putAndClose(final String key, final InputStreamAt input, final PutExtra extra, final JSONObjectRet ret) { + private int putAndClose(final String key, final InputStreamAt input, final PutExtra extra, final JSONObjectRet ret) { return put(key, input, extra, new JSONObjectRet() { @Override public void onSuccess(JSONObject obj) { @@ -121,6 +121,10 @@ public void onProcess(long current, long total) { @Override public void onFailure(Exception ex) { + if (failure[0]) { + ex.printStackTrace(); + return; + } if (--retryTime <= 0 || (ex.getMessage() != null && ex.getMessage().contains("Unauthorized"))) { removeTask(taskId); failure[0] = true; diff --git a/src/com/qiniu/utils/InputStreamAt.java b/src/com/qiniu/utils/InputStreamAt.java index ddac6314c..9e03821ce 100644 --- a/src/com/qiniu/utils/InputStreamAt.java +++ b/src/com/qiniu/utils/InputStreamAt.java @@ -58,14 +58,14 @@ public InputStreamAt(byte[] data) { mData = data; } - public long getCrc32(long offset, int length) { + public long getCrc32(long offset, int length) throws IOException { CRC32 crc32 = new CRC32(); byte[] data = read(offset, length); crc32.update(data); return crc32.getValue(); } - public long crc32() { + public long crc32() throws IOException { if (mCrc32 >= 0) return mCrc32; CRC32 crc32 = new CRC32(); long index = 0; @@ -120,22 +120,17 @@ protected static File storeToFile(Context context, InputStream is) { } } - public byte[] read(long offset, int length) { - if (mClosed) return null; - try { - if (mFileStream != null) { - return fileStreamRead(offset, length); - } - if (mData != null) { - byte[] ret = new byte[length]; - System.arraycopy(mData, (int) offset, ret, 0, length); - return ret; - } - } catch (IOException e) { - e.printStackTrace(); + public byte[] read(long offset, int length) throws IOException { + if (mClosed) throw new IOException("inputStreamAt closed"); + if (mFileStream != null) { + return fileStreamRead(offset, length); } - - return null; + if (mData != null) { + byte[] ret = new byte[length]; + System.arraycopy(mData, (int) offset, ret, 0, length); + return ret; + } + throw new IOException("inputStreamAt not init"); } protected byte[] fileStreamRead(long offset, int length) throws IOException { From 9e49982c06b257e811e0626e71c57015a849e207 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 20 Nov 2013 21:45:25 +0800 Subject: [PATCH 02/34] close idle connection after finish a request; --- src/com/qiniu/auth/Client.java | 2 ++ src/com/qiniu/utils/InputStreamAt.java | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/com/qiniu/auth/Client.java b/src/com/qiniu/auth/Client.java index 03917eada..053d76be8 100644 --- a/src/com/qiniu/auth/Client.java +++ b/src/com/qiniu/auth/Client.java @@ -22,6 +22,7 @@ import org.apache.http.util.EntityUtils; import java.io.IOException; +import java.util.concurrent.TimeUnit; public class Client { @@ -119,6 +120,7 @@ protected void onProgressUpdate(Object... values) { @Override protected void onPostExecute(Object o) { + mClient.getConnectionManager().closeIdleConnections(30, TimeUnit.SECONDS); if (o instanceof Exception) { mRet.onFailure((Exception) o); return; diff --git a/src/com/qiniu/utils/InputStreamAt.java b/src/com/qiniu/utils/InputStreamAt.java index 9e03821ce..f2d304b4f 100644 --- a/src/com/qiniu/utils/InputStreamAt.java +++ b/src/com/qiniu/utils/InputStreamAt.java @@ -135,6 +135,8 @@ public byte[] read(long offset, int length) throws IOException { protected byte[] fileStreamRead(long offset, int length) throws IOException { if (mFileStream == null) return null; + long fileLength = mFileStream.length(); + if (length + offset > fileLength) length = (int) (fileLength - offset); byte[] data = new byte[length]; int read; From 90ace81734ad51e43045bf11083aa09f98055dff Mon Sep 17 00:00:00 2001 From: Cheney Date: Thu, 21 Nov 2013 11:16:32 +0800 Subject: [PATCH 03/34] fix inputstreamat bug --- src/com/qiniu/utils/InputStreamAt.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/qiniu/utils/InputStreamAt.java b/src/com/qiniu/utils/InputStreamAt.java index f2d304b4f..fb09278f9 100644 --- a/src/com/qiniu/utils/InputStreamAt.java +++ b/src/com/qiniu/utils/InputStreamAt.java @@ -144,7 +144,7 @@ protected byte[] fileStreamRead(long offset, int length) throws IOException { synchronized (data) { mFileStream.seek(offset); do { - read = mFileStream.read(data, totalRead, length); + read = mFileStream.read(data, totalRead, length - totalRead); if (read <= 0) break; totalRead += read; } while (length > totalRead); From 691d6e3265db034e61a81790aa4c7ae3a06e3c0c Mon Sep 17 00:00:00 2001 From: Cheney Date: Mon, 2 Dec 2013 14:38:53 +0800 Subject: [PATCH 04/34] fix client output info --- src/com/qiniu/auth/Client.java | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/com/qiniu/auth/Client.java b/src/com/qiniu/auth/Client.java index 053d76be8..80e0d6085 100644 --- a/src/com/qiniu/auth/Client.java +++ b/src/com/qiniu/auth/Client.java @@ -91,22 +91,16 @@ protected Object doInBackground(Object... objects) { try { HttpResponse resp = roundtrip(mHttpRequest); int statusCode = resp.getStatusLine().getStatusCode(); - if (statusCode == 401) { // android 2.3 will not response - return new Exception("unauthorized!"); - } - byte[] data = EntityUtils.toByteArray(resp.getEntity()); + String xl = resp.getFirstHeader("X-Log").getValue(); + + if (statusCode == 401) return new Exception("unauthorized!"); // android 2.3 will not response + if (xl.contains("invalid BlockCtx")) return new Exception(xl); - if (statusCode / 100 != 2) { - if (data.length == 0) { - String xlog = resp.getFirstHeader("X-Log").getValue(); - if (xlog.length() > 0) { - return new Exception(xlog); - } - return new Exception(resp.getStatusLine().getReasonPhrase()); - } - return new Exception(new String(data)); - } - return data; + byte[] data = EntityUtils.toByteArray(resp.getEntity()); + if (statusCode / 100 == 2) return data; + if (data.length > 0) return new Exception(new String(data)); + if (xl.length() > 0) return new Exception(xl); + return new Exception(resp.getStatusLine().getStatusCode() + ":" + resp.getStatusLine().getReasonPhrase()); } catch (IOException e) { e.printStackTrace(); return e; From e500fe1ca85f909931e86b2c0bc4c8fe879f0078 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 14:09:24 +0800 Subject: [PATCH 05/34] change format --- src/com/qiniu/io/IO.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/com/qiniu/io/IO.java b/src/com/qiniu/io/IO.java index 631df9011..6b170da2b 100644 --- a/src/com/qiniu/io/IO.java +++ b/src/com/qiniu/io/IO.java @@ -54,7 +54,9 @@ public void put(String key, InputStreamAt isa, PutExtra extra, JSONObjectRet ret } } if (extra.checkCrc != PutExtra.UNUSE_CRC32) m.addField("crc32", extra.crc32 + ""); - for (Map.Entry i: extra.params.entrySet()) m.addField(i.getKey(), i.getValue()); + for (Map.Entry i: extra.params.entrySet()) { + m.addField(i.getKey(), i.getValue()); + } m.addField("token", mUptoken); m.addFile("file", extra.mimeType, key == null ? "?" : key, isa); From f671b8bce15ef4375325ee214f68ff90cb687fbc Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 16:05:26 +0800 Subject: [PATCH 06/34] fix MultipartEntity isRepeatable to true --- src/com/qiniu/utils/MultipartEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/qiniu/utils/MultipartEntity.java b/src/com/qiniu/utils/MultipartEntity.java index 93beb1eb6..141f2fa0a 100644 --- a/src/com/qiniu/utils/MultipartEntity.java +++ b/src/com/qiniu/utils/MultipartEntity.java @@ -33,7 +33,7 @@ public void addFile(String field, String contentType, String fileName, InputStre @Override public boolean isRepeatable() { - return false; + return true; } @Override From 7e18951a2d89e53ed4569d3fdc39310abc490c3b Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 18:31:22 +0800 Subject: [PATCH 07/34] add timeout for multipartEntity --- src/com/qiniu/utils/MultipartEntity.java | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/com/qiniu/utils/MultipartEntity.java b/src/com/qiniu/utils/MultipartEntity.java index 141f2fa0a..40c5b1077 100644 --- a/src/com/qiniu/utils/MultipartEntity.java +++ b/src/com/qiniu/utils/MultipartEntity.java @@ -6,8 +6,10 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.io.PipedOutputStream; import java.util.ArrayList; import java.util.Random; +import java.util.concurrent.*; public class MultipartEntity extends AbstractHttpEntity { private String mBoundary; @@ -80,6 +82,7 @@ public boolean isStreaming() { public void setProcessNotify(IOnProcess ret) { mNotify = ret; } + ExecutorService executor = Executors.newFixedThreadPool(1); class FileInfo { @@ -117,7 +120,12 @@ public void writeTo(OutputStream outputStream) throws IOException { long length = mIsa.length(); while (index < length) { int readLength = (int) StrictMath.min((long) blockSize, mIsa.length() - index); - outputStream.write(mIsa.read(index, readLength)); + try { + write(blockSize / 2, outputStream, mIsa.read(index, readLength)); + } catch (Exception e) { + e.printStackTrace(); + throw new IOException(e.getMessage()); + } index += blockSize; outputStream.flush(); writed += readLength; @@ -130,6 +138,19 @@ public void writeTo(OutputStream outputStream) throws IOException { } } + private void write(int blocksize, final OutputStream outputStream, final byte[] data) throws InterruptedException, ExecutionException, TimeoutException { + Callable readTask = new Callable() { + @Override + public Object call() throws Exception { + outputStream.write(data); + return null; + } + }; + Future future = executor.submit(readTask); + if (blocksize > 20000) blocksize = 20000; + future.get(blocksize, TimeUnit.MILLISECONDS); + } + private static String getRandomString(int length) { String base = "abcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); From 132f6637e853bcfe7f28dfef2c7f991f86e82cd1 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 18:36:43 +0800 Subject: [PATCH 08/34] fix --- src/com/qiniu/utils/MultipartEntity.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/com/qiniu/utils/MultipartEntity.java b/src/com/qiniu/utils/MultipartEntity.java index 40c5b1077..309ca8591 100644 --- a/src/com/qiniu/utils/MultipartEntity.java +++ b/src/com/qiniu/utils/MultipartEntity.java @@ -57,6 +57,7 @@ public InputStream getContent() throws IOException, IllegalStateException { @Override public void writeTo(OutputStream outputStream) throws IOException { + writed = 0; outputStream.write(mData.toString().getBytes()); outputStream.flush(); writed += mData.toString().getBytes().length; From a7d33a81be8b7c095e7210e1c4d5f7e51c2b1d13 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 18:38:58 +0800 Subject: [PATCH 09/34] fix --- src/com/qiniu/utils/MultipartEntity.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/qiniu/utils/MultipartEntity.java b/src/com/qiniu/utils/MultipartEntity.java index 309ca8591..e21cb8aee 100644 --- a/src/com/qiniu/utils/MultipartEntity.java +++ b/src/com/qiniu/utils/MultipartEntity.java @@ -6,7 +6,6 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.PipedOutputStream; import java.util.ArrayList; import java.util.Random; import java.util.concurrent.*; From dfe0583e7a56e0b5bca79d787fa7a499d108eb15 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 19:57:46 +0800 Subject: [PATCH 10/34] fix --- src/com/qiniu/io/IO.java | 4 +++- src/com/qiniu/utils/MultipartEntity.java | 9 ++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/com/qiniu/io/IO.java b/src/com/qiniu/io/IO.java index 6b170da2b..314ec10c1 100644 --- a/src/com/qiniu/io/IO.java +++ b/src/com/qiniu/io/IO.java @@ -22,15 +22,17 @@ public class IO { public static String UNDEFINED_KEY = null; private static Client mClient; private static String mUptoken; + private static long mClientUseTime; public IO(Client client, String uptoken) { mClient = client; mUptoken = uptoken; } private static Client defaultClient() { - if (mClient == null) { + if (mClient == null || System.currentTimeMillis() - mClientUseTime > 60 * 1000) { // 1 minute mClient = Client.defaultClient(); } + mClientUseTime = System.currentTimeMillis(); return mClient; } diff --git a/src/com/qiniu/utils/MultipartEntity.java b/src/com/qiniu/utils/MultipartEntity.java index e21cb8aee..7da1f9274 100644 --- a/src/com/qiniu/utils/MultipartEntity.java +++ b/src/com/qiniu/utils/MultipartEntity.java @@ -115,13 +115,13 @@ public void writeTo(OutputStream outputStream) throws IOException { int blockSize = (int) (getContentLength() / 100); if (blockSize > 256 * 1024) blockSize = 256 * 1024; - if (blockSize < 16 * 1024) blockSize = 16 * 1024; + if (blockSize < 32 * 1024) blockSize = 32 * 1024; long index = 0; long length = mIsa.length(); while (index < length) { int readLength = (int) StrictMath.min((long) blockSize, mIsa.length() - index); try { - write(blockSize / 2, outputStream, mIsa.read(index, readLength)); + write(outputStream, mIsa.read(index, readLength)); } catch (Exception e) { e.printStackTrace(); throw new IOException(e.getMessage()); @@ -138,7 +138,7 @@ public void writeTo(OutputStream outputStream) throws IOException { } } - private void write(int blocksize, final OutputStream outputStream, final byte[] data) throws InterruptedException, ExecutionException, TimeoutException { + private void write(final OutputStream outputStream, final byte[] data) throws InterruptedException, ExecutionException, TimeoutException { Callable readTask = new Callable() { @Override public Object call() throws Exception { @@ -147,8 +147,7 @@ public Object call() throws Exception { } }; Future future = executor.submit(readTask); - if (blocksize > 20000) blocksize = 20000; - future.get(blocksize, TimeUnit.MILLISECONDS); + future.get(60*1000, TimeUnit.MILLISECONDS); } private static String getRandomString(int length) { From 5988b4e5909a1fe925b64d6eb510a580a930ce71 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 20:01:34 +0800 Subject: [PATCH 11/34] fix --- src/com/qiniu/io/IO.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/com/qiniu/io/IO.java b/src/com/qiniu/io/IO.java index 314ec10c1..f6c8e74ce 100644 --- a/src/com/qiniu/io/IO.java +++ b/src/com/qiniu/io/IO.java @@ -29,7 +29,11 @@ public IO(Client client, String uptoken) { } private static Client defaultClient() { - if (mClient == null || System.currentTimeMillis() - mClientUseTime > 60 * 1000) { // 1 minute + if (mClient != null && System.currentTimeMillis() - mClientUseTime > 60 * 1000) { + mClient.close(); + mClient = null; + } + if (mClient == null) { // 1 minute mClient = Client.defaultClient(); } mClientUseTime = System.currentTimeMillis(); From 894c8f092c095a94ed3be7768d12d8c4eb8f7874 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 20:03:58 +0800 Subject: [PATCH 12/34] fix --- src/com/qiniu/io/IO.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/qiniu/io/IO.java b/src/com/qiniu/io/IO.java index f6c8e74ce..47545e63d 100644 --- a/src/com/qiniu/io/IO.java +++ b/src/com/qiniu/io/IO.java @@ -29,11 +29,11 @@ public IO(Client client, String uptoken) { } private static Client defaultClient() { - if (mClient != null && System.currentTimeMillis() - mClientUseTime > 60 * 1000) { + if (mClient != null && System.currentTimeMillis() - mClientUseTime > 60 * 1000) { // 1 minute mClient.close(); mClient = null; } - if (mClient == null) { // 1 minute + if (mClient == null) { mClient = Client.defaultClient(); } mClientUseTime = System.currentTimeMillis(); From d1659f87691890916710e5c0a4d889c8740296a2 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 20:05:11 +0800 Subject: [PATCH 13/34] add close for client --- src/com/qiniu/auth/Client.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/com/qiniu/auth/Client.java b/src/com/qiniu/auth/Client.java index 80e0d6085..7d8b4bb06 100644 --- a/src/com/qiniu/auth/Client.java +++ b/src/com/qiniu/auth/Client.java @@ -32,6 +32,11 @@ public Client(HttpClient client) { mClient = client; } + public void close() { + mClient.getConnectionManager().closeExpiredConnections(); + mClient.getConnectionManager().shutdown(); + } + public static ClientExecutor get(String url, CallRet ret) { Client client = Client.defaultClient(); return client.get(client.makeClientExecutor(), url, ret); From fdcbd59d364bac9d4834fb6a5f3a36a56e44c7b7 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 20:06:18 +0800 Subject: [PATCH 14/34] fix close Idle connection --- src/com/qiniu/auth/Client.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/qiniu/auth/Client.java b/src/com/qiniu/auth/Client.java index 7d8b4bb06..d56abdc15 100644 --- a/src/com/qiniu/auth/Client.java +++ b/src/com/qiniu/auth/Client.java @@ -119,7 +119,7 @@ protected void onProgressUpdate(Object... values) { @Override protected void onPostExecute(Object o) { - mClient.getConnectionManager().closeIdleConnections(30, TimeUnit.SECONDS); + mClient.getConnectionManager().closeExpiredConnections(); if (o instanceof Exception) { mRet.onFailure((Exception) o); return; From 2c9d466a2b373b5d9c289122e5e7f77cbc551ce1 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 20:41:47 +0800 Subject: [PATCH 15/34] add timeout --- src/com/qiniu/auth/Client.java | 14 +++++++++++++- src/com/qiniu/io/IO.java | 5 +++++ src/com/qiniu/utils/IOnProcess.java | 1 + src/com/qiniu/utils/MultipartEntity.java | 4 ++-- 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/com/qiniu/auth/Client.java b/src/com/qiniu/auth/Client.java index d56abdc15..95f2a94da 100644 --- a/src/com/qiniu/auth/Client.java +++ b/src/com/qiniu/auth/Client.java @@ -83,6 +83,7 @@ protected HttpResponse roundtrip(HttpRequestBase httpRequest) throws IOException public class ClientExecutor extends AsyncTask implements ICancel { HttpRequestBase mHttpRequest; CallRet mRet; + boolean failed; public void setup(HttpRequestBase httpRequest, CallRet ret) { mHttpRequest = httpRequest; mRet = ret; @@ -114,18 +115,29 @@ protected Object doInBackground(Object... objects) { @Override protected void onProgressUpdate(Object... values) { + if (failed) return; + if (values.length == 1 && values[0] instanceof Exception) { + mRet.onFailure((Exception) values[0]); + failed = true; + return; + } mRet.onProcess((Long) values[0], (Long) values[1]); } @Override protected void onPostExecute(Object o) { - mClient.getConnectionManager().closeExpiredConnections(); + if (failed) return; if (o instanceof Exception) { mRet.onFailure((Exception) o); return; } mRet.onSuccess((byte[]) o); } + + public void onFailure(Exception ex) { + publishProgress(ex); + cancel(true); + } }; public static Client defaultClient() { diff --git a/src/com/qiniu/io/IO.java b/src/com/qiniu/io/IO.java index 47545e63d..25251a006 100644 --- a/src/com/qiniu/io/IO.java +++ b/src/com/qiniu/io/IO.java @@ -74,6 +74,11 @@ public void put(String key, InputStreamAt isa, PutExtra extra, JSONObjectRet ret public void onProcess(long current, long total) { executor.upload(current, total); } + + @Override + public void onFailure(Exception ex) { + executor.onFailure(ex); + } }); client.call(executor, Conf.UP_HOST, m, ret); } diff --git a/src/com/qiniu/utils/IOnProcess.java b/src/com/qiniu/utils/IOnProcess.java index 9d45a08f2..03d2206df 100644 --- a/src/com/qiniu/utils/IOnProcess.java +++ b/src/com/qiniu/utils/IOnProcess.java @@ -2,4 +2,5 @@ public interface IOnProcess { public void onProcess(long current, long total); + public void onFailure(Exception ex); } diff --git a/src/com/qiniu/utils/MultipartEntity.java b/src/com/qiniu/utils/MultipartEntity.java index 7da1f9274..93dd7e7e7 100644 --- a/src/com/qiniu/utils/MultipartEntity.java +++ b/src/com/qiniu/utils/MultipartEntity.java @@ -123,8 +123,8 @@ public void writeTo(OutputStream outputStream) throws IOException { try { write(outputStream, mIsa.read(index, readLength)); } catch (Exception e) { - e.printStackTrace(); - throw new IOException(e.getMessage()); + mNotify.onFailure(e); + return; } index += blockSize; outputStream.flush(); From efc080b9e0e5207cad1d440da8f32e633339f3b3 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 21:23:18 +0800 Subject: [PATCH 16/34] add timeout --- src/com/qiniu/utils/MultipartEntity.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/com/qiniu/utils/MultipartEntity.java b/src/com/qiniu/utils/MultipartEntity.java index 93dd7e7e7..dc88707d6 100644 --- a/src/com/qiniu/utils/MultipartEntity.java +++ b/src/com/qiniu/utils/MultipartEntity.java @@ -120,8 +120,9 @@ public void writeTo(OutputStream outputStream) throws IOException { long length = mIsa.length(); while (index < length) { int readLength = (int) StrictMath.min((long) blockSize, mIsa.length() - index); + int timeout = readLength * 2; try { - write(outputStream, mIsa.read(index, readLength)); + write(timeout, outputStream, mIsa.read(index, readLength)); } catch (Exception e) { mNotify.onFailure(e); return; @@ -138,7 +139,7 @@ public void writeTo(OutputStream outputStream) throws IOException { } } - private void write(final OutputStream outputStream, final byte[] data) throws InterruptedException, ExecutionException, TimeoutException { + private void write(int timeout, final OutputStream outputStream, final byte[] data) throws InterruptedException, ExecutionException, TimeoutException { Callable readTask = new Callable() { @Override public Object call() throws Exception { @@ -147,7 +148,7 @@ public Object call() throws Exception { } }; Future future = executor.submit(readTask); - future.get(60*1000, TimeUnit.MILLISECONDS); + future.get(timeout, TimeUnit.MILLISECONDS); } private static String getRandomString(int length) { From 95703fc097c8b53053eda938753b38fb6144f640 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 21:35:45 +0800 Subject: [PATCH 17/34] use copyOfRange --- src/com/qiniu/utils/InputStreamAt.java | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/com/qiniu/utils/InputStreamAt.java b/src/com/qiniu/utils/InputStreamAt.java index fb09278f9..bb4ff622b 100644 --- a/src/com/qiniu/utils/InputStreamAt.java +++ b/src/com/qiniu/utils/InputStreamAt.java @@ -151,11 +151,20 @@ protected byte[] fileStreamRead(long offset, int length) throws IOException { } if (totalRead != data.length) { - data = Arrays.copyOfRange(data, 0, totalRead); + data = copyOfRange(data, 0, totalRead); } return data; } + public static byte[] copyOfRange(byte[] original, int from, int to) { + int newLength = to - from; + if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); + byte[] copy = new byte[newLength]; + System.arraycopy(original, from, copy, 0, + Math.min(original.length - from, newLength)); + return copy; + } + @Override public synchronized void close(){ if (mClosed) return; From 66bd46e858018275e8aad57a9a02a4a81b1c7f6a Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 21:36:26 +0800 Subject: [PATCH 18/34] use copyOfRange --- src/com/qiniu/utils/InputStreamAt.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/com/qiniu/utils/InputStreamAt.java b/src/com/qiniu/utils/InputStreamAt.java index bb4ff622b..5d8d6acc6 100644 --- a/src/com/qiniu/utils/InputStreamAt.java +++ b/src/com/qiniu/utils/InputStreamAt.java @@ -160,8 +160,7 @@ public static byte[] copyOfRange(byte[] original, int from, int to) { int newLength = to - from; if (newLength < 0) throw new IllegalArgumentException(from + " > " + to); byte[] copy = new byte[newLength]; - System.arraycopy(original, from, copy, 0, - Math.min(original.length - from, newLength)); + System.arraycopy(original, from, copy, 0, Math.min(original.length - from, newLength)); return copy; } From 84be071f1cc1039b143ce388d16ca964d2ea1bcb Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 21:52:56 +0800 Subject: [PATCH 19/34] fix arrays import --- src/com/qiniu/utils/InputStreamAt.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/qiniu/utils/InputStreamAt.java b/src/com/qiniu/utils/InputStreamAt.java index 5d8d6acc6..c4a1fc09a 100644 --- a/src/com/qiniu/utils/InputStreamAt.java +++ b/src/com/qiniu/utils/InputStreamAt.java @@ -7,7 +7,6 @@ import org.apache.http.entity.AbstractHttpEntity; import java.io.*; -import java.util.Arrays; import java.util.zip.CRC32; public class InputStreamAt implements Closeable { From 72c55cfb491f889ce38ebfb696dea056a7f57c00 Mon Sep 17 00:00:00 2001 From: Cheney Date: Wed, 4 Dec 2013 22:10:14 +0800 Subject: [PATCH 20/34] add httpclient pool expired to 3 minutes --- src/com/qiniu/io/IO.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/qiniu/io/IO.java b/src/com/qiniu/io/IO.java index 25251a006..506279dc4 100644 --- a/src/com/qiniu/io/IO.java +++ b/src/com/qiniu/io/IO.java @@ -29,7 +29,7 @@ public IO(Client client, String uptoken) { } private static Client defaultClient() { - if (mClient != null && System.currentTimeMillis() - mClientUseTime > 60 * 1000) { // 1 minute + if (mClient != null && System.currentTimeMillis() - mClientUseTime > 3 * 60 * 1000) { // 1 minute mClient.close(); mClient = null; } From 46c56332f3bacebfbd7aa05bc24db6ddda9832fb Mon Sep 17 00:00:00 2001 From: Liang Tao Date: Tue, 10 Dec 2013 13:38:03 +0800 Subject: [PATCH 21/34] Add Title for docs/README.md. --- docs/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/README.md b/docs/README.md index 74d515a7b..6240d6e27 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,6 +2,8 @@ title: Android SDK 使用指南 --- +# Android SDK 使用指南 + - Android SDK 下载地址: - Android SDK 源码地址: (请注意非 master 分支的代码在规格上可能承受变更) From 815fad99c879430f2f1d5f040ff0f1a0bf7a5c5a Mon Sep 17 00:00:00 2001 From: Hugh Lv Date: Thu, 9 Jan 2014 22:05:55 +0800 Subject: [PATCH 22/34] Refactoring developer guide. --- docs/README.md | 203 +++++++++++++++++++++---------------------------- 1 file changed, 87 insertions(+), 116 deletions(-) diff --git a/docs/README.md b/docs/README.md index 6240d6e27..dc9150abd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,170 +1,141 @@ --- -title: Android SDK 使用指南 +title: Android SDK使用文档 --- -# Android SDK 使用指南 +# Android SDK使用文档 -- Android SDK 下载地址: -- Android SDK 源码地址: (请注意非 master 分支的代码在规格上可能承受变更) +## 目录 -此 Android SDK 基于 [七牛云存储官方API](http://docs.qiniu.com/api/index.html) 构建。在开发者的 Android App 工程项目中使用此 SDK 能够非常方便地将 Android 系统里边的文件快速直传到七牛云存储。 +- [概述](#overview) +- [使用场景](#use-scenario) +- [接入SDK](#integration) +- [上传文件](#simple-upload) +- [分片上传(断点续上传)](#resumable-upload) +- [线程安全性](#thread-safety) -出于安全考虑,使用此 SDK 无需设置密钥(AccessKey / SecretKey)。所有涉及到授权的操作,比如生成上传授权凭证(uploadToken)或下载授权凭证(downloadToken)均在业务服务器端进行。 + +## 概述 -业务服务器负责生成和颁发授权,此 SDK 只负责实施具体的上传业务。 +Android SDK只包含了最终用户使用场景中的必要功能。相比服务端SDK而言,客户端SDK不会包含对云存储服务的管理和配置功能。 -## 目录 +该SDK支持不低于2.1的Android版本。 -- [上传流程](#upload-flow) -- [下载流程](#download-flow) -- [接入SDK](#load) -- [使用SDK上传文件](#upload) -- [SDK 内置 demo 说明](#demo) -- [并发特性](#concurrency) -- [贡献代码](#contributing) -- [许可证](#license) + +## 使用场景 - +在使用Android SDK开发基于七牛云存储的应用之前,请注意理解合适的开发场景。客户端属于不可控的场景,非一般用户在拿到客户端后可能会对其进行反向工程,因此客户端程序中不可包含任何可能导致安全漏洞的业务逻辑和关键信息。 -## 上传流程 +我们推荐的安全模型如下所示。 -1. 业务服务器使用七牛云存储服务端编程语言(如 PHP/Python/Ruby/Java)SDK 生成 uploadToken (上传授权凭证) +![安全模型](http://developer.qiniu.com/docs/v6/api/overview/img/token.png) -2. 客户端 Android 使用该 uploadToken 调用此 Android 封装的上传方法直传文件到七牛云存储 +开发者需要合理划分客户端程序和业务服务器的职责范围。分发给最终用户的客户端程序中不应有需要使用管理凭证及SecretKey的场景。这些可能导致安全风险的使用场景均应被设计为在业务服务器上进行。 -3. 文件直传成功,七牛云存储向 uploadToken 生成之前所指定的业务服务器地址发起回调 +更多的相关内容请查看[编程模型](http://developer.qiniu.com/docs/v6/api/overview/programming-model.html)和[安全机制](http://developer.qiniu.com/docs/v6/api/overview/security.html)。 -4. 业务服务器接收来自七牛云存储回调的 POST 请求,处理相关 POST 参数,最后响应输出一段 JSON + +## 接入SDK -5. 七牛云存储接收业务服务器响应输出的这段 JSON,原封不动地通过 HTTP 返回给 Android 客户端程序 +> TODO: 如何将该SDK整合到工作项目中?使用jar包?拷贝源文件? + +## 上传文件 -注意事项: +开发者可以选择SDK提供的两种上传方式:表单上传和分片上传。表单上传使用一个HTTP POST请求完成文件的上传,因此比较适合较小的文件和较好的网络环境。相比而言,分片上传更能适应不稳定的网络环境,也比较适合上传比较大的文件(数百MB或更大)。 -- 此 Android SDK 当前只提供上传方法,即负责上述流程中的第2个步骤。 -- 业务服务器响应回调请求后输出 JSON,HTTP Headers 必须输出 `Content-Type` 为 `application/json`。 -- 文件上传成功后,业务服务器输出的 JSON 数据,可从所调用SDK上传代码的返回值中获取到。 +若需深入了解上传方式之间的区别,请查看[上传类型](http://developer.qiniu.com/docs/v6/api/overview/up/upload-models.html#upload-types),[表单上传接口说明](http://developer.qiniu.com/docs/v6/api/overview/up/form-upload.html),[分片上传接口说明(断点续上传)](http://developer.qiniu.com/docs/v6/api/overview/up/chunked-upload.html)。 + +### 表单上传 - +开发者可以通过调用`IO.put()`方法来以表单形式上传一个文件。该方法的详细说明如下: -## 下载流程 +``` +public void put(String key, + InputStreamAt isa, + PutExtra extra, + JSONObjectRet ret); +``` -此 Android SDK 没有提供下载文件的方法。所有上传到七牛云存储的文件,都能以如下方式进行访问: +参数说明: -公开资源: +参数 | 类型 | 说明 +:---: | :----: | :--- +key | String | 将保存为的资源唯一标识。请参见[关键概念:键值对](http://developer.qiniu.com/docs/v6/api/overview/concepts.html#key-value)。 +isa | InputStreamAt | 待上传的本地文件。 +extra | PutExtra | 额外配置项,用于精确控制上传行为。请参见[高级设置](#upload-config)。 +ret | JSONObjectRet | 开发者需实现该接口以获取上传进度和上传结果。
若上传成功,该接口中的`onSuccess()`方法将被调用。否则`onFailure()`方法将被调用。 - http:/// +表单上传的示例代码请参见SDK示例中[MyActivity.doUpload()](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/demo/MyActivity.java)方法的实现。 -私有资源: + +### 分片上传 - http:///?token= +顾名思义,分片上传会将一个文件划分为多个指定大小的数据块,分别上传。分片上传的关键价值在于可更好的适应不稳定的网络环境,以及成功上传超大的文件。分片上传功能也是实现断点续上传功能的基础。 -其中\是bucket所对应的域名。七牛云存储为每一个bucket提供一个默认域名。默认域名可以到[七牛云存储开发者平台](https://portal.qiniu.com/)中,空间设置的域名设置一节查询。 +开发者可以通过调用`ResumableIO.put()`方法以分片形式上传一个文件。该方法签名和`IO.put()`一致。 -出于安全考虑,此 SDK 不提供 `downloadToken` 的生成。除 Android / iOS SDK 以外,七牛云存储其他编程语言的 SDK 都有提供签发私有资源下载授权凭证(downloadToken)的实现。 +分片上传的示例代码请参见SDK示例中[MyResumableActivity.doResumableUpload()](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/demo/MyResumableActivity.java)方法的实现。 -**注意: key必须采用utf8编码,如使用非utf8编码访问七牛云存储将反馈错误** + +### 断点续上传 - +开发者可以基于分片上传机制实现断点续上传功能。 -## 接入SDK +> TODO: 该SDK是否已经支持断点续上传功能?基本的要求是可反馈完整的进度信息给开发者进行持久化,并且在上传时可以传入之前持久化的上传进度信息。 -本SDK的开发环境是 [Intellij IDEA](http://www.jetbrains.com/idea/),如果开发者使用的编辑器同为 IDEA, 直接打开项目即可,对于使用 [Eclipse](http://www.eclipse.org/) 编辑器的开发者,可以尝试导入项目。 + +### 上传中的并发性 -导入后,填写相关必要参数即可运行SDK自带的 demo 程序,配置方法见 [SDK 内置 demo 说明](#demo) 。 +分片上传机制也提供了对一个文件并发上传的能力。 +> TODO: 现在这个SDK可以设置并发数量吗? - - -## 使用SDK上传文件 - -在 Android 中选择文件一般是通过 uri 作为路径, 一般调用以下代码 - -```{java} -// 在七牛绑定的对应bucket的域名. 默认是bucket.qiniudn.com -public static String bucketName = "bucketName"; -public static String domain = bucketName + ".qiniudn.com"; -// upToken 这里需要自行获取. SDK 将不实现获取过程. 当token过期后才再获取一遍 -public String UP_TOKEN = "token"; - -boolean uploading = false; -/** - * 普通上传文件 - * @param uri - */ -private void doUpload(Uri uri) { - if (uploading) { - hint.setText("上传中,请稍后"); - return; - } - uploading = true; - String key = IO.UNDEFINED_KEY; // 自动生成key - PutExtra extra = new PutExtra(); - extra.checkCrc = PutExtra.AUTO_CRC32; - extra.params.put("x:arg", "value"); - hint.setText("上传中"); - IO.putFile(this, UP_TOKEN, key, uri, extra, new JSONObjectRet() { - @Override - public void onSuccess(JSONObject resp) { - uploading = false; - String hash; - String value; - try { - hash = resp.getString("hash"); - value = resp.getString("x:arg"); - } catch (Exception ex) { - hint.setText(ex.getMessage()); - return; - } - String redirect = "http://" + domain + "/" + hash; - hint.setText("上传成功! " + hash); - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(redirect)); - startActivity(intent); - } - - @Override - public void onFailure(Exception ex) { - uploading = false; - hint.setText("错误: " + ex.getMessage()); - } - }); -} -``` - + +### 高级设置 - +几种不同的上传类型都支持上传时的参数配置,使用一个统一的`PutExtra`类型来管理。除了需要指定几个最基本的上传参数(哪个文件以及上传到哪里等)外,开发者还可以通过制定一系列高级参数来灵活的控制上传的后续动作和通过变量来传递一些特定信息。 -## SDK 内置 demo 说明 +设置方法请参见[`PutExtra`](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/resumableio/PutExtra.java)。开发者可以在调用`ResumableIO.put()`前往`PutExtra.params`中添加对应的参数即可,例如: -注意:demo 程序无法直接运行,需要配置 `UpToken`, `BucketName`, `Domain`信息, 将其填写到 MyActivity 之中。`key`值可以在操作界面修改。当文件上传成功时,会试图跳转到浏览器访问已经上传的资源。如果失败,会toast提示。 +``` +extra.params = new HashMap(); +extra.params.put("x:a", "bb"); // 设置一个自定义变量 +``` + +#### 上传后续动作 - +关于上传后可以进行哪些后续动作,请查看[上传后续动作](http://developer.qiniu.com/docs/v6/api/overview/up/response/)。上传的后续动作的设置通过在`PutExtra`中设置相应的参数来进行。对于Android开发者而言,这些后续动作都有各自的合适使用场景:[自定义响应内容](http://developer.qiniu.com/docs/v6/api/overview/up/response/response-body.html),[变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html),[数据预处理](http://developer.qiniu.com/docs/v6/api/overview/up/response/persistent-op.html),[回调](http://developer.qiniu.com/docs/v6/api/overview/up/response/callback.html)。 -## 并发特性 +对这些后续动作的合理组合使用可以大幅降低业务流程复杂度,并提升业务的健壮性。 -此 Android SDK 不是线程安全的,请勿在没有保护的情况下跨线程使用。 +举例说明,如果用户上传的是一个xxx格式的视频文件,开发者可以设置让该视频文件上传完成后转码为设定的目标格式。对应的设置项如下所示: +参数名称 | 参数内容 | 说明 +:---: | :----: | :--- +persistentOp | TODO:xxxxx | 符合数据处理规范的指令。这个指令表示要将码率调整为xxx,分辨率调整为xxx。 +persistentNotifyUrl | TODO:xxxxx | 结果通知地址,通常是向业务服务器发送该指定的请求。 - +> TODO:填写真实有效的一个示例。 -## 贡献代码 +完整的可设置参数和规格请参见[上传策略规格](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)。 -1. Fork -2. 创建您的特性分支 (`git checkout -b my-new-feature`) -3. 提交您的改动 (`git commit -am 'Added some feature'`) -4. 将您的修改记录提交到远程 `git` 仓库 (`git push origin my-new-feature`) -5. 然后到 github 网站的该 `git` 远程仓库的 `my-new-feature` 分支下发起 Pull Request + +#### 变量 +变量分为魔法变量和自定义变量,可帮助开发者快速的在客户端、业务服务器、云存储服务之间传递资源元信息。关于变量的作用,请参见[变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html)。 - +如同上面已经给出的示例,如果开发者需要配置变量,只需在调用上传方法前在`PutExtra.params`中添加相应的参数即可。 -## 许可证 + +## 下载文件 -Copyright (c) 2013 www.qiniu.com +该SDK并未提供下载文件相关的功能接口,因为文件下载是一个标准的HTTP GET过程。开发者只需理解资源URI的组成格式即可非常方便的构建资源URI,并在必要的时候加上下载凭证,即可使用HTTP GET请求获取相应资源。 -基于 MIT 协议发布: +具体做法请参见[资源下载](http://developer.qiniu.com/docs/v6/api/overview/dn/download.html)和[资源下载的安全机制](http://developer.qiniu.com/docs/v6/api/overview/dn/security.html)。 -* [www.opensource.org/licenses/MIT](http://www.opensource.org/licenses/MIT) + +## 线程安全性 +此 Android SDK 不是线程安全的,请勿在没有保护的情况下跨线程使用。 From dfd261476c6683218514ba6f2cf86f8ffdf784a9 Mon Sep 17 00:00:00 2001 From: Cheney Date: Fri, 10 Jan 2014 00:24:29 +0800 Subject: [PATCH 23/34] Update README.md --- docs/README.md | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/docs/README.md b/docs/README.md index dc9150abd..02fce0e62 100644 --- a/docs/README.md +++ b/docs/README.md @@ -18,7 +18,7 @@ title: Android SDK使用文档 Android SDK只包含了最终用户使用场景中的必要功能。相比服务端SDK而言,客户端SDK不会包含对云存储服务的管理和配置功能。 -该SDK支持不低于2.1的Android版本。 +该SDK支持不低于2.2的Android版本(api8)。 ## 使用场景 @@ -36,7 +36,7 @@ Android SDK只包含了最终用户使用场景中的必要功能。相比服务 ## 接入SDK -> TODO: 如何将该SDK整合到工作项目中?使用jar包?拷贝源文件? +该SDK没有包含工程文件,这时需要自己新建一个工程,然后将src里面的代码复制到代码目录里面。 ## 上传文件 @@ -53,7 +53,7 @@ Android SDK只包含了最终用户使用场景中的必要功能。相比服务 ``` public void put(String key, InputStreamAt isa, - PutExtra extra, + com.qiniu.io.PutExtra extra, JSONObjectRet ret); ``` @@ -64,7 +64,7 @@ public void put(String key, key | String | 将保存为的资源唯一标识。请参见[关键概念:键值对](http://developer.qiniu.com/docs/v6/api/overview/concepts.html#key-value)。 isa | InputStreamAt | 待上传的本地文件。 extra | PutExtra | 额外配置项,用于精确控制上传行为。请参见[高级设置](#upload-config)。 -ret | JSONObjectRet | 开发者需实现该接口以获取上传进度和上传结果。
若上传成功,该接口中的`onSuccess()`方法将被调用。否则`onFailure()`方法将被调用。 +ret | JSONObjectRet | 开发者需实现该接口以获取上传进度和上传结果。
若上传成功,该接口中的`onSuccess()`方法将被调用。否则`onFailure()`方法将被调用。 `onProgress()`会在文件上传量发生更改的时候被调用,而且处于MainThread环境之中,可以直接操作ProgressBar之类的进度提示控件。 表单上传的示例代码请参见SDK示例中[MyActivity.doUpload()](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/demo/MyActivity.java)方法的实现。 @@ -82,14 +82,22 @@ ret | JSONObjectRet | 开发者需实现该接口以获取上传进度和上传 开发者可以基于分片上传机制实现断点续上传功能。 -> TODO: 该SDK是否已经支持断点续上传功能?基本的要求是可反馈完整的进度信息给开发者进行持久化,并且在上传时可以传入之前持久化的上传进度信息。 +``` +class ResumableIO { + public static void put(String key, + InputStreamAt isa, + com.qiniu.resumableio.PutExtra extra, + JSONObjectRet ret); +} +``` +具体用法和`IO.put`的类似。 ### 上传中的并发性 分片上传机制也提供了对一个文件并发上传的能力。 -> TODO: 现在这个SDK可以设置并发数量吗? +> 目前底层采用AsyncTask来完成异步操作,系统底层默认是使用单线程来串行运行所有的AsyncTask,所以如果需要真正意义上的多线程上传,需要将AsyncTask放入线程池, 详细操作请参考[这里](http://developer.android.com/reference/android/os/AsyncTask.html) ### 高级设置 @@ -110,14 +118,6 @@ extra.params.put("x:a", "bb"); // 设置一个自定义变量 对这些后续动作的合理组合使用可以大幅降低业务流程复杂度,并提升业务的健壮性。 -举例说明,如果用户上传的是一个xxx格式的视频文件,开发者可以设置让该视频文件上传完成后转码为设定的目标格式。对应的设置项如下所示: - -参数名称 | 参数内容 | 说明 -:---: | :----: | :--- -persistentOp | TODO:xxxxx | 符合数据处理规范的指令。这个指令表示要将码率调整为xxx,分辨率调整为xxx。 -persistentNotifyUrl | TODO:xxxxx | 结果通知地址,通常是向业务服务器发送该指定的请求。 - -> TODO:填写真实有效的一个示例。 完整的可设置参数和规格请参见[上传策略规格](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)。 @@ -138,4 +138,6 @@ persistentNotifyUrl | TODO:xxxxx | 结果通知地址,通常是向业务服务 ## 线程安全性 -此 Android SDK 不是线程安全的,请勿在没有保护的情况下跨线程使用。 +Android 一般的情况下会使用一个主线程来控制UI,非主线程无法控制UI,在Android4.0+之后必须不能在主线程完成网络请求, +该SDK是根据以上的使用场景设计,所有网络的操作均使用AsyncTask异步运行,所有回调函数又都回到了主线程(onSuccess, onFailure, onProgress),在回调函数内可以直接操作UI控件。 +如果您没有额外使用`new Thread()`等命令,该SDK将不会发生线程安全性问题。 From 83d63e827c8afa2734c4273a3c56b8d19d9fa19f Mon Sep 17 00:00:00 2001 From: Hugh Lv Date: Fri, 10 Jan 2014 19:47:35 +0800 Subject: [PATCH 24/34] Refined doc. --- docs/README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/README.md b/docs/README.md index 02fce0e62..31568eb44 100644 --- a/docs/README.md +++ b/docs/README.md @@ -25,7 +25,7 @@ Android SDK只包含了最终用户使用场景中的必要功能。相比服务 在使用Android SDK开发基于七牛云存储的应用之前,请注意理解合适的开发场景。客户端属于不可控的场景,非一般用户在拿到客户端后可能会对其进行反向工程,因此客户端程序中不可包含任何可能导致安全漏洞的业务逻辑和关键信息。 -我们推荐的安全模型如下所示。 +我们推荐的安全模型如下所示: ![安全模型](http://developer.qiniu.com/docs/v6/api/overview/img/token.png) @@ -33,6 +33,11 @@ Android SDK只包含了最终用户使用场景中的必要功能。相比服务 更多的相关内容请查看[编程模型](http://developer.qiniu.com/docs/v6/api/overview/programming-model.html)和[安全机制](http://developer.qiniu.com/docs/v6/api/overview/security.html)。 + +## 安全性 + +该SDK未包含凭证生成相关的功能。开发者对安全性的控制应遵循[安全机制](http://developer.qiniu.com/docs/v6/api/overview/security.html)中建议的做法,即客户端应向业务服务器请求上传和下载凭证,而不是直接在客户端使用AccessKey/SecretKey生成对应的凭证。在客户端使用SecretKey会导致严重的安全隐患。 + ## 接入SDK @@ -97,7 +102,7 @@ class ResumableIO { 分片上传机制也提供了对一个文件并发上传的能力。 -> 目前底层采用AsyncTask来完成异步操作,系统底层默认是使用单线程来串行运行所有的AsyncTask,所以如果需要真正意义上的多线程上传,需要将AsyncTask放入线程池, 详细操作请参考[这里](http://developer.android.com/reference/android/os/AsyncTask.html) +目前本SDK的实现采用AsyncTask来进行异步操作,而Android系统底层默认是使用单线程来串行运行所有的AsyncTask,所以如果需要真正意义上的多线程上传,需要将AsyncTask放入线程池, 详细操作请参考[这里](http://developer.android.com/reference/android/os/AsyncTask.html)。 ### 高级设置 @@ -110,16 +115,12 @@ class ResumableIO { extra.params = new HashMap(); extra.params.put("x:a", "bb"); // 设置一个自定义变量 ``` - #### 上传后续动作 -关于上传后可以进行哪些后续动作,请查看[上传后续动作](http://developer.qiniu.com/docs/v6/api/overview/up/response/)。上传的后续动作的设置通过在`PutExtra`中设置相应的参数来进行。对于Android开发者而言,这些后续动作都有各自的合适使用场景:[自定义响应内容](http://developer.qiniu.com/docs/v6/api/overview/up/response/response-body.html),[变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html),[数据预处理](http://developer.qiniu.com/docs/v6/api/overview/up/response/persistent-op.html),[回调](http://developer.qiniu.com/docs/v6/api/overview/up/response/callback.html)。 - -对这些后续动作的合理组合使用可以大幅降低业务流程复杂度,并提升业务的健壮性。 - +关于上传后可以进行哪些后续动作,请查看[上传后续动作](http://developer.qiniu.com/docs/v6/api/overview/up/response/)。上传的后续动作的设置通过在`PutExtra`中设置相应的参数来进行。对于Android开发者而言,这些后续动作都有各自的合适使用场景:[自定义响应内容](http://developer.qiniu.com/docs/v6/api/overview/up/response/response-body.html),[变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html),[数据预处理](http://developer.qiniu.com/docs/v6/api/overview/up/response/persistent-op.html),[回调](http://developer.qiniu.com/docs/v6/api/overview/up/response/callback.html)。对这些后续动作的合理组合使用可以大幅降低业务流程复杂度,并提升业务的健壮性。 -完整的可设置参数和规格请参见[上传策略规格](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)。 +开发者可以在生成上传凭证前通过配置上传策略以控制上传后续动作,该工作在业务服务器端进行,因此非本SDK的功能范畴。完整的内容请参见[上传策略规格](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)。 #### 变量 From b395331092781a74e1b7060a12573d04af9e7dd6 Mon Sep 17 00:00:00 2001 From: Hugh Lv Date: Fri, 10 Jan 2014 20:51:33 +0800 Subject: [PATCH 25/34] Refined doc. --- docs/README.md | 67 ++++++++++++++++++++++++++------------------------ 1 file changed, 35 insertions(+), 32 deletions(-) diff --git a/docs/README.md b/docs/README.md index 31568eb44..228ef20ee 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,8 +9,12 @@ title: Android SDK使用文档 - [概述](#overview) - [使用场景](#use-scenario) - [接入SDK](#integration) -- [上传文件](#simple-upload) -- [分片上传(断点续上传)](#resumable-upload) +- [安全性](#security) +- [上传文件](#upload) + - [表单上传](#form-upload) + - [分片上传](#chunked-upload) + - [断点续上传](#resumable-upload) +- [下载文件](#download) - [线程安全性](#thread-safety) @@ -23,7 +27,7 @@ Android SDK只包含了最终用户使用场景中的必要功能。相比服务 ## 使用场景 -在使用Android SDK开发基于七牛云存储的应用之前,请注意理解合适的开发场景。客户端属于不可控的场景,非一般用户在拿到客户端后可能会对其进行反向工程,因此客户端程序中不可包含任何可能导致安全漏洞的业务逻辑和关键信息。 +在使用Android SDK开发基于七牛云存储的应用之前,请理解正确的开发模型。客户端属于不可控的场景,恶意用户在拿到客户端后可能会对其进行反向工程,因此客户端程序中不可包含任何可能导致安全漏洞的业务逻辑和关键信息。 我们推荐的安全模型如下所示: @@ -33,15 +37,19 @@ Android SDK只包含了最终用户使用场景中的必要功能。相比服务 更多的相关内容请查看[编程模型](http://developer.qiniu.com/docs/v6/api/overview/programming-model.html)和[安全机制](http://developer.qiniu.com/docs/v6/api/overview/security.html)。 + +## 接入SDK + +该SDK没有包含工程文件,这时需要自己新建一个工程,然后将src里面的代码复制到代码目录里面。 + ## 安全性 该SDK未包含凭证生成相关的功能。开发者对安全性的控制应遵循[安全机制](http://developer.qiniu.com/docs/v6/api/overview/security.html)中建议的做法,即客户端应向业务服务器请求上传和下载凭证,而不是直接在客户端使用AccessKey/SecretKey生成对应的凭证。在客户端使用SecretKey会导致严重的安全隐患。 - -## 接入SDK +开发者可以在生成上传凭证前通过配置上传策略以控制上传的后续动作,比如在上传完成后通过回调机制通知业务服务器。该工作在业务服务器端进行,因此非本SDK的功能范畴。 -该SDK没有包含工程文件,这时需要自己新建一个工程,然后将src里面的代码复制到代码目录里面。 +完整的内容请参见[上传策略规格](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)。关于上传后可以进行哪些后续动作,请查看[上传后续动作](http://developer.qiniu.com/docs/v6/api/overview/up/response/)。 ## 上传文件 @@ -68,9 +76,26 @@ public void put(String key, :---: | :----: | :--- key | String | 将保存为的资源唯一标识。请参见[关键概念:键值对](http://developer.qiniu.com/docs/v6/api/overview/concepts.html#key-value)。 isa | InputStreamAt | 待上传的本地文件。 -extra | PutExtra | 额外配置项,用于精确控制上传行为。请参见[高级设置](#upload-config)。 +extra | PutExtra | 上传参数。可以设置MIME类型等。 ret | JSONObjectRet | 开发者需实现该接口以获取上传进度和上传结果。
若上传成功,该接口中的`onSuccess()`方法将被调用。否则`onFailure()`方法将被调用。 `onProgress()`会在文件上传量发生更改的时候被调用,而且处于MainThread环境之中,可以直接操作ProgressBar之类的进度提示控件。 +开发者可以在调用方法前构造一个[`PutExtra`](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/resumableio/PutExtra.java)对象,往`PutExtra.params`中添加对应的上传参数以控制上传行为。可以设置的参数如下: + +参数 | 类型 | 说明 +:---: | :----: | :--- +mimeType | String | 指定上传文件的MIME类型。如果未指定,服务端将做自动检测。一般情况下无需设置。 +crc32 | long | 本文件的CRC校验码。服务端在上传完成后可以进行一次校验确认文件的完整性。 +params | HashMap | 可设置魔法变量和自定义变量。变量可帮助开发者快速的在客户端、业务服务器、云存储服务之间传递资源元信息。详见[变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html)。 + +以下是一个关于`PutExtra`使用的示例: + +``` +extra.mimeType = "application/json"; // 强制设置MIME类型 + +extra.params = new HashMap(); +extra.params.put("x:a", "bb"); // 设置一个自定义变量 +``` + 表单上传的示例代码请参见SDK示例中[MyActivity.doUpload()](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/demo/MyActivity.java)方法的实现。 @@ -95,8 +120,11 @@ class ResumableIO { JSONObjectRet ret); } ``` + 具体用法和`IO.put`的类似。 +> TODO: 这个没写完。这一节应该要告诉开发者,要断点续传的话应该持久化哪些东西,然后恢复上传时需要将之前持久化的东西设置到哪里去。 + ### 上传中的并发性 @@ -104,31 +132,6 @@ class ResumableIO { 目前本SDK的实现采用AsyncTask来进行异步操作,而Android系统底层默认是使用单线程来串行运行所有的AsyncTask,所以如果需要真正意义上的多线程上传,需要将AsyncTask放入线程池, 详细操作请参考[这里](http://developer.android.com/reference/android/os/AsyncTask.html)。 - -### 高级设置 - -几种不同的上传类型都支持上传时的参数配置,使用一个统一的`PutExtra`类型来管理。除了需要指定几个最基本的上传参数(哪个文件以及上传到哪里等)外,开发者还可以通过制定一系列高级参数来灵活的控制上传的后续动作和通过变量来传递一些特定信息。 - -设置方法请参见[`PutExtra`](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/resumableio/PutExtra.java)。开发者可以在调用`ResumableIO.put()`前往`PutExtra.params`中添加对应的参数即可,例如: - -``` -extra.params = new HashMap(); -extra.params.put("x:a", "bb"); // 设置一个自定义变量 -``` - -#### 上传后续动作 - -关于上传后可以进行哪些后续动作,请查看[上传后续动作](http://developer.qiniu.com/docs/v6/api/overview/up/response/)。上传的后续动作的设置通过在`PutExtra`中设置相应的参数来进行。对于Android开发者而言,这些后续动作都有各自的合适使用场景:[自定义响应内容](http://developer.qiniu.com/docs/v6/api/overview/up/response/response-body.html),[变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html),[数据预处理](http://developer.qiniu.com/docs/v6/api/overview/up/response/persistent-op.html),[回调](http://developer.qiniu.com/docs/v6/api/overview/up/response/callback.html)。对这些后续动作的合理组合使用可以大幅降低业务流程复杂度,并提升业务的健壮性。 - -开发者可以在生成上传凭证前通过配置上传策略以控制上传后续动作,该工作在业务服务器端进行,因此非本SDK的功能范畴。完整的内容请参见[上传策略规格](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)。 - - -#### 变量 - -变量分为魔法变量和自定义变量,可帮助开发者快速的在客户端、业务服务器、云存储服务之间传递资源元信息。关于变量的作用,请参见[变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html)。 - -如同上面已经给出的示例,如果开发者需要配置变量,只需在调用上传方法前在`PutExtra.params`中添加相应的参数即可。 - ## 下载文件 From df4c79574abffbb600d1a4866562d651dc8d922f Mon Sep 17 00:00:00 2001 From: Hugh Lv Date: Sat, 11 Jan 2014 01:07:33 +0800 Subject: [PATCH 26/34] Refined doc. --- docs/README.md | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/docs/README.md b/docs/README.md index 228ef20ee..bba4eacdd 100644 --- a/docs/README.md +++ b/docs/README.md @@ -49,7 +49,7 @@ Android SDK只包含了最终用户使用场景中的必要功能。相比服务 开发者可以在生成上传凭证前通过配置上传策略以控制上传的后续动作,比如在上传完成后通过回调机制通知业务服务器。该工作在业务服务器端进行,因此非本SDK的功能范畴。 -完整的内容请参见[上传策略规格](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html)。关于上传后可以进行哪些后续动作,请查看[上传后续动作](http://developer.qiniu.com/docs/v6/api/overview/up/response/)。 +完整的内容请参考[上传策略规格](http://developer.qiniu.com/docs/v6/api/reference/security/put-policy.html),[上传凭证规格](http://developer.qiniu.com/docs/v6/api/reference/security/upload-token.html),[下载凭证规格](http://developer.qiniu.com/docs/v6/api/reference/security/download-token.html)。关于上传后可以进行哪些后续动作,请查看[上传后续动作](http://developer.qiniu.com/docs/v6/api/overview/up/response/)。 ## 上传文件 @@ -61,7 +61,9 @@ Android SDK只包含了最终用户使用场景中的必要功能。相比服务 ### 表单上传 -开发者可以通过调用`IO.put()`方法来以表单形式上传一个文件。该方法的详细说明如下: +开发者可以通过调用`IO.put()`方法来以表单形式上传一个文件。使用该方式时应确认相应的资源大小合适于使用单一HTTP请求即可上传。过大的文件在使用该方式上传时比较容易出现超时失败的问题。该方式比较适合用于上传经压缩的小图片和短音频等,不适合用于上传较大的视频(比如尺寸超过100MB的)。 + +该方法的详细说明如下: ``` public void put(String key, @@ -74,18 +76,18 @@ public void put(String key, 参数 | 类型 | 说明 :---: | :----: | :--- -key | String | 将保存为的资源唯一标识。请参见[关键概念:键值对](http://developer.qiniu.com/docs/v6/api/overview/concepts.html#key-value)。 -isa | InputStreamAt | 待上传的本地文件。 -extra | PutExtra | 上传参数。可以设置MIME类型等。 -ret | JSONObjectRet | 开发者需实现该接口以获取上传进度和上传结果。
若上传成功,该接口中的`onSuccess()`方法将被调用。否则`onFailure()`方法将被调用。 `onProgress()`会在文件上传量发生更改的时候被调用,而且处于MainThread环境之中,可以直接操作ProgressBar之类的进度提示控件。 +`key` | `String` | 将保存为的资源唯一标识。请参见[关键概念:键值对](http://developer.qiniu.com/docs/v6/api/overview/concepts.html#key-value)。 +`isa` | `InputStreamAt` | 待上传的本地文件。 +`extra` | [`PutExtra`](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/resumableio/PutExtra.java) | 上传参数。可以设置MIME类型等。 +`ret` | [`JSONObjectRet`](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/auth/JSONObjectRet.java) | 开发者需实现该接口以获取上传进度和上传结果。
若上传成功,该接口中的`onSuccess()`方法将被调用。否则`onFailure()`方法将被调用。 `onProgress()`会在文件上传量发生更改的时候被调用,而且处于MainThread环境之中,可以直接操作ProgressBar之类的进度提示控件。 -开发者可以在调用方法前构造一个[`PutExtra`](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/resumableio/PutExtra.java)对象,往`PutExtra.params`中添加对应的上传参数以控制上传行为。可以设置的参数如下: +开发者可以在调用方法前构造一个`PutExtra`对象,设置对应的上传参数以控制上传行为。可以设置的参数如下: 参数 | 类型 | 说明 :---: | :----: | :--- -mimeType | String | 指定上传文件的MIME类型。如果未指定,服务端将做自动检测。一般情况下无需设置。 -crc32 | long | 本文件的CRC校验码。服务端在上传完成后可以进行一次校验确认文件的完整性。 -params | HashMap | 可设置魔法变量和自定义变量。变量可帮助开发者快速的在客户端、业务服务器、云存储服务之间传递资源元信息。详见[变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html)。 +`mimeType` | `String` | 指定上传文件的MIME类型。如果未指定,服务端将做自动检测。一般情况下无需设置。 +`crc32` | `long` | 本文件的CRC校验码。服务端在上传完成后可以进行一次校验确认文件的完整性。 +`params` | `HashMap` | 可设置魔法变量和自定义变量。变量可帮助开发者快速的在客户端、业务服务器、云存储服务之间传递资源元信息。详见[变量](http://developer.qiniu.com/docs/v6/api/overview/up/response/vars.html)。 以下是一个关于`PutExtra`使用的示例: @@ -130,7 +132,7 @@ class ResumableIO { 分片上传机制也提供了对一个文件并发上传的能力。 -目前本SDK的实现采用AsyncTask来进行异步操作,而Android系统底层默认是使用单线程来串行运行所有的AsyncTask,所以如果需要真正意义上的多线程上传,需要将AsyncTask放入线程池, 详细操作请参考[这里](http://developer.android.com/reference/android/os/AsyncTask.html)。 +目前本SDK的实现采用AsyncTask来进行异步操作,而Android系统底层默认是使用单线程来串行运行所有的AsyncTask。如果需要真正意义上的多线程上传,需要将AsyncTask放入线程池。详细操作请参考[这里](http://developer.android.com/reference/android/os/AsyncTask.html)。 ## 下载文件 @@ -139,6 +141,8 @@ class ResumableIO { 具体做法请参见[资源下载](http://developer.qiniu.com/docs/v6/api/overview/dn/download.html)和[资源下载的安全机制](http://developer.qiniu.com/docs/v6/api/overview/dn/security.html)。 +从安全性和代码可维护性的角度考虑,我们建议下载URL的拼装过程也在业务服务器进行,让客户端从业务服务器请求。 + ## 线程安全性 From 33db1b9602a930319547cf6bdf49675f17b1277c Mon Sep 17 00:00:00 2001 From: Hugh Lv Date: Sat, 11 Jan 2014 01:10:43 +0800 Subject: [PATCH 27/34] Refined doc. --- docs/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index bba4eacdd..6439eb5cc 100644 --- a/docs/README.md +++ b/docs/README.md @@ -98,7 +98,7 @@ extra.params = new HashMap(); extra.params.put("x:a", "bb"); // 设置一个自定义变量 ``` -表单上传的示例代码请参见SDK示例中[MyActivity.doUpload()](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/demo/MyActivity.java)方法的实现。 +表单上传的示例代码请参见SDK示例中[`MyActivity.doUpload()`](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/demo/MyActivity.java)方法的实现。 ### 分片上传 @@ -107,7 +107,7 @@ extra.params.put("x:a", "bb"); // 设置一个自定义变量 开发者可以通过调用`ResumableIO.put()`方法以分片形式上传一个文件。该方法签名和`IO.put()`一致。 -分片上传的示例代码请参见SDK示例中[MyResumableActivity.doResumableUpload()](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/demo/MyResumableActivity.java)方法的实现。 +分片上传的示例代码请参见SDK示例中[`MyResumableActivity.doResumableUpload()`](https://github.com/qiniu/android-sdk/blob/develop/src/com/qiniu/demo/MyResumableActivity.java)方法的实现。 ### 断点续上传 @@ -147,5 +147,5 @@ class ResumableIO { ## 线程安全性 Android 一般的情况下会使用一个主线程来控制UI,非主线程无法控制UI,在Android4.0+之后必须不能在主线程完成网络请求, -该SDK是根据以上的使用场景设计,所有网络的操作均使用AsyncTask异步运行,所有回调函数又都回到了主线程(onSuccess, onFailure, onProgress),在回调函数内可以直接操作UI控件。 +该SDK是根据以上的使用场景设计,所有网络的操作均使用AsyncTask异步运行,所有回调函数又都回到了主线程(`onSuccess()`, `onFailure()`, `onProgress()`),在回调函数内可以直接操作UI控件。 如果您没有额外使用`new Thread()`等命令,该SDK将不会发生线程安全性问题。 From 69dd162afc15e347ecd76546e2a398ca83d042a9 Mon Sep 17 00:00:00 2001 From: Cheney Date: Mon, 13 Jan 2014 22:52:55 +0800 Subject: [PATCH 28/34] Update README.md --- docs/README.md | 36 ++++++++++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/docs/README.md b/docs/README.md index 6439eb5cc..383d6c0a7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -65,7 +65,7 @@ Android SDK只包含了最终用户使用场景中的必要功能。相比服务 该方法的详细说明如下: -``` +```java public void put(String key, InputStreamAt isa, com.qiniu.io.PutExtra extra, @@ -91,7 +91,7 @@ public void put(String key, 以下是一个关于`PutExtra`使用的示例: -``` +```java extra.mimeType = "application/json"; // 强制设置MIME类型 extra.params = new HashMap(); @@ -114,7 +114,7 @@ extra.params.put("x:a", "bb"); // 设置一个自定义变量 开发者可以基于分片上传机制实现断点续上传功能。 -``` +```java class ResumableIO { public static void put(String key, InputStreamAt isa, @@ -125,7 +125,35 @@ class ResumableIO { 具体用法和`IO.put`的类似。 -> TODO: 这个没写完。这一节应该要告诉开发者,要断点续传的话应该持久化哪些东西,然后恢复上传时需要将之前持久化的东西设置到哪里去。 +#### 续上传 +续上传的进度信息都储存在com.qiniu.resumableio.PutExtra. 所以当上传失败的时候,可以将PutExtra持久化下来,等到下一次上传的时候,再使用这个PutExtra,具体代码实现如下 + +失败状况 +```java +final PutExtra extra = new PutExtra(); +final String key = "key"; +final String filepath = "xx/xx/xx"; +ResumableIO.put(key, InputStreamAt.fromFile(new File(filepath)), extra, new JSONObjectRet() { + int process; + public void onProcess(int current, int total) { + process = current/total; + } + // ...省略成功分支处理代码 + public void onFailure(Exception ex) { + // 忽略处理exception, + db.execute("INSERT INTO `table_resumable_table` (`key`, `filepath`, `extraJson`, `process`) VALUES ('" + key + "', '" + filepath + "', '" + extra.toJSON() + "', " + process + ")"); + } +}) +``` + +续传恢复 +```java +JSONObject ret = db.GetOne("SELECT * FROM `table_resumable_table` LIMIT 0, 1"); +PutExtra extra = new PutExtra(ret.optString("extraJson", "")); +String key = ret.optString("key", ""); +String filepath = ret.optString("filepath", ""); +ResumableIO.put(key, InputStreamAt.fromFile(new File(filepath)), extra, new JSONObjectRet() {...}); +``` ### 上传中的并发性 From 30d36c26d73719225b589797b71411b48f07f14f Mon Sep 17 00:00:00 2001 From: Cheney Date: Mon, 13 Jan 2014 23:27:24 +0800 Subject: [PATCH 29/34] Update README.md --- docs/README.md | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 383d6c0a7..44503775d 100644 --- a/docs/README.md +++ b/docs/README.md @@ -133,15 +133,28 @@ class ResumableIO { final PutExtra extra = new PutExtra(); final String key = "key"; final String filepath = "xx/xx/xx"; +// 准备上传 +db.execute("INSERT INTO `table_resumable_table` (`key`, `filepath`) VALUES ('" + key + "', '" + filepath + "')"); ResumableIO.put(key, InputStreamAt.fromFile(new File(filepath)), extra, new JSONObjectRet() { int process; + private void persist() { + // 持久化 + db.execute("UPDATE `table_resumable_table` SET extra='" + extra.toJSON() + "', process=" + process + " WHERE `key`='" + key + "' and `filepath`='" + filepath + "'"); + } + public void onSuccess(JSONObject obj) { + // 上传成功,删除记录 + db.execute("DELETE FROM `table_resumable_table` WHERE `key`='" + key + "' and `filepath`='" + filepath + "'"); + } public void onProcess(int current, int total) { - process = current/total; + process = current*100/total; + // 每5%持久化一次 + if (process % 5 == 0) { + persist(); + } } - // ...省略成功分支处理代码 public void onFailure(Exception ex) { // 忽略处理exception, - db.execute("INSERT INTO `table_resumable_table` (`key`, `filepath`, `extraJson`, `process`) VALUES ('" + key + "', '" + filepath + "', '" + extra.toJSON() + "', " + process + ")"); + persist(); } }) ``` From 052248945d190278318b67c28b138483c5cdcdfc Mon Sep 17 00:00:00 2001 From: Cheney Date: Tue, 14 Jan 2014 10:30:40 +0800 Subject: [PATCH 30/34] Update README.md --- docs/README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/README.md b/docs/README.md index 44503775d..915753939 100644 --- a/docs/README.md +++ b/docs/README.md @@ -146,11 +146,12 @@ ResumableIO.put(key, InputStreamAt.fromFile(new File(filepath)), extra, new JSON db.execute("DELETE FROM `table_resumable_table` WHERE `key`='" + key + "' and `filepath`='" + filepath + "'"); } public void onProcess(int current, int total) { - process = current*100/total; + int newProcess = current*100/total; // 每5%持久化一次 - if (process % 5 == 0) { + if (newProcess % 5 == 0 && newProcess - process > 1) { persist(); } + process = newProcess; } public void onFailure(Exception ex) { // 忽略处理exception, From f8df9930f2dd9ac6e5b8fdea355c5ded7f6dc62a Mon Sep 17 00:00:00 2001 From: Cheney Date: Tue, 14 Jan 2014 10:45:20 +0800 Subject: [PATCH 31/34] Update README.md --- docs/README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/README.md b/docs/README.md index 915753939..e1bb42ca8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -130,6 +130,7 @@ class ResumableIO { 失败状况 ```java +final int PERSIST_PACE = 5; final PutExtra extra = new PutExtra(); final String key = "key"; final String filepath = "xx/xx/xx"; @@ -137,6 +138,7 @@ final String filepath = "xx/xx/xx"; db.execute("INSERT INTO `table_resumable_table` (`key`, `filepath`) VALUES ('" + key + "', '" + filepath + "')"); ResumableIO.put(key, InputStreamAt.fromFile(new File(filepath)), extra, new JSONObjectRet() { int process; + int lastPersistProcess = 0; private void persist() { // 持久化 db.execute("UPDATE `table_resumable_table` SET extra='" + extra.toJSON() + "', process=" + process + " WHERE `key`='" + key + "' and `filepath`='" + filepath + "'"); @@ -146,12 +148,12 @@ ResumableIO.put(key, InputStreamAt.fromFile(new File(filepath)), extra, new JSON db.execute("DELETE FROM `table_resumable_table` WHERE `key`='" + key + "' and `filepath`='" + filepath + "'"); } public void onProcess(int current, int total) { - int newProcess = current*100/total; + process = current*100/total; // 每5%持久化一次 - if (newProcess % 5 == 0 && newProcess - process > 1) { + if (process - lastPersistProcess > PERSIST_PACE) { persist(); + lastPersistProcess = process; } - process = newProcess; } public void onFailure(Exception ex) { // 忽略处理exception, From 1da2e1971dd186c3d7f39341ecbe13118e88f99d Mon Sep 17 00:00:00 2001 From: Hugh Lv Date: Tue, 14 Jan 2014 11:09:39 +0800 Subject: [PATCH 32/34] Refined doc. --- docs/README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/docs/README.md b/docs/README.md index e1bb42ca8..1e7130168 100644 --- a/docs/README.md +++ b/docs/README.md @@ -126,11 +126,12 @@ class ResumableIO { 具体用法和`IO.put`的类似。 #### 续上传 -续上传的进度信息都储存在com.qiniu.resumableio.PutExtra. 所以当上传失败的时候,可以将PutExtra持久化下来,等到下一次上传的时候,再使用这个PutExtra,具体代码实现如下 +续上传的进度信息都储存在com.qiniu.resumableio.PutExtra. 所以当上传失败的时候,可以将PutExtra持久化下来,等到下一次上传的时候,再使用这个PutExtra,具体代码实现如下。 + +上传进度持久化: -失败状况 ```java -final int PERSIST_PACE = 5; +final int PERSIST_PACE = 5; // 每5%进度持久化一次 final PutExtra extra = new PutExtra(); final String key = "key"; final String filepath = "xx/xx/xx"; @@ -149,7 +150,7 @@ ResumableIO.put(key, InputStreamAt.fromFile(new File(filepath)), extra, new JSON } public void onProcess(int current, int total) { process = current*100/total; - // 每5%持久化一次 + // 每特定进度持久化一次 if (process - lastPersistProcess > PERSIST_PACE) { persist(); lastPersistProcess = process; @@ -162,12 +163,16 @@ ResumableIO.put(key, InputStreamAt.fromFile(new File(filepath)), extra, new JSON }) ``` -续传恢复 +恢复上传进度: + ```java JSONObject ret = db.GetOne("SELECT * FROM `table_resumable_table` LIMIT 0, 1"); PutExtra extra = new PutExtra(ret.optString("extraJson", "")); String key = ret.optString("key", ""); String filepath = ret.optString("filepath", ""); + +// 实际情况中,很可能出现本地文件在续传时已被删除或者修改的情况,开发者应在恢复上传前先做相应的校验。 + ResumableIO.put(key, InputStreamAt.fromFile(new File(filepath)), extra, new JSONObjectRet() {...}); ``` From 2431dd6eb7b8ffbe79266f655cd20c88ddd3705d Mon Sep 17 00:00:00 2001 From: Bai Long Date: Thu, 3 Apr 2014 17:01:06 +0800 Subject: [PATCH 33/34] Update README.md --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index d2377c9ea..cb40b8bef 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,8 @@ Qiniu Resource Storage SDK for Android ## 许可证 -Copyright (c) 2013 qiniu.com +Copyright (c) 2012-2014 qiniu.com 基于 MIT 协议发布: * [www.opensource.org/licenses/MIT](http://www.opensource.org/licenses/MIT) -* From 17e8299f7193812be85342f7b19b6d834291d1c8 Mon Sep 17 00:00:00 2001 From: longbai Date: Thu, 3 Apr 2014 17:13:31 +0800 Subject: [PATCH 34/34] changelog update --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a8f07a00..cdeeb40a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,12 @@ ## CHANGE LOG +### v6.0.1 +2014-04-03 issue [#40](https://github.com/qiniu/android-sdk/pull/40) + +- [#35] fix bugs and close idle connection +- [#36] 增加连接超时处理 + + ### v6.0.0 增加 SDK 实现规范