Android HTTP協議請求網路(三)之HttpURLConnection方式

艾陽丶發表於2017-05-08

Android HTTP協議請求網路(一)之認識探索

Android HTTP協議請求網路(二)之HttpClient方式

githup練習demo地址:https://github.com/aiyangtianci/NetworkRequestDemo


一、介紹

在Android API Level 9(Android 2.2)之前只能使用DefaultHttpClient類傳送http請求。DefaultHttpClient是Apache用於傳送http請求的客戶端,其提供了強大的API支援,而且基本沒有什麼bug,但是由於其太過複雜,Android團隊在保持向後相容的情況下,很難對DefaultHttpClient進行增強。為此,Android團隊從Android API Level 9開始自己實現了一個傳送http請求的客戶端類——–HttpURLConnection。

相比於DefaultHttpClient,HttpURLConnection比較輕量級,雖然功能沒有DefaultHttpClient那麼強大,但是能夠滿足大部分的需求,所以Android推薦使用HttpURLConnection代替DefaultHttpClient,並不強制使用HttpURLConnection。

但從Android API Level 23(Android 6.0)開始,不能再在Android中使用DefaultHttpClient,強制使用HttpURLConnection。

二、Get方式:

實現流程步驟:

    第一步:例項化URL物件。

    第二步:例項化HttpUrlConnection物件。

    第三步:設定請求連線屬性,傳遞引數等。

    第四步:獲取返回碼判斷是否連結成功。

    第五步:讀取輸入流。

    第六步:關閉連結。

 public void run() {
            HttpURLConnection connection = null;
            try {
                URL url = new URL("http://192.168.23.1:8080/TestProject/GetTest");
                connection = (HttpURLConnection) url.openConnection();
                // 設定請求方法,預設是GET      
                connection.setRequestMethod("GET");
                // 設定字符集
                connection.setRequestProperty("Charset", "UTF-8");
                // 設定檔案型別
                connection.setRequestProperty("Content-Type", "text/xml; charset=UTF-8");
                // 設定請求引數,可通過Servlet的getHeader()獲取
                connection.setRequestProperty("Cookie", "AppName=" + URLEncoder.encode("你好", "UTF-8"));
                // 設定自定義引數
                connection.setRequestProperty("MyProperty", "this is me!");
                
                if(connection.getResponseCode() == 200){
                    InputStream is = connection.getInputStream();
                    result = StringStreamUtil.inputStreamToString(is);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(connection != null){
                    connection.disconnect(); 
                }
            }


三、Post方式:

 public void run() {
            HttpURLConnection connection = null;
            try {
                URL url = new URL("http://192.168.23.1:8080/TestProject/PostTest");
                connection = (HttpURLConnection) url.openConnection();
                // 設定請求方式
                connection.setRequestMethod("POST");
                // 設定編碼格式
                connection.setRequestProperty("Charset", "UTF-8");
                // 傳遞自定義引數
                connection.setRequestProperty("MyProperty", "this is me!");
                // 設定容許輸出
                connection.setDoOutput(true);

                // 上傳一張圖片|上傳引數: byte[] requestBody = new String("name=孫群&age=27").getBytes("UTF-8");
                FileInputStream file = new FileInputStream(Environment.getExternalStorageDirectory().getPath() 
                        + "/Pictures/Screenshots/Screenshot_2015-12-19-08-40-18.png");
                OutputStream os = connection.getOutputStream();
                int count = 0;
                while((count=file.read()) != -1){
                    os.write(count);
                }
                os.flush();
                os.close();
                
                // 獲取返回資料
                if(connection.getResponseCode() == 200){
                    InputStream is = connection.getInputStream();
                    result = StringStreamUtil.inputStreamToString(is);
                }
            } catch (MalformedURLException e) {              
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(connection!=null){
                    connection.disconnect();
                }
            }
        };


四、上傳APK包大檔案:

 public void run() {
            HttpURLConnection connection = null;
            try {
                URL url = new URL("http://192.168.23.1:8080/TestProject/FileTest");
                connection = (HttpURLConnection) url.openConnection();
                // 設定每次傳輸的流大小,可以有效防止手機因為記憶體不足崩潰
                // 此方法用於在預先不知道內容長度時啟用沒有進行內部緩衝的 HTTP請求正文的流。
                connection.setChunkedStreamingMode(51200); // 128K
                // 不使用快取
                connection.setUseCaches(false);
                // 設定請求方式
                connection.setRequestMethod("POST");
                // 設定編碼格式
                connection.setRequestProperty("Charset", "UTF-8");
                // 設定容許輸出
                connection.setDoOutput(true);

                // 上傳檔案
                FileInputStream file = new FileInputStream(Environment.getExternalStorageDirectory().getPath() 
                        + "/aaaaa/baidu_map.apk");
                OutputStream os = connection.getOutputStream();
                byte[] b = new byte[1024];
                int count = 0;
                while((count = file.read(b)) != -1){
                    os.write(b, 0, count);
                }
                os.flush();
                os.close();
                
                // 獲取返回資料
                if(connection.getResponseCode() == 200){
                    InputStream is = connection.getInputStream();
                    result = StringStreamUtil.inputStreamToString(is);              
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if(connection != null){
                    connection.disconnect();
                }
            }
        };


五、檔案斷點上傳:

安卓端程式碼:
public class MoreUploadActivity extends Activity {
    private TextView mTvMsg;
    
    private String result = "";
    
    private long start = 0;           // 開始讀取的位置
    private long stop = 1024 * 1024;  // 結束讀取的位置
    private int times = 0;            //讀取次數
    
    private long fileSize = 0;  //檔案大小
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_times_upload);
        
        initView();
    }
    
    private void initView(){
        mTvMsg = (TextView) findViewById(R.id.tv_upload);
        
        try {
            FileInputStream file = new FileInputStream(Environment.getExternalStorageDirectory().getPath() + "/aaaaa/baidu_map.apk");
            fileSize = file.available();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        
        new Thread(uploadThread).start();
    }
    
    private Thread uploadThread = new Thread(){
        public void run() {
            HttpURLConnection connection = null;
            try {
                URL url = new URL("http://192.168.23.1:8080/TestProject/MoreUploadTest");
                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("POST");
                connection.setChunkedStreamingMode(51200);
                connection.setUseCaches(false);
                // 設定允許輸出
                connection.setDoOutput(true);
                // 設定斷點開始,結束位置
                connection.setRequestProperty("Range", "bytes=" + start + "-" + stop);
                
                String path = Environment.getExternalStorageDirectory().getPath() + "/aaaaa/baidu_map.apk";
                RandomAccessFile file = new RandomAccessFile(path, "rw");                
                file.seek(start);
                byte[] buffer = new byte[1024];
                int count = 0;
                OutputStream os = connection.getOutputStream();
                if(fileSize > 1024*1024){
                    for(int i=0; i<1024 && count!=-1; i++){
                        count = file.read(buffer);
                        os.write(buffer, 0, count);
                    }
                }else{
                    for(int i=0; i<(fileSize/1024)+1 && count!=-1; i++){
                        count = file.read(buffer);
                        os.write(buffer, 0, count);
                    }
                }
                os.flush();
                os.close();
                
                Log.e("ABC", connection.getResponseCode() + "");
                if(connection.getResponseCode() == 200){
                    result += StringStreamUtil.inputStreamToString(connection.getInputStream()) + "\n";
                }
                
                start = stop + 1;
                stop += 1024*1024;
                fileSize -= 1024*1024;
                
                Message msg = Message.obtain();
                msg.what = 0;
                uploadHandler.sendMessage(msg);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(connection != null){
                    connection.disconnect();
                }
            }
        };
    };
    
    private Handler uploadHandler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            if(msg.what == 0){
                if(times >= 8){
                    mTvMsg.setText(result);
                }else{
                    times += 1;
                    new Thread(uploadThread).start();
                    mTvMsg.setText(result);
                }
            }
        };
    };
}

