android HTTP网络请求回顾

1.HTTP协议了解

http是一种应用层的协议,底层通过TCP来进行可靠的数据传输。HTTP是基于TCP的应用层协议,它在更高的层次封装了TCP的使用细节,使网络请求更加易用,TCP连接是因特网基于流的一种可靠连接,它为HTTP提供了一条可靠的比特传输管道。从TCP连接一端填入的字节会从另一端以原有的顺序、正确的传送过来。

  • HTTP的7种请求方式
    GET
    POST
    DELETE
    PUT
    HEAD
    TRACE
    POTIONS

[图片上传失败...(image-aa15fa-1517135394572)]
HTTP报文格式解析
不同的请求方式,它们的请求格式也是不一样的,请求格式也就是报文格式。 通常来说一个HTTP请求报文由 请求行(request line)、请求头部(head)、空行、请求数据 4个部分组成。

[图片上传失败...(image-fbc600-1517135394572)]

  1. 请求行
    报文的第一行就是请求行,这一行说明了这段报文以什么方式请求,包含了HTTP的版本号等一些协议信息。
  2. 请求头部
    请求头部是以 key:value的形式来说明请求数据的,这里面说明了请求服务器的一些host,content-tye,Encoding的一些说明。
  3. 请求数据
    POST请求的方式才会有请求数据,如果请求是以POST提交过来,那么这里面就会有post的请求数据,可以文本形式或者二进制数据根据你请求post提交的数据类型决定。

GET 请求报文格式

www.jinweime.com?id=2
这个是一个典型的get请求,get请求的参数会跟在url后面。问号后面作为第一个参数,以&进行参数拼接。

http请求协议如下

GET /?id=2 HTTP/1.1
Host: jinweime.com
Cache-Control: no-cache

可以看到第一行为请求行,请求方式为GET,子路径是?id=2 代表参数id的值为2,HTTP版本为1.1。 后面二行是head区域,第一个请求头是主机地址,第三行也是一个head。GET方式的请求参数都是附加的URL中所以请求数据部分为空。

POST请求报文格式

POST /api/feed. HTTP/1.1
Accept-Encoding: gzip
Content-Length: 225873
Content-Ttpe: multipart/form-data; boundary=Ocxx3329f....
Host: www.myhost.com
Connection: Keep-Alive

--OCxxqFJE...
Content-Dispotition: form-data; name=="username"
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

MrSimple
--Cxxii32F..
Content-Dispotition: form-data; name=="title"

Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

TEST
--Ffsaxx......--

这串报代表向 www.myhost.com/api/feed/这个地址发送了一个POST请求。 请求的数据格式为 Content-Type: multipart.form-data,报文有二个参数 username title,username的值为MrSimple。title的值为TEST。 最后一行是结束行以 -- boundary值 -- 结束, 如果格式不正确服务器将会解析不到的你请求。

响应报文
HTTP响应报文也是由三个部分组成,分别是:状态行 消息head 响应报文,和请求报文的格式十分相似。

<状态行>
<响应报文 head>
<空行>
<响应报文内容>

可以看到和请求报文相比,只是把第一行的请求行换成了状态行了,状态行提供一个状态码来说明此次请求的资源情况。

HTTP-Version Status-Code Reason-Phrase CRLF

其中的HTTP-Version表示服务器HTTP协议的版本,Status-Code表示服务器响应请求的状态码;Reason-Phrase表示状态码的文本描述。状态码是一个三位的数字,第一个数字定义了响应的类别。

常见的状态码

  • 200 OK;客户端请求成功
  • 400 Bad Request:客户端请求有语法错误,服务器不能正确的解析
  • 401 Unauthorized;请求未授权
  • 403 Forbidden; 服务器收到请求,但是拒绝提供服务
  • 404 Not Found; 请求的资源不存在, 比如输入了错误的地址;
  • 500 Internal Server Error; 服务器发生了错误
  • 503 Server Unavailable; 服务器当前不能处理客户端请求

常见的请求头部

  • Content-Type: 请求数据的格式
  • Content-Length; 消息的长度
  • Host: 请求主机名
  • User-Agent; 发出请求的浏览器类型,可以自定义
  • Accept: 客户端可识别的内容类型
  • Accept-Encoding: 客户端可识别的数据编码
  • Connection: 允许客户端和服务器指定请求/响应连接有关的选项,比如设置Keep-Alive 表示保持连接

Android中执行网络请求

