4.2.1 網絡請求之HTTP

HTTP請求&響應:(經常使用的只有Post與Get,還有Head/put/delete/connect/options/trace)php

Get&Post建議用post規範參數傳遞方式,並無什麼更優秀,只是你們都這樣社會更和諧。android

網絡請求中咱們經常使用鍵值對來傳輸參數(少部分API用json來傳遞,畢竟不是主流)。web

經過上面的介紹,能夠看出雖然Post與Get本意一個是表單提交一個是請求頁面,但本質並無什麼區別。編程

  • Get方式:在url中填寫參數:  http://xxxx.xx.com/xx.php?params1=value1&params2=value2
  • Post方式:參數是通過編碼放在請求體中的。編碼包括x-www-form-urlencoded 與 form-data。

由於url是存在於請求行中的,因此Get與Post區別本質就是參數是放在請求行中仍是放在請求體json

固然不管用哪一種都能放在請求頭中。通常在請求頭中放一些發送端的常量。瀏覽器

表單提交中get post方式的區別有4點:緩存

1) get是從服務器上獲取數據,post是向服務器傳送數據。安全

2) get是把參數數據隊列加到提交表單的 ACTION屬性所指的URL中,值和表單內各個字段一一對應,在URL中能夠看到。post是經過HTTPpost機制,將表單內各個字段與其內容放置在HTML HEADER內一塊兒傳送到ACTION屬性所指的URL地址。用戶看不到這個過程。因此,get安全性很是低,post安全性較高。服務器

3) get,服務器端用 Request.QueryString獲取變量的值;post,服務器端用Request.Form獲取提交的數據。網絡

4) get 傳送的數據量較小,不能大於2KB。post傳送的數據量較大,通常被默認爲不受限制。但理論上,IIS4中最大量爲80KB,IIS5中爲100KB。

常見問題:

1. Get是明文,Post隱藏:  錯,不用https全都是明文。

2. Get傳遞數據上限XXX有限制的是url長度,不是Http。Http服務器部分有限制的設置一下便可。

3. Get中文須要編碼:      是真的...要注意:URLEncoder.encode(params, "gbk");

  請求是鍵值對,但返回數據咱們經常使用Json。對於內存中的結構數據,確定要用數據描述語言將對象序列化成文本,再用Http傳遞,接收端並從文本還原成結構數據。對象(服務器)<-->文本(Http傳輸)<-->對象(移動端) 。

  服務器返回的數據大部分都是複雜的結構數據,因此Json最適合。另:要求傳輸性能的話用FlatBuffers。

HttpURLConnection( HttpClient 被廢棄了)

1. 入門級

public class NetUtils {
        public static String post(String url, String content) {
            HttpURLConnection conn = null;
            try {
                URL mURL = new URL(url);                       // 建立一個URL對象
                conn = (HttpURLConnection) mURL.openConnection(); .// 獲取HttpURLConnection對象
                conn.setRequestMethod("POST");                   // 設置請求方法爲post
                conn.setReadTimeout(5000);                       // 設置讀取超時爲5秒
                conn.setConnectTimeout(10000);                   .// 設置鏈接網絡超時爲10秒
                conn.setDoOutput(true);                          .// 設置此方法,容許向服務器輸出內容
                String data = content;                            .//  post請求的參數
                OutputStream out = conn.getOutputStream();        // 得到一個輸出流,向服務器寫數據
                out.write(data.getBytes());                         // GET方式不須要
                out.flush();  out.close();
                int responseCode = conn.getResponseCode();        // 調用此方法就沒必要再使用.connect()方法
                if (responseCode == 200) {
                    InputStream is = conn.getInputStream();
                    String response = getStringFromInputStream(is);
                    return response;
                } else 
                    throw new NetworkErrorException("response status is "+responseCode);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (conn != null)  conn.disconnect();               // 關閉鏈接
            }
            return null;
        }
        public static String get(String url) {
            HttpURLConnection conn = null;
            try {
                URL mURL = new URL(url); // 利用string url構建URL對象
                conn = (HttpURLConnection) mURL.openConnection();
                conn.setRequestMethod("GET");
                conn.setReadTimeout(5000);
                conn.setConnectTimeout(10000);
                int responseCode = conn.getResponseCode();
                if (responseCode == 200) {
                    InputStream is = conn.getInputStream();
                    String response = getStringFromInputStream(is);
                    return response;
                } else 
                    throw new NetworkErrorException("response status is "+responseCode);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (conn != null)  conn.disconnect();
            }
            return null;
        }
    // 模板代碼 必須熟練
        private static String getStringFromInputStream(InputStream is) throws IOException {
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            byte[] buffer = new byte[1024];
            int len = -1;
            while ((len = is.read(buffer)) != -1) 
                os.write(buffer, 0, len);
            is.close();
            String state = os.toString();  // 把流中的數據轉換成字符串,採用的編碼是utf-8(模擬器默認編碼)
            os.close();
            return state;
        }
    }

  注意網絡權限:  <uses-permission android:name="android.permission.INTERNET"/>

