1. 程式人生 > >Android網路程式設計之一:原生網路訪問簡單封裝

Android網路程式設計之一:原生網路訪問簡單封裝

前言:轉眼間2017了,去年給自己定的一週一篇部落格被拖成了一月一篇,後來忙著找工作也荒廢了。竟然還被CSDN部落格之星提名,又點燃了擼部落格的激情。
作為一個android codder,網略訪問基本上在專案中都會用到,常用的獲取JSON資料、下載檔案(圖片等)、上傳資料、上傳問檔案等等。Android原生的訪問網路有HttpClientHttpURLConnection。在Android 6.0時刪除了HttpClientOkHttp的呼聲也隨之高漲。

下面會對這幾種網路訪問模式寫下簡單的示例,網上大多示例都是訪問別人的伺服器或者本地的伺服器,不便於大家測試,我用阿里雲搭建了個簡單的服務端。

吐下槽,之前使用的MyEclipse開發Java Web,換電腦之後在網上搜怎麼配MacJava Web開發環境,有很多人提到使用IntelliJ IDEA,搞環境又搞了一兩天,本人後端小菜~

NetworkTask編寫

這裡我們使用AsyncTask的方式進行子執行緒訪問網路並回調到主執行緒,在建立NetworkTask的時候要傳入是GET還是POST請求,然後傳入引數開始訪問網略獲取資料。

1. 這裡我們約定`GET`請求時引數即為拼接的請求地址,如:
    new xxxNetworkTask(NetworkTask.GET).execute("http://123.57.31.11/androidnet/getJoke?id=5");
2. 約定`POST`請求時引數第一個為地址,其他為請求引數對,如:
    new xxxNetworkTask(NetworkTask.POST).execute("http://123.57.31.11/androidnet/getJoke", "id=5");
/**
 * 訪問網路AsyncTask基類,訪問網路在子執行緒進行並返回主執行緒通知訪問的結果
 */
public abstract class NetworkTask extends AsyncTask<String, Integer, String> {

    public static final String TAG = "NetworkTask";

    public static String GET = "GET";
    public static String POST = "POST";
    private String mRequestMethod;

    public NetworkTask(String method) {
        this
.mRequestMethod = method; } @Override protected String doInBackground(String... params) { String data; if(GET.equals(mRequestMethod)) { data = doGet(params[0]); } else if(POST.equals(mRequestMethod)) { data = doPost(params); } else { throw new RuntimeException("Request mode can only be GET or POST!"); } return data; } @Override protected void onPostExecute(String result) { super.onPostExecute(result); } /** * 以GET的方式訪問網路 * * @param url * @return 返回的字串資料 */ public abstract String doGet(String url); /** * 以POST的方式訪問網路 * * @param params * @return 返回的字串資料 */ public abstract String doPost(String[] params); }

然後我們加上返回資料的監聽回撥

/**
 * 訪問網路AsyncTask基類,訪問網路在子執行緒進行並返回主執行緒通知訪問的結果
 */
public abstract class NetworkTask extends AsyncTask<String, Integer, String> {

    public static final String TAG = "NetworkTask";

    public static String GET = "GET";
    public static String POST = "POST";
    // 訪問方式,只能是GET或POST
    private String mRequestMethod;
    // 是否訪問網路成功
    protected boolean isSuccess = true;
    // 監聽回撥
    private ResponceLintener mResponceLintener;

    public NetworkTask(String method) {
        this.mRequestMethod = method;
    }

    @Override
    protected String doInBackground(String... params) {
        String data;
        String url = params[0];
        if(GET.equals(mRequestMethod)) {
            data = doGet(url);
        } else if(POST.equals(mRequestMethod)) {
            Map<String, String> paramMap = new HashMap<String, String>();
            // 第一個引數為訪問的介面,不為body引數
            for (int i = 1; i < params.length; i++) {
                String[] value = new String[2];
                if (params[i].split("=").length == 1) {
                    value[0] = params[i].split("=")[0];
                    value[1] = "";
                } else {
                    value = params[i].split("=");
                }
                paramMap.put(value[0], value[1]);
            }
            data = doPost(url, paramMap);
        } else {
            throw new RuntimeException("Request mode can only be GET or POST!");
        }
        return data;
    }

    @Override
    protected void onPostExecute(String result) {
        if(null != mResponceLintener) {
            if(isSuccess) {
                mResponceLintener.onSuccess(result);
            } else {
                mResponceLintener.onError(result);
            }
        }
    }

    /**
     * 以GET的方式訪問網路
     * 
     * @param url
     * @return 返回的字串資料
     */
    public abstract String doGet(String url);

    /**
     * 以POST的方式訪問網路
     * 
     * @param url
     * @param paramMap
     * @return 返回的字串資料
     */
    public abstract String doPost(String url, Map<String, String> paramMap);

