Android網絡請求發(fā)展簡史和RxJava+Retrofit+OkHttp實踐

Android開發(fā)網絡使用小結

概述

Android 作為與IOS并駕齊驅的一個智能手機平臺吨艇,在將近十年的時間內有了長足的發(fā)展诽俯,而這兩大平臺之所以能PK掉當年盛極一時的諾基亞及其使用的塞班系統(tǒng)彤恶,基于網絡的豐富的功能功不可沒。做了幾年Android開發(fā)后,今天把Android的網絡使用小結一下伍俘。

Android 網絡請求推薦使用和發(fā)展歷史

  • 2.2之前:HttpClient
  • 2.3之后:HttpURLConnection
  • 2013年Google IO大會后:Google官方團隊推出的volley
  • OkHttp出現(xiàn)以后:OkHttp
  • Android6.0以后Google官方Api移除HttpClient(繼續(xù)使用HttpClient及基于其封裝的網絡庫會出異常)
  • 現(xiàn)在:推薦retrofit+OkHttp
  • 我負責的項目中實際使用的網絡層封裝為rxJava+retrofit+OkHttp邪锌,簡述一下:
    • rxjava:負責訂閱回調,將請求回調切到主線程癌瘾、中間回調攔截處理:統(tǒng)一異常處理等觅丰;
    • Retrofit:網絡請求框架,配合OkHttp使用柳弄,使得網絡請求更方便舶胀、更強大;
    • OkHttp:與HttpClient和HttpURLConnection類似碧注,最底層實際處理Http請求嚣伐。

1 網絡請求的原始方式

1.1 HttpClient

Apache公司提供的庫,提供高效的萍丐、最新的轩端、功能豐富的支持HTTP協(xié)議工具包,支持HTTP協(xié)議最新的版本和建議逝变,是個很不錯的開源框架基茵,封裝了Http的請求,參數(shù)壳影,內容體拱层,響應等,擁有眾多API宴咧。
最原始的網絡請求方式根灯,很強大但API很復雜,2.3之后Google官方便更推薦使用HttpURLConnection掺栅。在此不做展開烙肺。

1.2 HttpURLConnection

Sun公司提供的庫,也是Java的標準類庫java.net中的一員氧卧,但這個類什么都沒封裝桃笙,用起來很原始,若需要高級功能沙绝,則會顯得不太方便搏明,比如重訪問的自定義,會話和cookie等一些高級功能宿饱。
在Android平臺熏瞄,Android2.2版本之前,HttpURLConnection還不太完善谬以,存在一些問題强饮,最好的請求方式是HttpClient,Android2.3版本之后为黎,HttpURLConnection功能趨于完善邮丰,成為官方推薦的網絡請求方式行您,很多開源網絡庫的封裝也是選擇在2.3版本后網絡請求最終用HttpURLConnection的方式,例如Google官方推出的網絡框架Volley剪廉。

具體HttpClient和HttpURLConnection的區(qū)別可以參考這篇文章:HttpClient和HttpURLConnection的區(qū)別(點擊查看)
HttpURLConnection 請求網絡代碼示例:

/**
 * 基于HttpURLConnection的Http請求的工具類
 *
 */
public class HttpUtils {

    private static final int TIMEOUT_IN_MILLIONS = 5000;

    public interface CallBack {
        void onRequestComplete(String result);
    }