六、檔案斷點下載:

1、下載重要的是實現技巧: 一:設定斷點請求setRequestProperty("Range", "bytes=0-1024");

                                 二:通過RandomAccessFile來將下載的位元組插入到指定的位置。

2、對於輸出流的三個方法的對比:

    os.write(byte[] buffer);  可能錯誤,因為你每次讀取的資料小於等於1024,但你每次寫入的資料仍然是1024, 對圖片有一定影響,對安裝包絕對是致命的影響。
    os.write(int oneByte);    效率低
    os.write(byte[] buffer, int byteOffset, int byteCount);   效率高,和第二個方法相比有一個數量級的差別(主觀上看,有興趣的可以測幾下)。

3、實現多執行緒斷點下載大家可以自己思考一下。
/**
 * 斷點下載
 */
public class MoreTimesActivity extends Activity {
    private TextView mTvMsg;
    
    private String result = "";
    
    private long start = 0;
    private long stop = 1024 * 1024;
    
    private int times = 0;  // 根據檔案大小自己設的,
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_times_download);
        
        initView();
    }
    
    private void initView(){
        mTvMsg = (TextView) findViewById(R.id.tv_msg);
        
        new Thread(moreThread).start();
    }
    
    private Thread moreThread = new Thread(){
        public void run() {
            HttpURLConnection connection = null;
            try {
                URL url = new URL("http://ftp-apk.pconline.com.cn/ef19af4e28462271af1117efaf868bc2/pub/download/201010/renshengrili_v4.0.04.05.apk");
                connection = (HttpURLConnection) url.openConnection();
                connection.setRequestMethod("GET");
                connection.setDoInput(true);
                // 設定開始下載的位置和結束下載的位置,單位為位元組
                connection.setRequestProperty("Range", "bytes=" + start + "-" + stop);
                
                String path = Environment.getExternalStorageDirectory().getPath()  + "/aaaaa/baidu_map.apk";
                // 斷點下載使用的檔案物件RandomAccessFile
                RandomAccessFile access = new RandomAccessFile(path, "rw");
                // 移動指標到開始位置
                access.seek(start);
                InputStream is = null;
                Log.e("ADB----", connection.getResponseCode() + "");
                if(connection.getResponseCode() == 206){
                    is = connection.getInputStream();
                    int count = 0;
                    byte[] buffer = new byte[1024];
                    while((count = is.read(buffer)) != -1){
                        access.write(buffer, 0, count);
                    }
                }
                
                if(access != null){
                    access.close();
                }
                if(is != null){
                    is.close();
                }
                
                start = stop + 1;
                stop += 1024*1024;   // 每次下載1M
                
                Message msg = Message.obtain();
                msg.what = 0;
                result += "檔案" + times + "下載成功" + ":" + start + "---" + stop + "\n";
                moreHandler.sendMessage(msg);
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if(connection != null){
                    connection.disconnect();
                }
            }
        };
    };
    
    private Handler moreHandler = new Handler(){
        public void handleMessage(android.os.Message msg) {
            if(msg.what == 0 && result!=null){
                if(times >= 10){
                    Message msg1 = Message.obtain();
                    msg1.what = 1;
                    moreHandler.sendMessage(msg1);
                }else{
                    new Thread(moreThread).start();
                    times += 1;
                }
                
                mTvMsg.setText(result);
            }else if(msg.what == 1){
                mTvMsg.setText(result);
            }
        };
    };
}


三、Demo講解

為了演示HttpURLConnection的常見用法,我做了一個App,介面如下所示:

這裡寫圖片描述

主介面MainActivity有四個按鈕,分別表示用GET傳送請求、用POST傳送鍵值對資料、用POST傳送XML資料以及用POST傳送JSON資料,點選對應的按鈕會啟動NetworkActivity並執行相應的操作。

NetworkActivity的原始碼如下所示,此處先貼出程式碼,後面會詳細說明。

package com.ispring.httpurlconnection;