android中提供了二种执行网络请求的方式,一种使用 Apache的HttpClient,另一种Java提供的 HttpUrlConnection。这二种方法都提供了完整的 API, 都很很好的实现对网络的请求功能,但是某些情况下我们需要做取舍分清楚二种方式的区别。

  • HttpClient
    android SDK自带了 Apache的HttpClient,它提供了对HTTP协议的全面支持,可以使用HttpClient来执行 HTTP GET和HTTP POST请求。

  • HttpUrlConnection

最佳选择HttpUrlConnection。二者对比来说,在android 2.2版本之前,HttpClient有较少的一些BUG,而HttpURLConnection一直存在一些让厌烦的BUG,比如在对一个可读的InputStream 调用colse()方法时,就有可能导致连接池失败。因此在 android 2.2版本之前使用 HttpClient是比较好的选择。 但是在 android2.3及之后 HttpUrlConnect有了进一步的更新, 它api 简单,体积小,因此非常适用于 Android项目中。 HttpUrlConnection的压缩和缓存机制可以有效的减少网络访问的流量,这块在提升手机省电和流量方面也起来很多的作用。另外在Android 6.0中,HttpClient以及被移除了,所以以后开发中HttpUrlConnection是我们唯一的选择了。

使用HttpUrlConnection请求