    /**
     * 異步的Get請求
     *
     * @param urlStr
     * @param callBack
     */
    public static void doGetAsyn(final String urlStr, final CallBack callBack) {
        new Thread() {
            public void run() {
                try {
                    String result = doGet(urlStr);
                    if (callBack != null) {
                        callBack.onRequestComplete(result);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }

            ;
        }.start();
    }

    /**
     * 異步的Post請求
     *
     * @param urlStr
     * @param params
     * @param callBack
     * @throws Exception
     */
    public static void doPostAsyn(final String urlStr, final String params,
                                  final CallBack callBack) throws Exception {
        new Thread() {
            public void run() {
                try {
                    String result = doPost(urlStr, params);
                    if (callBack != null) {
                        callBack.onRequestComplete(result);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }

            }

        }.start();

    }

    /**
     * Get請求娃循,獲得返回數(shù)據(jù)
     *
     * @param urlStr
     * @return
     * @throws Exception
     */
    public static String doGet(String urlStr) {
        URL url = null;
        HttpURLConnection conn = null;
        InputStream is = null;
        ByteArrayOutputStream baos = null;
        try {
            url = new URL(urlStr);
            conn = (HttpURLConnection) url.openConnection();
            conn.setReadTimeout(TIMEOUT_IN_MILLIONS);
            conn.setConnectTimeout(TIMEOUT_IN_MILLIONS);
            conn.setRequestMethod("GET");
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            if (conn.getResponseCode() == 200) {
                is = conn.getInputStream();
                baos = new ByteArrayOutputStream();
                int len = -1;
                byte[] buf = new byte[128];

                while ((len = is.read(buf)) != -1) {
                    baos.write(buf, 0, len);
                }
                baos.flush();
                return baos.toString();
            } else {
                throw new RuntimeException(" responseCode is not 200 ... ");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null)
                    is.close();
            } catch (IOException e) {
            }
            try {
                if (baos != null)
                    baos.close();
            } catch (IOException e) {
            }
            conn.disconnect();
        }

        return null;

    }

    /**
     * 向指定 URL 發(fā)送POST方法的請求
     *
     * @param url   發(fā)送請求的 URL
     * @param param 請求參數(shù),請求參數(shù)應該是 name1=value1&name2=value2 的形式斗蒋。
     * @return 所代表遠程資源的響應結果
     * @throws Exception
     */
    public static String doPost(String url, String param) {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        try {
            URL realUrl = new URL(url);
            // 打開和URL之間的連接
            HttpURLConnection conn = (HttpURLConnection) realUrl
                    .openConnection();
            // 設置通用的請求屬性
            conn.setRequestProperty("accept", "*/*");
            conn.setRequestProperty("connection", "Keep-Alive");
            conn.setRequestMethod("POST");
            conn.setRequestProperty("Content-Type",
                    "application/x-www-form-urlencoded");
            conn.setRequestProperty("charset", "utf-8");
            conn.setUseCaches(false);
            // 發(fā)送POST請求必須設置如下兩行
            conn.setDoOutput(true);
            conn.setDoInput(true);
            conn.setReadTimeout(TIMEOUT_IN_MILLIONS);
            conn.setConnectTimeout(TIMEOUT_IN_MILLIONS);

            if (param != null && !param.trim().equals("")) {
                // 獲取URLConnection對象對應的輸出流
                out = new PrintWriter(conn.getOutputStream());
                // 發(fā)送請求參數(shù)
                out.print(param);
                // flush輸出流的緩沖
                out.flush();
            }
            // 定義BufferedReader輸入流來讀取URL的響應
            in = new BufferedReader(
                    new InputStreamReader(conn.getInputStream()));
            String line;
            while ((line = in.readLine()) != null) {
                result += line;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        // 使用finally塊來關閉輸出流捌斧、輸入流
        finally {
            try {
                if (out != null) {
                    out.close();
                }
                if (in != null) {
                    in.close();
                }
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return result;
    }
}

2 網絡請求的進階模式

2.1 Volley

Google官方于2013年 Google IO大會上推出的 網絡訪問框架,現(xiàn)已經不推薦使用泉沾。了解可以參考這篇文章:Volley 源碼解析

2.2 其他封裝的網絡請求框架

與volley類似的第三方封裝的網絡請求框架有FinalHttp捞蚂、android-async-http等,基本都是基于HttpClient和HttpURLConnection封裝的跷究,本質上沒有太大區(qū)別姓迅,每個庫封裝的成都和使用的便利性和靈活性不同。

3 網絡請求的時尚模式(RxJava+Retrofit+OkHttp實踐)

3.1 OkHttp

OkHttp是一款優(yōu)秀的HTTP框架俊马,它支持get請求和post請求丁存,支持基于Http的文件上傳和下載,支持加載圖片柴我,支持下載文件透明的GZIP壓縮解寝,支持響應緩存避免重復的網絡請求,支持使用連接池來降低響應延遲問題艘儒。
OkHttp的入門和集成請參考這篇官方教程:OkHttp 官方教程解析 - 徹底入門 OkHttp 使用

3.2 與Retrofit結合的OkHttp

使用OkHttp的項目多半會配合同是 出的Retrofit编丘,兩者相結合,更加強大彤悔。
而我在實際開發(fā)中使用的網絡框架是rxJava+Retrofit+OkHttp的結合。
不廢話索守,直接上代碼:

先看調用示例:
步驟一:在ApiService中添加接口

  • 1 寫上請求網絡的方法名rxGetTest晕窑、接口的返回參數(shù)的Json反序列化出的Bean對象WeatherInfo;
  • 2 @GET參數(shù)上配置上這個接口具體請求Url的后綴卵佛,如@GET("data/sk/101091001.html")杨赤;
    實際請求的url為 http://www.weather.com.cn/data/sk/101091001.html
    retrofit中配置的BaseUrl為http://www.weather.com.cn/ 所以此處只寫去除Baseurl的后綴即可。
package com.bailiangjin.httprequest.rxokdemo;

import com.bailiangjin.httprequest.net.rxretrofitokhttp.design.BaseData;
import com.bailiangjin.httprequest.rxokdemo.model.PostInfo;
import com.bailiangjin.httprequest.rxokdemo.model.WeatherInfo;

import java.util.Map;

import retrofit2.http.FieldMap;
import retrofit2.http.FormUrlEncoded;
import retrofit2.http.GET;
import retrofit2.http.POST;
import rx.Observable;

/**
 * 網絡請求Service 方法匯總 將調用的方法在此處進行rx綁定
 * Created by bailiangjin on 2017/2/15.
 */
public interface ApiService {

    @GET("data/sk/101091001.html")
    Observable<WeatherInfo> rxGetTest();

    @GET("query?type=yuantong&postid=200382770316")
    Observable<PostInfo> rxGetPostInfo();


    /**
     * Post方式請求需要添加 FormUrlEncoded標識
     * @param map
     * @return
     */
    @FormUrlEncoded
    @POST("/")
    Observable<BaseData<PostInfo>> rxPostTest(@FieldMap Map<String, String> map);

}

第二部 請求網絡 代碼如下截汪,是不是很爽:

//get調用方式示例   RxRequestHelper.requestNotDealCommonError(getWeatherApiService().rxGetTest(), new CommonResponseSubscriber<WeatherInfo>() {
          @Override
          public void onNext(WeatherInfo weatherInfoBaseData) {
            //拿到回調的數(shù)據(jù)具體處理
          }
      });

//post調用方式示例
  /**
   * post示例 不使用公有異常處理 post只寫上了使用方式 具體測試請使用自己的接口測試
   * @param subscriber
   */ 
 Map<String, String> paramMap = new HashMap<>();
 RxRequestHelper.requestNotDealCommonError(getWeatherApiService().rxPostTest1(paramMap), new CommonResponseSubscriber<PostInfo>() {
          @Override
          public void onNext(PostInfo postInfoBaseData) {
               //拿到回調的數(shù)據(jù)具體處理
          }
      });

對于上面代碼中用到的getWeatherApiService() 其實是一個ApiService對象疾牲,是通過如下代碼生成的,如果BaseUrl一致 只用一個ApiService即可衙解,寫好一次阳柔,以后每次添加接口只走上面兩部即可,如果請求的接口BaseUrl不只一個那也沒關系蚓峦,添加一行代碼即可添加一個新的BaseUrl的Retrofit舌剂,是不是很強大济锄,其實這主要是借助了枚舉,具體可以看工程中的代碼霍转,在此不展開討論了荐绝。

package com.bailiangjin.httprequest.rxokdemo;

import com.bailiangjin.httprequest.net.rxretrofitokhttp.tools.RetrofitCollection;

import retrofit2.Retrofit;

/**
 * 天氣Service
 * Created by bailiangjin on 2017/2/16.
 */

public enum WeatherApiService {
    INSTANCE;
    private ApiService apiService;
    private Retrofit retrofit;

    WeatherApiService() {
        retrofit = RetrofitCollection.WEATHER_INSTANCE.getRetrofit();
        apiService = retrofit.create(ApiService.class);
    }

    public ApiService getApiService() {
        return apiService;
    }

    public String getBaseUrl() {
        return retrofit.baseUrl().toString();
    }
}

其他核心代碼如下:

package com.bailiangjin.httprequest.net.rxretrofitokhttp.tools;

import java.io.IOException;
import java.util.concurrent.TimeUnit;
import okhttp3.CacheControl;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.logging.HttpLoggingInterceptor;

/**
 *
 * 最底層的 OKHttpClient
 * Created by bailiangjin on 2017/2/16.
 */

public enum MyOkHttpClient {

    INSTANCE;

    private OkHttpClient okHttpClient;

    MyOkHttpClient() {
        okHttpClient = new OkHttpClient.Builder()
                //.cache(cache)  //禁用okhttp自身的的緩存
                .connectTimeout(10, TimeUnit.SECONDS)
                .readTimeout(10, TimeUnit.SECONDS)
                .writeTimeout(10, TimeUnit.SECONDS)
                .retryOnConnectionFailure(true)
                .addInterceptor(new MyInterceptor())
                .addInterceptor(new HttpLoggingInterceptor())
                .build();
    }

    public OkHttpClient getOkHttpClient() {
        return okHttpClient;
    }

    static class MyInterceptor implements Interceptor {
        @Override
        public Response intercept(Chain chain) throws IOException {
            Request.Builder builder = chain.request().newBuilder();
            //添加header
            //CommonNetUtils.addHeader(builder);
            //修改請求為只從網絡中讀數(shù)據(jù)
            Request request = builder
                    .cacheControl(CacheControl.FORCE_NETWORK).build();
            return chain.proceed(request);
        }
    }
}

package com.bailiangjin.httprequest.net.rxretrofitokhttp.tools;


import retrofit2.Retrofit;
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * Retrofit 集合 可以通過添加枚舉元素的方式 方便地添加 不同 root url的 retrofit
 * @author bailiangjin 2017-02-16
 */
public enum RetrofitCollection {
    WEATHER_INSTANCE("http://www.weather.com.cn/"),
    EXPRESS_INSTANCE("http://www.kuaidi100.com/");
    private Retrofit retrofit;

    RetrofitCollection(String baseUrl) {
        retrofit = new Retrofit.Builder()
                .client(MyOkHttpClient.INSTANCE.getOkHttpClient())
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                //也可以添加自定義的RxJavaCallAdapterFactory
                .build();
    }

    public Retrofit getRetrofit() {
        return retrofit;
    }
}
package com.bailiangjin.httprequest.net.rxretrofitokhttp.tools;

import com.bailiangjin.httprequest.net.rxretrofitokhttp.design.BaseData;
import com.bailiangjin.httprequest.net.rxretrofitokhttp.design.CommonErrors;

import rx.Observable;
import rx.Subscriber;
import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers;

/**
 * 網絡請求的RxHelper類 功能 1、將網絡請求的回調綁定到主線程 2避消、進行統(tǒng)一的異常處理 可根據(jù)需要選擇
 * Created by bailiangjin on 2017/2/16.
 */

public class RxRequestHelper {

    /**
     * 綁定回調到mainthread 并統(tǒng)一處理異常
     * @param observable
     * @param subscriber
     * @param <T>
     * @return
     */
    public static <T> Observable requestDealCommonError(Observable<BaseData<T>> observable, Subscriber<BaseData<T>> subscriber) {
        mapCommonErrors(observable);
        setSubscribeToAndroidMainThread(observable, subscriber);
        return observable;
    }



    /**
     * 綁定回調到mainthread 不統(tǒng)一處理異常
     * @param observable
     * @param subscriber
     * @param <T>
     */
    public static <T> void requestNotDealCommonError(Observable<T> observable, Subscriber<T> subscriber) {

        setSubscribeToAndroidMainThread(observable, subscriber);
    }


    /**
     * 將回調切換到MainThread
     * @param observable
     * @param subscriber
     * @param <T>
     */
    private static <T> void setSubscribeToAndroidMainThread(Observable<T> observable, Subscriber<T> subscriber) {
        observable.subscribeOn(Schedulers.io())
                .unsubscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(subscriber);
    }

    /**
     * 異常統(tǒng)一處理
     * @param observable
     * @param <T>
     */
    private static <T> void mapCommonErrors(Observable<BaseData<T>> observable) {
        observable.map(new CommonErrors<T>());
    }

}

篇幅問題低滩,文章里只貼了部分代碼,想實際體驗請clone我的github項目代碼具體運行:Github網絡請求代碼鏈接:AndroidHttpRequest (點擊查看)

3.3 其他基于OkHttp的開源庫

其實OkHttp并非一個網絡框架岩喷,他的屬性和功能與HttpClient和HttpURLConnection類似恕沫,都是Http請求網絡的具體方式,當OkHttp廣泛使用后均驶,也會有很多基于OkHttp封裝的可能更方便的第三方框架昏兆,這里就不在展開,感興趣的可以Google一下妇穴。

本文所有代碼都可到我的github項目AndroidHttpRequest中查看爬虱。

更多精彩文章推薦:
Android Activity 全局管理 終極解決方案
Android BaseAdapter的極簡封裝

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市腾它,隨后出現(xiàn)的幾起案子跑筝,更是在濱河造成了極大的恐慌,老刑警劉巖瞒滴,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件曲梗,死亡現(xiàn)場離奇詭異,居然都是意外死亡妓忍,警方通過查閱死者的電腦和手機虏两,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來世剖,“玉大人定罢,你說我怎么就攤上這事∨蕴保” “怎么了祖凫?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長酬凳。 經常有香客問我惠况,道長,這世上最難降的妖魔是什么宁仔? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任稠屠,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘完箩。我一直安慰自己赐俗,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布弊知。 她就那樣靜靜地躺著阻逮,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秩彤。 梳的紋絲不亂的頭發(fā)上叔扼,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音漫雷,去河邊找鬼瓜富。 笑死,一個胖子當著我的面吹牛降盹,可吹牛的內容都是我干的与柑。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼蓄坏,長吁一口氣:“原來是場噩夢啊……” “哼价捧!你這毒婦竟也來了?” 一聲冷哼從身側響起涡戳,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤结蟋,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后渔彰,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體嵌屎,經...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年恍涂,在試婚紗的時候發(fā)現(xiàn)自己被綠了宝惰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡再沧,死狀恐怖掌测,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情产园,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布夜郁,位于F島的核電站什燕,受9級特大地震影響,放射性物質發(fā)生泄漏竞端。R本人自食惡果不足惜屎即,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧技俐,春花似錦乘陪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至井赌,卻和暖如春谤逼,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背仇穗。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工流部, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纹坐。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓枝冀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親耘子。 傳聞我的和親對象是個殘疾皇子果漾,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內容