/* * Copyright (c) 2013. wyouflf (wyouflf@gmail.com) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.robam.xutils; import android.text.TextUtils; import org.apache.http.Header; import org.apache.http.HeaderElement; import org.apache.http.HttpEntity; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; import org.apache.http.HttpResponseInterceptor; import org.apache.http.HttpVersion; import org.apache.http.client.HttpClient; import org.apache.http.conn.params.ConnManagerParams; import org.apache.http.conn.params.ConnPerRouteBean; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.protocol.BasicHttpContext; import org.apache.http.protocol.HTTP; import org.apache.http.protocol.HttpContext; import org.robam.xutils.core.SimpleSSLSocketFactory; import org.robam.xutils.http.HttpException; import org.robam.xutils.http.HttpCache; import org.robam.xutils.http.HttpHandler; import org.robam.xutils.http.RequestParams; import org.robam.xutils.http.ResponseStream; import org.robam.xutils.http.SyncHttpHandler; import org.robam.xutils.http.callback.HttpRedirectHandler; import org.robam.xutils.http.callback.RequestCallBack; import org.robam.xutils.http.client.HttpRequest; import org.robam.xutils.http.client.RetryHandler; import org.robam.xutils.http.client.entity.GZipDecompressingEntity; import java.io.File; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; /** * Http请求获取数据..主要分四种.1.异步请求一般的字符串.2.同步请求字符串.3.下载.4.上传 * 此类只要一创建出来,线程池就已经建好了.所以为了效率考虑,当不需要就不要随便new出来.避免资源的浪费. * <p/> * 以下是Http上传的示例 * <p/> * RequestParams params = new RequestParams(); * params.addHeader("name", "value"); * params.addQueryStringParameter("name", "value"); * <p/> * // 只包含字符串参数时默认使用BodyParamsEntity, * // 类似于UrlEncodedFormEntity("application/x-www-form-urlencoded")。 * params.addBodyParameter("name", "value"); * <p/> * // 加入文件参数后默认使用MultipartEntity("multipart/form-data"), * // 如需"multipart/related",xUtils中提供的MultipartEntity支持设置subType为"related"。 * // 使用params.setBodyEntity(httpEntity)可设置更多类型的HttpEntity(如: * // MultipartEntity,BodyParamsEntity,FileUploadEntity,InputStreamUploadEntity,StringEntity)。 * // 例如发送json参数:params.setBodyEntity(new StringEntity(jsonStr,charset)); * params.addBodyParameter("file", new File("path")); * ... * <p/> * HttpUtils http = new HttpUtils(); * http.send(HttpRequest.HttpMethod.POST, * "uploadUrl....", * params, * new RequestCallBack<String>() { * * @Override public void onStart() { * testTextView.setText("conn..."); * } * @Override public void onLoading(long total, long current, boolean isUploading) { * if (isUploading) { * testTextView.setText("upload: " + current + "/" + total); * } else { * testTextView.setText("reply: " + current + "/" + total); * } * } * @Override public void onSuccess(ResponseInfo<String> responseInfo) { * testTextView.setText("reply: " + responseInfo.result); * } * @Override public void onFailure(HttpException error, String msg) { * testTextView.setText(error.getExceptionCode() + ":" + msg); * } * }); */ public class HttpUtils { /** * HTTP缓存.都使用默认的设置 */ public final static HttpCache sHttpCache = new HttpCache(); /** * Default HTTP client.Http是使用HttpClient的方式请求.但是官方已经不推荐使用HttpClient,而是HttpUrlConnect */ private final DefaultHttpClient httpClient; /** * TODO:??? */ private final HttpContext httpContext = new BasicHttpContext(); /** * 重定向处理器. */ private HttpRedirectHandler httpRedirectHandler; // ************************************ default settings & fields **************************** private String responseTextCharset = HTTP.UTF_8; private long currentRequestExpiry = HttpCache.getDefaultExpiryTime(); private final static int DEFAULT_CONN_TIMEOUT = 1000 * 15; // 15s private final static int DEFAULT_RETRY_TIMES = 5; private static final String HEADER_ACCEPT_ENCODING = "Accept-Encoding"; private static final String ENCODING_GZIP = "gzip"; private static final String USER_AGENT = "robam"; /** * 线程池大小.只用3个.考虑到Http是长时间连接,如果太多的话,可能会消耗过多的CPU资源. */ private static int threadPoolSize = 3; /** * ExecutorService是线程池服务.系统会自动维护. * Executors是工厂类,生产线程池. * newFixedThreadPool可以产生固定执行线程的线程池.参数:ThreadFactory就是传递一个可以产生线程的工厂类给他, * 它会使用这个工厂类产生新线程. * 当线程超过线程固定执行的数量时候,必须要等待执行中的结束. */ private static ExecutorService executor; /** * 用来新建线程出来,工厂类.参考:DefaultThreadFactory. */ private static ThreadFactory sThreadFactory; static { sThreadFactory = new ThreadFactory() { //自增的 int 值 private final AtomicInteger mCount = new AtomicInteger(1); @Override public Thread newThread(Runnable r) { Thread thread = new Thread(r, "HttpUtils #" + mCount.getAndIncrement()); //设置优先级.都比默认的低1. thread.setPriority(Thread.NORM_PRIORITY - 1); return thread; } }; executor = Executors.newFixedThreadPool(threadPoolSize, sThreadFactory); } public HttpClient getHttpClient() { return this.httpClient; } public HttpUtils() { this(HttpUtils.DEFAULT_CONN_TIMEOUT); } /** * 创建一个HTTPUtils * * @param connTimeout */ public HttpUtils(int connTimeout) { // HTTP请求参数.Notice:这写参数只是设置到params存起来,暂时还没有应用,所以后面要调用,否则不会生效. HttpParams params = new BasicHttpParams(); // 设置HTTP请求参数. // 这是设置连接超时 ConnManagerParams.setTimeout(params, connTimeout); HttpConnectionParams.setConnectionTimeout(params, connTimeout); // Socket超时 HttpConnectionParams.setSoTimeout(params, connTimeout); //UserAgent HttpProtocolParams.setUserAgent(params, USER_AGENT); ConnManagerParams.setMaxConnectionsPerRoute(params, new ConnPerRouteBean(10)); ConnManagerParams.setMaxTotalConnections(params, 10); HttpConnectionParams.setTcpNoDelay(params, true); HttpConnectionParams.setSocketBufferSize(params, 1024 * 8); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); // 支持的协议集合,比如http,https等. Schemes are identified by lowercase names. SchemeRegistry schemeRegistry = new SchemeRegistry(); // 注册支持的协议 schemeRegistry.register(new Scheme("http", PlainSocketFactory.getSocketFactory(), 80)); schemeRegistry.register(new Scheme("https", SimpleSSLSocketFactory.getSocketFactory(), 443)); // ThreadSafeClientConnManager:Manages a pool of client connections // 创建新的Http客户端 httpClient = new DefaultHttpClient(new ThreadSafeClientConnManager(params, schemeRegistry), params); // 重试 httpClient.setHttpRequestRetryHandler(new RetryHandler(DEFAULT_RETRY_TIMES)); /** * 请求拦截器. */ httpClient.addRequestInterceptor(new HttpRequestInterceptor() { /** *拦截器里面对Http增加一个请求头,对即将发送出去的数据,如果没有增加压缩方式,就添加"GZIP". * @param httpRequest * @param httpContext * @throws org.apache.http.HttpException * @throws java.io.IOException */ @Override public void process(org.apache.http.HttpRequest httpRequest, HttpContext httpContext) throws org.apache.http.HttpException, IOException { // 添加HTTP请求头.压缩方法+ GZIP if (!httpRequest.containsHeader(HEADER_ACCEPT_ENCODING)) { httpRequest.addHeader(HEADER_ACCEPT_ENCODING, ENCODING_GZIP); } } }); /** * HTTP响应拦截器.为的是添加一个GZIP压缩标志 */ httpClient.addResponseInterceptor(new HttpResponseInterceptor() { @Override public void process(HttpResponse response, HttpContext httpContext) throws org.apache.http.HttpException, IOException { final HttpEntity entity = response.getEntity(); if (entity == null) { return; } final Header encoding = entity.getContentEncoding(); if (encoding != null) { // 遍历每个元素 for (HeaderElement element : encoding.getElements()) { if (element.getName().equalsIgnoreCase("gzip")) { // 添加压缩头 response.setEntity(new GZipDecompressingEntity(response.getEntity())); return; } } } } }); } // ***************************************** config ******************************************* /** * 设置响应的字符集.默认为UTF-8.. * * @param charSet * @return */ public HttpUtils configResponseTextCharset(String charSet) { if (!TextUtils.isEmpty(charSet)) { this.responseTextCharset = charSet; } return this; } /** * 重试次数. * * @param count * @return */ public HttpUtils configRequestRetryCount(int count) { this.httpClient.setHttpRequestRetryHandler(new RetryHandler(count)); return this; } /** * 线程池大小.这个很重要 * * @param threadPoolSize * @return */ public HttpUtils configRequestThreadPoolSize(int threadPoolSize) { if (threadPoolSize > 0 && threadPoolSize != HttpUtils.threadPoolSize) { HttpUtils.threadPoolSize = threadPoolSize; HttpUtils.executor = Executors.newFixedThreadPool(threadPoolSize, sThreadFactory); } return this; } // ***************************************** send request ******************************************* /** * 最简单的GET请求.异步 * * @param url URL * @param callBack 回调方法. * @param <T> * @return */ public <T> HttpHandler<T> send(String url, RequestCallBack<T> callBack) { return send(HttpRequest.HttpMethod.GET, url, null, callBack); } /** * 异步请求. * * @param method * @param url * @param params * @param callBack * @param <T> * @return */ public <T> HttpHandler<T> send(HttpRequest.HttpMethod method, String url, RequestParams params, RequestCallBack<T> callBack) { if (url == null) throw new IllegalArgumentException("url may not be null"); HttpRequest request = new HttpRequest(method, url); return sendRequest(request, params, callBack); } /** * 同步请求.没有使用线程. * * @param method * @param url * @param params * @return * @throws HttpException */ public ResponseStream sendSync(HttpRequest.HttpMethod method, String url, RequestParams params) throws HttpException { if (url == null) throw new IllegalArgumentException("url may not be null"); HttpRequest request = new HttpRequest(method, url); return sendSyncRequest(request, params); } // ***************************************** download ******************************************* public HttpHandler<File> download(String url, String target, RequestParams params, boolean autoResume, boolean autoRename, RequestCallBack<File> callback) { return download(HttpRequest.HttpMethod.GET, url, target, params, autoResume, autoRename, callback); } /** * 这才是下载的关键. * * @param method HTTP方法,Get,Post * @param url 下载Url * @param target 路径.包括文件名.如果需要重命名的话也需要传一个名称过来.后面是以Parent为准的. * @param params 自定义的请求参数 * @param autoResume 断点续传 * @param autoRename 重命名,根据响应头. * @param callback 下载状况监听器. * @return */ public HttpHandler<File> download(HttpRequest.HttpMethod method, String url, String target, RequestParams params, boolean autoResume, boolean autoRename, RequestCallBack<File> callback) { if (url == null) throw new IllegalArgumentException("url may not be null"); if (target == null) throw new IllegalArgumentException("target may not be null"); HttpRequest request = new HttpRequest(method, url); HttpHandler<File> handler = new HttpHandler<>(httpClient, httpContext, responseTextCharset, callback); handler.setExpiry(currentRequestExpiry); handler.setHttpRedirectHandler(httpRedirectHandler); request.setRequestParams(params, handler); handler.executeOnExecutor(executor, request, target, autoResume, autoRename); return handler; } //////////////////////////////////////////////////////////////////////////////////////////////// /** * 异步请求. * 异步请求和同步请求设计得不好.HttpHandler应该设计成一个接口,需要实现请求的就实现这个接口. * * @param request * @param params * @param callBack * @param <T> * @return */ private <T> HttpHandler<T> sendRequest(HttpRequest request, RequestParams params, RequestCallBack<T> callBack) { HttpHandler<T> handler = new HttpHandler<T>(httpClient, httpContext, responseTextCharset, callBack); handler.setExpiry(currentRequestExpiry); handler.setHttpRedirectHandler(httpRedirectHandler); request.setRequestParams(params, handler); handler.executeOnExecutor(executor, request); return handler; } /** * 同步请求. * * @param request * @param params * @return * @throws HttpException */ private ResponseStream sendSyncRequest(HttpRequest request, RequestParams params) throws HttpException { SyncHttpHandler handler = new SyncHttpHandler(httpClient, httpContext, responseTextCharset); handler.setExpiry(currentRequestExpiry); handler.setHttpRedirectHandler(httpRedirectHandler); request.setRequestParams(params); return handler.sendRequest(request); } }