fun sendHttpClient(url: String) {
        val url = URL(url)
        var conn = url.openConnection() as HttpURLConnection
        //读取时时间为 2s
        conn.readTimeout = 2000
        //请求超时时间为 5s
        conn.connectTimeout = 5000
        //设置请求方式
        conn.requestMethod = "POST"
        //接收输入流
        conn.doInput = true
        //启动输出流,需要传递参数时需要开启
        conn.doInput = true
        //添加 Header
        conn.setRequestProperty("Connection", "Keep-Alive")
        //添加请求参数
        var paramsList = ArrayList<NameValuePair>()
        paramsList.add(BasicNameValuePair("username", "jinwei"))
        paramsList.add(BasicNameValuePair("pwd", "pwd.com"))
        writeParams(conn.outputStream, paramsList)
        //发起请求
        conn.connect()
        var input = conn.inputStream
        //获取结果
        var str = convertStreamToString(input)
        Log.i(TAG, "Request Data: " + str)
        input.close()
    }

    fun writeParams(outpit: OutputStream, paramsList: List<NameValuePair>) {
        val paramStr = StringBuffer()
        for (value in paramsList) {
            if (!TextUtils.isEmpty(paramStr)) {
                paramStr.append("&")
            }
            paramStr.append(URLEncoder.encode(value.name, "UTF-8"))
            paramStr.append("=")
            paramStr.append(URLEncoder.encode(value.value, "UTF-8"))
        }
        var writer = BufferedWriter(OutputStreamWriter(outpit, "UTF-8"))
        //写入参数写入输入流
        Log.i(TAG, paramStr.toString())
        writer.write(paramStr.toString())
        writer.flush()
        writer.close()
    }


    fun convertStreamToString(input: InputStream): String {
        var buffer = BufferedReader(InputStreamReader(input))
        var sb = StringBuffer()
        var line: String
        try {
            while (true) {
                line = buffer.readLine()
                if (!TextUtils.isEmpty(line)) {
                    sb.append(line + "\n")
                } else {
                    break
                }
            }
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return sb.toString()
    }

2. Volley使用

官方文档

github

VIDEO介绍

安静

volley的架构图

架构图

volley是2013年 google I/O大会上推出的一款网络请求相关的框架

它有以下好处

  • 网络请求的自动调度。
  • 多个并发网络连接。
  • 具有标准HTTP缓存一致性的透明磁盘和内存响应缓存。
  • 支持请求优先级。
  • 取消请求api。可以取消单个请求,也可以设置要取消的请求的块或范围。
  • 自定义重试

缺点

  • 不适合数据量过大的传输操作

构建一个stringRequest

一般来说我们一个应用启动后只需要全局获取一个
Volley.newRequestQueue(this)实例就够了,这样可以有效的节省系统资源的消耗。

 fun sendStringRequest() {
        var request = Volley.newRequestQueue(this)
        var url = "http://baidu.com"
        var strrequest = StringRequest(Request.Method.GET, url,
                object : Response.Listener<String> {
                    override fun onResponse(response: String?) {
                        Log.i(TAG, response)
                    }
                },
                object : Response.ErrorListener {
                    override fun onErrorResponse(error: VolleyError?) {
                        Log.i(TAG, "error")
                    }

                })
        request.add(strrequest)
    }

很简单成功的打印了返回的Html数据

<html>

                                                           <meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
                                                            </html>

JsonRequest

这里我们用的JsonRequest的子类JsonObjectRequest来构建了一个请求,它支持JSON格式的Request和Response。

fun sendJsonRequest() {
        var request = Volley.newRequestQueue(this)
        var url = "http://baike.baidu.com/api/openapi/BaikeLemmaCardApi?scope=103&format=json&appid=379020&bk_key=关键字&bk_length=600"
        var strrequest = JsonObjectRequest(Request.Method.POST, url, null,
                object : Response.Listener<JSONObject> {
                    override fun onResponse(response: JSONObject?) {
                        Log.i(TAG, response.toString())
                    }

                },
                object : Response.ErrorListener {
                    override fun onErrorResponse(error: VolleyError?) {
                        Log.i(TAG, "error")
                    }

                })
        request.add(strrequest)
    }

成功的返回了一串JSON格式的数据

{"id":390935,"subLemmaId":390935,"newLemmaId":7105697,"key".......

ImageRequest

Volley还支持对图片的获取

fun sendImageRequest() {
        var request = Volley.newRequestQueue(this)
        var url = "http://img.my.csdn.net/uploads/201603/26/1458988468_5804.jpg"
        var strrequest = ImageRequest(url,
                object : Response.Listener<Bitmap> {
                    override fun onResponse(response: Bitmap?) {
                        findViewById<ImageView>(R.id.imageView).setImageBitmap(response)
                    }

                }, 511, 511, Bitmap.Config.ARGB_8888,
                object : Response.ErrorListener {
                    override fun onErrorResponse(error: VolleyError?) {
                        Log.i(TAG, "error")
                    }

                })
        request.add(strrequest)
    }

这段代码成功的Bitmap显示到了ImageView上面

3. Volley源码分析

执行

var request = Volley.newRequestQueue(this)

最终会到newRequestQueue方法

public static RequestQueue newRequestQueue(Context context, BaseHttpStack stack) {
        BasicNetwork network;
        if (stack == null) {
            if (Build.VERSION.SDK_INT >= 9) {
                network = new BasicNetwork(new HurlStack());
            } else {
                // Prior to Gingerbread, HttpUrlConnection was unreliable.
                // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
                // At some point in the future we'll move our minSdkVersion past Froyo and can
                // delete this fallback (along with all Apache HTTP code).
                String userAgent = "volley/0";
                try {
                    String packageName = context.getPackageName();
                    PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0);
                    userAgent = packageName + "/" + info.versionCode;
                } catch (NameNotFoundException e) {
                }

                network = new BasicNetwork(
                        new HttpClientStack(AndroidHttpClient.newInstance(userAgent)));
            }
        } else {
            network = new BasicNetwork(stack);
        }

        return newRequestQueue(context, network);
    }

可以看到代码进入到Build.VERSION.SDK_INT >= 9的逻辑,network = new BasicNetwork(new HurlStack());创建了一个network对象,我们重点关系HrlStack()这个对象,这个对象是最终执行网络请求的地方。

 @Override
    public HttpResponse executeRequest(Request<?> request, Map<String, String> additionalHeaders)
            throws IOException, AuthFailureError {
        String url = request.getUrl();
//        Log.i("jinwei"," ## executeRequest =  "+url);
        HashMap<String, String> map = new HashMap<>();
        map.putAll(request.getHeaders());
        map.putAll(additionalHeaders);
        if (mUrlRewriter != null) {
            String rewritten = mUrlRewriter.rewriteUrl(url);
            if (rewritten == null) {
                throw new IOException("URL blocked by rewriter: " + url);
            }
            url = rewritten;
        }
        URL parsedUrl = new URL(url);
        HttpURLConnection connection = openConnection(parsedUrl, request);
        for (String headerName : map.keySet()) {
            connection.addRequestProperty(headerName, map.get(headerName));
        }
        setConnectionParametersForRequest(connection, request);
        // Initialize HttpResponse with data from the HttpURLConnection.
        int responseCode = connection.getResponseCode();
        if (responseCode == -1) {
            // -1 is returned by getResponseCode() if the response code could not be retrieved.
            // Signal to the caller that something was wrong with the connection.
            throw new IOException("Could not retrieve response code from HttpUrlConnection.");
        }

        if (!hasResponseBody(request.getMethod(), responseCode)) {
            return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()));
        }

        return new HttpResponse(responseCode, convertHeaders(connection.getHeaderFields()),
                connection.getContentLength(), inputStreamFromConnection(connection));
    }

