Android项目框架升级尝鲜OkHttp



   本文来自http://blog.csdn.net/liuxian13183/ ,引用必须注明出处!
java

随着项目日趋稳定,需求再也不老是变化,那么是时间来整理下项目了。先简单介绍下,本项目最初使用loop4j(即async-http)框架,仅98kb大小,使用也比较方便,为何要选用它呢?13年的时候其余框架还没那么成熟,我们作项目稳定第一,其次流畅,再次性能,而它恰好知足这个条件;很差的地方在于请求慢,并且回调显得烦琐。android

使用方法以下:ios

一、初始化请求客户端json

    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;
            }
        });
    }
}
二、设置返回调用

    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.onError(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.onError(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);
                }
            }
        }
    };
三、发起请求

    protected void sendRequest(String method, LXBaseRequest request, AsyncHttpResponseHandler responseHandler) {
        RequestParams requestParams = FactoryProxy.getInstance().getAccountManager().getRequestParams(request);
        HttpBusinessAPI.post(method, requestParams, responseHandler);
    }

固然一些方法是本项目中封装过的,好比获取参数的AccountManager、网络信号管理器,也包含了一些项目的加载过程,可见一斑,有兴趣者能够一块儿讨论。

关于OkHttp是一个月前准备开工的,当时也想把项目总体框架重构一下,最大块也是网络请求层和逻辑处理层,其余公用组件仅作相应的适配便可,使用初期遇到一些问题,最后改完整个项目以后,发现请求速度快了大概30%左右吧。后端

这里先讲几个重构过程当中遇到的问题:缓存

一、是项目要求上传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来实现,最终也没有实现。

二、项目要求有图片上传功能,而图片格式多种多样,如何实现呢,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);
三、在决定用同步仍是异步的时候 ,发现同步须要每次开一个线程,好比用AsyncTask去处理,但对于整个框架就须要写多少这样的task类呀,所以考虑用异步+ViewThread的方法,最终请求结果做用于View;固然同步也有一个自然好处,就是不用判断当前callback返回的数据是否是本身view的。所以异步线程,整体仍是同步的,当前页面一个执行完才能再执行另一个;而同步线程反而能够同时开启多个,不用担忧返回结果,由于它是直接拿到的,不用靠callback;整体上来讲,网络的请求速度来讲,硬件给予的网速已经限定,除非你要占用全网速,这个就跟前面“并发激发处理器的所有工做能力”是同样的道理,必定程度来请多线程能够加速获得咱们想要的结果,但不是绝对的。

四、okhttp的超时问题,真的头疼,由于第一callback不管onResponse仍是onFailure都要throws IOException,第二网络超时+无网络,第三其余错误;固然本次只请网络问题,其余两种属于框架层次的问题;不像loop4j能够设置超时从新请求和次数,而okhttp仅能设置读(Response)、写(Request)、链接(Link)的超时时间,因此请求失败的次数天然会比loop4j多一些,怎么解决呢,设置网络链接时间最长,60s。session

上面均是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为底层网络请求框架,之后也会作的更好吧。

经测试AsnycHttp的get请求最大传输数据量为8k。

OkHttp的get请求最大传输数据量为16M

java.lang.OutOfMemoryError: Failed to allocate a 268435468 byte allocation with 16777120 free bytes and 93MB until OOM


顺便讲下HttpConnections与HttpClient的差异;简而言之,前者是后者的升级版本。

一、在系统层作了缓存策略,加快请求速度

二、直接支持gzip数据压缩包(服务端也要支持)-Accept-Encoding: gzip

三、链接池不会主动关闭,支持多程序共用,如关闭须要调用disconnect方法

四、设计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)
五、所以,请求速度是HttpClient的几倍

再说网络安全问题,若是不用https
一、黑客经过aircrack假造wifi(名字、ssid、mac地址、路由参数),经过某些购物app未作安全校验的http请求,得到用户的卡号、密码、csv、有效期、验证码等,直接能够将用户现金取走
二、经过https能够设置服务器白名单、黑名单等规则
三、https能够检验证书是否合法、过时等,防止非法请求和拦截

在播放视频时,作一个本地代理,转换成本地Url(127.0.0.1)开头的,请求的时候使用本地代理数据,不够时再去请求服务器缓存数据,能够防止盗链、方便作缓存、限制网速,增长打开视频的成功率,提高用户体验。
防止盗链能够采用本地几个参数用https的方式传给服务端,没有这些参数就不给返回数据。
经过计算本地连接的key来更换连接,作好服务端本地buffer的动态设置,使用H265编码代替H264能够压缩掉视频一半多体积。
相关文章
相关标签/搜索