2. 初級

同步&異步

這2個概念僅存在於多線程編程中。

android中默認只有一個主線程,也叫UI線程。由於View繪製只能在這個線程內進行。因此若是你阻塞了(某些操做使這個線程在此處運行了N秒)這個線程,這期間View繪製將不能進行,UI就會卡。因此要極力避免在UI線程進行耗時操做。網絡請求是一個典型耗時操做

經過上面的Utils類進行網絡請求只有一行代碼:NetUtils.get("http://www.baidu.com");    //這行代碼將執行幾百毫秒。

若是你這樣寫:

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        String response = Utils.get("http://www.baidu.com");
    }

就會死!!!

這就是同步方式。直接耗時操做阻塞線程直到數據接收完畢而後返回。Android不容許的。

異步方式:(在子線程進行耗時操做,完成後經過Handler將更新UI的操做發送到主線程執行。)

//在主線程new的Handler,就會在主線程進行後續處理。
    private Handler handler = new Handler();
    private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.text);
        new Thread(new Runnable() {
            @Override
            public void run() {
                final String response = NetUtils.get("http://www.baidu.com");  //從網絡獲取數據
                handler.post(new Runnable() {                          //向Handler發送處理操做
                    @Override
                    public void run() {
                        textView.setText(response);   //在UI線程更新UI
                    }
                });
            }
        }).start();
    }

但這樣寫好難看。異步一般伴隨者他的好基友回調

這是經過回調封裝的AsynNetUtils類

public class AsynNetUtils {
        public interface Callback{
            void onResponse(String response);
        }
        public static void get(final String url, final Callback callback){
            final Handler handler = new Handler();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    final String response = NetUtils.get(url);
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            callback.onResponse(response);
                        }
                    });
                }
            }).start();
        }
        public static void post(final String url, final String content, final Callback callback){
            final Handler handler = new Handler();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    final String response = NetUtils.post(url,content);
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            callback.onResponse(response);
                        }
                    });
                }
            }).start();
        }
    }

而後使用方法:

private TextView textView;
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        textView = (TextView) findViewById(R.id.webview);
        AsynNetUtils.get("http://www.baidu.com", new AsynNetUtils.Callback() { @Override public void onResponse(String response) { textView.setText(response); } });

可是,愚蠢的地方有不少:

  1. 每次都new Thread,new Handler消耗過大
  2. 沒有異常處理機制
  3. 沒有緩存機制
  4. 沒有完善的API(請求頭,參數,編碼,攔截器等)與調試模式
  5. 沒有Https

3. 高級 OKHttp

加入HTTP緩存機制

緩存對於移動端是很是重要的存在。

  • 減小請求次數,減少服務器壓力.
  • 本地數據讀取速度更快,讓頁面不會空白幾百毫秒。
  • 在無網絡的狀況下提供數據。
  1. 1.      高級 OKHttp

加入HTTP緩存機制

緩存對於移動端是很是重要的存在。

  • 減小請求次數,減少服務器壓力.
  • 本地數據讀取速度更快,讓頁面不會空白幾百毫秒。
  • 在無網絡的狀況下提供數據。

緩存通常由服務器控制(經過某些方式能夠本地控制緩存,好比向過濾器添加緩存控制信息)。經過在請求頭添加幾個字段,正式使用時按需求也許只包含其中部分字段

 客戶端要根據這些信息儲存此次請求信息。而後在客戶端發起請求的時候要檢查緩存。遵循下面步驟:

注意 服務器返回304意思是數據沒有變更滾去讀緩存信息。

   如今Android網絡方面的第三方庫不少,volley,Retrofit,OKHttp等,各有各自的特色。不過再怎麼封裝Volley在功能拓展性上始終沒法與OkHttp相比。Volley中止了更新,而OkHttp獲得了官方的承認,並在不斷優化。

