package cn.trinea.android.common.service; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import android.content.Context; import android.os.AsyncTask; import android.os.Build; import cn.trinea.android.common.constant.HttpConstants; import cn.trinea.android.common.dao.HttpCacheDao; import cn.trinea.android.common.dao.impl.HttpCacheDaoImpl; import cn.trinea.android.common.entity.HttpRequest; import cn.trinea.android.common.entity.HttpResponse; import cn.trinea.android.common.service.impl.ImageCache; import cn.trinea.android.common.util.ArrayUtils; import cn.trinea.android.common.util.HttpUtils; import cn.trinea.android.common.util.SqliteUtils; import cn.trinea.android.common.util.StringUtils; import cn.trinea.android.common.util.SystemUtils; /** * <strong>Http Cache</strong><br/> * <br/> * It applies to get and cache api data from server, like json or xml and so on. It applies to apps like weixin, weibo, * twitter, taobao and so on. If want to cache image, please use {@link cn.trinea.android.common.service.impl.ImageCache}<br/> * <ul> * <strong>Constructor</strong> * <li>{@link #HttpCache(android.content.Context)} to init cache</li> * </ul> * <ul> * <strong>Get data asynchronous</strong> * <li>{@link #httpGet(cn.trinea.android.common.entity.HttpRequest, cn.trinea.android.common.service.HttpCache.HttpCacheListener)}</li> * <li>{@link #httpGet(String, cn.trinea.android.common.service.HttpCache.HttpCacheListener)}</li> * </ul> * <ul> * <strong>Get data synchronous</strong> * <li>{@link #httpGet(cn.trinea.android.common.entity.HttpRequest)}</li> * <li>{@link #httpGet(String)}</li> * <li>{@link #httpGetString(cn.trinea.android.common.entity.HttpRequest)}</li> * <li>{@link #httpGetString(String)}</li> * </ul> * * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-11-1 */ public class HttpCache { private Context context; /** http memory cache **/ private Map<String, HttpResponse> cache; /** dao to get data from http db cache **/ private HttpCacheDao httpCacheDao; private int type = -1; /** Default {@link java.util.concurrent.Executor} that be used to execute tasks in parallel. **/ public static final Executor THREAD_POOL_EXECUTOR = Executors .newFixedThreadPool(SystemUtils.DEFAULT_THREAD_POOL_SIZE); public HttpCache(Context context) { if (context == null) { throw new IllegalArgumentException("The context can not be null."); } this.context = context; cache = new ConcurrentHashMap<String, HttpResponse>(); httpCacheDao = new HttpCacheDaoImpl(SqliteUtils.getInstance(context)); } /** * waiting to be perfect^_^ * * @param context * @param type get httpResponse whose type is type into memory as primary cache to improve performance */ private HttpCache(Context context, int type) { this(context); this.type = type; initData(type); } /** * get httpResponse whose type is type into memory as primary cache to improve performance * * @param type */ private void initData(int type) { this.cache = httpCacheDao.getHttpResponsesByType(type); if (cache == null) { cache = new HashMap<String, HttpResponse>(); } } /** * http get * <ul> * <strong>Attentions:</strong> * <li>Don't call this on the ui thread, it may costs some times. Becaust if not in cache, it get from network * synchronous.</li> * <li>If you want get data asynchronous, use {@link cn.trinea.android.common.service.HttpCache#httpGet(cn.trinea.android.common.entity.HttpRequest, cn.trinea.android.common.service.HttpCache.HttpCacheListener)}</li> * </ul> * * @param httpRequest * @return the response of the url, if null represents http error */ public HttpResponse httpGet(HttpRequest request) { String url; if (request == null || StringUtils.isEmpty(url = request.getUrl())) { return null; } HttpResponse cacheResponse = null; boolean isNoCache = false, isNoStore = false; String requestCacheControl = request.getRequestProperty(HttpConstants.CACHE_CONTROL); if (!StringUtils.isEmpty(requestCacheControl)) { String[] requestCacheControls = requestCacheControl.split(","); if (!ArrayUtils.isEmpty(requestCacheControls)) { List<String> requestCacheControlList = new ArrayList<String>(); for (String s : requestCacheControls) { if (s == null) { continue; } requestCacheControlList.add(s.trim()); } if (requestCacheControlList.contains("no-cache")) { isNoCache = true; } if (requestCacheControlList.contains("no-store")) { isNoStore = true; } } } if (!isNoCache) { cacheResponse = getFromCache(url); } return cacheResponse == null ? (isNoStore ? HttpUtils.httpGet(url) : putIntoCache(HttpUtils.httpGet(url))) : cacheResponse; } /** * http get * <ul> * <li>It gets data from cache or network asynchronous.</li> * <li>If you want get data synchronous, use {@link cn.trinea.android.common.service.HttpCache#httpGet(cn.trinea.android.common.entity.HttpRequest)} or * {@link cn.trinea.android.common.service.HttpCache#httpGetString(cn.trinea.android.common.entity.HttpRequest)}</li> * </ul> * * @param url * @param listener listener which can do something before or after HttpGet. this can be null if you not want to do * something */ public void httpGet(String url, HttpCacheListener listener) { // if bigger than android 4.0 use executeOnExecutor, else use execute if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { new HttpCacheStringAsyncTask(listener).executeOnExecutor(THREAD_POOL_EXECUTOR, url); } else { new HttpCacheStringAsyncTask(listener).execute(url); } } /** * http get * <ul> * <li>It gets data from cache or network asynchronous.</li> * <li>If you want get data synchronous, use {@link cn.trinea.android.common.service.HttpCache#httpGet(cn.trinea.android.common.entity.HttpRequest)} or * {@link cn.trinea.android.common.service.HttpCache#httpGetString(cn.trinea.android.common.entity.HttpRequest)}</li> * </ul> * * @param request * @param listener listener which can do something before or after HttpGet. this can be null if you not want to do * something */ public void httpGet(HttpRequest request, HttpCacheListener listener) { // if bigger than android 4.0 use executeOnExecutor, else use execute if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { new HttpCacheRequestAsyncTask(listener).executeOnExecutor(THREAD_POOL_EXECUTOR, request); } else { new HttpCacheRequestAsyncTask(listener).execute(request); } } /** * http get * <ul> * <strong>Attentions:</strong> * <li>Don't call this on the ui thread, it may costs some times. Becaust if not in cache, it get from network * synchronous.</li> * <li>If you want get data asynchronous, use {@link cn.trinea.android.common.service.HttpCache#httpGet(cn.trinea.android.common.entity.HttpRequest, cn.trinea.android.common.service.HttpCache.HttpCacheListener)}</li> * </ul> * * @param url * @return the response of the url, if null represents http error */ public HttpResponse httpGet(String url) { return httpGet(new HttpRequest(url)); } /** * http get * <ul> * <strong>Attentions:</strong> * <li>Don't call this on the ui thread, it may costs some times. Becaust if not in cache, it get from network * synchronous.</li> * <li>If you want get data asynchronous, use {@link cn.trinea.android.common.service.HttpCache#httpGet(String, cn.trinea.android.common.service.HttpCache.HttpCacheListener)}</li> * </ul> * * @param url * @return the response body of the url, if null represents http error */ public String httpGetString(String url) { HttpResponse cacheResponse = httpGet(new HttpRequest(url)); return cacheResponse == null ? null : cacheResponse.getResponseBody(); } /** * http get * <ul> * <strong>Attentions:</strong> * <li>Don't call this on the ui thread, it may costs some times. Becaust if not in cache, it get from network * synchronous.</li> * <li>If you want get data asynchronous, use {@link cn.trinea.android.common.service.HttpCache#httpGet(cn.trinea.android.common.entity.HttpRequest, cn.trinea.android.common.service.HttpCache.HttpCacheListener)}</li> * </ul> * * @param httpRequest * @return the response body of the url, if null represents http error */ public HttpResponse httpGetString(HttpRequest httpRequest) { return httpGet(httpRequest); } /** * whether this cache contains the specified url. * * @param url * @return true if this cache contains the specified url and the element is valid, false otherwise. */ public boolean containsKey(String url) { return getFromCache(url) != null; } /** * whether the element of the specified url has invalided * * @param url * @return true if the element of the specified url has invalided, false otherwise. */ protected boolean isExpired(String url) { return getFromCache(url) == null; } /** * Removes all elements from this cache, leaving it empty. */ public void clear() { cache.clear(); httpCacheDao.deleteAllHttpResponse(); } /** * HttpCacheListener, can do something before or after HttpGet * * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-11-15 */ public static abstract class HttpCacheListener { /** * Runs on the UI thread before httpGet.<br/> * <ul> * <li>this can be null if you not want to do something</li> * </ul> */ protected void onPreGet() {} /** * Runs on the UI thread after httpGet. The httpResponse is returned by httpGet. * <ul> * <li>this can be null if you not want to do something</li> * </ul> * * @param httpResponse get by the url * @param isInCache the data responsed to the url whether is in cache */ protected void onPostGet(HttpResponse httpResponse, boolean isInCache) {} } /** * get type, waiting to be perfect^_^ * * @return the type */ private int getType() { return type; } /** * put response into cache * <ul> * <li>put response to db, if {@link cn.trinea.android.common.entity.HttpResponse#getType()} == {@link cn.trinea.android.common.service.HttpCache#getType()}, also put into memory * cache</li> * </ul> * * @param httpResponse * @return if insert into db error, return null, otherwise return HttpResponse */ private HttpResponse putIntoCache(HttpResponse httpResponse) { String url; if (httpResponse == null || (url = httpResponse.getUrl()) == null) { return null; } if (type != -1 && type == httpResponse.getType()) { cache.put(url, httpResponse); } return (httpCacheDao.insertHttpResponse(httpResponse) == -1) ? null : httpResponse; } /** * get from memory cache first, if not exist in memory cache, get from db * * @param url * @return <ul> * <li>if neither exit in memory cache nor db, return null</li> * <li>if is expired, return null, otherwise return cache response</li> * </ul> */ public HttpResponse getFromCache(String url) { if (StringUtils.isEmpty(url)) { return null; } HttpResponse cacheResponse = cache.get(url); if (cacheResponse == null) { cacheResponse = httpCacheDao.getHttpResponse(url); } return (cacheResponse == null || cacheResponse.isExpired()) ? null : cacheResponse.setInCache(true); } /** * AsyncTask to get data by String url * * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-11-15 */ private class HttpCacheStringAsyncTask extends AsyncTask<String, Void, HttpResponse> { private HttpCacheListener listener; public HttpCacheStringAsyncTask(HttpCacheListener listener) { this.listener = listener; } protected HttpResponse doInBackground(String... url) { if (ArrayUtils.isEmpty(url)) { return null; } return httpGet(url[0]); } protected void onPreExecute() { if (listener != null) { listener.onPreGet(); } } protected void onPostExecute(HttpResponse httpResponse) { if (listener != null) { listener.onPostGet(httpResponse, httpResponse == null ? false : httpResponse.isInCache()); } } } /** * AsyncTask to get data by HttpRequest * * @author <a href="http://www.trinea.cn" target="_blank">Trinea</a> 2013-11-15 */ private class HttpCacheRequestAsyncTask extends AsyncTask<HttpRequest, Void, HttpResponse> { private HttpCacheListener listener; public HttpCacheRequestAsyncTask(HttpCacheListener listener) { this.listener = listener; } protected HttpResponse doInBackground(HttpRequest... httpRequest) { if (ArrayUtils.isEmpty(httpRequest)) { return null; } return httpGet(httpRequest[0]); } protected void onPreExecute() { if (listener != null) { listener.onPreGet(); } } protected void onPostExecute(HttpResponse httpResponse) { if (listener != null) { listener.onPostGet(httpResponse, httpResponse == null ? false : httpResponse.isInCache()); } } } }