美文网首页Android开发收藏夹android开发Android 轮子
Android 网络--我是怎么做的: Volley+OkHtt

Android 网络--我是怎么做的: Volley+OkHtt

作者: 荆全齐 | 来源:发表于2015-07-29 20:38 被阅读25977次

Volley 已经发布很长时间了, 也已被广泛应用, 相关教程到处都是. 本文只说两个值得注意的地方.
本文讲解部分比较少, 请参阅提供的相关链接. 完整的实现代码在 [Github dodocat/AndroidNetworkDemo] 可能看起来比这里更清晰.

使用 OkHttp 作为传输层的实现.

Volley 默认根据 Android 系统版本使用不同的 Http 传输协议实现.
在 Android 2.3以下使用 ApacheHttpStack 作为传输协议, 在 3.0 及以下使用 HttpURLConnection 作为传输层协议 (感谢评论中指正的朋友).

OkHttp 相较于其它的实现有以下的优点.

  • 支持SPDY,允许连接同一主机的所有请求分享一个socket。
  • 如果SPDY不可用,会使用连接池减少请求延迟。
  • 使用GZIP压缩下载内容,且压缩操作对用户是透明的。
  • 利用响应缓存来避免重复的网络请求。
  • 当网络出现问题的时候,OKHttp会依然有效,它将从常见的连接问题当中恢复。
  • 如果你的服务端有多个IP地址,当第一个地址连接失败时,OKHttp会尝试连接其他的地址,这对IPV4和IPV6以及寄宿在多个数据中心的服务而言,是非常有必要的。

因此使用 OkHttp 作为替代是好的选择.

  1. 首先用 OkHttp 实现一个新的 HurlStack 用于构建 Volley 的 requestQueue.

public class OkHttpStack extends HurlStack {

private OkHttpClient okHttpClient;

/**
 * Create a OkHttpStack with default OkHttpClient.
 */
public OkHttpStack() {
    this(new OkHttpClient());
}

/**
 * Create a OkHttpStack with a custom OkHttpClient
 * @param okHttpClient Custom OkHttpClient, NonNull
 */
public OkHttpStack(OkHttpClient okHttpClient) {
    this.okHttpClient = okHttpClient;
}

@Override
protected HttpURLConnection createConnection(URL url) throws IOException {
    OkUrlFactory okUrlFactory = new OkUrlFactory(okHttpClient);
    return okUrlFactory.open(url);
}

}


1. 然后使用 OkHttpStack 创建新的 Volley requestQueue. 
    ``` java
requestQueue = Volley.newRequestQueue(getContext(), new OkHttpStack());
requestQueue.start();

这样就行了.

使用 Https

作为一个有节操的开发者应该使用 Https 来保护用户的数据, Android 开发者网站上文章[Security with HTTPS and SSL]做了详尽的阐述.

OkHttp 自身是支持 Https 的. 参考文档 [OkHttp Https], 直接使用上面的 OkHttpStack 就可以了, 但是如果遇到服务器开发哥哥使用了自签名的证书(不要问我为什么要用自签名的), 就无法正常访问了.

网上有很多文章给出的方案是提供一个什么事情都不做的TrustManager 跳过 SSL 的验证, 这样做很容受到攻击, Https 也就形同虚设了.

我采用的方案是将自签名的证书打包入 APK 加入信任.

好处:

  • 应用难以逆向, 应用不再依赖系统的 trust store, 使得 Charles 抓包等工具失效. 要分析应用 API 必须反编译 APK.
  • 不用额外购买证书, 省钱....

缺点:

  • 证书部署灵活性降低, 一旦变更证书必须升级程序.

实现步骤

以最著名的自签名网站12306为例说明

  1. 导出证书

     echo | openssl s_client -connect kyfw.12306.cn:443 2>&1 |  sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > kyfw.12306.cn.pem
    
  2. 将证书转为 bks 格式
    下载最新的bcprov-jdk, 执行下面的命令. storepass 是导出密钥文件的密码.

export CERTSTORE=kyfw.12306.cn.bks
keytool -importcert -v
-trustcacerts
-alias 0
-file <(openssl x509 -in kyfw.12306.cn.pem)
-keystore $CERTSTORE -storetype BKS
-providerclass org.bouncycastle.jce.provider.BouncyCastleProvider
-providerpath ./bcprov-jdk16-1.46.jar
-storepass asdfqaz
```

  1. 将导出的 kyfw.bks 文件放入 res/raw 文件夹下.

  2. 创建 SelfSignSslOkHttpStack