OkHttp是一個高效的HTTP:

  • 支持 SPDY ,共享同一個 Socket 來處理同一個服務器的全部請求
  • 若是 SPDY 不可用,則經過鏈接池來減小請求延時
  • 無縫的支持GZIP來減小數據流量
  • 緩存響應數據來減小重複的網絡請求

  SPDY(讀做「SPeeDY」)是Google開發的基於TCP的應用層協議,用以最小化網絡延遲,提高網絡速度,優化用戶的網絡使用體驗。SPDY並非一種用於替代HTTP的協議,而是對HTTP協議的加強。新協議的功能包括數據流的多路複用、請求優先級以及HTTP報頭壓縮。谷歌表示,引入SPDY協議後,在實驗室測試中頁面加載速度比原先快64%。

  OKHttp(com.squareup.okhttp)是Android版Http客戶端,很是高效,會自動處理常見的網絡問題,像二次鏈接、SSL的握手問題。若是你的應用程序中集成了OKHttp,Retrofit默認會使用OKHttp處理其餘網絡層請求。OkHttp支持Android 2.3及其以上版本。對於Java, JDK1.7以上。Android4.4開始HttpURLConnection的底層實現採用的是okHttp

  在OKHttp,每次網絡請求就是一個Request,咱們在Request裏填寫咱們須要的url,header等其餘參數,再經過Request構造出Call,Call內部去請求參數,獲得回覆,並將結果告訴調用者。

詳細使用步驟以下:

  • 同步請求 excute()

HTTP GET

OkHttpClient client = new OkHttpClient();
String run(String url) throws IOException {
Request request = new Request.Builder() .url(url) .build();
Response response = client.newCall(request).execute();    
if (response.isSuccessful())      
  return response.body().string();
else       
  throw new IOException("Unexpected code " + response);
}  // Request是OkHttp中訪問的請求,Builder是輔助類。Response即OkHttp中的響應。

HTTP POST

1) POST提交Json數據

public static final MediaType JSON = MediaType.parse("application/json; charset=utf-8");
OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
     RequestBody body = RequestBody.create(JSON, json);
     Request request = new Request.Builder() .url(url) .post(body) .build();   // 對比GET,放入post數據
     Response response = client.newCall(request).execute();
     if (response.isSuccessful()) 
        return response.body().string();
    else 
        throw new IOException("Unexpected code " + response);
}  //使用Request的post方法來提交請求體RequestBody

2) POST提交鍵值對

OkHttpClient client = new OkHttpClient();
String post(String url, String json) throws IOException {
     RequestBody formBody = new FormEncodingBuilder()
                                  .add("platform", "android")
                                  .add("name", "bug")
                                  .build();
    Request request = new Request.Builder() .url(url) .post(formBody) .build();
    Response response = client.newCall(request).execute();
    if (response.isSuccessful()) {
        return response.body().string();
    } else 
        throw new IOException("Unexpected code " + response);
}

  OkHttp官方文檔並不建議咱們建立多個OkHttpClient。若是有須要,可使用clone方法,再進行自定義。

  • 異步請求 enqueue()

  咱們經過Request.Builder傳入url,而後直接execute執行獲得Response,經過Response能夠獲得code,message等信息。這是經過同步的方式去操做網絡請求,而android是不容許在UI線程作網絡請求操做的,所以咱們須要本身開啓一個線程。固然,OKHttp也支持異步線程而且有回調返回,有了上面同步的基礎,異步只要稍加改動便可

private void enqueue(){
        Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt") .build();
        client.newCall(request).enqueue(new Callback() {   //就是在同步的基礎上講execute改爲enqueue
            public void onFailure(Request request, IOException e) {       }
            public void onResponse(Response response) throws IOException {
                //NOT UI Thread
                if(response.isSuccessful()){
                    System.out.println(response.code());
                    System.out.println(response.body().string());
                }
            }
        });
}// execute改爲enqueue,接口回調的代碼是在非UI線程的,有更新UI的操做要用Handler或者其餘方式。

響應緩存

  爲了緩存響應,你須要一個你能夠讀寫的緩存目錄,和緩存大小的限制。這個緩存目錄應該是私有的,不信任的程序應不能讀取緩存內容。

  一個緩存目錄同時擁有多個緩存訪問是錯誤的。大多數程序只須要調用一次new OkHttp(),在第一次調用時配置好緩存,而後其餘地方只須要調用這個實例就能夠了。不然兩個緩存示例互相干擾,破壞響應緩存,並且有可能會致使程序崩潰。

private final OkHttpClient client;
public CacheResponse(File cacheDirectory) throws Exception {
    int cacheSize = 10 * 1024 * 1024;   // 10 MiB
    Cache cache = new Cache(cacheDirectory, cacheSize);
    client = new OkHttpClient();
    client.setCache(cache);
}
public void run() throws Exception {
    Request request = new Request.Builder().url("http://publicobject.com/helloworld.txt").build();
    Response response1 = client.newCall(request).execute();
    if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
    String response1Body = response1.body().string();
    System.out.println("Response 1 response:          " + response1);
    System.out.println("Response 1 cache response:    " + response1.cacheResponse());
    System.out.println("Response 1 network response:  " + response1.networkResponse());

    Response response2 = client.newCall(request).execute();
    if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
    String response2Body = response2.body().string();
    System.out.println("Response 2 response:         " + response2);
    System.out.println("Response 2 cache response:    " + response2.cacheResponse());
    System.out.println("Response 2 network response:  " + response2.networkResponse());
    System.out.println("Response 2 equals Response 1 ? " + response1Body.equals(response2Body));
}

  response1 的結果在networkresponse,表明是從網絡請求加載過來的;response2的networkresponse 就爲null,而cacheresponse有數據。由於設置了緩存所以第二次請求時發現緩存裏有就再也不去走網絡請求了。

  但有時候,即便在有緩存的狀況下咱們依然須要去後臺請求最新的資源(好比資源更新了)這個時候可使用強制走網絡來要求必須請求網絡數據

