Android 網絡請求詳解

咱們知道大多數的 Android 應用程序都是經過和服務器進行交互來獲取數據的。若是使用 HTTP 協議來發送和接收網絡數據,就免不了使用 HttpURLConnection 和 HttpClient,而 Android 中主要提供了上述兩種方式來進行 HTTP 操做。而且這兩種方式都支持 HTTPS 協議、以流的形式進行上傳和下載、配置超時時間、IPv六、以及鏈接池等功能。html

可是 Googl e發佈 6.0 版本的時候聲明原生剔除 HttpClient,可是筆者認爲 HttpClient 會提供相應的 jar 包作支持,畢竟 Google 對向下兼容這方面一直都作的很好,相信在選擇網絡功能的時候咱們會選本身喜歡的方法。java

HttpURLConnection

接着咱們來看一下如何使用 HttpURLConnection 來處理簡單的網絡請求。android

// Get方式請求  
public static void requestByGet() throws Exception {  
    String path = "10.128.7.34:3000/name=helloworld&password=android";  
    // 新建一個URL對象  
    URL url = new URL(path);  
    // 打開一個HttpURLConnection鏈接  
    HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();  
    // 設置鏈接超時時間  
    urlConn.setConnectTimeout(6 * 1000);  
    // 開始鏈接  
    urlConn.connect();  
    // 判斷請求是否成功  
    if (urlConn.getResponseCode() == HTTP_200) {  
        // 獲取返回的數據  
        byte[] data = readStream(urlConn.getInputStream());  
        Log.i(TAG_GET, new String(data, "UTF-8"));  
    } else {  
        Log.i(TAG_GET, "Get方式請求失敗");  
    }  
    // 關閉鏈接  
    urlConn.disconnect();  
}
// Post方式請求  
public static void requestByPost() throws Throwable {  
    String path = "http://10.128.7.34:3000/login";  
    // 請求的參數轉換爲byte數組  
    String params = "name+ URLEncoder.encode("helloworld", "UTF-8")  
            + "&password=" + URLEncoder.encode("android", "UTF-8");  
    byte[] postData = params.getBytes();  
    // 新建一個URL對象  
    URL url = new URL(path);  
    // 打開一個HttpURLConnection鏈接  
    HttpURLConnection urlConn = (HttpURLConnection) url.openConnection();  
    // 設置鏈接超時時間  
    urlConn.setConnectTimeout(5 * 1000);  
    // Post請求必須設置容許輸出  
    urlConn.setDoOutput(true);  
    // Post請求不能使用緩存  
    urlConn.setUseCaches(false);  
    // 設置爲Post請求  
    urlConn.setRequestMethod("POST");  
    urlConn.setInstanceFollowRedirects(true);  
    // 配置請求Content-Type  
    urlConn.setRequestProperty("Content-Type",  
            "application/x-www-form-urlencode");  
    // 開始鏈接  
    urlConn.connect();  
    // 發送請求參數  
    DataOutputStream dos = new DataOutputStream(urlConn.getOutputStream());  
    dos.write(postData);  
    dos.flush();  
    dos.close();  
    // 判斷請求是否成功  
    if (urlConn.getResponseCode() == HTTP_200) {  
        // 獲取返回的數據  
        byte[] data = readStream(urlConn.getInputStream());  
        Log.i(TAG_POST, new String(data, "UTF-8"));  
    } else {  
        Log.i(TAG_POST, "Post方式請求失敗");  
    }  
}

關於HttpURLConnection的相關開源工程

因爲 Google 已經代表了推薦廣大開發者使用 HttpURLConnection,那麼想必是有必定緣由的。數據庫

xUtils3

這裏就推薦大夥都很熟悉的開源項目 xUtils 的「弟弟「,xUtils3。xUtils 包含了 view 注入,圖片處理,數據庫操做以及網絡請求封裝,xUtils 使用的網絡請求封裝是基於 HttpClient 的。xUtils3 使用的網絡請求是基於 HttpURLConnection,咱們着重說明一下xUtils3。apache