    public void setResponceLintener(ResponceLintener l) {
        this.mResponceLintener = l;
    }

    public interface ResponceLintener {
        /**
         * 成功的監聽回撥
         * @param result
         */
        void onSuccess(String result);
        /**
         * 失敗的監聽回撥
         * @param error
         */
        void onError(String error);
    }

}

OK,經過簡單的封裝之後,我們在使用不同的形式訪問網路時只需要對應的去實現doGetdoPost就可以了。

HttpClient

儘管HttpClient刪除了,我們還是簡單回顧下它的使用。

HttpClientNetworkTask

建立HttpClientNetworkTask繼承NetworkTask並實現抽象方法。

/**
 * 以HttpClient訪問網路AsyncTask,訪問網路在子執行緒進行並返回主執行緒通知訪問的結果
 */
public class HttpClientNetworkTask extends NetworkTask {

    public HttpClientNetworkTask(String method) {
        super(method);
    }

    @Override
    public String doGet(String url) {
        return null;
    }

    @Override
    public String doPost(String url, Map<String, String> paramMap) {
        return null;
    }

}

下面我們只需要使用HttpClient去完成響應的網路請求就可以啦~ 是不是很清爽~~

HttpClient使用步驟:

  1. 建立HttpClient物件;
  2. 建立請求的物件,如果為GET請求,則建立HttpGet物件,如果為POST請求,則建立HttpPost物件;
  3. 呼叫HttpClient物件的execute傳送請求,執行該方法後,將獲得伺服器返回的HttpResponse物件;
  4. 檢查相應狀態是否正常;
  5. 獲得相應物件當中的資料。

GET

@Override
public String doGet(String url) {
    String result = null;
    HttpClient httpCient = new DefaultHttpClient();
    HttpGet httpGet = new HttpGet(url);
    try {
        HttpResponse httpResponse = httpCient.execute(httpGet);
        if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
             HttpEntity entity = httpResponse.getEntity();
             result = EntityUtils.toString(entity,"utf-8");
        } else {
            isSuccess = false;
            result = "網路響應狀態碼不為200!";
        }
    } catch (IOException e) {
        isSuccess = false;
        result = "網路訪問錯誤:" + e.getMessage();
    }

    return result;
}

使用如下:

NetworkTask networkTask = new HttpClientNetworkTask(NetworkTask.GET);
networkTask.execute("http://123.57.31.11/androidnet/getJoke?id=5");
networkTask.setResponceLintener(new NetworkTask.ResponceLintener() {

    @Override
    public void onSuccess(String result) {
        tv.setText(result);
    }

    @Override
    public void onError(String error) {
        Toast.makeText(getApplicationContext(), error, Toast.LENGTH_SHORT).show();
    }
});

據說沒圖不便於理解,上圖:

網路訪問

POST

@Override
public String doPost(String url, Map<String, String> paramMap) {
    String result;
    HttpClient httpCient = new DefaultHttpClient();
    HttpPost httpRequest = new HttpPost(url);
    // 使用NameValuePair來儲存要傳遞的Post引數
    List<NameValuePair> params = new ArrayList<NameValuePair>();
    for (Map.Entry<String, String> entry : paramMap.entrySet()) {
        params.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
    }
    try {
        httpRequest.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8));
        HttpResponse response = httpCient.execute(httpRequest);
        if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) {
            result = EntityUtils.toString(response.getEntity());
        } else {
            isSuccess = false;
            result = "網路響應狀態碼不為200!";
        }
    } catch (IOException e) {
        isSuccess = false;
        result = "網路訪問錯誤:" + e.getMessage();
    }

    return result;
}

使用如下:

NetworkTask networkTask = new HttpClientNetworkTask(NetworkTask.POST);
networkTask.execute("http://123.57.31.11/androidnet/getJoke", "id=5");
networkTask.setResponceLintener(new NetworkTask.ResponceLintener() {

    @Override
    public void onSuccess(String result) {
        tv.setText(result);
    }

    @Override
    public void onError(String error) {
        Toast.makeText(getApplicationContext(), error, Toast.LENGTH_SHORT).show();
    }
});

HttpURLConnection

HttpURLConnectionNetworkTask

建立HttpURLConnectionNetworkTask繼承NetworkTask並實現抽象方法。

/**
 * 以HttpURLConnection訪問網路AsyncTask,訪問網路在子執行緒進行並返回主執行緒通知訪問的結果
 */
public class HttpURLConnectionNetworkTask extends NetworkTask {

    public HttpURLConnectionNetworkTask(String method) {
        super(method);
    }

    @Override
    public String doGet(String url) {
        return null;
    }

    @Override
    public String doPost(String url, Map<String, String> paramMap) {
        return null;
    }

}

