本文旨在提出一种提高移动端网络性能的可行方案。我们知道目前移动端使用的网络请求协议基本上都是http。用的最多的是http/1.1,http/2.0正在逐渐壮大,实际上http/2.0是基于google提出的SPDY协议改进而来。废话不多说,马上进入正题。
关于SPDY协议的详细介绍,请参看 OkHttp完全解析(七)SPDY协议详细介绍 。
关于OkHttp的使用及源码分析网上相关的资料很多,推荐OkHttp完全解析(八)源码解析一 系列文章。写的很不错。
从速度上来讲,Http/1.1用明文传输,无需加密验证,速度快;SPDY加入了SSL加密握手,但是SPDY协议允许一个TCP连接复用,可以一个域名只提供一个TCP连接即可完成通信,虽然由于TCP连接复用,但是SSL加密握手协商过程又耗费了一定的时间;Http/2.0是Http的加密版本,也是SPDY的升级版本,如果追求高安全性,可以选用Http/2.0,速度上Http/2.0比Http/1.1稍慢。实验证明单纯的将Http/1.1升级到SPDY,速度提升并不明显,原因在于SSL加密层的添加,一定程度上拖慢了SPDY协议的效率。本文在基于OkHttp开源库的基础上,使用SPDY协议,但是强制忽略了SSL握手验证过程,强制使用SPDY/3.1协议,这样就将SPDY的速度优势发挥出来了。Okhttp的源码量比较大,逻辑比较复杂,我费了不少时间,看懂了大致的思路。请在阅读本文之前阅读前面的OkHttp源码系列文章。
针对某个公司的产品,服务器端可以针对域名进行配置SPDY协议的使用。针对自家产品完全可以免去SSL协商过程,直接默认使用SPDY协议。关于服务器怎么配置,在此不过多赘述,自行查阅相关文章。
客户端具体实现过程:
下载okHttp源码并进行修改,只需修改一处即可完成。直接上代码:
Connection.java中的connect()函数:
//当新建连接或者可用连接无效的时候进入此函数
void connect(int connectTimeout, int readTimeout, int writeTimeout,Request request, List connectionSpecs,boolean connectionRetryEnabled) throws RouteException {
if (connected)
throw new IllegalStateException("already connected");
SocketConnector socketConnector = new SocketConnector(this, pool);
SocketConnector.ConnectedSocket connectedSocket;
if (route.address.getSslSocketFactory() != null) {
// https:// communication
connectedSocket = socketConnector.connectTls(connectTimeout, readTimeout, writeTimeout, request, route, connectionSpecs, connectionRetryEnabled);
} else {
// http:// communication.
if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
throw new RouteException(new UnknownServiceException(
"CLEARTEXT communication not supported: " + connectionSpecs));
}
connectedSocket = socketConnector.connectCleartext(connectTimeout,
readTimeout, route);
}
socket = connectedSocket.socket;
handshake = connectedSocket.handshake;
//此处已完成协议的协商,protocol变量的值则决定了下面所走的协议。
protocol = connectedSocket.alpnProtocol == null ? Protocol.HTTP_1_1
: connectedSocket.alpnProtocol;
/***********加入代码**************/
String hostName = request.httpUrl().host();
if ("xxx.com".equals(hostName) {
protocol = Protocol.SPDY_3;
}
/***********加入的代码结束**************/
try {
if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
socket.setSoTimeout(0); // SPDY timeouts are set per-stream.
spdyConnection = new SpdyConnection.Builder(route.address.uriHost,
true, socket).protocol(protocol).build();
spdyConnection.sendConnectionPreface();
} else {
httpConnection = new HttpConnection(pool, this, socket);
}
} catch (IOException e) {
throw new RouteException(e);
}
connected = true;
}
由于时间有限,加上第一次正式写博客,很多地方语法用的不是太好,凑合看哈,connect()函数中加入的代码:
String hostName = request.httpUrl().host();
if ("xxx.com".equals(hostName) {
protocol = Protocol.SPDY_3;
}
只是通过很简单的逻辑即可实现,xxx.com代表的是服务器端支持SPDY的域名,此段代码的含义即为强制客户端针对域名选用SPDY协议。值得注意的是,在OkHttp框架中,若为https则默认会进行handshake过程,此时相当于会进行TLS握手过程,此时,这样添加实际上无法提高效率,且既然用了https加密协议,那TLS握手肯定是不能去掉的,否则https变的毫无意义。
具体的实现则需要追踪源码。这里给出简单的说明。Connection.java中的connectAndSetOwner函数是每次请求都会进来的函数,注意此处会进行判断,当第一次建立连接时,isConnected返回false,此时会进入connect函数,建立连接,选择协议,完成连接后,会进行isSpdy判断,如果是Spdy协议,那么会将此connnection放入连接池中进行共享,下次相同域名下的请求到来时,isConnected判断就会为true,则不需要进行重复创建连接,实现了连接的共享。而若是http/1.1,一般不会进行连接共享,每次都需要重建连接,效率较低。
void connectAndSetOwner(OkHttpClient client, Object owner, Request request)throws RouteException {
setOwner(owner);
if (!isConnected()) {
List connectionSpecs = route.address.getConnectionSpecs();
connect(client.getConnectTimeout(), client.getReadTimeout(),
client.getWriteTimeout(), request, connectionSpecs,
client.getRetryOnConnectionFailure());
if (isSpdy()) {
client.getConnectionPool().share(this);
}
client.routeDatabase().connected(getRoute());
}
setTimeouts(client.getReadTimeout(), client.getWriteTimeout());
}
下面给出一个简单的代码进行验证:
public class MainActivity extends Activity {
private Button myButton;
private String[] urls = {
"http://api.caipiao.163.com/clientCommonConfig_getNaviBottomAlert.html?mobileType=android&ver=4.13&channel=netease&apiVer=1.1&apiLevel=19",
"http://api.caipiao.163.com/lottery_newActivity.html?mobileType=android&ver=4.13&channel=netease&apiVer=1.1&apiLevel=19",
"http://api.caipiao.163.com/clientHall_hallInfoAll.html?mobileType=android&ver=4.13&channel=netease&apiVer=1.1&apiLevel=19",
"http://api.caipiao.163.com/getClientNaviLogos.html?mobileType=android&ver=4.13&channel=netease&apiVer=1.1&apiLevel=19",
"http://api.caipiao.163.com/classifyABTest.html?mobileType=android&ver=4.13&channel=netease&apiVer=1.1&apiLevel=19" };
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
myButton = (Button) findViewById(R.id.myBtn);
myButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new Thread() {
@Override
public void run() {
long start = System.currentTimeMillis();
for (int i = 0; i < urls.length * 4; i++) {
OkHttpClient okHttpClient = new OkHttpClient();
Request request = new Request.Builder().url(
urls[i % urls.length]).build();
try {
Response okHttpResponse = okHttpClient.newCall(
request).execute();
Log.i("SpdyTest", "i:" + i + " protocol:"
+ okHttpResponse.protocol().name()
+ "code:" + okHttpResponse.code());
} catch (IOException e) {
e.printStackTrace();
}
}
long end = System.currentTimeMillis();
Log.i("aTest", "total time: " + (end - start) + "ms");
}
}.start();
}
});
}
}
代码很简单,利用已配置好服务器端的url进行测试,每进行20次请求, 进行一次时间计算。
Spdy协议:
测试环境为家里wifi信号,几乎同时进行测试,也切换过其他网络,同等环境下,去掉TLS握手的SPDY协议要比Http快大概40%。
当然了对于大多数应用来说,一般情况下不会产生明显的卡顿,但是效果总不怕好嘛,这里只给大家提供一个比较可行的思路而已。
最近在家里闲来无事,在看okhttp的源码,确实学到了很多东西,本来想写几篇源码分析的文章,发现写起来真是繁琐,且网上有很多博客解释的很清晰,这里就不做重复性工作了。
本文提供的思路几乎只用一行代码就能实现,不过很遗憾,okhttp框架并不能直接设置想要使用的协议,没有暴露接口给我们,所以我们只能把源码下载下来,在进行打jar包啦。
这是我第一次写博客,很多地方没注意到,看到的请见谅,关于OkHttp这个开源库,我强烈推荐,亲自实践了它失败重连等功能,有任何相关问题,欢迎留言交流。