mirror of
https://github.com/NativeScript/NativeScript.git
synced 2025-11-05 13:26:48 +08:00
feat(Async): add onError of CompleteCallback interface (#147)
* fix(Android): better Async error handling * fix(Async): use Log instead of printStackTrace * refactor(Async): replace Log.v() with Log.e() * style(Async): reformat file
This commit is contained in:
committed by
Vasil Chimev
parent
6055a6a278
commit
ce45e75d2b
@@ -1,5 +1,13 @@
|
||||
package org.nativescript.widgets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
@@ -8,6 +16,7 @@ import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.CookieHandler;
|
||||
import java.net.CookieManager;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
@@ -16,23 +25,40 @@ import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.util.Base64;
|
||||
|
||||
import java.net.CookieManager;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
public class Async
|
||||
{
|
||||
public class Async {
|
||||
static final String TAG = "Async";
|
||||
static ThreadPoolExecutor executor = null;
|
||||
|
||||
static ThreadPoolExecutor threadPoolExecutor() {
|
||||
if (executor == null) {
|
||||
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
|
||||
ThreadFactory backgroundPriorityThreadFactory = new PriorityThreadFactory(android.os.Process.THREAD_PRIORITY_BACKGROUND);
|
||||
|
||||
executor = new ThreadPoolExecutor(
|
||||
NUMBER_OF_CORES * 2,
|
||||
NUMBER_OF_CORES * 2,
|
||||
60L,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
backgroundPriorityThreadFactory
|
||||
);
|
||||
}
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
public interface CompleteCallback {
|
||||
void onComplete(Object result, Object tag);
|
||||
|
||||
void onError(Object tag);
|
||||
}
|
||||
|
||||
static class PriorityThreadFactory implements ThreadFactory {
|
||||
private final int mThreadPriority;
|
||||
|
||||
@@ -57,29 +83,6 @@ public class Async
|
||||
}
|
||||
}
|
||||
|
||||
static ThreadPoolExecutor executor = null;
|
||||
static ThreadPoolExecutor threadPoolExecutor(){
|
||||
if(executor == null) {
|
||||
int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();
|
||||
ThreadFactory backgroundPriorityThreadFactory = new PriorityThreadFactory(android.os.Process.THREAD_PRIORITY_BACKGROUND);
|
||||
|
||||
executor = new ThreadPoolExecutor(
|
||||
NUMBER_OF_CORES * 2,
|
||||
NUMBER_OF_CORES * 2,
|
||||
60L,
|
||||
TimeUnit.SECONDS,
|
||||
new LinkedBlockingQueue<Runnable>(),
|
||||
backgroundPriorityThreadFactory
|
||||
);
|
||||
}
|
||||
|
||||
return executor;
|
||||
}
|
||||
|
||||
public interface CompleteCallback {
|
||||
void onComplete(Object result, Object tag);
|
||||
}
|
||||
|
||||
public static class Image {
|
||||
/*
|
||||
* The request id parameter is needed for the sake of the JavaScript implementation.
|
||||
@@ -171,24 +174,28 @@ public class Async
|
||||
Bitmap bmp = BitmapFactory.decodeStream(stream);
|
||||
return bmp;
|
||||
} catch (MalformedURLException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "Failed to decode stream, MalformedURLException: " + e.getMessage());
|
||||
return null;
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "Failed to decode stream, IOException: " + e.getMessage());
|
||||
return null;
|
||||
} finally {
|
||||
if (stream != null) {
|
||||
try {
|
||||
stream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "Failed to close stream, IOException: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPostExecute(final Bitmap result) {
|
||||
if (result != null) {
|
||||
this.callback.onComplete(result, this.context);
|
||||
} else {
|
||||
this.callback.onError(this.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +224,11 @@ public class Async
|
||||
}
|
||||
|
||||
protected void onPostExecute(final Bitmap result) {
|
||||
if (result != null) {
|
||||
this.callback.onComplete(result, this.requestId);
|
||||
} else {
|
||||
this.callback.onError(this.requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -236,7 +247,11 @@ public class Async
|
||||
}
|
||||
|
||||
protected void onPostExecute(final Bitmap result) {
|
||||
if (result != null) {
|
||||
this.callback.onComplete(result, this.requestId);
|
||||
} else {
|
||||
this.callback.onError(this.requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -256,32 +271,54 @@ public class Async
|
||||
}
|
||||
|
||||
protected void onPostExecute(final Bitmap result) {
|
||||
if (result != null) {
|
||||
this.callback.onComplete(result, this.requestId);
|
||||
} else {
|
||||
this.callback.onError(this.requestId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Http
|
||||
{
|
||||
public static class Http {
|
||||
private static final String DELETE_METHOD = "DELETE";
|
||||
private static final String GET_METHOD = "GET";
|
||||
private static final String HEAD_METHOD = "HEAD";
|
||||
private static CookieManager cookieManager;
|
||||
|
||||
public static class KeyValuePair
|
||||
{
|
||||
public static void MakeRequest(final RequestOptions options, final CompleteCallback callback, final Object context) {
|
||||
if (cookieManager == null) {
|
||||
cookieManager = new CookieManager();
|
||||
CookieHandler.setDefault(cookieManager);
|
||||
}
|
||||
|
||||
final android.os.Handler mHandler = new android.os.Handler();
|
||||
threadPoolExecutor().execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HttpRequestTask task = new HttpRequestTask(callback, context);
|
||||
final RequestResult result = task.doInBackground(options);
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
task.onPostExecute(result);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static class KeyValuePair {
|
||||
public String key;
|
||||
public String value;
|
||||
|
||||
public KeyValuePair(String key, String value)
|
||||
{
|
||||
public KeyValuePair(String key, String value) {
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestOptions
|
||||
{
|
||||
public static class RequestOptions {
|
||||
public String url;
|
||||
public String method;
|
||||
public ArrayList<KeyValuePair> headers;
|
||||
@@ -291,16 +328,13 @@ public class Async
|
||||
public int screenHeight = -1;
|
||||
public boolean dontFollowRedirects = false;
|
||||
|
||||
public void addHeaders(HttpURLConnection connection)
|
||||
{
|
||||
if (this.headers == null)
|
||||
{
|
||||
public void addHeaders(HttpURLConnection connection) {
|
||||
if (this.headers == null) {
|
||||
return;
|
||||
}
|
||||
boolean hasAcceptHeader = false;
|
||||
|
||||
for (KeyValuePair pair : this.headers)
|
||||
{
|
||||
for (KeyValuePair pair : this.headers) {
|
||||
String key = pair.key.toString();
|
||||
connection.addRequestProperty(key, pair.value.toString());
|
||||
if (key.toLowerCase().contentEquals("accept-encoding")) {
|
||||
@@ -314,10 +348,8 @@ public class Async
|
||||
}
|
||||
}
|
||||
|
||||
public void writeContent(HttpURLConnection connection, Stack<Closeable> openedStreams) throws IOException
|
||||
{
|
||||
if (this.content == null || this.content.getClass() != String.class)
|
||||
{
|
||||
public void writeContent(HttpURLConnection connection, Stack<Closeable> openedStreams) throws IOException {
|
||||
if (this.content == null || this.content.getClass() != String.class) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -331,27 +363,7 @@ public class Async
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestResult
|
||||
{
|
||||
public static final class ByteArrayOutputStream2 extends ByteArrayOutputStream
|
||||
{
|
||||
public ByteArrayOutputStream2()
|
||||
{
|
||||
super();
|
||||
}
|
||||
|
||||
public ByteArrayOutputStream2(int size)
|
||||
{
|
||||
super(size);
|
||||
}
|
||||
|
||||
/** Returns the internal buffer of this ByteArrayOutputStream, without copying. */
|
||||
public synchronized byte[] buf()
|
||||
{
|
||||
return this.buf;
|
||||
}
|
||||
}
|
||||
|
||||
public static class RequestResult {
|
||||
public ByteArrayOutputStream raw;
|
||||
public ArrayList<KeyValuePair> headers = new ArrayList<KeyValuePair>();
|
||||
public int statusCode;
|
||||
@@ -361,18 +373,15 @@ public class Async
|
||||
public String url;
|
||||
public String statusText;
|
||||
|
||||
public void getHeaders(HttpURLConnection connection)
|
||||
{
|
||||
public void getHeaders(HttpURLConnection connection) {
|
||||
Map<String, List<String>> headers = connection.getHeaderFields();
|
||||
if (headers == null)
|
||||
{
|
||||
if (headers == null) {
|
||||
// no headers, this may happen if there is no internet connection currently available
|
||||
return;
|
||||
}
|
||||
|
||||
int size = headers.size();
|
||||
if (size == 0)
|
||||
{
|
||||
if (size == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -384,8 +393,7 @@ public class Async
|
||||
}
|
||||
}
|
||||
|
||||
public void readResponseStream(HttpURLConnection connection, Stack<Closeable> openedStreams, RequestOptions options) throws IOException
|
||||
{
|
||||
public void readResponseStream(HttpURLConnection connection, Stack<Closeable> openedStreams, RequestOptions options) throws IOException {
|
||||
int contentLength = connection.getContentLength();
|
||||
|
||||
InputStream inStream =
|
||||
@@ -393,8 +401,7 @@ public class Async
|
||||
? connection.getErrorStream()
|
||||
: connection.getInputStream();
|
||||
|
||||
if (inStream == null)
|
||||
{
|
||||
if (inStream == null) {
|
||||
// inStream is null when receiving status code 401 or 407
|
||||
// see this thread for more information http://stackoverflow.com/a/24986433
|
||||
return;
|
||||
@@ -417,8 +424,7 @@ public class Async
|
||||
|
||||
byte[] buff = new byte[4096];
|
||||
int read = -1;
|
||||
while ((read = buffer.read(buff, 0, buff.length)) != -1)
|
||||
{
|
||||
while ((read = buffer.read(buff, 0, buff.length)) != -1) {
|
||||
responseStream.write(buff, 0, read);
|
||||
}
|
||||
|
||||
@@ -429,8 +435,7 @@ public class Async
|
||||
// world for better performance
|
||||
// since we do not have some explicit way to determine whether
|
||||
// the content-type is image
|
||||
try
|
||||
{
|
||||
try {
|
||||
// TODO: Generally this approach will not work for very
|
||||
// large files
|
||||
BitmapFactory.Options bitmapOptions = new BitmapFactory.Options();
|
||||
@@ -438,22 +443,19 @@ public class Async
|
||||
|
||||
// check the size of the bitmap first
|
||||
BitmapFactory.decodeByteArray(responseStream.buf(), 0, responseStream.size(), bitmapOptions);
|
||||
if (bitmapOptions.outWidth > 0 && bitmapOptions.outHeight > 0)
|
||||
{
|
||||
if (bitmapOptions.outWidth > 0 && bitmapOptions.outHeight > 0) {
|
||||
int scale = 1;
|
||||
final int height = bitmapOptions.outHeight;
|
||||
final int width = bitmapOptions.outWidth;
|
||||
|
||||
if ((options.screenWidth > 0 && bitmapOptions.outWidth > options.screenWidth) ||
|
||||
(options.screenHeight > 0 && bitmapOptions.outHeight > options.screenHeight))
|
||||
{
|
||||
(options.screenHeight > 0 && bitmapOptions.outHeight > options.screenHeight)) {
|
||||
final int halfHeight = height / 2;
|
||||
final int halfWidth = width / 2;
|
||||
|
||||
// scale down the image since it is larger than the
|
||||
// screen resolution
|
||||
while ((halfWidth / scale) > options.screenWidth && (halfHeight / scale) > options.screenHeight)
|
||||
{
|
||||
while ((halfWidth / scale) > options.screenWidth && (halfHeight / scale) > options.screenHeight) {
|
||||
scale *= 2;
|
||||
}
|
||||
}
|
||||
@@ -462,63 +464,48 @@ public class Async
|
||||
bitmapOptions.inSampleSize = scale;
|
||||
this.responseAsImage = BitmapFactory.decodeByteArray(responseStream.buf(), 0, responseStream.size(), bitmapOptions);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// bitmap decoding failed, the stream is not an image
|
||||
e.printStackTrace();
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Failed to decode byte array, Exception: " + e.getMessage());
|
||||
}
|
||||
|
||||
if (this.responseAsImage == null)
|
||||
{
|
||||
if (this.responseAsImage == null) {
|
||||
// convert to string
|
||||
this.responseAsString = responseStream.toString();
|
||||
}
|
||||
}
|
||||
|
||||
public static final class ByteArrayOutputStream2 extends ByteArrayOutputStream {
|
||||
public ByteArrayOutputStream2() {
|
||||
super();
|
||||
}
|
||||
|
||||
public static void MakeRequest(final RequestOptions options, final CompleteCallback callback, final Object context)
|
||||
{
|
||||
if (cookieManager == null)
|
||||
{
|
||||
cookieManager = new CookieManager();
|
||||
CookieHandler.setDefault(cookieManager);
|
||||
public ByteArrayOutputStream2(int size) {
|
||||
super(size);
|
||||
}
|
||||
|
||||
final android.os.Handler mHandler = new android.os.Handler();
|
||||
threadPoolExecutor().execute(new Runnable() {
|
||||
@Override
|
||||
public void run () {
|
||||
final HttpRequestTask task = new HttpRequestTask(callback, context);
|
||||
final RequestResult result = task.doInBackground(options);
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run () {
|
||||
task.onPostExecute(result);
|
||||
/**
|
||||
* Returns the internal buffer of this ByteArrayOutputStream, without copying.
|
||||
*/
|
||||
public synchronized byte[] buf() {
|
||||
return this.buf;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static class HttpRequestTask
|
||||
{
|
||||
static class HttpRequestTask {
|
||||
private CompleteCallback callback;
|
||||
private Object context;
|
||||
|
||||
public HttpRequestTask(CompleteCallback callback, Object context)
|
||||
{
|
||||
public HttpRequestTask(CompleteCallback callback, Object context) {
|
||||
this.callback = callback;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
protected RequestResult doInBackground(RequestOptions... params)
|
||||
{
|
||||
protected RequestResult doInBackground(RequestOptions... params) {
|
||||
RequestResult result = new RequestResult();
|
||||
Stack<Closeable> openedStreams = new Stack<Closeable>();
|
||||
|
||||
try
|
||||
{
|
||||
try {
|
||||
RequestOptions options = params[0];
|
||||
URL url = new URL(options.url);
|
||||
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
|
||||
@@ -531,8 +518,7 @@ public class Async
|
||||
options.addHeaders(connection);
|
||||
|
||||
// apply timeout
|
||||
if (options.timeout > 0)
|
||||
{
|
||||
if (options.timeout > 0) {
|
||||
connection.setConnectTimeout(options.timeout);
|
||||
}
|
||||
|
||||
@@ -542,8 +528,7 @@ public class Async
|
||||
}
|
||||
|
||||
// Do not attempt to write the content (body) for DELETE method, Java will throw directly
|
||||
if (!requestMethod.equals(DELETE_METHOD))
|
||||
{
|
||||
if (!requestMethod.equals(DELETE_METHOD)) {
|
||||
options.writeContent(connection, openedStreams);
|
||||
}
|
||||
|
||||
@@ -558,8 +543,7 @@ public class Async
|
||||
result.url = options.url;
|
||||
result.statusCode = connection.getResponseCode();
|
||||
result.statusText = connection.getResponseMessage();
|
||||
if (!requestMethod.equals(HEAD_METHOD))
|
||||
{
|
||||
if (!requestMethod.equals(HEAD_METHOD)) {
|
||||
result.readResponseStream(connection, openedStreams, options);
|
||||
}
|
||||
|
||||
@@ -570,36 +554,30 @@ public class Async
|
||||
connection.disconnect();
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception e) // TODO: Catch all exceptions?
|
||||
} catch (Exception e) // TODO: Catch all exceptions?
|
||||
{
|
||||
result.error = e;
|
||||
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
try
|
||||
{
|
||||
} finally {
|
||||
try {
|
||||
this.closeOpenedStreams(openedStreams);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
// TODO: Java rules - what to do here???
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Failed to close opened streams, IOException: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void onPostExecute(final RequestResult result)
|
||||
{
|
||||
protected void onPostExecute(final RequestResult result) {
|
||||
if (result != null) {
|
||||
this.callback.onComplete(result, this.context);
|
||||
} else {
|
||||
this.callback.onError(this.context);
|
||||
}
|
||||
}
|
||||
|
||||
private void closeOpenedStreams(Stack<Closeable> streams) throws IOException
|
||||
{
|
||||
while (streams.size() > 0)
|
||||
{
|
||||
private void closeOpenedStreams(Stack<Closeable> streams) throws IOException {
|
||||
while (streams.size() > 0) {
|
||||
Closeable stream = streams.pop();
|
||||
stream.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user