下面我們用HttpURLConnection去完成響應的網路。

HttpURLConnection使用步驟:

  1. 例項化URL物件;
  2. 例項化HttpUrlConnection物件;
  3. 設定請求連線屬性,傳遞引數等;
  4. 獲取返回碼判斷是否連結成功;
  5. 讀取輸入流;
  6. 關閉連結。

GET

@Override
public String doGet(String httpUrl) {
    String result;
    try {
        URL url = new URL(httpUrl);
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod("GET");
        urlConnection.setReadTimeout(5000);
        urlConnection.setConnectTimeout(5000);
        urlConnection.setRequestProperty("Charset", "UTF-8");
        if (urlConnection.getResponseCode() == 200) {
            InputStream is = urlConnection.getInputStream();
            result = readFromStream(is);
        } else {
            isSuccess = false;
            result = "網路響應狀態碼不為200!";
        }
    } catch(IOException e) {
        isSuccess = false;
        result = "網路訪問錯誤:" + e.getMessage();
    }

    return result;
}

這裡編寫了一個從流獲取字串的工具

/**
 * 輸入流獲取字串
 * 
 * @param is 輸入流
 * @return String 返回的字串
 * @throws IOException
 */
public String readFromStream(InputStream is) throws IOException {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int len = 0;
    while ((len = is.read(buffer)) != -1) {
        baos.write(buffer, 0, len);
    }
    is.close();
    String result = baos.toString();
    baos.close();
    return result;
}

使用方法和HttpClientNetworkTask一樣,只不過是把實現類由HttpClientNetworkTask換為HttpURLConnection:

NetworkTask networkTask = new HttpURLConnectionNetworkTask(NetworkTask.GET);
networkTask.execute("http://123.57.31.11/androidnet/getJoke?id=6");
networkTask.setResponceLintener(new NetworkTask.ResponceLintener() {

    @Override
    public void onSuccess(String result) {
        tv.setText(result);
    }

    @Override
    public void onError(String error) {
        Toast.makeText(getApplicationContext(), error, Toast.LENGTH_SHORT).show();
    }
});

POST

@Override
public String doPost(String httpUrl, Map<String, String> paramMap) {
    String result;
    try {
        URL url = new URL(httpUrl);
        HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
        urlConnection.setRequestMethod("POST");
        urlConnection.setReadTimeout(5000);
        urlConnection.setConnectTimeout(5000);
        // 配置連線的Content-type
        urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
        urlConnection.setDoOutput(true);    // 傳送POST請求必須設定允許輸出 
        urlConnection.setDoInput(true);     // 傳送POST請求必須設定允許輸入 

        String data = "";
        boolean firstParam = true;
        for (Map.Entry<String, String> entry : paramMap.entrySet()) {
            if(firstParam) {
                data += entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), "UTF-8");
                firstParam = false;
            } else {
                data += "&" + entry.getKey() + "=" + URLEncoder.encode(entry.getValue(), "UTF-8");
            }
        }
        OutputStream os = urlConnection.getOutputStream();
        os.write(data.getBytes());
        os.flush();

        if (urlConnection.getResponseCode() == 200) {
            InputStream is = urlConnection.getInputStream();
            result = readFromStream(is);
        } else {
            isSuccess = false;
            result = "網路響應狀態碼不為200!";
        }
    } catch (IOException e) {
        isSuccess = false;
        result = "網路訪問錯誤:" + e.getMessage();
    }

    return result;
}

使用方法和HttpClientNetworkTask一樣,只不過是把實現類由HttpClientNetworkTask換為HttpURLConnection:

NetworkTask networkTask = new HttpURLConnectionNetworkTask(NetworkTask.POST);
networkTask.execute("http://123.57.31.11/androidnet/getJoke", "id=6");
networkTask.setResponceLintener(new NetworkTask.ResponceLintener() {

    @Override
    public void onSuccess(String result) {
        tv.setText(result);
    }

    @Override
    public void onError(String error) {
        Toast.makeText(getApplicationContext(), error, Toast.LENGTH_SHORT).show();
    }
});

總結

其實開發的時候我們不是很關心具體是怎麼聯網的,封裝好之後使用起來越簡單越好。我給你地址和引數你去訪問網略然後返回我關心的資料就可以了。

以上是使用HttpClientHttpURLConnection進行了GETPOST的返回資料為字串型別簡單封裝,有興趣的朋友也可以進行OkHttp或其他方式的封裝。

當然在使用的時候可能會有更加複雜的場景,比如:上傳一個圖片、下載一個檔案等。

我們最常使用的還是獲取JSON型別的資料,然後封裝到實體。那麼這一塊如何封裝呢?接下來的一篇部落格將拋開NetworkTask進行獲取JSON資料並封裝到實體的簡單編寫。