迈向高阶:优秀Android程序员必知必会的网络基础

一、前言

网络通讯一直是Android项目里比较重要的一个模块,Android开源项目上出现过不少优秀的网络框架,从一开始只是一些对HttpClient和HttpUrlConnection简易封装使用的工具类,到后来Google开源的比较完善丰富的Volley,再到现在比较流行的OkhttpRetrofitphp

要想理解他们之间存在的异同(或者具体点说,要想更深刻地掌握Android开发中的网络通讯技术),必须对网络基础知识、Android网络框架的基本原理等作到心中有数、信手拈来,关键时刻才能找到适合您APP的最佳网络通讯技术实践。html

事实证实在Android的平常开发和源码阅读中也会常常碰到相关知识,掌握这些网络基础知识,也是Android程序员真正迈向高阶的过程当中必备的一些基本技术素质之一。git

有鉴于此,本文将主要介绍计算机网络的一些基础,以及在Android开发中的一些使用及遇到的问题和解决。程序员

本篇主要分为如下几部分:github

1)计算机网络体系结构;算法

2)Http相关;编程

3)Tcp相关;浏览器

4)Socket。缓存

学习交流:安全

- 即时通信开发交流3群:185926912[推荐]

- 移动端IM开发入门文章:《新手入门一篇就够:从零开发移动端IM

(本文同步发布于:http://www.52im.net/thread-1963-1-1.html

二、关于做者

舒大飞:携程网Android开发工程师,做者博客:https://www.jianshu.com/u/ae379336190b

注:在收录本文时,为了更易于理解,对内容作了更为细致的修订。

三、计算机网络体系结构

计算机网络体系结构,即常常看到的计算机网络体系的分层结构,理清这个仍是有必要的,防止对Http和Tcp两个根本不在同一层的协议纠缠不清。 根据不一样的参考模型,分层结构有几个不一样的版本,如OSI模型以及TCP/IP模型。

下面就以比较常常看到的的5层结构为例:

 

(更为清晰完整的图,请见《计算机网络通信协议关系图(中文珍藏版)[附件下载] 》)

如上图所示,五层的体系结构至上往下,最终能够实现端对端之间的数据传输与通讯,他们各自负责一些什么,最终如何实现端对端之间的通讯?

1)应用层:如http协议,它其实是定义了如何包装和解析数据,应用层是http协议的话,则会按照协议规定包装数据,如按照请求行、请求头、请求体包装,包装好数据后将数据传至运输层。

2)运输层:运输层有TCP和UDP两种协议,分别对应可靠的运输和不可靠的运输,如TCP由于要提供可靠的传输,因此内部要解决如何创建链接、如何保证传输是可靠的不丢数据、如何调节流量控制和拥塞控制。关于这一层,咱们日常通常都是和Socket打交道,Socket是一组封装的编程调用接口,经过它,咱们就能操做TCP、UDP进行链接的创建等。咱们日常使用Socket进行链接创建的时候,通常都要指定端口号,因此这一层指定了把数据送到对应的端口号。

3)网络层:这一层IP协议,以及一些路由选择协议等等,因此这一层的指定了数据要传输到哪一个IP地址。中间涉及到一些最优线路,路由选择算法等等。

4)数据链路层:印象比较深的就是ARP协议,负责把IP地址解析为MAC地址,即硬件地址,这样就找到了对应的惟一的机器。

5)物理层:这一层就是最底层了,提供二进制流传输服务,也就是也就是真正开始经过传输介质(有线、无线)开始进行数据的传输了。

因此经过上面五层的各司其职,实现物理传输介质--MAC地址--IP地址--端口号--获取到数据根据应用层协议解析数据最终实现了网络通讯和数据传输。

下面会着重讲一下HTTP和TCP相关的东西,关于其余层,毕业了这么久也忘的不少,若是想更加细致具体的了解像下面三层的如路由选择算法、ARP寻址以及物理层等等仍是要从新去看一下《TCP/IP详解 卷1:协议》~

四、HTTP相关

本节主要讲一些关于Http的基础知识,以及在Android中的一些实际应用和碰到的问题和解决。

限于篇幅缘由,本文在一些知识点上只作简要性的概述,若是想要全面深刻地掌握HTTP协议,请阅读如下文章:

网络编程懒人入门(七):深刻浅出,全面理解HTTP协议

从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

脑残式网络编程入门(三):HTTP协议必知必会的一些知识

4.1 正确理解HTTP的“无链接”“与无状态”

Http是无链接无状态的。

无链接并非说不须要链接,Http协议只是一个应用层协议,最终仍是要靠运输层的如TCP协议向上提供的服务进行链接。

无链接的含义是http约定了每次链接只处理一个请求,一次请求完成后就断开链接,这样主要是为了缓解服务器的压力,减少链接对服务器资源的占用。个人理解是,创建链接其实是运输层的事,面向应用层的http来讲的话,它就是无链接的,由于上层对下层无感知。

无状态的指每一个请求之间都是独立的,对于以前的请求事务没有记忆的能力。因此就出现了像Cookie这种,用来保存一些状态的东西。

4.2 请求报文与响应报文

这里主要简单说一下HTTP请求报文和响应报文的格式方面的基础知识。

请求报文:

 

响应报文:

 

关于Get和Post,咱们都熟知的关于Get和Post的区别大体有如下几点:

1)Get会把请求参数都拼接在url后面,最终显示在地址栏,而Post则会把请求参数数据放进请求体中,不会再地址栏显示出来;

2)传递参数的长度限制。

问题:

对于第1)点,若是是在浏览器里把隐私数据暴露在地址栏上确实不妥,可是若是是在App开发中呢,没有地址栏的概念,那么这一点是否是还会成为选择post仍是get的制约条件;

对于第2)点,长度的限制应该是浏览器的限制,跟get自己无关,若是是在App开发中,这一点是否也能够忽略。

4.3 HTTP的缓存机制

之因此想介绍如下Http的缓存机制,是由于Okhttp中对于网络请求缓存这一块就是利用了Http的的缓存机制,而不是像Volley等框架那样客户端彻底本身写一套缓存策略本身玩。

Http的缓存主要利用header里的两个字段来控制:即Cache-control和ETag,下面将分别来介绍。

1)Cache-control主要包含以及几个字段:

private:则只有客户端能够缓存;

public:客户端和代理服务器均可以缓存;

max-age:缓存的过时时间;

no-cache:须要使用对比缓存来验证缓存数据;

no-store:全部内存都不会进行缓存。

实际上就是在这里面设置了一个缓存策略,由服务端第一次经过header下发给客户端,能够看到:

max-age:即缓存过时的时间,则以后再次请求,若是没有超过缓存失效的时间则能够直接使用缓存;

no-cache:表示须要使用对比缓存来验证缓存数据,若是这个字段是打开的,则就算max-age缓存没有失效,则仍是须要发起一次请求向服务端确认一下资源是否有更新,是否须要从新请求数据,至于怎么作对比缓存,就是下面要说的Etag的做用。若是服务端确认资源没有更新,则返回304,取本地缓存便可,若是有更新,则返回最新的资源;

no-store:这个字段打开,则不会进行缓存,也不会取缓存。

2)ETag:即用来进行对比缓存,Etag是服务端资源的一个标识码

当客户端发送第一次请求时服务端会下发当前请求资源的标识码Etag,下次再请求时,客户端则会经过header里的If-None-Match将这个标识码Etag带上,服务端将客户端传来的Etag与最新的资源Etag作对比,若是同样,则表示资源没有更新,返回304。

3)小结:

经过Cache-control和Etag的配合来实现Http的缓存机制。更多有关HTTP缓存方面的的知识,在文章《脑残式网络编程入门(三):HTTP协议必知必会的一些知识》的相关章节作了详细的读解,能够参阅之。

4.4 HTTP的Cookie

上面说了Http协议是无状态的,而Cookie就是用来在本地缓存记住一些状态的,一个Cookie通常都包含domin(所属域)、path、Expires(过时时间)等几个属性。服务端能够经过在响应头里的set-cookies来将状态写入客户端的Cookie里。下次客户端发起请求时能够将Cookie带上。

Android开发中遇到的问题及解决:

提及Cookie,通常若是日常只是作App开发,比较不常常遇到,可是若是是涉及到WebView的需求,则有可能会遇到。