import android.content.Intent;
import android.content.res.AssetManager;
import android.os.AsyncTask;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class NetworkActivity extends AppCompatActivity {

    private NetworkAsyncTask networkAsyncTask = new NetworkAsyncTask();

    private TextView tvUrl = null;
    private TextView tvRequestHeader = null;
    private TextView tvRequestBody = null;
    private TextView tvResponseHeader = null;
    private TextView tvResponseBody = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_network);
        tvUrl = (TextView) findViewById(R.id.tvUrl);
        tvRequestHeader = (TextView) findViewById(R.id.tvRequestHeader);
        tvRequestBody = (TextView) findViewById(R.id.tvRequestBody);
        tvResponseHeader = (TextView) findViewById(R.id.tvResponseHeader);
        tvResponseBody = (TextView) findViewById(R.id.tvResponseBody);
        Intent intent = getIntent();
        if (intent != null && intent.getExtras() != null) {
            String networkAction = intent.getStringExtra("action");
            networkAsyncTask.execute(networkAction);
        }
    }

    //用於進行網路請求的AsyncTask
    class NetworkAsyncTask extends AsyncTask<String, Integer, Map<String, Object>> {
        //NETWORK_GET表示傳送GET請求
        public static final String NETWORK_GET = "NETWORK_GET";
        //NETWORK_POST_KEY_VALUE表示用POST傳送鍵值對資料
        public static final String NETWORK_POST_KEY_VALUE = "NETWORK_POST_KEY_VALUE";
        //NETWORK_POST_XML表示用POST傳送XML資料
        public static final String NETWORK_POST_XML = "NETWORK_POST_XML";
        //NETWORK_POST_JSON表示用POST傳送JSON資料
        public static final String NETWORK_POST_JSON = "NETWORK_POST_JSON";

        @Override
        protected Map<String, Object> doInBackground(String... params) {
            Map<String,Object> result = new HashMap<>();
            URL url = null;//請求的URL地址
            HttpURLConnection conn = null;
            String requestHeader = null;//請求頭
            byte[] requestBody = null;//請求體
            String responseHeader = null;//響應頭
            byte[] responseBody = null;//響應體
            String action = params[0];//http請求的操作型別

            try {
                if (NETWORK_GET.equals(action)) {
                    //傳送GET請求
                    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet?name=孫群&age=27");
                    conn = (HttpURLConnection) url.openConnection();
                    //HttpURLConnection預設就是用GET傳送請求,所以下面的setRequestMethod可以省略
                    conn.setRequestMethod("GET");
                    //HttpURLConnection預設也支援從服務端讀取結果流,所以下面的setDoInput也可以省略
                    conn.setDoInput(true);
                    //用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
                    conn.setRequestProperty("action", NETWORK_GET);
                    //禁用網路快取
                    conn.setUseCaches(false);
                    //獲取請求頭
                    requestHeader = getReqeustHeader(conn);
                    //在對各種引數配置完成後,通過呼叫connect方法建立TCP連線,但是並未真正獲取資料
                    //conn.connect()方法不必顯式呼叫,當呼叫conn.getInputStream()方法時內部也會自動呼叫connect方法
                    conn.connect();
                    //呼叫getInputStream方法後,服務端才會收到請求,並阻塞式地接收服務端返回的資料
                    InputStream is = conn.getInputStream();
                    //將InputStream轉換成byte陣列,getBytesByInputStream會關閉輸入流
                    responseBody = getBytesByInputStream(is);
                    //獲取響應頭
                    responseHeader = getResponseHeader(conn);
                } else if (NETWORK_POST_KEY_VALUE.equals(action)) {
                    //用POST傳送鍵值對資料
                    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
                    conn = (HttpURLConnection) url.openConnection();
                    //通過setRequestMethod將conn設定成POST方法
                    conn.setRequestMethod("POST");
                    //呼叫conn.setDoOutput()方法以顯式開啟請求體
                    conn.setDoOutput(true);
                    //用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
                    conn.setRequestProperty("action", NETWORK_POST_KEY_VALUE);
                    //獲取請求頭
                    requestHeader = getReqeustHeader(conn);
                    //獲取conn的輸出流
                    OutputStream os = conn.getOutputStream();
                    //獲取兩個鍵值對name=孫群和age=27的位元組陣列,將該位元組陣列作為請求體
                    requestBody = new String("name=孫群&age=27").getBytes("UTF-8");
                    //將請求體寫入到conn的輸出流中
                    os.write(requestBody);
                    //記得呼叫輸出流的flush方法
                    os.flush();
                    //關閉輸出流
                    os.close();
                    //當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
                    InputStream is = conn.getInputStream();
                    //獲得響應體的位元組陣列
                    responseBody = getBytesByInputStream(is);
                    //獲得響應頭
                    responseHeader = getResponseHeader(conn);
                } else if (NETWORK_POST_XML.equals(action)) {
                    //用POST傳送XML資料
                    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
                    conn = (HttpURLConnection) url.openConnection();
                    //通過setRequestMethod將conn設定成POST方法
                    conn.setRequestMethod("POST");
                    //呼叫conn.setDoOutput()方法以顯式開啟請求體
                    conn.setDoOutput(true);
                    //用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
                    conn.setRequestProperty("action", NETWORK_POST_XML);
                    //獲取請求頭
                    requestHeader = getReqeustHeader(conn);
                    //獲取conn的輸出流
                    OutputStream os = conn.getOutputStream();
                    //讀取assets目錄下的person.xml檔案,將其位元組陣列作為請求體
                    requestBody = getBytesFromAssets("person.xml");
                    //將請求體寫入到conn的輸出流中
                    os.write(requestBody);
                    //記得呼叫輸出流的flush方法
                    os.flush();
                    //關閉輸出流
                    os.close();
                    //當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
                    InputStream is = conn.getInputStream();
                    //獲得響應體的位元組陣列
                    responseBody = getBytesByInputStream(is);
                    //獲得響應頭
                    responseHeader = getResponseHeader(conn);
                } else if (NETWORK_POST_JSON.equals(action)) {
                    //用POST傳送JSON資料
                    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
                    conn = (HttpURLConnection) url.openConnection();
                    //通過setRequestMethod將conn設定成POST方法
                    conn.setRequestMethod("POST");
                    //呼叫conn.setDoOutput()方法以顯式開啟請求體
                    conn.setDoOutput(true);
                    //用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
                    conn.setRequestProperty("action", NETWORK_POST_JSON);
                    //獲取請求頭
                    requestHeader = getReqeustHeader(conn);
                    //獲取conn的輸出流
                    OutputStream os = conn.getOutputStream();
                    //讀取assets目錄下的person.json檔案,將其位元組陣列作為請求體
                    requestBody = getBytesFromAssets("person.json");
                    //將請求體寫入到conn的輸出流中
                    os.write(requestBody);
                    //記得呼叫輸出流的flush方法
                    os.flush();
                    //關閉輸出流
                    os.close();
                    //當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
                    InputStream is = conn.getInputStream();
                    //獲得響應體的位元組陣列
                    responseBody = getBytesByInputStream(is);
                    //獲得響應頭
                    responseHeader = getResponseHeader(conn);
                }
            } catch (MalformedURLException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //最後將conn斷開連線
                if (conn != null) {
                    conn.disconnect();
                }
            }

            result.put("url", url.toString());
            result.put("action", action);
            result.put("requestHeader", requestHeader);
            result.put("requestBody", requestBody);
            result.put("responseHeader", responseHeader);
            result.put("responseBody", responseBody);
            return result;
        }

        @Override
        protected void onPostExecute(Map<String, Object> result) {
            super.onPostExecute(result);
            String url = (String)result.get("url");//請求的URL地址
            String action = (String) result.get("action");//http請求的操作型別
            String requestHeader = (String) result.get("requestHeader");//請求頭
            byte[] requestBody = (byte[]) result.get("requestBody");//請求體
            String responseHeader = (String) result.get("responseHeader");//響應頭
            byte[] responseBody = (byte[]) result.get("responseBody");//響應體

            //更新tvUrl,顯示Url
            tvUrl.setText(url);

            //更新tvRequestHeader,顯示請求頭
            if (requestHeader != null) {
                tvRequestHeader.setText(requestHeader);
            }

            //更新tvRequestBody,顯示請求體
            if(requestBody != null){
                try{
                    String request = new String(requestBody, "UTF-8");
                    tvRequestBody.setText(request);
                }catch (UnsupportedEncodingException e){
                    e.printStackTrace();
                }
            }

            //更新tvResponseHeader,顯示響應頭
            if (responseHeader != null) {
                tvResponseHeader.setText(responseHeader);
            }

            //更新tvResponseBody,顯示響應體
            if (NETWORK_GET.equals(action)) {
                String response = getStringByBytes(responseBody);
                tvResponseBody.setText(response);
            } else if (NETWORK_POST_KEY_VALUE.equals(action)) {
                String response = getStringByBytes(responseBody);
                tvResponseBody.setText(response);
            } else if (NETWORK_POST_XML.equals(action)) {
                //將表示xml的位元組陣列進行解析
                String response = parseXmlResultByBytes(responseBody);
                tvResponseBody.setText(response);
            } else if (NETWORK_POST_JSON.equals(action)) {
                //將表示json的位元組陣列進行解析
                String response = parseJsonResultByBytes(responseBody);
                tvResponseBody.setText(response);
            }
        }

        //讀取請求頭
        private String getReqeustHeader(HttpURLConnection conn) {
            //https://github.com/square/okhttp/blob/master/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/HttpURLConnectionImpl.java#L236
            Map<String, List<String>> requestHeaderMap = conn.getRequestProperties();
            Iterator<String> requestHeaderIterator = requestHeaderMap.keySet().iterator();
            StringBuilder sbRequestHeader = new StringBuilder();
            while (requestHeaderIterator.hasNext()) {
                String requestHeaderKey = requestHeaderIterator.next();
                String requestHeaderValue = conn.getRequestProperty(requestHeaderKey);
                sbRequestHeader.append(requestHeaderKey);
                sbRequestHeader.append(":");
                sbRequestHeader.append(requestHeaderValue);
                sbRequestHeader.append("\n");
            }
            return sbRequestHeader.toString();
        }

        //讀取響應頭
        private String getResponseHeader(HttpURLConnection conn) {
            Map<String, List<String>> responseHeaderMap = conn.getHeaderFields();
            int size = responseHeaderMap.size();
            StringBuilder sbResponseHeader = new StringBuilder();
            for(int i = 0; i < size; i++){
                String responseHeaderKey = conn.getHeaderFieldKey(i);
                String responseHeaderValue = conn.getHeaderField(i);
                sbResponseHeader.append(responseHeaderKey);
                sbResponseHeader.append(":");
                sbResponseHeader.append(responseHeaderValue);
                sbResponseHeader.append("\n");
            }
            return sbResponseHeader.toString();
        }

        //根據位元組陣列構建UTF-8字串
        private String getStringByBytes(byte[] bytes) {
            String str = "";
            try {
                str = new String(bytes, "UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return str;
        }

        //從InputStream中讀取資料,轉換成byte陣列,最後關閉InputStream
        private byte[] getBytesByInputStream(InputStream is) {
            byte[] bytes = null;
            BufferedInputStream bis = new BufferedInputStream(is);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            BufferedOutputStream bos = new BufferedOutputStream(baos);
            byte[] buffer = new byte[1024 * 8];
            int length = 0;
            try {
                while ((length = bis.read(buffer)) > 0) {
                    bos.write(buffer, 0, length);
                }
                bos.flush();
                bytes = baos.toByteArray();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            return bytes;
        }

        //根據檔名,從asserts目錄中讀取檔案的位元組陣列
        private byte[] getBytesFromAssets(String fileName){
            byte[] bytes = null;
            AssetManager assetManager = getAssets();
            InputStream is = null;
            try{
                is = assetManager.open(fileName);
                bytes = getBytesByInputStream(is);
            }catch (IOException e){
                e.printStackTrace();
            }
            return bytes;
        }

        //將表示xml的位元組陣列進行解析
        private String parseXmlResultByBytes(byte[] bytes) {
            InputStream is = new ByteArrayInputStream(bytes);
            StringBuilder sb = new StringBuilder();
            List<Person> persons = XmlParser.parse(is);
            for (Person person : persons) {
                sb.append(person.toString()).append("\n");
            }
            return sb.toString();
        }

        //將表示json的位元組陣列進行解析
        private String parseJsonResultByBytes(byte[] bytes){
            String jsonString = getStringByBytes(bytes);
            List<Person> persons = JsonParser.parse(jsonString);
            StringBuilder sb = new StringBuilder();
            for (Person person : persons) {
                sb.append(person.toString()).append("\n");
            }
            return sb.toString();
        }

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367

這個App是用來傳送http請求的客戶端,除此之外,我還建立了一個JSP的WebProject作為服務端,用Servlet對客戶端發來的請求進行處理,Servlet的程式碼如下所示:

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.Enumeration;

@WebServlet(name = "MyServlet")
public class MyServlet extends HttpServlet {
    //GET請求
    private static final String NETWORK_GET = "NETWORK_GET";
    //用POST傳送鍵值對
    private static final String NETWORK_POST_KEY_VALUE = "NETWORK_POST_KEY_VALUE";
    //用POST傳送XML資料
    private static final String NETWORK_POST_XML = "NETWORK_POST_XML";
    //用POST傳送JSON資料
    private static final String NETWORK_POST_JSON = "NETWORK_POST_JSON";

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        String action = request.getHeader("action");
        //將輸入與輸出都設定為UTF-8編碼
        request.setCharacterEncoding("UTF-8");
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/plain;charset=UTF-8");
        //response.setHeader("content-type","text/plain;charset=UTF-8");
        if(NETWORK_GET.equals(action) || NETWORK_POST_KEY_VALUE.equals(action)){
            //對於NETWORK_GET和NETWORK_POST_KEY_VALUE,遍歷鍵值對,並將鍵值對重新寫回到輸出結果中
            Enumeration<String> parameterNames = request.getParameterNames();
            PrintWriter writer = response.getWriter();
            while(parameterNames.hasMoreElements()){
                String name = parameterNames.nextElement();
                String value = request.getParameter(name);
                if(request.getMethod().toUpperCase().equals("GET")){
                    //GET請求需要進行編碼轉換,POST不需要
                    value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
                }
                writer.write(name + "=" + value + "\n");
            }
            writer.flush();
            writer.close();
        }else if(NETWORK_POST_XML.equals(action) || NETWORK_POST_JSON.equals(action)){
            //對於NETWORK_POST_XML和NETWORK_POST_JSON,將請求體重新寫入到響應體的輸出流中
            //通過request.getInputStream()得到http請求的請求體
            BufferedInputStream bis  = new BufferedInputStream(request.getInputStream());
            //通過response.getOutputStream()得到http請求的響應體
            BufferedOutputStream bos = new BufferedOutputStream(response.getOutputStream());
            byte[] buffer = new byte[1024 * 8];
            int length = 0;
            while ( (length = bis.read(buffer)) > 0){
                bos.write(buffer, 0, length);
            }
            bos.flush();
            bos.close();
            bis.close();
        }else{
            PrintWriter writer = response.getWriter();
            writer.write("非法的請求頭: action");
            writer.flush();
            writer.close();
        }
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67

傳送GET請求

由於網路請求耗時而且會阻塞當前執行緒,所以我們將傳送http請求的操作都放到NetworkAsyncTask中,NetworkAsyncTask是繼承自AsyncTask。

點選”GET”按鈕後,介面如下所示: 
這裡寫圖片描述

GET請求是最簡單的http請求,其傳送請求的程式碼如下所示:

if (NETWORK_GET.equals(action)) {
    //傳送GET請求
    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet?name=孫群&age=27");
    conn = (HttpURLConnection) url.openConnection();
    //HttpURLConnection預設就是用GET傳送請求,所以下面的setRequestMethod可以省略
    conn.setRequestMethod("GET");
    //HttpURLConnection預設也支援從服務端讀取結果流,所以下面的setDoInput也可以省略
    conn.setDoInput(true);
    //用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
    conn.setRequestProperty("action", NETWORK_GET);
    //禁用網路快取
    conn.setUseCaches(false);
    //獲取請求頭
    requestHeader = getReqeustHeader(conn);
    //在對各種引數配置完成後,通過呼叫connect方法建立TCP連線,但是並未真正獲取資料
    //conn.connect()方法不必顯式呼叫,當呼叫conn.getInputStream()方法時內部也會自動呼叫connect方法
    conn.connect();
    //呼叫getInputStream方法後,服務端才會收到請求,並阻塞式地接收服務端返回的資料
    InputStream is = conn.getInputStream();
    //將InputStream轉換成byte陣列,getBytesByInputStream會關閉輸入流
    responseBody = getBytesByInputStream(is);
    //獲取響應頭
    responseHeader = getResponseHeader(conn);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

上面的註釋寫的比較詳細了,此處對程式碼進行一下簡單說明。

  • 我們在URL的後面新增了?name=孫群&age=27,這樣就相當於新增了兩個鍵值對,其形式如?key1=value1&key2=value2&key3=value3,在?後面新增鍵值對,鍵和值之間用=相連,鍵值對之間用&分隔,服務端可以讀取這些鍵值對資訊。

  • HttpURLConnection預設就是用GET傳送請求,當然也可以用conn.setRequestMethod(“GET”)將其顯式地設定為GET請求。

  • 通過setRequestProperty方法可以設定請求頭,既可以是標準的請求頭,也可以是自定義的請求頭,此處我們設定了自定義的請求頭action,用於服務端判斷請求的型別。

  • GET請求容易被快取,我們可以用conn.setUseCaches(false)禁用快取。

  • 通過呼叫connect方法可以讓客戶端和伺服器之間建立TCP連線,建立連線之後不會立即傳輸資料,只是表示處於connected狀態了。該方法不必顯式呼叫,因為在之後的getInputStream方法中會隱式地呼叫該方法。

  • 呼叫HttpURLConnection的getInputStream()方法可以獲得響應結果的輸入流,即伺服器向客戶端輸出的資訊,需要注意的是,getInputStream()方法的呼叫必須在一系列的set方法之後進行。

  • 然後在方法getBytesByInputStream中,通過輸入流的read方法得到位元組陣列,read方法是阻塞式的,每read一次,其實就是從伺服器上下載一部分資料,直到將伺服器的輸出全部下載完成,這樣就得到響應體responseBody了。

  • 我們可以通過getReqeustHeader方法讀取請求頭,程式碼如下所示:

        //讀取請求頭
        private String getReqeustHeader(HttpURLConnection conn) {            
            Map<String, List<String>> requestHeaderMap = conn.getRequestProperties();
            Iterator<String> requestHeaderIterator = requestHeaderMap.keySet().iterator();
            StringBuilder sbRequestHeader = new StringBuilder();
            while (requestHeaderIterator.hasNext()) {
                String requestHeaderKey = requestHeaderIterator.next();
                String requestHeaderValue = conn.getRequestProperty(requestHeaderKey);
                sbRequestHeader.append(requestHeaderKey);
                sbRequestHeader.append(":");
                sbRequestHeader.append(requestHeaderValue);
                sbRequestHeader.append("\n");
            }
            return sbRequestHeader.toString();
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    由上可以看出,以上方法主要還是呼叫了HttpURLConnection的方法getRequestProperty獲取請求頭,需要注意的是getRequestProperty方法執行時,客戶端和伺服器之間必須還未建立TCP連線,即還沒有呼叫connect方法,在connected之後執行getRequestProperty會丟擲異常,詳見https://github.com/square/okhttp/blob/master/okhttp-urlconnection/src/main/java/okhttp3/internal/huc/HttpURLConnectionImpl.java#L236

  • 我們還可以通過getResponseHeader方法獲取響應頭,程式碼如下所示:

        //讀取響應頭
        private String getResponseHeader(HttpURLConnection conn) {
            Map<String, List<String>> responseHeaderMap = conn.getHeaderFields();
            int size = responseHeaderMap.size();
            StringBuilder sbResponseHeader = new StringBuilder();
            for(int i = 0; i < size; i++){
                String responseHeaderKey = conn.getHeaderFieldKey(i);
                String responseHeaderValue = conn.getHeaderField(i);
                sbResponseHeader.append(responseHeaderKey);
                sbResponseHeader.append(":");
                sbResponseHeader.append(responseHeaderValue);
                sbResponseHeader.append("\n");
            }
            return sbResponseHeader.toString();
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    通過方法getHeaderFieldKey可以獲得響應頭的key值,通過方法getHeaderField可以獲得響應頭的value值。

在後臺的Servelt中將鍵值對資訊重新原樣寫入到客戶端,服務端的程式碼如下所示: 

if(NETWORK_GET.equals(action) || NETWORK_POST_KEY_VALUE.equals(action)){
            //對於NETWORK_GET和NETWORK_POST_KEY_VALUE,遍歷鍵值對,並將鍵值對重新寫回到輸出結果中
            Enumeration<String> parameterNames = request.getParameterNames();
            PrintWriter writer = response.getWriter();
            while(parameterNames.hasMoreElements()){
                String name = parameterNames.nextElement();
                String value = request.getParameter(name);
                if(request.getMethod().toUpperCase().equals("GET")){
                    //GET請求需要進行編碼轉換,POST不需要
                    value = new String(value.getBytes("ISO-8859-1"), "UTF-8");
                }
                writer.write(name + "=" + value + "\n");
            }
            writer.flush();
            writer.close();
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

在接收到服務端返回的資料後,在AsyncTask的onPostExecute方法中會將Url、請求頭、響應頭、響應體的值展示在UI上。


用POST傳送鍵值對資料

點選”POST KEY VALUE”按鈕,可以用POST傳送鍵值對資料,介面如下所示: 
這裡寫圖片描述

程式碼如下所示:

if (NETWORK_POST_KEY_VALUE.equals(action)) {
    //用POST傳送鍵值對資料
    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
    conn = (HttpURLConnection) url.openConnection();
    //通過setRequestMethod將conn設定成POST方法
    conn.setRequestMethod("POST");
    //呼叫conn.setDoOutput()方法以顯式開啟請求體
    conn.setDoOutput(true);
    //用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
    conn.setRequestProperty("action", NETWORK_POST_KEY_VALUE);
    //獲取請求頭
    requestHeader = getReqeustHeader(conn);
    //獲取conn的輸出流
    OutputStream os = conn.getOutputStream();
    //獲取兩個鍵值對name=孫群和age=27的位元組陣列,將該位元組陣列作為請求體
    requestBody = new String("name=孫群&age=27").getBytes("UTF-8");
    //將請求體寫入到conn的輸出流中
    os.write(requestBody);
    //記得呼叫輸出流的flush方法
    os.flush();
    //關閉輸出流
    os.close();
    //當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
    InputStream is = conn.getInputStream();
    //獲得響應體的位元組陣列
    responseBody = getBytesByInputStream(is);
    //獲得響應頭
    responseHeader = getResponseHeader(conn);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

使用POST傳送請求的程式碼與用GET傳送請求的程式碼大部分類似,我們只對其中不同的地方做下說明。

  • 需要通過setRequestMethod將conn設定成POST方法。

  • 如果想用POST傳送請求體,那麼需要呼叫setDoOutput方法,將其設定為true。

  • 通過conn.getOutputStream()獲得輸出流,可以向輸出流中寫入請求體,最後記得呼叫輸出流的flush方法,注意此時並沒有真正將請求體傳送到伺服器端。

  • 當呼叫getInputStream方法後,才真正將請求體的內容傳送到伺服器。

在我們的伺服器端的Servlet中,在接收到POST請求傳送的鍵值對資料後,也只是簡單地將鍵值對資料原樣寫入給客戶端,具體程式碼參見上文,不再贅述。


用POST傳送XML資料

點選”POST XML”按鈕,可以用POST傳送XML資料,介面如下所示: 
這裡寫圖片描述

程式碼如下所示:

if (NETWORK_POST_XML.equals(action)) {
    //用POST傳送XML資料
    url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
    conn = (HttpURLConnection) url.openConnection();
    //通過setRequestMethod將conn設定成POST方法
    conn.setRequestMethod("POST");
    //呼叫conn.setDoOutput()方法以顯式開啟請求體
    conn.setDoOutput(true);
    //用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
    conn.setRequestProperty("action", NETWORK_POST_XML);
    //獲取請求頭
    requestHeader = getReqeustHeader(conn);
    //獲取conn的輸出流
    OutputStream os = conn.getOutputStream();
    //讀取assets目錄下的person.xml檔案,將其位元組陣列作為請求體
    requestBody = getBytesFromAssets("person.xml");
    //將請求體寫入到conn的輸出流中
    os.write(requestBody);
    //記得呼叫輸出流的flush方法
    os.flush();
    //關閉輸出流
    os.close();
    //當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
    InputStream is = conn.getInputStream();
    //獲得響應體的位元組陣列
    responseBody = getBytesByInputStream(is);
    //獲得響應頭
    responseHeader = getResponseHeader(conn);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

上面的程式碼與用POST傳送鍵值對的程式碼很相似,對其進行簡單說明。

  • 上述程式碼通過getBytesFromAssets方法讀取了assets目錄下的person.xml檔案,將xml檔案的位元組流作為請求體requestBody,然後將該請求體傳送到伺服器。

  • person.xml檔案如下所示:

    <?xml version="1.0" encoding="utf-8"?>
    <persons>
        <person id="101">
            <name>張三</name>
            <age>27</age>
        </person>
        <person id="102">
            <name>李四</name>
            <age>28</age>
        </person>
    </persons>
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    <person>標籤對應著Person類,Person類程式碼如下所示:

    package com.ispring.httpurlconnection;
    
    public class Person {
        private String id = "";
        private String name = "";
        private int age = 0;
    
        public String getId(){
            return id;
        }
    
        public void setId(String id){
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        @Override
        public String toString() {
            return new StringBuilder().append("name:").append(getName()).append(", age:").append(getAge()).toString();
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
  • 服務端接收到客戶端傳送來的XML資料之後,只是簡單的將其原樣寫回到客戶端,即客戶端接收到的響應體還是原來的XML資料,然後客戶端通過parseXmlResultByBytes方法對該XML資料進行解析,將位元組陣列轉換成List<Person>,parseXmlResultByBytes程式碼如下所示: 

        //將表示xml的位元組陣列進行解析
        private String parseXmlResultByBytes(byte[] bytes) {
            InputStream is = new ByteArrayInputStream(bytes);
            StringBuilder sb = new StringBuilder();
            List<Person> persons = XmlParser.parse(is);
            for (Person person : persons) {
                sb.append(person.toString()).append("\n");
            }
            return sb.toString();
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    該方法使用了自定義的XmlParser類對XML資料進行解析,其原始碼如下所示:

    package com.ispring.httpurlconnection;
    
    import org.xml.sax.Attributes;
    import org.xml.sax.SAXException;
    import org.xml.sax.helpers.DefaultHandler;
    
    import java.io.InputStream;
    import java.util.ArrayList;
    import java.util.List;
    
    import javax.xml.parsers.SAXParser;
    import javax.xml.parsers.SAXParserFactory;
    
    public class XmlParser {
    
        public static List<Person> parse(InputStream is) {
            List<Person> persons = new ArrayList<>();
            try{
                SAXParserFactory factory = SAXParserFactory.newInstance();
                SAXParser parser = factory.newSAXParser();
                PersonHandler personHandler = new PersonHandler();
                parser.parse(is, personHandler);
                persons = personHandler.getPersons();
            }catch (Exception e){
                e.printStackTrace();
            }
            return persons;
        }
    
        static class PersonHandler extends DefaultHandler {
            private List<Person> persons;
            private Person temp;
            private StringBuilder sb;
    
            public List<Person> getPersons(){
                return persons;
            }
    
            @Override
            public void startDocument() throws SAXException {
                super.startDocument();
                persons = new ArrayList<>();
                sb = new StringBuilder();
            }
    
            @Override
            public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
                super.startElement(uri, localName, qName, attributes);
                sb.setLength(0);
                if(localName.equals("person")){
                    temp = new Person();
                    int length = attributes.getLength();
                    for(int i = 0; i < length; i++){
                        String name = attributes.getLocalName(i);
                        if(name.equals("id")){
                            String value = attributes.getValue(i);
                            temp.setId(value);
                        }
                    }
                }
            }
    
            @Override
            public void characters(char[] ch, int start, int length) throws SAXException {
                super.characters(ch, start, length);
                sb.append(ch, start, length);
            }
    
            @Override
            public void endElement(String uri, String localName, String qName) throws SAXException {
                super.endElement(uri, localName, qName);
                if(localName.equals("name")){
                    String name = sb.toString();
                    temp.setName(name);
                }else if(localName.equals("age")){
                    int age = Integer.parseInt(sb.toString());
                    temp.setAge(age);
                }else if(localName.equals("person")){
                    persons.add(temp);
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84

用POST傳送JSON資料

點選”POST JSON”按鈕,可以用POST傳送JSON資料,介面如下所示: 
這裡寫圖片描述

程式碼如下所示:

if (NETWORK_POST_JSON.equals(action)) {
     //用POST傳送JSON資料
     url = new URL("http://192.168.31.200:8080/HttpServer/MyServlet");
     conn = (HttpURLConnection) url.openConnection();
     //通過setRequestMethod將conn設定成POST方法
     conn.setRequestMethod("POST");
     //呼叫conn.setDoOutput()方法以顯式開啟請求體
     conn.setDoOutput(true);
     //用setRequestProperty方法設定一個自定義的請求頭:action,由於後端判斷
     conn.setRequestProperty("action", NETWORK_POST_JSON);
     //獲取請求頭
     requestHeader = getReqeustHeader(conn);
     //獲取conn的輸出流
     OutputStream os = conn.getOutputStream();
     //讀取assets目錄下的person.json檔案,將其位元組陣列作為請求體
     requestBody = getBytesFromAssets("person.json");
     //將請求體寫入到conn的輸出流中
     os.write(requestBody);
     //記得呼叫輸出流的flush方法
     os.flush();
     //關閉輸出流
     os.close();
     //當呼叫getInputStream方法時才真正將請求體資料上傳至伺服器
     InputStream is = conn.getInputStream();
     //獲得響應體的位元組陣列
     responseBody = getBytesByInputStream(is);
     //獲得響應頭
     responseHeader = getResponseHeader(conn);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

上面的程式碼與用POST傳送XML的程式碼很相似,對其進行簡單說明。

  • 上述程式碼通過getBytesFromAssets方法讀取了assets目錄下的person.json檔案,將json檔案的位元組流作為請求體requestBody,然後將該請求體傳送到伺服器。
  • person.json檔案如下所示:

    {
      "persons": [{
        "id": "101",
        "name":"張三",
        "age":27
      }, {
        "id": "102",
        "name":"李四",
        "age":28
      }]
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    persons陣列中的每個元素都對應著一個Person物件。

  • 服務端接收到客戶端傳送來的JSON資料之後,只是簡單的將其原樣寫回到客戶端,即客戶端接收到的響應體還是原來的JSON資料,然後客戶端通過parseJsonResultByBytes方法對該XML資料進行解析,將位元組陣列轉換成List,parseXmlResultByBytes程式碼如下所示:

        //將表示json的位元組陣列進行解析
        private String parseJsonResultByBytes(byte[] bytes){
            String jsonString = getStringByBytes(bytes);
            List<Person> persons = JsonParser.parse(jsonString);
            StringBuilder sb = new StringBuilder();
            for (Person person : persons) {
                sb.append(person.toString()).append("\n");
            }
            return sb.toString();
        }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    該方法又使用了自定義的JsonParset類,原始碼如下所示:

    package com.ispring.httpurlconnection;
    
    import org.json.JSONArray;
    import org.json.JSONException;
    import org.json.JSONObject;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class JsonParser {
        public static List<Person> parse(String jsonString){
            List<Person> persons = new ArrayList<>();
    
            try{
                JSONObject jsonObject = new JSONObject(jsonString);
                JSONArray jsonArray = jsonObject.getJSONArray("persons");
                int length = jsonArray.length();
                for(int i = 0; i < length; i++){
                    JSONObject personObject = jsonArray.getJSONObject(i);
                    String id = personObject.getString("id");
                    String name = personObject.getString("name");
                    int age = personObject.getInt("age");
                    Person person = new Person();
                    person.setId(id);
                    person.setName(name);
                    person.setAge(age);
                    persons.add(person);
                }
            }catch (JSONException e){
                e.printStackTrace();
            }
    
            return persons;
        }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

其他

  • 如果Http請求體的資料很大,就可以認為該請求主要是完成資料上傳的作用;如果響應體的資料很大,就可以認為該請求主要完成資料下載的作用。

  • 上面我們通過demo演示瞭如何上傳XML檔案和JSON檔案,並對二者進行解析。在上傳的過程中,Android要寫入Content-Length這個請求頭,Content-Length就是請求體的位元組長度,注意是位元組長度,而不是字元長度(漢字等會佔用兩個位元組)。預設情況下,Android為了得到Content-Length的長度,Android會把請求體放到記憶體中的,直到輸出流呼叫了close方法後,才會讀取記憶體中請求體的位元組長度,將其作為請求頭Content-Length。當要上傳的請求體很大時,這會非常佔用記憶體,為此Android提供了兩個方法來解決這個問題。

    • setFixedLengthStreamingMode (int contentLength) 
      如果請求體的大小是知道的,那麼可以呼叫HttpURLConnection的setFixedLengthStreamingMode (int contentLength) 方法,該方法會告訴Android要傳輸的請求頭Content-Length的大小,這樣Android就無需讀取整個請求體的大小,從而不必一下將請求體全部放到記憶體中,這樣就避免了請求體佔用巨大記憶體的問題。

    • setChunkedStreamingMode (int chunkLength) 
      如果請求體的大小不知道,那麼可以呼叫setChunkedStreamingMode (int chunkLength)方法。該方法將傳輸的請求體分塊傳輸,即將原始的資料分成多個資料塊,chunkLength表示每塊傳輸的位元組大小。比如我們要傳輸的請求體大小是10M,我們將chunkLength設定為1024 * 1024 byte,即1M,那麼Android會將請求體分10次傳輸,每次傳輸1M,具體的傳輸規則是:每次傳輸一個資料塊時,首先在一行中寫明該資料塊的長度,比如1024 * 1024,然後在後面的一行中寫入要傳輸的資料塊的位元組陣列,再然後是一個空白行,這樣第一資料塊就這樣傳輸,在空白行之後就是第二個資料塊的傳輸,與第一個資料塊的格式一樣,直到最後沒有資料塊要傳輸了,就在用一行寫明要傳輸的位元組為0,這樣在伺服器端就知道讀取完了整個請求體了。

      如果設定的chunkLength的值為0,那麼表示Android會使用預設的一個值作為實際的chunkLength。

      使用setChunkedStreamingMode方法的前提是伺服器支援分塊資料傳輸,分塊資料傳輸是從HTTP 1.1開始支援的,所以如果你的伺服器只支援HTTP 1.0的話,那麼不能使用setChunkedStreamingMode方法。

希望本文對大家使用HttpURLConnection有所幫助!


相關文章