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能夠壓縮掉視頻一半多體積。
相關文章
相關標籤/搜索