/**

  • A HttpStack implement witch can verify specified self-signed certification.
    */
    public class SelfSignSslOkHttpStack extends HurlStack {

    private OkHttpClient okHttpClient;

    private Map<String, SSLSocketFactory> socketFactoryMap;

    /**

    • Create a OkHttpStack with default OkHttpClient.
      */
      public SelfSignSslOkHttpStack(Map<String, SSLSocketFactory> factoryMap) {
      this(new OkHttpClient(), factoryMap);
      }

    /**

    • Create a OkHttpStack with a custom OkHttpClient
    • @param okHttpClient Custom OkHttpClient, NonNull
      */
      public SelfSignSslOkHttpStack(OkHttpClient okHttpClient, Map<String, SSLSocketFactory> factoryMap) {
      this.okHttpClient = okHttpClient;
      this.socketFactoryMap = factoryMap;
      }

    @Override
    protected HttpURLConnection createConnection(URL url) throws IOException {
    if ("https".equals(url.getProtocol()) && socketFactoryMap.containsKey(url.getHost())) {
    HttpsURLConnection connection = (HttpsURLConnection) new OkUrlFactory(okHttpClient).open(url);
    connection.setSSLSocketFactory(socketFactoryMap.get(url.getHost()));
    return connection;
    } else {
    return new OkUrlFactory(okHttpClient).open(url);
    }
    }
    }

    
    
  1. 然后用 SelfSignSslOkHttpStack 创建 Volley 的 RequestQueue.

    String[] hosts = {"kyfw.12306.cn"};
    int[] certRes = {R.raw.kyfw};
    String[] certPass = {"asdfqaz"};
    socketFactoryMap = new Hashtable<>(hosts.length);
    
    for (int i = 0; i < certRes.length; i++) {
        int res = certRes[i];
        String password = certPass[i];
        SSLSocketFactory sslSocketFactory = createSSLSocketFactory(context, res, password);
        socketFactoryMap.put(hosts[i], sslSocketFactory);
    }
    
    HurlStack stack = new SelfSignSslOkHttpStack(socketFactoryMap);
    
    requestQueue = Volley.newRequestQueue(context, stack);
    requestQueue.start();
    
  2. 我们来试一试, 用上一步穿件的 RequestQueue 替换掉原来的, 然后发请求试试.

    
        StringRequest request = new StringRequest(
                Request.Method.GET,
                "https://kyfw.12306.cn/otn/",
                new Response.Listener<String>() {
                    @Override
                    public void onResponse(String response) {
                        responseContentTextView.setText(response);
                    }
                },
                new Response.ErrorListener() {
                    @Override
                    public void onErrorResponse(VolleyError error) {
                        responseContentTextView.setText(error.toString());
                    }
                });
        RequestManager.getInstance(this).addRequest(request, this);
    

1. done


[Volley]:http://developer.android.com/training/volley/index.html
[OkHttp]:http://square.github.io/okhttp/
[Gson]:https://github.com/google/gson

[Security with HTTPS and SSL]:https://developer.android.com/training/articles/security-ssl.html
[OkHttp Https]:https://github.com/square/okhttp/wiki/HTTPS
[Github dodocat/AndroidNetworkDemo]:https://github.com/dodocat/AndroidNetworkdemo

相关文章

