Android 主流开源框架(一)OkHttp 铺垫-HttpClient 与 HttpURLConnection 使用详解

前言

最近有个想法——就是把 Android 主流开源框架进行深入分析,然后写成一系列文章,包括该框架的详细使用与源码解析。目的是通过鉴赏大神的源码来了解框架底层的原理,也就是做到不仅要知其然,还要知其所以然。

这里我说下自己阅读源码的经验,我一般都是按照平时使用某个框架或者某个系统源码的使用流程入手的,首先要知道怎么使用,然后再去深究每一步底层做了什么,用了哪些好的设计模式,为什么要这么设计。

系列文章:

更多干货请关注 AndroidNotes

一、HttpClient 与 HttpURLConnection 介绍

1.1 HttpClient

HttpClient 是 Apache 提供的开源库,可以用来提供高效的、最新的、功能丰富的支持 HTTP 协议的客户端编程工具包。原本 Android SDK 中是包含了 HttpClient 的,但是 Google 在 Android 6.0 版本已经删除了该库的相关代码,只能通过依赖库的方式进行使用。

1.2 HttpURLConnection

HttpURLConnection 是 java.net 包中提供用来访问 HTTP 协议的基本功能的类,它继承自 URLConnection,可用于向指定网站发送 GET 请求、POST 请求。HttpURLConnection 相对于 HttpClient 没什么封装,但也是因为这个我们可以更容易的去扩展它。

不过在 Android 2.2 版本之前,HttpURLConnection 一直存在着一些令人厌烦的 bug。比如说对一个可读的 InputStream 调用 close() 方法时,就有可能会导致连接池失效了。那么我们通常的解决办法就是直接禁用掉连接池的功能,如下:

private void disableConnectionReuseIfNecessary() {
      // 这是一个2.2版本之前的bug
      if (Integer.parseInt(Build.VERSION.SDK) < Build.VERSION_CODES.FROYO) {
            System.setProperty("http.keepAlive", "false");
      }
}

二、HttpClient 的使用

2.1 使用前准备

  1. 加入网络权限
<uses-permission android:name="android.permission.INTERNET"/>
  1. 如果使用 Android Studio,则需要在使用的 module 下的 build.gradle 中加入如下:
android {
    useLibrary 'org.apache.http.legacy'
}
  1. 从 Android 9 开始,默认情况下该库已从 bootclasspath 中移除且不可用于应用。
    要继续使用 Apache HTTP 客户端,以 Android 9 及更高版本为目标的应用可以向其 AndroidManifest.xml 的 application 节点下添加以下内容(具体可看-Apache HTTP 客户端弃用):
<uses-library android:name="org.apache.http.legacy" android:required="false"/>

2.2 HttpClient 的 GET 请求