通过代码可以看到执行网络请求使用的HttpURLConnection处理的,具体的调用过程我们继续分析。

newRequestQueue方法最好执行到了return newRequestQueue(context, network);

private static RequestQueue newRequestQueue(Context context, Network network) {
        File cacheDir = new File(context.getCacheDir(), DEFAULT_CACHE_DIR);
        RequestQueue queue = new RequestQueue(new DiskBasedCache(cacheDir), network);
        queue.start();
        return queue;
    }

在这里执行了queue.start()方法

public void start() {
        stop();  // Make sure any currently running dispatchers are stopped.
        // Create the cache dispatcher and start it.
        mCacheDispatcher = new CacheDispatcher(mCacheQueue, mNetworkQueue, mCache, mDelivery);
        mCacheDispatcher.start();

        // Create network dispatchers (and corresponding threads) up to the pool size.
        Log.i("jinwei"," ## mDispatchers.length ## " +mDispatchers.length);
        for (int i = 0; i < mDispatchers.length; i++) {
            NetworkDispatcher networkDispatcher = new NetworkDispatcher(mNetworkQueue, mNetwork,
                    mCache, mDelivery);
            mDispatchers[i] = networkDispatcher;
            networkDispatcher.start();
        }
    }

这里总共开启了5个 Thread,一个CacheThread和四个NetWorkThread。

NetWorkThread的run方法

@Override
    public void run() {
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
        while (true) {
            try {
                Log.i("jinwei"," ## run ##");
                processRequest();
            } catch (InterruptedException e) {
                // We may have been interrupted because it was time to quit.
                Log.i("jinwei"," ## mQuit ##");
                if (mQuit) {
                    return;
                }
            }
        }
    }

这里是一个while循环,内部有一个PriorityBlockingQueue队列take数据,如果返回为Null线程就会挂起等待新的队列进来。

重点方法

 private void processRequest() throws InterruptedException {
        long startTimeMs = SystemClock.elapsedRealtime();
        // Take a request from the queue.
        Request<?> request = mQueue.take();

        try {
            request.addMarker("network-queue-take");

            // If the request was cancelled already, do not perform the
            // network request.
            if (request.isCanceled()) {
                request.finish("network-discard-cancelled");
                request.notifyListenerResponseNotUsable();
                return;
            }

            addTrafficStatsTag(request);

            // Perform the network request.
            NetworkResponse networkResponse = mNetwork.performRequest(request);
            request.addMarker("network-http-complete");

            // If the server returned 304 AND we delivered a response already,
            // we're done -- don't deliver a second identical response.
            if (networkResponse.notModified && request.hasHadResponseDelivered()) {
                request.finish("not-modified");
                request.notifyListenerResponseNotUsable();
                return;
            }

            // Parse the response here on the worker thread.
            Response<?> response = request.parseNetworkResponse(networkResponse);
            request.addMarker("network-parse-complete");

            // Write to cache if applicable.
            // TODO: Only update cache metadata instead of entire record for 304s.
            if (request.shouldCache() && response.cacheEntry != null) {
                mCache.put(request.getCacheKey(), response.cacheEntry);
                request.addMarker("network-cache-written");
            }

            // Post the response back.
            request.markDelivered();
            mDelivery.postResponse(request, response);
            request.notifyListenerResponseReceived(response);
        } catch (VolleyError volleyError) {
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            parseAndDeliverNetworkError(request, volleyError);
            request.notifyListenerResponseNotUsable();
        } catch (Exception e) {
            VolleyLog.e(e, "Unhandled exception %s", e.toString());
            VolleyError volleyError = new VolleyError(e);
            volleyError.setNetworkTimeMs(SystemClock.elapsedRealtime() - startTimeMs);
            mDelivery.postError(request, volleyError);
            request.notifyListenerResponseNotUsable();
        }
    }

Request<?> request = mQueue.take()取出之前request.add(strrequest)的数据,如果没有则会一直挂起。

执行这里就会执行到之前HurlStack类中的executeRequest方法来通过HttpUrlConnection来构建一个网络请求了

NetworkResponse networkResponse = mNetwork.performRequest(request)

源码的简单分析就到这了,具体的设计思路和实现还需要深入研究了。

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

推荐阅读更多精彩内容