public void execute() throws Exception {
    Request request = new Request.Builder() .url("http://publicobject.com/helloworld.txt").build();
        Response response1 = client.newCall(request).execute();
        if (!response1.isSuccessful()) throw new IOException("Unexpected code " + response1);
        String response1Body = response1.body().string();
        System.out.println("Response 1 response:          "  +  response1);
        System.out.println("Response 1 cache response:     "  +  response1.cacheResponse());
        System.out.println("Response 1 network response:   "  +  response1.networkResponse());
        
  request = request.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build();
        Response response2 = client.newCall(request).execute();
        if (!response2.isSuccessful()) throw new IOException("Unexpected code " + response2);
        String response2Body = response2.body().string();
        System.out.println("Response 2 response:          "  +  response2);
        System.out.println("Response 2 cache response:     "  +  response2.cacheResponse());
        System.out.println("Response 2 network response:   "  +  response2.networkResponse());
        System.out.println("Response 2 equals Response 1?  "  +  response1Body.equals(response2Body));
}
// response2的cache response爲null,network response依然有數據。

  一樣的咱們可使用 FORCE_CACHE 強制只要使用緩存的數據,但若是請求必須從網絡獲取纔有數據,但又使用了FORCE_CACHE 策略就會返回504錯誤

HTTPHTTPS

  1. HTTP是一個屬於應用層的面向對象的協議,使用80端口。HTTP協議的主要特色可歸納以下:

  • 簡單快速:客戶向服務器請求服務時,只需傳送請求方法和路徑。請求方法經常使用的有GET、POST。每種方法規定了客戶與服務器聯繫的類型不一樣。因爲HTTP協議簡單,使得服務器的程序規模小,於是通訊速度很快。
  • 靈活:HTTP容許傳輸任意類型的數據對象。正在傳輸的類型由Content-Type加以標記。
  • 無鏈接:無鏈接的含義是限制每次鏈接只處理一個請求。服務器處理完客戶的請求,並收到客戶的應答後,即斷開鏈接。採用這種方式能夠節省傳輸時間。
  • 無狀態:HTTP協議對於事務處理沒有記憶能力。缺乏狀態意味着若是後續處理須要前面的信息,則它必須重傳,這樣可能致使每次鏈接傳送的數據量增大。另外一方面,在服務器不須要先前信息時它的應答就較快。

  2. HTTPS(基於SSL的HTTP協議)使用了HTTP協議,但使用不一樣於HTTP協議的默認端口,使用443端口,以及一個加密、身份驗證層(HTTP與TCP之間),即HTTP下加入SSL層

使用HTTPS方式與Web服務器通訊時有如下幾個步驟,如圖所示:

  1. 客戶使用https的URL訪問Web服務器,要求與Web服務器創建SSL鏈接。
  2. Web服務器收到客戶端請求後,會將網站的證書信息(證書中包含公鑰)傳送一份給客戶端。
  3. 客戶端的瀏覽器與Web服務器開始協商SSL鏈接的安全等級,也就是信息加密的等級。
  4. 客戶端的瀏覽器根據雙方贊成的安全等級,創建會話密鑰,而後利用網站的公鑰將會話密鑰加密,並傳送給網站。
  5. Web服務器利用本身的私鑰解密出會話密鑰。
  6. Web服務器利用會話密鑰加密與客戶端之間的通訊。

 

SSL介紹:

  安全套接字(Secure Socket Layer,SSL)協議是Web瀏覽器與Web服務器之間安全交換信息的協議,提供兩個基本的安全服務:鑑別與保密。

  SSL介於應用層和TCP層之間。應用層數據再也不直接傳遞給傳輸層,而是傳遞給SSL層,SSL層對從應用層收到的數據進行加密,並增長本身的SSL頭。

  SSL協議的三個特性

  ① 保密:在握手協議中定義了會話密鑰後,全部的消息都被加密。

  ② 鑑別:可選的客戶端認證,和強制的服務器端認證。

  ③ 完整性:傳送的消息包括消息完整性檢查(使用MAC)  

相關文章
相關標籤/搜索