RequestParams params = new RequestParams("http://10.128.7.34:3000/upload");
        // 加到url裏的參數, http://xxxx/s?wd=xUtils
        params.addQueryStringParameter("wd", "xUtils");
        // 添加到請求 body 體的參數, 只有 POST, PUT, PATCH, DELETE 請求支持.
        // params.addBodyParameter("wd", "xUtils");

        // 使用 multipart 表單上傳文件
        params.setMultipart(true);
        params.addBodyParameter(
                "file",
                new File("/sdcard/test.jpg"),
                null); // 若是文件沒有擴展名, 最好設置contentType 參數.
        params.addBodyParameter(
                "file2",
                new FileInputStream(new File("/sdcard/test2.jpg")),
                "image/jpeg",
                // 測試中文文件名
                "你+& \" 好.jpg"); // InputStream 參數獲取不到文件名, 最好設置, 除非服務端不關心這個參數.
        x.http().post(params, new Callback.CommonCallback<String>() {
            @Override
            public void onSuccess(String result) {
                Toast.makeText(x.app(), result, Toast.LENGTH_LONG).show();
            }

            @Override
            public void onError(Throwable ex, boolean isOnCallback) {
                Toast.makeText(x.app(), ex.getMessage(), Toast.LENGTH_LONG).show();
            }

            @Override
            public void onCancelled(CancelledException cex) {
                Toast.makeText(x.app(), "cancelled", Toast.LENGTH_LONG).show();
            }

            @Override
            public void onFinished() {

            }
        });

具備 cache 功能json

RequestParams params = new RequestParams("http://10.128.7.34:3000/upload");
        params.wd = "xUtils";
        // 默認緩存存活時間, 單位:毫秒.(若是服務沒有返回有效的max-age 或 Expires)
        params.setCacheMaxAge(1000 * 60);
        Callback.Cancelable cancelable
                // 使用 CacheCallback, xUtils 將爲該請求緩存數據.
                = x.http().get(params, new Callback.CacheCallback<String>() {

            private boolean hasError = false;
            private String result = null;

            @Override
            public boolean onCache(String result) {
                // 獲得緩存數據, 緩存過時後不會進入這個方法.
                // 若是服務端沒有返回過時時間, 參考params.setCacheMaxAge(maxAge)方法.
                //
                // * 客戶端會根據服務端返回的 header 中 max-age 或 expires 來肯定本地緩存是否給 onCache 方法.
                //   若是服務端沒有返回 max-age 或 expires, 那麼緩存將一直保存, 除非這裏本身定義了返回false的
                //   邏輯, 那麼xUtils將請求新數據, 來覆蓋它.
                //
                // * 若是信任該緩存返回 true, 將再也不請求網絡;
                //   返回 false 繼續請求網絡, 但會在請求頭中加上ETag, Last-Modified等信息,
                //   若是服務端返回 304, 則表示數據沒有更新, 不繼續加載數據.
                //
                this.result = result;
                return false; // true: 信任緩存數據, 不在發起網絡請求; false 不信任緩存數據.
            }

            @Override
            public void onSuccess(String result) {
                // 注意: 若是服務返回 304 或 onCache 選擇了信任緩存, 這裏將不會被調用,
                // 可是 onFinished 總會被調用.
                this.result = result;
            }

            @Override
            public void onError(Throwable ex, boolean isOnCallback) {
                hasError = true;
                Toast.makeText(x.app(), ex.getMessage(), Toast.LENGTH_LONG).show();
                if (ex instanceof HttpException) { // 網絡錯誤
                    HttpException httpEx = (HttpException) ex;
                    int responseCode = httpEx.getCode();
                    String responseMsg = httpEx.getMessage();
                    String errorResult = httpEx.getResult();
                    // ...
                } else { // 其餘錯誤
                    // ...
                }
            }

            @Override
            public void onCancelled(CancelledException cex) {
                Toast.makeText(x.app(), "cancelled", Toast.LENGTH_LONG).show();
            }

            @Override
            public void onFinished() {
                if (!hasError && result != null) {
                    // 成功獲取數據
                    Toast.makeText(x.app(), result, Toast.LENGTH_LONG).show();
                }
            }
        });

post請求直接更改 post 方式,以及須要提交的參數便可,篇幅問題這裏就不在一一列舉了。經過以上代碼能夠看到,咱們在使用 xUtils 來請求網絡的時候會很是的方便,只用在回調函數裏面作對應的代碼邏輯處理便可,不用關心線程問題,極大的解放了咱們的生產力。數組