下面就说一下我在项目里遇到过的一个关于WebView Cookie的揪心往事:需求是这样的,加载的WebView中的H5页面须要是已登陆状态的,因此咱们须要在原生页面登陆后,手动将ticket写入WebView的Cookie,以后WebView里加载的H5页面带着Cookie里的ticket给服务端验证经过就行了。

可是遇到一个问题:经过Chrome inspect调试WebView,手动写的Cookie确实是已经写进去了,可是发起请求的时候,Cookie就是没有带上,致使请求验证失败,以后经过排查,是WebView的属性默认关闭引发,经过下面的代码设置打开便可:

CookieManager cookieManager = CookieManager.getInstance();

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {

    cookieManager.setAcceptThirdPartyCookies(mWebView, true);

} else{

    cookieManager.setAcceptCookie(true);

}

4.5 Https

咱们都知道Https保证了咱们数据传输的安全,Https=Http+Ssl,之因此能保证安全主要的原理就是利用了非对称加密算法,日常用的对称加密算法之因此不安全,是由于双方是用统一的密匙进行加密解密的,只要双方任意一方泄漏了密匙,那么其余人就能够利用密匙解密数据。

而非对称加密算法之因此能实现安全传输的核心精华就是:公钥加密的信息只能用私钥解开,私钥加密的信息只能被公钥解开。

1)简述非对称加密算法为何安全:

服务端申请CA机构颁发的证书,则获取到了证书的公钥和私钥,私钥只有服务器端本身知道,而公钥能够告知其余人,如能够把公钥传给客户端,这样客户端经过服务端传来的公钥来加密本身传输的数据,而服务端利用私钥就能够解密这个数据了。因为客户端这个用公钥加密的数据只有私钥能解密,而这个私钥只有服务端有,因此数据传输就安全了。

上面只是简单说了一下非对称加密算法是如何保证数据安全的,实际上Https的工做过程远比这要复杂(篇幅限制这里就不细说了,网上有不少相关文章):

一个是客户端还须要验证服务端传来的CA证书的合法性、有效性,由于存在传输过程CA证书被人调包的风险,涉及到客户端如何验证服务器证书的合法性的问题,保证通讯双方的身份合法;

另外一个是非对称算法虽然保证了数据的安全,可是效率相对于对称算法来讲比较差,如何来优化,实现既保证了数据的安全,又提升了效率。

2)客户端如何验证证书的合法性:

首先CA证书通常包括如下内容:

证书的颁发机构以及版本;

证书的使用者;

证书的公钥;

证书有效时间;

证书的数字签名Hash值以及签名Hash算法(这个数字签名Hash值是用证书的私钥加密过的值);

等等。

客户端验证服务端传过来的证书的合法性是经过:先利用获取到的公钥来解密证书中的数字签名Hash值1(由于它是利用私钥加密的嘛),而后在利用证书里的签名Hash算法生成一个Hash值2,若是两个值相等,则表示证书合法,服务器端能够被信任。

3)Android开发中遇到的问题及解决:

顺便说一个在项目开发中使用Android WebView加载公司测试服务器上网页证书过时致使网页加载不出来白屏的问题。

解决方案就是测试环境下暂时忽略SSL的报错,这样就能够把网页加载出来,固然在生产上不要这么作,一个是会有安全问题,一个是google play应该审核也不会经过。

最佳办法是重写WebViewClient的onReceivedSslError():

@Override

publicvoidonReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {

    if(ContextHolder.sDebug) {

        handler.proceed();

        return;

    }

    super.onReceivedSslError(view, handler, error);

}

最后:有关HTTPS更为详细全面的知识,请深刻阅读《即时通信安全篇(七):若是这样来理解HTTPS原理,一篇就够了》。

4.6 Http 2.0

Okhttp支持配置使用Http 2.0协议,Http2.0相对于Http1.x来讲提高是巨大的,主要有如下几点。

1)二进制格式:http1.x是文本协议,而http2.0是二进制以帧为基本单位,是一个二进制协议,一帧中除了包含数据外同时还包含该帧的标识:Stream Identifier,即标识了该帧属于哪一个request,使得网络传输变得十分灵活;

2)多路复用:一个很大的改进,原先http1.x一个链接一个请求的状况有比较大的局限性,也引起了不少问题,如创建多个链接的消耗以及效率问题。

