Android网络请求发展简史和RxJava+Retrofit+OkHttp实践

Android开发网络使用小结

概述

Android 作为与IOS并驾齐驱的一个智能手机平台,在将近十年的时间内有了长足的发展,而这两大平台之所以能PK掉当年盛极一时的诺基亚及其使用的塞班系统,基于网络的丰富的功能功不可没。做了几年Android开发后,今天把Android的网络使用小结一下。

Android 网络请求推荐使用和发展历史

  • 2.2之前:HttpClient
  • 2.3之后:HttpURLConnection
  • 2013年Google IO大会后:Google官方团队推出的volley
  • OkHttp出现以后:OkHttp
  • Android6.0以后Google官方Api移除HttpClient(继续使用HttpClient及基于其封装的网络库会出异常)
  • 现在:推荐retrofit+OkHttp
  • 我负责的项目中实际使用的网络层封装为rxJava+retrofit+OkHttp,简述一下:
    • rxjava:负责订阅回调,将请求回调切到主线程、中间回调拦截处理:统一异常处理等;
    • Retrofit:网络请求框架,配合OkHttp使用,使得网络请求更方便、更强大;
    • OkHttp:与HttpClient和HttpURLConnection类似,最底层实际处理Http请求。

1 网络请求的原始方式

1.1 HttpClient

Apache公司提供的库,提供高效的、最新的、功能丰富的支持HTTP协议工具包,支持HTTP协议最新的版本和建议,是个很不错的开源框架,封装了Http的请求,参数,内容体,响应等,拥有众多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的区别可以参考这篇文章:HttpClient和HttpURLConnection的区别(点击查看)
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请求,获得返回数据
     *
     * @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 发送POST方法的请求
     *
     * @param url   发送请求的 URL
     * @param param 请求参数,请求参数应该是 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);
            // 发送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());
                // 发送请求参数
                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大会上推出的 网络访问框架,现已经不推荐使用。了解可以参考这篇文章:Volley 源码解析

2.2 其他封装的网络请求框架

与volley类似的第三方封装的网络请求框架有FinalHttp、android-async-http等,基本都是基于HttpClient和HttpURLConnection封装的,本质上没有太大区别,每个库封装的成都和使用的便利性和灵活性不同。

3 网络请求的时尚模式(RxJava+Retrofit+OkHttp实践)

3.1 OkHttp

OkHttp是一款优秀的HTTP框架,它支持get请求和post请求,支持基于Http的文件上传和下载,支持加载图片,支持下载文件透明的GZIP压缩,支持响应缓存避免重复的网络请求,支持使用连接池来降低响应延迟问题。
OkHttp的入门和集成请参考这篇官方教程:OkHttp 官方教程解析 - 彻底入门 OkHttp 使用

3.2 与Retrofit结合的OkHttp

使用OkHttp的项目多半会配合同是 出的Retrofit,两者相结合,更加强大。
而我在实际开发中使用的网络框架是rxJava+Retrofit+OkHttp的结合。
不废话,直接上代码:

先看调用示例:
步骤一:在ApiService中添加接口

  • 1 写上请求网络的方法名rxGetTest、接口的返回参数的Json反序列化出的Bean对象WeatherInfo;
  • 2 @GET参数上配置上这个接口具体请求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) {
            //拿到回调的数据具体处理
          }
      });

//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) {
               //拿到回调的数据具体处理
          }
      });

对于上面代码中用到的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);
            //修改请求为只从网络中读数据
            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、进行统一的异常处理 可根据需要选择
 * Created by bailiangjin on 2017/2/16.
 */

public class RxRequestHelper {

    /**
     * 绑定回调到mainthread 并统一处理异常
     * @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 不统一处理异常
     * @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);
    }

    /**
     * 异常统一处理
     * @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的极简封装

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容