Android Stuido Gradle使用方法以下:緩存

compile 'org.xutils:xutils:3.1.+'

Volley

在 Android 2.3 及以上版本,使用的是 HttpURLConnection,而在Android 2.2 及如下版本,使用的是 HttpClient。鑑於如今的手機行業發展速度,咱們已經不考慮 Android2.2 了。性能優化

簡單提供一些 Volley 的實例:服務器

//簡單的 GET 請求
mQueue = Volley.newRequestQueue(getApplicationContext());  
mQueue.add(new JsonObjectRequest(Method.GET, url, null,  
            new Listener() {  
                @Override  
                public void onResponse(JSONObject response) {  
                    Log.d(TAG, "response : " + response.toString());  
                }  
            }, null));  
mQueue.start();
//對 ImageView 的操做
ImageListener listener = ImageLoader.getImageListener(imageView, android.R.drawable.ic_launcher, android.R.drawable.ic_launcher);  
mImageLoader.get(url, listener);  

//對 ImageView 網絡加載的處理
mImageView.setImageUrl(url, imageLoader);

固然咱們也能夠定製本身的 request

mRequestQueue.add( new GsonRequest(url, ListResponse.class, null,  
    new Listener() {  
        public void onResponse(ListResponse response) {  
            appendItemsToList(response.item);  
            notifyDataSetChanged();  
        }  
    }  
}

HttpClient

一樣,咱們來看一下 HttpClient 的簡單請求。

// HttpGet 方式請求  
public static void requestByHttpGet() throws Exception {  
    String path = "http://10.128.7.34:3000/name=helloworld&password=android";  
    // 新建 HttpGet 對象  
    HttpGet httpGet = new HttpGet(path);  
    // 獲取 HttpClient 對象  
    HttpClient httpClient = new DefaultHttpClient();  
    // 獲取 HttpResponse 實例  
    HttpResponse httpResp = httpClient.execute(httpGet);  
    // 判斷是夠請求成功  
    if (httpResp.getStatusLine().getStatusCode() == HTTP_200) {  
        // 獲取返回的數據  
        String result = EntityUtils.toString(httpResp.getEntity(), "UTF-8");  
        Log.i(TAG_HTTPGET, "HttpGet 方式請求成功,返回數據以下:");  
        Log.i(TAG_HTTPGET, result);  
    } else {  
        Log.i(TAG_HTTPGET, "HttpGet 方式請求失敗");  
    }  
}
// HttpPost 方式請求  
public static void requestByHttpPost() throws Exception {  
    String path = "https://reg.163.com/login";  
    // 新建 HttpPost 對象  
    HttpPost httpPost = new HttpPost(path);  
    // Post 參數  
    List<NameValuePair> params = new ArrayList<NameValuePair>();  
    params.add(new BasicNameValuePair("name", "helloworld"));  
    params.add(new BasicNameValuePair("password", "android"));  
    // 設置字符集  
    HttpEntity entity = new UrlEncodedFormEntity(params, HTTP.UTF_8);  
    // 設置參數實體  
    httpPost.setEntity(entity);  
    // 獲取 HttpClient 對象  
    HttpClient httpClient = new DefaultHttpClient();  
    // 獲取 HttpResponse 實例  
    HttpResponse httpResp = httpClient.execute(httpPost);  
    // 判斷是夠請求成功  
    if (httpResp.getStatusLine().getStatusCode() == HTTP_200) {  
        // 獲取返回的數據  
        String result = EntityUtils.toString(httpResp.getEntity(), "UTF-8");  
        Log.i(TAG_HTTPGET, "HttpPost 方式請求成功,返回數據以下:");  
        Log.i(TAG_HTTPGET, result);  
    } else {  
        Log.i(TAG_HTTPGET, "HttpPost 方式請求失敗");  
    }  
}

關於 HttpClinet 的相關開源工程

HttpClient 也擁有這大量優秀的開源工程,afinal、xUtils 以及AsyncHttpClient,也能夠爲廣大開發者提供已經造好的輪子。因爲xUtils 是基於 afinal 重寫的,如今 xUtils3 也有替代 xUtils 的趨勢,因此咱們在這就簡單介紹一下 AsyncHttpClient。

AsyncHttpClient

見名知意,AsyncHttpClient 對處理異步 Http 請求至關擅長,並經過匿名內部類處理回調結果,Http 異步請求均位於非 UI 線程,不會阻塞 UI 操做,經過線程池處理併發請求處理文件上傳、下載、響應結果自動打包 JSON 格式。使用起來會很方便。

//GET請求
AsyncHttpClient client = new AsyncHttpClient();
//固然這裏也能夠換成 new JsonHttpResponseHandler(),咱們就能直接得到 JSON 數據了。
client.get("http://www.google.com", new AsyncHttpResponseHandler() {
 
    @Override
    public void onStart() {
        // called before request is started
    }
 
    @Override
    public void onSuccess(int statusCode, Header[] headers, byte[] response) {
        // called when response HTTP status is "200 OK"
    }
 
    @Override
    public void onFailure(int statusCode, Header[] headers, byte[] errorResponse, Throwable e) {
        // called when response HTTP status is "4XX" (eg. 401, 403, 404)
    }
 
    @Override
    public void onRetry(int retryNo) {
        // called when request is retried
    }
});
//POST 請求
AsyncHttpClient client = new AsyncHttpClient();
RequestParams params = new RequestParams();
params.put("key", "value");
params.put("more", "data");
//同上,這裏同樣能夠改爲處理 JSON 數據的方法
client.get("http://www.google.com", params, new
    TextHttpResponseHandler() {
        @Override
        public void onSuccess(int statusCode, Header[] headers, String response) {
            System.out.println(response);
        }
 
        @Override
        public void onFailure(int statusCode, Header[] headers, String responseBody, Throwable error) {
            Log.d("ERROR", error);
        }    
    }
);

通過上面的代碼發現,AsyncHttpClient 使用起來也是異常簡潔,主要靠回調方法來處理成功或失敗以後的邏輯。仔細想一想,xUtils 的處理方式和這個處理方式很相似,看來好用設計仍是很受人青睞的。

OkHttp

若是兩種網絡請求都想使用怎麼辦?那麼 OkHttp 是一個最佳解決方案了。

OkHttp 在網絡請求方面的口碑很好,就連 Google 本身也有使用OkHttp。雖然 Google6.0 中剔除了 HttpClient 的 Api,可是因爲OkHttp 的影響力以及其強大的功能,使用 OkHttp 無需重寫您程序中的網絡代碼。同時最重要的一點 OkHttp 實現了幾乎和java.net.HttpURLConnection 同樣的 API。若是您用了 Apache HttpClient,則 OkHttp 也提供了一個對應的 okhttp-apache 模塊。足以說明 OkHttp 的強大,下面是一些例子。

  • 通常的 get 請求

  • 通常的 post 請求

  • 基於 Http 的文件上傳

  • 文件下載

  • 加載圖片

  • 支持請求回調,直接返回對象、對象集合

  • 支持 session 的保持

簡單代碼實例

//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();
  return response.body().string();
}
//POST 請求
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();
  Response response = client.newCall(request).execute();
  return response.body().string();
}

Android Studio Gradle 使用方式:

compile 'com.squareup.okhttp:okhttp:2.7.0'

總結

Android 開發應用少不了使用網絡,移動互聯時代,搶佔終端入口變得異常重要,那麼咱們在開發過程當中,無論使用哪種網絡請求,HttpURLConnection 或者是 HttpClient,均可以知足咱們和服務器的溝通。

但是發佈的 App 到用戶手中後,有用 WIFI 的,有用流量的,網絡環境多樣,咱們怎麼能知道用戶在什麼樣的狀況下訪問服務器的流暢度呢?

答案很簡單,只要集成了OneAPM Mobile Insight,便可輕鬆知曉網絡交互狀況,輕鬆瞭解用戶在使用App的過程當中哪裏容易出問題,並對症下藥。

OneAPM Mobile Insight ,監控網絡請求及網絡錯誤,提高用戶留存。訪問 OneAPM 官方網站感覺更多應用性能優化體驗,想閱讀更多技術文章,請訪問 OneAPM 官方技術博客
本文轉自 OneAPM 官方博客

相關文章
相關標籤/搜索