http1.x为了解决效率问题,可能会尽可能多的发起并发的请求去加载资源,然而浏览器对于同一域名下的并发请求有限制,而优化的手段通常是将请求的资源放到不一样的域名下来突破这种限制。

而http2.0支持的多路复用能够很好的解决这个问题,多个请求共用一个TCP链接,多个请求能够同时在这个TCP链接上并发,一个是解决了创建多个TCP链接的消耗问题,一个也解决了效率的问题。

那么是什么原理支撑多个请求能够在一个TCP链接上并发呢?基本原理就是上面的二进制分帧,由于每一帧都有一个身份标识,因此多个请求的不一样帧能够并发的无序发送出去,在服务端会根据每一帧的身份标识,将其整理到对应的request中。

3)header头部压缩:主要是经过压缩header来减小请求的大小,减小流量消耗,提升效率。由于以前存在一个问题是,每次请求都要带上header,而这个header中的数据一般是一层不变的。

4)支持服务端推送。

有关HTTP2的更多知识,请阅读《从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路》。

五、TCP相关

TCP面向链接,提供可靠的数据传输。在这一层,咱们一般都是经过Socket Api来操做TCP,创建链接等等。

5.1 三次握手创建链接

 

第一次:发送SNY=1表示这次握手是请求创建链接的,而后seq生成一个客户端的随机数X

第二次:发送SNY=1,ACK=1表示是回复请求创建链接的,而后ack=客户端的seq+1(这样客户端收到后就能确认是以前想要链接的那个服务端),而后把服务端也生成一个表明本身的随机数seq=Y发给客户端。

第三次:ACK=1。  seq=客户端随机数+1,ack=服务端随机数+1(这样服务端就知道是刚刚那个客户端了)

为何创建链接须要三次握手?

首先很是明确的是两次握手是最基本的,第一次握手,C端发了个链接请求消息到S端,S端收到后S端就知道本身与C端是能够链接成功的,可是C端此时并不知道S端是否接收到这个消息,因此S端接收到消息后得应答,C端获得S端的回复后,才能肯定本身与S端是能够链接上的,这就是第二次握手。

C端只有肯定了本身能与S端链接上才能开始发数据。因此两次握手确定是最基本的。

那么为何须要第三次握手呢?假设一下若是没有第三次握手,而是两次握手后咱们就认为链接创建,那么会发生什么?

第三次握手是为了防止已经失效的链接请求报文段忽然又传到服务端,于是产生错误。

具体状况就是:

C端发出去的第一个网络链接请求因为某些缘由在网络节点中滞留了,致使延迟,直到链接释放的某个时间点才到达S端,这是一个早已失效的报文,可是此时S端仍然认为这是C端的创建链接请求第一次握手,因而S端回应了C端,第二次握手。

若是只有两次握手,那么到这里,链接就创建了,可是此时C端并无任何数据要发送,而S端就会傻傻的等待着,形成很大的资源浪费。因此须要第三次握手,只有C端再次回应一下,就能够避免这种状况。

要想深入理解TCP三次握手,请不要错过如下文章:

TCP/IP详解 - 第18章·TCP链接的创建与终止

通俗易懂-深刻理解TCP协议(上):理论基础

理论经典:TCP协议的3次握手与4次挥手过程详解

理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程

脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

5.2 四次挥手断开链接

 

通过上面的创建链接图的解析,这个图应该不难看懂。

这里主要有一个问题:为何比创建链接时多了一次挥手?

能够看到这里服务端的ACK(回复客户端)和FIN(终止)消息并非同时发出的,而是先ACK,而后再FIN,这也很好理解,当客户端要求断开链接时,此时服务端可能还有未发送完的数据,因此先ACK,而后等数据发送完再FIN。这样就变成了四次握手了。

上面讲了TCP创建链接和断开链接的过程,TCP最主要的特色就是提供可靠的传输,那么他是如何保证数据传输是可靠的呢,这就是下面要讲的滑动窗口协议。

相关知识请深刻阅读:

理论经典:TCP协议的3次握手与4次挥手过程详解

理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程

脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

5.3 滑动窗口协议

