本文来自 http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!
随着项目日趋稳定,需求不再总是变化,那么是时间来整理下项目了。先简单介绍下,本项目最初使用loop4j(即async-http)框架,仅98kb大小,使用也比较方便,为什么要选用它呢?13年的时候其他框架还没那么成熟,咱们做项目稳定第一,其次流畅,再次性能,而它刚好满足这个条件;不好的地方在于请求慢,而且回调显得烦琐。
使用方法如下:
1、初始化请求客户端
private static AsyncHttpClient client; /** * 重试3次<br> * * 超时20s */ static { client = new MyAsyncHttpClient(); client.setTimeout(10 * 1000);//要设置超时间,默认的为10s client.setMaxRetriesAndTimeout(3, 10 * 1000); client.setEnableRedirects(false); // 允许环形重定向和设置重定向最大次数。 client.getHttpClient().getParams().setParameter(ClientPNames.MAX_REDIRECTS, 3); client.getHttpClient().getParams().setParameter(ClientPNames.ALLOW_CIRCULAR_REDIRECTS, false); }
class MyAsyncHttpClient extends AsyncHttpClient { @Override public void setEnableRedirects(final boolean enableRedirects) { ((DefaultHttpClient) getHttpClient()).setRedirectHandler(new DefaultRedirectHandler() { @Override public boolean isRedirectRequested(HttpResponse response, HttpContext context) { int statusCode = response.getStatusLine().getStatusCode(); if (statusCode == 301 || statusCode == 302) { return enableRedirects; } return false; } }); } }
2、设置返回调用
protected JsonHttpResponseHandler responseHandler = new JsonHttpResponseHandler() { /** * Returns when request failed * * @param statusCode http response status line * @param headers response headers if any * @param throwable throwable describing the way request failed * @param errorResponse parsed response if any */ public void onFailure(int statusCode, Throwable throwable, JSONObject errorResponse) { boolean isNetAvailable = FactoryProxy.getInstance().getNetStatusManager().isNetAvailable(); mTask.on Error(DEFAULT_TASK_WHAT, isNetAvailable ? "加载失败,请重试" : "网络异常"); Message message = new Message(); message.what = MSG_CODE_LOAD_ERROR; message.obj = DEFAULT_TASK_WHAT; handler.sendMessage(message); } @Override public void onSuccess(int statusCode, JSONObject response) { super.onSuccess(statusCode, response); setPtrFinished(); // 访问失败 if (statusCode != 200) { // showToast(R.string.common_str_net_invailable); return; } // 如果返回的编号小于0的话证明有错误 int error_no = response.optInt("erro_no"); if (error_no < 0) { switch (error_no) { case -102:// 异常处理 //要用到在当前页面处理的 mTask.on Error(DEFAULT_TASK_WHAT, response.optString("error_no", "暂无数据")); break; default: mTask.reset(); break; } Message message = handler.obtainMessage(error_no, response); handler.sendMessage(message);//主要用于toast } else { // 否则没有错误解析数据 if (response.has(API_METHOD_DATA)) { Object json = response.get(API_METHOD_DATA); if (json instanceof JSONObject) { CMYJSONObject obj = response.optBaseJSONObject(API_METHOD_DATA); mTask.onFinish(DEFAULT_TASK_WHAT, obj); } else if (json instanceof JSONArray) { mTask.onFinish(DEFAULT_TASK_WHAT, response); } else { mTask.onFinish(DEFAULT_TASK_WHAT, response); } } else { mTask.onFinish(DEFAULT_TASK_WHAT, response); } } } };
3、发起请求
protected void sendRequest(String method, LXBaseRequest request, AsyncHttpResponseHandler responseHandler) { RequestParams requestParams = FactoryProxy.getInstance().getAccountManager().getRequestParams(request); HttpBusinessAPI.post(method, requestParams, responseHandler); }
当然一些方法是本项目中封装过的,比如获取参数的AccountManager、网络信号管理器,也包含了一些项目的加载过程,可见一斑,有兴趣者可以一起讨论。
关于OkHttp是一个月前准备开工的,当时也想把项目整体框架重构一下,最大块也是网络请求层和逻辑处理层,其他公用组件仅做相应的适配即可,使用初期遇到一些问题,最后改完整个项目之后,发现请求速度快了大概30%左右吧。
这里先讲几个重构过程中遇到的问题:
1、是项目要求上传JsonRequest而非JsonString,而demo和网上也多是基于JsonString,这时两个解决办法,一个是要求后端改上传数据的要求,一种就是自己修改,跟后端沟通中也提出上传参数效率的问题,但项目比较大而且涉及到ios方面改动太大而作罢,最终使用下面的方案解决-使用对象格式
FormBody.Builder builder = new FormBody.Builder(); for (Iterator<String> iterator = params.keys(); iterator.hasNext(); ) { String key = iterator.next().toString(); String value = params.optString(key); builder.add(key, value); } FormBody body = builder.build(); Request request = new Request.Builder() .url(url) .post(body) .build();
下面是网上的示例-使用Json数据格式
RequestBody body = RequestBody.create(JSON, json); Request request = new Request.Builder() .url(url) .post(body) .build(); Response response = client.newCall(request).execute();
同时这个问题的解决还有个小插曲,关于数据格式的,OkHttp默认使用application/x-www-form-urlencoded默认是键值对;而另外一种multipart/form-data一般用来传输图片,接下来会讲到;最后一种是text/plain主要传输文字;当然还有很多其他类型,而这三种最常见。最初想更换content-type来实现,最终也没有实现。
2、项目要求有图片上传功能,而图片格式多种多样,如何实现呢,okhttp也没提供现成的方法可用,那就逐步撕源码吧,上传图片第一要考虑文本类型,第二Content-type,最后以什么数据封装,答案是使用image/*包含所有图片,第二设置为Form类型,最后以FormDataPart的形式封装,代码如下
CMYJSONObject object = CMYApplication.getInstance().getAccountManager().getJsonParams(null); //参数类型 MediaType MEDIA_TYPE_IMAGE = MediaType.parse("image/*"); MultipartBody.Builder builder = new MultipartBody.Builder().setType(MultipartBody.FORM); for (Iterator<String> iterator = object.keys(); iterator.hasNext(); ) { String key = (String) iterator.next(); builder.addFormDataPart(key, object.optString(key)); } File file = new File(params[1]); builder.addFormDataPart(params[0], file.getName(), RequestBody.create(MEDIA_TYPE_IMAGE, file)); //构建请求体 RequestBody body = builder.build(); Request buildRequest = new Request.Builder() .url(url) .post(body) .build(); client.newCall(buildRequest).enqueue(callback);
3、在决定用同步还是异步的时候 ,发现同步需要每次开一个线程,比如用AsyncTask去处理,但对于整个框架就需要写多少这样的task类呀,因此考虑用异步+ViewThread的方法,最终请求结果作用于View;当然同步也有一个天然好处,就是不用判断当前callback返回的数据是不是自己view的。因此异步线程,总体还是同步的,当前页面一个执行完才能再执行另外一个;而同步请求反而可以同时开启多个,不用担心返回结果,因为它是直接拿到的,不用靠callback;总体上来说,网络的请求速度来说,硬件给予的网速已经限定,除非你要占用全网速,这个就跟前面“并发激发处理器的全部工作能力”是一样的道理,一定程度来请多线程可以加速得到我们想要的结果,但不是绝对的。
4、okhttp的超时问题,真的头疼,因为第一callback无论onResponse还是onFailure都要throws IOException,第二网络超时+无网络,第三其他错误;当然本次只请网络问题,其他两种属于框架层次的问题;不像loop4j可以设置超时重新请求和次数,而okhttp仅能设置读(Response)、写(Request)、连接(
上面均是post请求,下面给出get请求的两个案例,一个同步一个异步
Request buildRequest = new Request.Builder().url(url).build(); Response response = client.newCall(buildRequest).execute(); if (response.isSuccessful()) { return response.body().string(); } else { throw new IOException("Unexpected code " + response); }
Request buildRequest = new Request.Builder() .url(url) .build(); client.newCall(buildRequest).enqueue(callback);
最后为什么采用OkHttp,因为它支持HttpUrlConnection(Android系统层做了优化),而Android已然放弃HttpClient(Apache开源);最重要的是android底层的网络请求已经使用okhttp(在打log时发现),而loop4j是典型的HttpClient使用者;而前者目前的优势还不是很明显,如后面的优势很是明显;就像现在转向AS开发工具一样,相信它会越来越好,成为越来越专业的Android开发工具。
介绍完okhttp总觉得还缺点什么,再类比下跟其他框架的区别吧
先说最古老的那种,使用HttpClient,传入url,设置相关超时、content-type等属性后,获得返回结果;后面的都是在这个基础之上(原理),增强功能
public class HttpClientConnector { public static String getStringByUrl(String url) { String outputString = ""; // DefaultHttpClient DefaultHttpClient httpclient = new DefaultHttpClient(); // HttpGet HttpGet httpget = new HttpGet( url); // 链接超时 httpclient.getParams().setParameter( CoreConnectionPNames.CONNECTION_TIMEOUT, 6000); // 读取超时 httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 10000); // ResponseHandler ResponseHandler<String> responseHandler = new BasicResponseHandler(); try { outputString = httpclient.execute(httpget, responseHandler); String replacement = "www.book.com.cn"; switch (WholeMagDatas.netType) { case 1: replacement = "www.book.com.cn"; break; case 2: replacement = "www1.book.com.cn"; break; case 3: replacement = "www2.book.com.cn"; break; default: replacement = "www.book.com.cn"; break; } outputString.replaceAll("www.book.com.cn", replacement); // Log.i(WholeMagConstants.APP_NAME, "连接成功"); } catch (Exception e) { // Log.i(WholeMagConstants.APP_NAME, "连接失败"); // httpget = new HttpGet(WholeMagDatas.WMSERVER_BASE_URL2+url); // try { // outputString = httpclient.execute(httpget, responseHandler); // } catch (ClientProtocolException e1) { // // TODO Auto-generated catch block // e1.printStackTrace(); // } catch (IOException e1) { // // TODO Auto-generated catch block // e1.printStackTrace(); // } e.printStackTrace(); } httpclient.getConnectionManager().shutdown(); Log.i(AppData.WM_LOG_HOME, "outputString:" + outputString); return outputString; } }
框架最主要的改动还在于增强请求的安全性和请求速度,另外还有支持丰富的数据上传和接收。
okhttp,支持同步和异步请求,以及不同的数据如xml或json或string或其他数据的传输,当然可以设置请求头,跟上面无异,主要在于优化了加载过程,因此加载速度更快,同时可以取消请求,支持session的保持;支持http/2和spdy,连接池,gziping
突出优势在于:压缩请求包和请求可取消。
(经测试AsnycHttp的get请求最大传输数据量为8k。
OkHttp的get请求最大传输数据量为16M,再大会说头文件过大。设置太大的数据往往出现下面错误。
java.lang.OutOfMemoryError: Failed to allocate a 268435468 byte allocation with 16777120 free bytes and 93MB until OOM)
虽然说get数据一般http请求都会限制,但封装、压缩后的数据,可以传输更多。
volly,基本跟okhttp差不多,但支持的数据丰富度和优化程度不如okhttp
其他暂不做介绍,因为google已经默认okhttp为底层网络请求框架,以后也会做的更好吧。
顺便讲下HttpConnections与HttpClient的差别;简而言之,前者是后者的升级版本。
1、在系统层做了缓存策略,加快请求速度
2、直接支持gzip 数据 压缩包(服务端也要支持)- Accept-Encoding: gzip
3、连接池不会主动关闭,支持多程序共用,如关闭需要调用disconnect方法
4、设计HttpResponseCache,做数据请求缓存,减少服务器压力
long httpCacheSize = 10 * 1024 * 1024;// 10M File httpCacheDir = new File(getCacheDir(), "http"); Class.forName("android.net.http.HttpResponseCache") .getMethod("install", File.class, long.class) .invoke(null, httpCacheDir, httpCacheSize);
okhttp的话可以直接设置缓存
new Request.Builder().cacheControl(CacheControl.FORCE_CACHE)5、因此,请求速度是HttpClient的几倍
再说网络安全问题,如果不用https
1、黑客通过aircrack假造wifi(名字、ssid、mac地址、路由参数),通过某些购物app未做安全校验的http请求,获得用户的卡号、密码、csv、有效期、验证码等,直接可以将用户现金取走
2、通过https可以设置服务器白名单、黑名单等规则
3、https可以检验证书是否合法、过期等,防止非法请求和拦截
在播放视频时,做一个本地代理,转换成本地Url(127.0.0.1)开头的,请求的时候使用本地代理数据,不够时再去请求服务器缓存数据,可以防止盗链、方便做缓存、限制网速,增加打开视频的成功率,提升用户体验。
防止盗链可以采用本地几个参数用https的方式传给服务端,没有这些参数就不给返回数据。
通过计算本地链接的key来更换链接,做好服务端本地buffer的 动态 设置,使用H265编码代替H264可以压缩掉视频一半多体积。
讲到流量节省就不得不说一说缓存
CacheControl:(http1.0的expires )
max-age:客户端缓存多久
s-maxage:服务端缓存多久
must-revalidate:客户端缓存一旦过期,马上请求服务器
proxy-revalidate:代理缓存 一旦过期,马上请求服务器
max-stale:客户端可以接收超出此时间内的请求
no-cache:不做缓存
no-store:不做
public:告诉服务器所有都缓存
private:告诉代理不要缓存,但客户端可以缓存
no-transform:告诉客户端某些图片不用做缓存
pragma:同http1.1的no-cache
一般情况下服务器如果做缓存配置,则客户端跟其配合就行;那问题来了,万一服务端没配置呢?
private static final OkHttpClient client; static { client = new OkHttpClient(); File cacheFile = CMYApplication.getInstance().getExternalCacheDir(); client.newBuilder().readTimeout(15, TimeUnit.SECONDS).connectTimeout(15, TimeUnit.SECONDS).writeTimeout(15, TimeUnit.SECONDS) .addNetworkInterceptor(new CacheInterceptor()).cache(new Cache(cacheFile, 10 * 1024 * 1024)); } static class CacheInterceptor implements Interceptor {//服务端未做相关cache配置时的下策 @Override public Response intercept(Chain chain) throws IOException { Response originResponse = chain.proceed(chain.request()); //设置缓存时间为60秒,并移除了pragma消息头,移除它的原因是因为pragma也是控制缓存的一个消息头属性 return originResponse.newBuilder().removeHeader("pragma") .header("Cache-Control", "max-age=60").build(); } }使用网络拦截器hook的方式处理
默认的缓存方式只有两种,FORCE_NETWORK完全不用缓存,FORCE_CACHE缓存24000多天且只用缓存