使用流程已在注释中详细描述,具体代码如下:

    private void getRequestByHttpClient() {
        //1. 创建 HttpClient 对象(DefaultHttpClient 为 HttpClient 的实现类)
        HttpClient httpClient = new DefaultHttpClient();
        //2. 创建请求对象(这里是 GET 请求),参数为请求地址
        HttpGet httpGet = new HttpGet("https://www.baidu.com");
        try {
            //3.调用 HttpClient 对象的 execute 方法发送请求,返回 HttpResponse
            HttpResponse httpResponse = httpClient.execute(httpGet);
            //4. 检查是否请求成功,状态码 200 表示成功
            if (httpResponse.getStatusLine().getStatusCode() == 200) {
                //5. 调用 HttpEntity 对象的 getContent 方法获取响应数据的输入流
                InputStream inputStream = httpResponse.getEntity().getContent();
                //6. 将输入流转换成字符串
                String data = inputStream2String(inputStream);
                Log.i(TAG, "data: " + data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

其中,inputStream2String 方法如下:

    private String inputStream2String(InputStream inputStream) {
        String data = "";
        BufferedReader bufferedReader = null;
        try {
            InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
            bufferedReader = new BufferedReader(inputStreamReader);
            StringBuilder stringBuilder = new StringBuilder();
            String line = null;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line).append("\n");
            }
            data = stringBuilder.toString();
            if (!TextUtils.isEmpty(data)) {
                //去除最后一个多余的换行符
                data = data.substring(0, data.length() - 1);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

接下来直接调用就可以了,需要注意的是网络请求需要在子线程中调用,如下:

        new Thread(new Runnable() {
            @Override
            public void run() {
                getRequestByHttpClient();
            }
        }).start();

打印结果如下:

<!DOCTYPE html>
    <!--STATUS OK--><html> <head><meta http-equiv=content-type content=text/html;charset=utf-8><meta http-equiv=X-UA-Compatible content=IE=Edge><meta content=always name=referrer><link rel=stylesheet type=text/css href=http://s1.bdstatic.com/r/www/cache/bdorz/baidu.min.css>
<title>百度一下,你就知道</title>
</head> <body link=#0000cc> 省略部分日志...</body> 
</html>

2.3 HttpClient 的 POST 请求

POST 请求与 GET 请求类似,不同的是需要将请求对象 HttpGet 换成 HttpPost,然后通过 HttpPost 的 setEntity 方法设置请求参数。使用流程已在注释中详细描述,具体代码如下:

    private void postRequestByHttpClient() {
        //1. 创建 HttpClient 对象(DefaultHttpClient 为 HttpClient 的实现类)
        HttpClient httpClient = new DefaultHttpClient();
        //2. 创建请求对象(这里是 POST 请求),参数为请求地址(这里用的是 postman 提供的测试地址)
        HttpPost httpPost = new HttpPost("https://postman-echo.com/post");
        try {
            //3. 调用 HttpPost 对象的 setEntity 方法设置需要的参数
            List<NameValuePair> postParams = new ArrayList<>();
            postParams.add(new BasicNameValuePair("username", "wildma"));
            postParams.add(new BasicNameValuePair("password", "123456"));
            httpPost.setEntity(new UrlEncodedFormEntity(postParams));

            //4.调用 HttpClient 对象的 execute 方法发送请求,返回 HttpResponse
            HttpResponse httpResponse = httpClient.execute(httpPost);
            //5. 检查是否请求成功,状态码 200 表示成功
            if (httpResponse.getStatusLine().getStatusCode() == 200) {
                //6. 调用 HttpEntity 对象的 getContent 方法获取响应数据的输入流
                InputStream inputStream = httpResponse.getEntity().getContent();
                //7. 将输入流转换成字符串
                String data = inputStream2String(inputStream);
                Log.i(TAG, "data: " + data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

在子线程中调用该方法,打印结果如下:

{
 "args": {},
 "data": "",
 "files": {},
 "form": {
  "username": "wildma",
  "password": "123456"
 },
 "headers": {
  "x-forwarded-proto": "https",
  "host": "postman-echo.com",
  "content-length": "31",
  "content-type": "application/x-www-form-urlencoded",
  "user-agent": "Apache-HttpClient/UNAVAILABLE (java 1.4)",
  "x-forwarded-port": "443"
 },
 "json": {
  "username": "wildma",
  "password": "123456"
 },
 "url": "https://postman-echo.com/post"
}

三、HttpURLConnection 的使用

3.1 使用前准备

加入网络权限

<uses-permission android:name="android.permission.INTERNET"/>

3.2 HttpURLConnection 的 GET 请求

使用流程已在注释中详细描述,具体代码如下:

    private void getRequestByHttpURLConnection() {
        try {
            //1. 创建 URL 对象
            URL url = new URL("https://www.baidu.com");
            //2. 调用 URL 对象的 openConnection 方法获取 HttpURLConnection 实例
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            //3. 设置请求方法(这里是 GET 请求)
            httpURLConnection.setRequestMethod("GET");
            //4. 连接
            httpURLConnection.connect();
            //5. 检查是否请求成功,状态码 200 表示成功
            if (httpURLConnection.getResponseCode() == 200) {
                //6. 调用 HttpURLConnection 对象的 getInputStream 方法获取响应数据的输入流
                InputStream inputStream = httpURLConnection.getInputStream();
                //7. 将输入流转换成字符串
                String data = inputStream2String(inputStream);
                Log.i(TAG, "data: " + data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

在子线程中调用该方法,打印结果与 HttpClient 的 GET 请求一样。

3.3 HttpURLConnection 的 POST 请求

POST 请求与 GET 请求类似,不同的是需要将请求方法 GET 换成 POST,然后通过输出流设置请求参数,还需要设置允许输入输出,即 setDoInput(true) 与 setDoOutput(true)。使用流程已在注释中详细描述,具体代码如下:

    private void postRequestByHttpURLConnection() {
        try {
            //1. 创建 URL 对象
            URL url = new URL("https://postman-echo.com/post");
            //2. 调用 URL 对象的 openConnection 方法获取 HttpURLConnection 实例
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            //3. 设置请求方法(这里是 POST 请求)
            httpURLConnection.setRequestMethod("POST");
            /*4. 设置允许输入输出*/
            httpURLConnection.setDoInput(true);
            httpURLConnection.setDoOutput(true);
            /*5. 设置请求参数*/
            String params = new StringBuilder()
                    .append("username=" + URLEncoder.encode("wildma", "UTF-8"))
                    .append("&password=" + URLEncoder.encode("123456", "UTF-8")).toString();
            OutputStream outputStream = httpURLConnection.getOutputStream();
            outputStream.write(params.getBytes());
            outputStream.flush();
            outputStream.close();

            //6. 连接
            httpURLConnection.connect();
            //7. 检查是否请求成功,状态码 200 表示成功
            if (httpURLConnection.getResponseCode() == 200) {
                //8. 调用 HttpURLConnection 对象的 getInputStream 方法获取响应数据的输入流
                InputStream inputStream = httpURLConnection.getInputStream();
                //9. 将输入流转换成字符串
                String data = inputStream2String(inputStream);
                Log.i(TAG, "data: " + data);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

在子线程中调用该方法,打印结果与 HttpClient 的 POST 请求一样。

四、HttpClient 与 HttpURLConnection 如何选择?

在 Android 2.2 版本之前,HttpClient 拥有较少的 bug,因此使用它是最好的选择。

而在 Android 2.3 版本及以后,HttpURLConnection 则是最佳的选择。它的 API 简单,体积较小,因而非常适用于Android 项目。压缩和缓存机制可以有效地减少网络访问的流量,在提升速度和省电方面也起到了较大的作用。

其实参考 Google 的网络框架 Volley 也能得出结论,Volley 源码中在 Android 2.3 及以上版本,使用的是 HttpURLConnection,而在 Android 2.2 及以下版本,使用的是 HttpClient。

五、源码

HttpClient 与 HttpURLConnection 的使用 demo

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

推荐阅读更多精彩内容