滑动窗口协议是保证TCP的可靠传输的根本,由于发送窗口只有收到确认帧才会向后移动窗口继续发送其余帧。

下面举个例子:假如发送窗口是3帧

 

一开始发送窗口在前3帧[1,2,3],则前3帧是能够发送的,后面的则暂时不能够发送,好比[1]帧发送出去后,收到了来自接收方的确认消息,则此时发送窗口才能够日后移1帧,发送窗口来到[2,3,4],一样只有发送窗口内的帧才能够被发送,一次类推。

而接收窗口接收到帧后将其放入对应的位置,而后移动接收窗口,接口窗口与发送窗口同样也有一个大小,如接收窗口是5帧,则落在接收窗口以外的帧会被丢弃。

发送窗口和接收窗口大小的不一样设定就延伸出了不一样的协议:

 

中止-等待协议:每发一帧都要等到确认消息才能发送下一帧,缺点:效率较差。

后退N帧协议:采起累计确认的方式,接收方正确的接受到N帧后发一个累计确认消息给发送窗口,确认N帧已正确收到,若是发送方规定时间内未收到确认消息则认为超时或数据丢失,则会从新发送确认帧以后的全部帧。缺点:出错序号后面的PDU已经发送过了,可是仍是要从新发送,比较浪费。

选择重传协议:若出现差错,只从新传输出现差错涉及须要的PDU,提升了传输效率,减小没必要要的重传。

到这里还剩下最后一个问题:因为发送窗口与接收窗口之间会存在发送效率和接收效率不匹配的问题,就会致使拥塞,解决这个问题TCP有一套流量控制和拥塞控制的机制。

5.4 流量控制和拥塞控制

1)流量控制:

流量控制是对一条通讯路径上的流量进行控制,就是发送方经过获取接收方的回馈来动态调整发送的速率,来达到控制流量的效果,其目的是保证发送者的发送速度不超过接收者的接收速度。

2)拥塞控制:

拥塞控制是对整个通讯子网的流量进行控制,属于全局控制。

① 慢开始+拥塞避免

先来看一张经典的图:

 

一开始使用慢启动,即拥塞窗口设为1,而后拥塞窗口指数增加到慢开始的门限值(ssthresh=16),则切换为拥塞避免,即加法增加,这样增加到必定程度,致使网络拥塞,则此时会把拥塞窗口从新降为1,即从新慢开始,同时调整新的慢开始门限值为12,以后以此类推。

② 快重传+快恢复

快重传:上面咱们说的重传机制都是等到超时还未收到接收方的回复,才开始进行重传。而快重传的设计思路是:若是发送方收到3个重复的接收方的ACK,就能够判断有报文段丢失,此时就能够当即重传丢失的报文段,而不用等到设置的超时时间到了才开始重传,提升了重传的效率。

快恢复:上面的拥塞控制会在网络拥塞时将拥塞窗口降为1,从新慢开始,这样存在的一个问题就是网络没法很快恢复到正常状态。快恢复就是来优化这个问题的,使用快恢复,则出现拥塞时,拥塞窗口只会下降到新的慢开始门阀值(即12),而不会降为1,而后直接开始进入拥塞避免加法增加,以下图所示:

 

快重传和快恢复是对拥塞控制的进一步改进。

要更深刻地理解本小节问题,请详读:《TCP/IP详解 - 第21章·TCP的超时与重传》、《通俗易懂-深刻理解TCP协议(下):RTT、滑动窗口、拥塞处理》。

六、有关Socket

Socket是一组操做TCP/UDP的API,像HttpURLConnection和Okhttp这种涉及到比较底层的网络请求发送的,最终固然也都是经过Socket来进行网络请求链接发送,而像Volley、Retrofit则是更上层的封装,最后是依靠HttpURLConnection或者Okhttp来进行最终的链接创建和请求发送。

Socket的简单使用的话应该都会,两个端各创建一个Socket,服务端的叫ServerSocket,而后创建链接便可。

相关资料,请阅读:

网络编程懒人入门(八):手把手教你写基于TCP的Socket长链接

脑残式网络编程入门(二):咱们在读写Socket时,究竟在读写什么?

七、本文小结