网友评论

  • MiBoy:那公签证书的验证怎么做呢
  • 09f85f57010e:sn:
    volley使用okhttp作为传输层实现;
    什么都不做的TrustManager跨过SSL使https形同虚设
    bcprov-jdk下载连接:http://pan.baidu.com/s/1bo8Xfun
  • fe205e5a28a5:请求不到数据,说证书未找到。求楼主指点
  • fe205e5a28a5:请问createSSLSocketFactory(mCxt, res, password);方法是怎么写的
  • fe205e5a28a5:请问OkhttClient,SSLSocketFactory分别是哪个包下面的
  • zT2m8T:请问你是怎么知道kyfw.12306.cn 的 storepass是 asdfqaz
  • 271a34992389:写的很好,
    不过SSLContext.getInstance("TSL")中的TSL和SSL的区别是什么啊?
    不知道该怎么使用这个
  • LulPerer:好文谢谢分享!
  • Kaede:OkHttp3已经移除了对HttpUrlConnection的适配……
  • 63390e87c162:同求createSSLSOcketFactory
    荆全齐:@龙出滔尖 文章的第一段 github 的 demo 里面有完整的代码
  • 8cfe5f2585cb:createSSLSocketFactory
    这个方法在哪啊
    荆全齐:@终极小强 文章的第一段 github 的 demo 里面有完整的代码
  • ce52e67fec26:java.lang.NoSuchFieldError: com.squareup.okhttp.internal.http.HttpMethod.METHODS
    at com.squareup.okhttp.internal.huc.HttpURLConnectionImpl.setRequestMethod(HttpURLConnectionImpl.java:550)
    at com.android.volley.toolbox.HurlStack.setConnectionParametersForRequest(HurlStack.java:250)
    at com.android.volley.toolbox.HurlStack.performRequest(HurlStack.java:111)
    at com.android.volley.toolbox.BasicNetwork.performRequest(BasicNetwork.java:97)
    at com.android.volley.NetworkDispatcher.run(NetworkDispatcher.java:112)

    问下,楼主,我使用okhttp2.6.0 ,okhttp-urlconnection.jar2.0.0 报这个异常是什么原因啊
  • 5ff6284723c5:你好,我命令输入后发生错误是什么情况,谢谢
    C:\Users\YTR-101\Desktop\Office2013含破解工具>keytool -importcert -v -trustcacer
    ts -file "yycaishen.com.crt" -alias "ddd" -keystore "my.bks" -provider org.bounc
    ycastle.jce.provider.BouncyCastleProvider -providerpath "bcprov-jdk15on-152.jar"
    -storetype BKS
    keytool 错误: java.lang.IllegalArgumentException
    java.lang.IllegalArgumentException
    at sun.net.www.ParseUtil.decode(ParseUtil.java:202)
    at sun.misc.URLClassPath$FileLoader.<init>(URLClassPath.java:1204)
    at sun.misc.URLClassPath$3.run(URLClassPath.java:525)
    at sun.misc.URLClassPath$3.run(URLClassPath.java:520)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.misc.URLClassPath.getLoader(URLClassPath.java:519)
    at sun.misc.URLClassPath.getLoader(URLClassPath.java:492)
    at sun.misc.URLClassPath.getNextLoader(URLClassPath.java:457)
    at sun.misc.URLClassPath.getResource(URLClassPath.java:211)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:365)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at sun.security.tools.keytool.Main.doCommands(Main.java:670)
    at sun.security.tools.keytool.Main.run(Main.java:340)
    at sun.security.tools.keytool.Main.main(Main.java:333)
  • 8cfe5f2585cb:OkUrlFactory
    这个类找不到了,okhttp版本 2.5.0
    8cfe5f2585cb:@曙光之辉 导入 okhttp-urlconnection 3.2.0 包
    Jianshu_xiaojin:okhttp 3.2 找不到okurlFactory怎么解决的
    8cfe5f2585cb:@终极小强 解决了~忘了导入okhttp-urlconnection包
  • e2dc6ce9367c:用DEMO跑了一个 完全成功 但是换到自己的BKS文件后 却报错 host是服务器的地址
    求教哪里有问题

    com.android.volley.NoConnectionError:
    javax.net.ssl.SSLHandshakeException:
    java.security.cert.CerPathValidatorException:Trust anchor for certification path not found.
    1282e64d3aee:@白衣漂漂 域名写错了。擦
    1282e64d3aee:@nervvvv 我也遇到同样的问题 请问解决了吗
  • e371c64445aa:请问dump.sh需要怎样才可以运行成功的?
    e371c64445aa:@荆全齐 找到了个方法:http://blog.csdn.net/arjinmc/article/details/47108061
    e371c64445aa:@荆全齐 运行后就产生了这个文件:kyfw.12306.cn.pem 而已
    荆全齐:@qq137712630 不是可以直接运行的么?
  • 江上夜泊人5728:感觉既然用okhttp没必要和volley掺和到一块
  • c20db604f09a:请问楼主用的库都是什么版本的
    这些代码,在
    okhttp 2.5
    okio 1.0
    okhttp-urlconnection 2.0
    volley 1.0.11
    就会报错
    java.lang.NoSuchFieldError: com.squareup.okhttp.internal.http.HttpMethod.METHODS

    把okhttp换成2.0版本就能行
  • kass:看了几篇博客,好些人都是这么用的。
    想问下将 volley 和 okhttp 嫁接是出于什么目的?OKhttp不也自带异步请求队列么?
    volley 的API更好用?还是什么?
    darkengine:如果已有项目已经用volley实现了,这样修改的升级代价应该是最小的。
    半岛铁盒里的猫:对OkHttp不熟悉,同问两者结合使用和单独使用OkHttp有什么区别?
  • Haogg:在 Android 3.0 以上 Volley 使用 ApacheHttpStack 作为传输协议, 在2.3 及以下使用 HttpURLConnection 作为传输层协议


    -----------------------这是很明显的错误,一切要以代码为准。不要看别人博客怎么说就怎么说。

    if (stack == null) {
    if (Build.VERSION.SDK_INT >= 9) {
    stack = new HurlStack();
    } else {
    // Prior to Gingerbread, HttpUrlConnection was unreliable.
    // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html
    stack = new HttpClientStack(AndroidHttpClient.newInstance(userAgent));
    }
    }
    荆全齐:@Haogg 感谢指正!
  • 7c7f58e2a6dc:好奇怎么做到的?为什么我写的时候代码格式会被毁掉
  • 微凉一季:写错了吧,2.3以上用的是urlconnection,2.3以下用的httpclient、
    微凉一季:@慕课网 这个对markdown支持的还不太完善吧。尽量使用简单的格式就好了。另外你是女的还是男的 :smiley:
    妙法莲花1234:@微凉一季 原来你也在这里。。。
    7c7f58e2a6dc:@微凉一季 为什么我写的时候代码格式会被毁掉
  • MrRock:@荆全齐 也看过很多开源项目改造volley的,只是考虑到不利于volley的升级,每次更新都要再次改造?
    荆全齐:@慕女神 我是用 markdown 编写的 有一个代码的标记.
    7c7f58e2a6dc:@荆全齐 为什么我写的时候代码格式会被毁掉
    荆全齐:@MrRock 继承重写又不用改 Volley 的源码...
  • MrRock:想请教一下,volley本身不自带multipart参数功能,okhttp貌似可以,但是这样整合后还能用到吗?我一直在思考弄一个完整功能的网络请求模块,苦于没有找到非常好的解决方案
    荆全齐:@MrRock Multipart 也只是一个数据组合的定义而已, 可以轻松通过集成重写 Volley Request 实现.
  • alighters:非常不错,有个问题请教一下啊。res/raw 跟assets下面的资源在打包生成apk的过程中,会原封不动地放进apk中,这样在反编译apk之后,不是kyfw.bks又可以被别人拿到的么?
    荆全齐:@lighters 这里指的是不能通过代理抓包的方式对 api 进行逆向分析, 想要获取应用的网络 API 设计必须逆向.
    alighters:@荆全齐 所以所说的“难以逆向”,是指反编译APK这一步么?
    荆全齐:@lighters 这个证书本身就是公钥的角色, 本来就是公开的. 你看到上面的代码, 这个证书也是通过所有人都能访问的公开方式获取到的.

本文标题:Android 网络--我是怎么做的: Volley+OkHtt

本文链接:https://www.haomeiwen.com/subject/apzpqttx.html