package yuku.afw.rpc; import android.content.Context; import android.os.AsyncTask; import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; import android.widget.Toast; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import yuku.afw.App; import yuku.afw.D; import yuku.afw.rpc.Response.Validity; /** * Listener tree: * - OnResponse * - OnSuccess * - OnFailed * - OnApiError * * When a listener returns false, the parent listener will be called. Otherwise no more listeners will be called. */ public class AsyncRequest<Z extends BaseData> { public static final String TAG = AsyncRequest.class.getSimpleName(); protected static List<OnLoadingStatusChangedListener> onLoadingStatusChangedListeners = new ArrayList<AsyncRequest.OnLoadingStatusChangedListener>(16); protected static AtomicInteger activeCount = new AtomicInteger(0); protected static AtomicInteger serialNumber = new AtomicInteger(0); private static Object onLoadingStatusChangedListeners_lock = new Object(); public enum LoadingStatus { Start, Stop, } public interface OnLoadingStatusChangedListener { void onLoadingStatusChanged(LoadingStatus status, int activeCount); } protected void onSuccess(Response response, Z data) { } protected void onFailed(Response response, Z data) { } protected void onResponse(Response response, Z data) { } protected void onApiError(Response response, Z data) { } private final Request request; private Z data; private Task<Z> task; private SparseArray<Object> tags; private boolean finished = false; private int id; private boolean consumed; public AsyncRequest(Request request, Z data) { this.request = request; this.data = data; this.task = new Task<Z>(); this.id = serialNumber.incrementAndGet(); } public void cancel() { task.cancel(false); finished = true; } public synchronized void setTag(Object tag) { setTag(0, tag); } public synchronized void setTag(int id, Object tag) { if (tags == null) { tags = new SparseArray<Object>(2); } tags.put(id, tag); } public synchronized <T> T getTag() { return this.<T>getTag(0); } @SuppressWarnings("unchecked") public synchronized <T> T getTag(int id) { return (T) tags.get(id); } public class Task<Y> extends AsyncTask<Request, Integer, Void> { final Z return_data; // should be never null Response return_response; Request request; public Task() { return_data = AsyncRequest.this.data; } @Override protected Void doInBackground(Request... params) { request = params[0]; HttpPerformer httpPerformer = new HttpPerformer(this, request); if (D.EBUG) Log.d(TAG, "async start [" + id + "] (" + getActiveCount() + " active, total " + Thread.activeCount() + " threads) " + request.toString()); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ return_response = httpPerformer.perform(); if (return_data.isSuccessResponse(return_response)) { ResponseProcessor rp = return_data.getResponseProcessor(return_response); try { rp.process(return_response.data); } catch (Exception e) { return_response.validity = Validity.ProcessError; Log.w(TAG, "Error during ResponseProcessor#process", e); } } return null; } @Override protected void onCancelled() { return_response = new Response(request, Validity.Cancelled, "cancelled from asynctask#onCancelled"); onPostExecute(null); //$NON-NLS-1$ } @Override protected void onPostExecute(Void result) { if (D.EBUG) Log.d(TAG, "async stop [" + id + "] response: " + return_response.toString()); //$NON-NLS-1$//$NON-NLS-2$ try { onReceiveResponse(return_response, return_data); } finally { trackStop(); } } } public static void trackStop() { int activeCount = AsyncRequest.activeCount.decrementAndGet(); synchronized (onLoadingStatusChangedListeners) { for (int i = 0, len = onLoadingStatusChangedListeners.size(); i < len; i++) { onLoadingStatusChangedListeners.get(i).onLoadingStatusChanged(LoadingStatus.Stop, activeCount); } } } protected void onReceiveResponse(Response response, Z data) { if (data.isSuccessResponse(response)) { onSuccess(response, data); } else { if (response.validity == Validity.IoError) { showErrorToastIfNoRecentErrorToast(App.context, "Network error"); } else if (response.validity == Validity.JsonError) { showErrorToastIfNoRecentErrorToast(App.context, "Response from network error"); } if (response.validity == Validity.Ok) { onApiError(response, data); } if (! consumed) { onFailed(response, data); } } if (! consumed) { onResponse(response, data); } this.finished = true; } private static long lastErrorToastTime = -1; private static void showErrorToastIfNoRecentErrorToast(Context context, String msg) { long now = SystemClock.uptimeMillis(); if (lastErrorToastTime == -1 || now - lastErrorToastTime > 2000L) { Toast.makeText(context, msg, Toast.LENGTH_SHORT).show(); lastErrorToastTime = now; } } public static void setLastErrorToastTime(long lastErrorToastTime) { AsyncRequest.lastErrorToastTime = lastErrorToastTime; } public AsyncRequest<Z> start() { trackStart(); task.execute(request); return this; } public static void trackStart() { int activeCount = AsyncRequest.activeCount.incrementAndGet(); synchronized (onLoadingStatusChangedListeners_lock) { for (int i = 0, len = onLoadingStatusChangedListeners.size(); i < len; i++) { onLoadingStatusChangedListeners.get(i).onLoadingStatusChanged(LoadingStatus.Start, activeCount); } } } public Request getRequest() { return request; } public static void addOnLoadingStatusChangedListener(OnLoadingStatusChangedListener onLoadingStatusChangedListener) { synchronized (onLoadingStatusChangedListeners_lock) { onLoadingStatusChangedListeners.add(onLoadingStatusChangedListener); } } public static void removeOnLoadingStatusChangedListener(OnLoadingStatusChangedListener onLoadingStatusChangedListener) { synchronized (onLoadingStatusChangedListeners_lock) { onLoadingStatusChangedListeners.remove(onLoadingStatusChangedListener); } } public static int getActiveCount() { return activeCount.get(); } /** * Used to determine if this request has no more effect and hence safe to do additional requests whose response * may conflict with the currently executing request's response. * @return true if this task has been returned with a response OR {@link #cancel()} has been called. */ public boolean isFinished() { return finished; } public boolean isCancelled() { return task.isCancelled(); } protected void consume() { consumed = true; } }