固然,以上这些内容只是我本身知道的而且认为挺重要的计算机网络基础,还有很是多的网络基础知识须要去深刻了解去探索。写了不少,算是对本身网络基础的一个整理,可能也会有纰漏,权当抛砖引玉,还请各位大牛不吝赐教。

附录:更多网络基础知识文章

TCP/IP详解 - 第11章·UDP:用户数据报协议

TCP/IP详解 - 第17章·TCP:传输控制协议

TCP/IP详解 - 第18章·TCP链接的创建与终止

TCP/IP详解 - 第21章·TCP的超时与重传

技术往事:改变世界的TCP/IP协议(珍贵多图、手机慎点)

通俗易懂-深刻理解TCP协议(上):理论基础

通俗易懂-深刻理解TCP协议(下):RTT、滑动窗口、拥塞处理

理论经典:TCP协议的3次握手与4次挥手过程详解

理论联系实际:Wireshark抓包分析TCP 3次握手、4次挥手过程

计算机网络通信协议关系图(中文珍藏版)

UDP中一个包的大小最大能多大?

P2P技术详解(一):NAT详解——详细原理、P2P简介

P2P技术详解(二):P2P中的NAT穿越(打洞)方案详解

P2P技术详解(三):P2P技术之STUN、TURN、ICE详解

通俗易懂:快速理解P2P技术中的NAT穿透原理

高性能网络编程(一):单台服务器并发TCP链接数到底能够有多少

高性能网络编程(二):上一个10年,著名的C10K并发链接问题

高性能网络编程(三):下一个10年,是时候考虑C10M并发问题了

高性能网络编程(四):从C10K到C10M高性能网络应用的理论探索

高性能网络编程(五):一文读懂高性能网络编程中的I/O模型

高性能网络编程(六):一文读懂高性能网络编程中的线程模型

鲜为人知的网络编程(一):浅析TCP协议中的疑难杂症(上篇)

鲜为人知的网络编程(二):浅析TCP协议中的疑难杂症(下篇)

鲜为人知的网络编程(三):关闭TCP链接时为何会TIME_WAIT、CLOSE_WAIT

鲜为人知的网络编程(四):深刻研究分析TCP的异常关闭

鲜为人知的网络编程(五):UDP的链接性和负载均衡

鲜为人知的网络编程(六):深刻地理解UDP协议并用好它

鲜为人知的网络编程(七):如何让不可靠的UDP变的可靠?

网络编程懒人入门(一):快速理解网络通讯协议(上篇)

网络编程懒人入门(二):快速理解网络通讯协议(下篇)

网络编程懒人入门(三):快速理解TCP协议一篇就够

网络编程懒人入门(四):快速理解TCP和UDP的差别

网络编程懒人入门(五):快速理解为何说UDP有时比TCP更有优点

网络编程懒人入门(六):史上最通俗的集线器、交换机、路由器功能原理入门

网络编程懒人入门(七):深刻浅出,全面理解HTTP协议

网络编程懒人入门(八):手把手教你写基于TCP的Socket长链接

技术扫盲:新一代基于UDP的低延时网络传输层协议——QUIC详解

让互联网更快:新一代QUIC协议在腾讯的技术实践分享

现代移动端网络短链接的优化手段总结:请求速度、弱网适应、安全保障

聊聊iOS中网络编程长链接的那些事

移动端IM开发者必读(一):通俗易懂,理解移动网络的“弱”和“慢”

移动端IM开发者必读(二):史上最全移动弱网络优化方法总结

IPv6技术详解:基本概念、应用现状、技术实践(上篇)

IPv6技术详解:基本概念、应用现状、技术实践(下篇)

从HTTP/0.9到HTTP/2:一文读懂HTTP协议的历史演变和设计思路

脑残式网络编程入门(一):跟着动画来学TCP三次握手和四次挥手

脑残式网络编程入门(二):咱们在读写Socket时,究竟在读写什么?

脑残式网络编程入门(三):HTTP协议必知必会的一些知识

脑残式网络编程入门(四):快速理解HTTP/2的服务器推送(Server Push)

以网游服务端的网络接入层设计为例,理解实时通讯的技术挑战

迈向高阶:优秀Android程序员必知必会的网络基础

>> 更多同类文章 ……

(本文同步发布于:http://www.52im.net/thread-1963-1-1.html

相关文章
相关标签/搜索