Retrofit 2.0 超能实践(一),完美支持加密Https传输
来源:互联网 发布:ubuntu系统镜像 编辑:程序博客网 时间:2024/05/16 17:24
转载自:http://www.jianshu.com/p/16994e49e2f6
前阵子看到圈子里Retrofit 2.0,RxJava(Android), OkHttp3.3 ,加之支持Android和 iOS 的React Native 热更新技术, 火的不要不要的, 2015年新技术一大波来袭 ,看着自己项目还在用httpClient, asyncTask的原生开发 感觉自己已成火星人,实在顶不住内心强烈的自卑感,加之对新技术的追求,入手移动开发新三剑客,运用在目前的项目中,虽然目前关于他们的介绍资料网上一大把,但是自己亲自实践后,发现坑不少,为了能方便其他人安全顺利入坑,今天就先从Retrofit说起,前方高能,准备躲避。
Retrofit 2.0
Retrofit是SQUARE美国一家移动支付公司最近新发布的在Android平台上http访问的开源项目
一 什么Retrofit
官方标语;A type-safe HTTP client for Android and Java
语意很明显一款android安全类型的http客户端, 那么怎么样才算安全?支持https?支持本地线程安全?
发现Rertofit其内部都是支持lambda语法(国内称只链式语法),内部支持okhttp, 并且支持响应式RxJAava,当然jdk1.8 和android studio工具也支持lambda。带着这些疑问 我开始探究一下。
在此之前准备入手资料:
国外博客
https://inthecheesefactory.com/blog/retrofit-2.0/en官方github
http://square.github.io/retrofit/
二 Retrofit怎么使用
下文之前先给大家看下传统的httpclient(urlConnection) + AsyncTask实现的登录功能,这样我们才能发现Retrofit的优雅之处.
传统方式:
/** * Represents an asynchronous login/registration task used to authenticate * the user. */public class UserLoginTask extends AsyncTask<Void, Void, Boolean> { private final String mEmail; private final String mPassword; UserLoginTask(String email, String password) { mEmail = email; mPassword = password; } @Override protected Boolean doInBackground(Void... params) { // TODO: attempt authentication against a network service. try { // Simulate network access. String result = ""; BufferedReader in = null; String path ="http://localhost:8080/login/?" +"email =" + mEmail + "& password =" + mPassword; URL url =new URL(path); HttpURLConnection conn = (HttpURLConnection)url.openConnection(); conn.setConnectTimeout(5 * 1000); conn.setRequestMethod("GET"); InputStream inStream = conn.getInputStream(); in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine()) != null) { result += "\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\n" + line; } }catch (MalformedURLException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } //在这里我们还要对返回的json数据进行 要主动映射到modle上 ……… for (String credential : DUMMY_CREDENTIALS) { String[] pieces = credential.split(":"); if (pieces[0].equals(mEmail)) { // Account exists, return true if the password matches. return pieces[1].equals(mPassword); } } // TODO: register the new account here. return true; } @Override protected void onPostExecute(final Boolean success) { mAuthTask = null; if (success) { // do SomeThing } else { mPasswordView.setError(getString(R.string.error_incorrect_password)); mPasswordView.requestFocus(); } } @Override protected void onCancelled() { mAuthTask = null; showProgress(false); }}private void enterhome() { Intent intent = new Intent(LoginActivity.this, MainListActivity.class); startActivity(intent);}
发现姿势也很简单,点击loginbtn
开启一个异步线程 在AsyncTask
在 doInBackground
中访问登录API,在onPostExecute
中进行UI更新;也能很简单流畅的解决UI线程请求网络 非UI线程更新UI的问题, 但是AsyncTask
处理大数据耗时就会有弊端,况且他默认线程也是5个,容易造成泄漏,接下来介绍用Retrofit
实现以上相同的功能的方式
2 Retrofit
/** * 登录! */private void getLogin() { Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:8080/") .addConverterFactory(GsonConverterFactory.create()) .build(); ApiManager apiService = retrofit.create(ApiManager.class); Call<LoginResult> call = apiService.getData("lyk", "1234"); call.enqueue(new Callback<LoginResult>() { @Override public void onResponse(Call<LoginResult> call, Response<LoginResult> response) { if (response.isSuccess()) { // do SomeThing } else { //直接操作UI 返回的respone被直接解析成你指定的modle } } @Override public void onFailure(Call<LoginResult> call, Throwable t) { // do onFailure代码 } });}
ApiManager接口
/** * Created by LIUYONGKUI on 2016-05-03.*/public interface ApiManager { @GET("login/") Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);
好了 看了以上代码 或许你已经看到了他的链式优雅高大上的地方了,也许看不懂,有点蒙逼,但没关系我们继续入门。
1 配置gradle
compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'
com.squareup.retrofit2:converter-gson:2.0.0-beta4 此依赖非必须,只是方便我对http返回的数据进行解析。
2 定义实例化
1》初始化Retrofit
Retrofit retrofit = new Retrofit.Builder() .baseUrl("http://localhost:8080/") .addConverterFactory(GsonConverterFactory.create()) .build();
通过 Retrofit.Builder 来创建一个retrofit客户端,接着添加host url, 然后制定数据解析器,上面依赖的gson就是用在这里做默认数据返回的, 之后通过build()创建出来
Retrofit也支持且内部自带如下格式:
- Gson: com.squareup.retrofit2:converter-gson
- Jackson: com.squareup.retrofit2:converter-jackson
- Moshi: com.squareup.retrofit2:converter-moshi
- Protobuf: com.squareup.retrofit2:converter-protobuf
- Wire: com.squareup.retrofit2:converter-wire
- Simple XML: com.squareup.retrofit2:converter-simplexml
- Scalars (primitives, boxed, and String): com.squareup.retrofit2:converter-scalars
2》编写API
@GET("login/") Call<LoginResult> getData(@Query("name") String name, @Query("password") String pw);
Call<T>是继承Cloneable的 并支持泛型,且此类是Retrofit统一返回对象,支持Callback<T>回调,在2.0上已支持RxJava观察者对象Observable<T>,此案例暂时用call ,后面入门了retrofit之后再接入RxJava,接着我们可以传入制定的解析Modle,就会在主线程里返回对应的model数据,无需开发者手动解析json数据,返回格式由开发者自己设置,这里主要用注解@get @post 设置请求方式,后面“login/”是方法Url,@Query("name")来设定body的parameters.
- 如果想用表单 @FieldMap
@FormUrlEncoded@POST("/url") Call<T> postForm( @FieldMap Map<String , Object> maps);
如果直接用对象 @Body
@POST("url") Call<T> PostBody( @Body Objects objects);
如果直接多参数 @QueryMap
@PUT("/url")Call<T> queryMap( @QueryMap Map<String, String> maps);
如果上传文件 @Part
@Multipart@POST("/url")Call<ResponseBody> uploadFlie( @Part("description") RequestBody description, @Part("files") MultipartBody.Part file);
如果多文件上传 @PartMap()
@Multipart@POST("{url}")Call<T> uploadFiles( @Path("url") String url, @PartMap() Map<String, RequestBody> maps);
3》 调用API
Retrofit支持异步和同步,案例中用call.enqueue(new Callback<LoginResult>)来采用异步请求,如果 调用call.execute() 则采用同步方式
Call<LoginResult> call = apiService.getData("lyk", "1234"); call.enqueue(new Callback<LoginResult>() { @Override public void onResponse(Call<LoginResult> call, Response<LoginResult> response) { } @Override public void onFailure(Call<LoginResult> call, Throwable t) { } });}
取消请求
直接用call实例进行cancel即可
call.cancel();
如果还未理解请阅读参考入门资料:Retrofit 2.0:有史以来最大的改进
三 进阶拓展
通过以上的介绍和案列,我们了解了怎样运用Retrofit请求网络数据,展现数据更新UI,用什么数据模型接收 Retroifit就会返回什么类型的数据,我们也不用关心是否在主线程里访问网络 还是子线程更新ui的问题,但实际开发中会存在很多问题,很多同学会遇到:Retrofit的内部Log都无法输出 , header怎么加入,请求怎么支持https,包括怎么结合RxJava.? 不用担心,这些Retrofit 2.0 都提供了支持okhttp的自定义的Interceptor(拦截器),通过不同的Interceptor可以实现不同的自定义请求形式,比如统一加head,参数,加入证书(ssl)等,前提必须结合okhttp来实现 , 通过给OkHttpClient添加Interceptor,然后给Retrofit设置http客户端即可.Retrofit提供了
.client()方法供我们传入自定义的网络客户端,当然默认请求客户端就是okhttps.
OkHttp入门请移步:
~https://github.com/square/okhttp
~ OKHttp源码解析
1 开启Log
可以用拦截器自己实现, retrofit已经提供了HttpLoggingInterceptor
里面有四种级别,输出的格式 可以看下面介绍。
public enum Level { /** No logs. */ NONE, /** * Logs request and response lines. * * <p>Example: * <pre>{@code * --> POST /greeting http/1.1 (3-byte body) * * <-- 200 OK (22ms, 6-byte body) * }</pre> */ BASIC, /** * Logs request and response lines and their respective headers. * * <p>Example: * <pre>{@code * --> POST /greeting http/1.1 * Host: example.com * Content-Type: plain/text * Content-Length: 3 * --> END POST * * <-- 200 OK (22ms) * Content-Type: plain/text * Content-Length: 6 * <-- END HTTP * }</pre> */ HEADERS, /** * Logs request and response lines and their respective headers and bodies (if present). * * <p>Example: * <pre>{@code * --> POST /greeting http/1.1 * Host: example.com * Content-Type: plain/text * Content-Length: 3 * * Hi? * --> END GET * * <-- 200 OK (22ms) * Content-Type: plain/text * Content-Length: 6 * * Hello! * <-- END HTTP * }</pre> */ BODY }
开启请求头
Retrofit retrofit = new Retrofit.Builder().client(new OkHttpClient.Builder() .addNetworkInterceptor( new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.HEADERS)) .build())
开启body日志
.addNetworkInterceptor( new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
基础输出
.addNetworkInterceptor( new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BASIC))
2 增加头部信息
通用请求头
new Retrofit.Builder() .addConverterFactory(GsonConverterFactory.create()) .client(new OkHttpClient.Builder() .addInterceptor(new Interceptor() { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request() .newBuilder() .addHeader("mac", "f8:00:ea:10:45") .addHeader("uuid", "gdeflatfgfg5454545e") .addHeader("userId", "Fea2405144") .addHeader("netWork", "wifi") .build(); return chain.proceed(request); } }) .build()
单独加入
@Headers({ "Accept: application/vnd.github.v3.full+json", "User-Agent: Retrofit-your-App"})@get("users/{username}")Call<User> getUser(@Path("username") String username);
3 添加证书Pinning
证书可以在自定义的OkHttpClient加入certificatePinner 实现
OkHttpClient client = new OkHttpClient.Builder() .certificatePinner(new CertificatePinner.Builder() .add("YOU API.com", "sha1/DmxUShsZuNiqPQsX2Oi9uv2sCnw=") .add("YOU API..com", "sha1/SXxoaOSEzPC6BgGmxAt/EAcsajw=") .add("YOU API..com", "sha1/blhOM3W9V/bVQhsWAcLYwPU6n24=") .add("YOU API..com", "sha1/T5x9IXmcrQ7YuQxXnxoCmeeQ84c=") .build())
4 支持https
加密和普通http客户端请求支持https一样,步骤如下:
1 CertificateFactory 得到Context.getSocketFactory
2 添加证书源文件
3 绑定到okhttpClient
4设置okhttpClient到retrofit中
证书同样可以设置到okhttpclient中,我们可以把证书放到raw路径下
SLSocketFactory sslSocketFactory =getSSLSocketFactory_Certificate(context,"BKS", R.raw.XXX);
准备证书源文件
加入证书源文件,我的证书是放在Raw下面的:
绑定证书
protected static SSLSocketFactory getSSLSocketFactory(Context context, int[] certificates) {if (context == null) { throw new NullPointerException("context == null");}CertificateFactory certificateFactory;try { certificateFactory = CertificateFactory.getInstance("X.509"); KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); keyStore.load(null, null); for (int i = 0; i < certificates.length; i++) { InputStream certificate = context.getResources().openRawResource(certificates[i]); keyStore.setCertificateEntry(String.valueOf(i), certificateFactory.generateCertificate(certificate)); if (certificate != null) { certificate.close(); } } SSLContext sslContext = SSLContext.getInstance("TLS"); TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); trustManagerFactory.init(keyStore); sslContext.init(null, trustManagerFactory.getTrustManagers(), new SecureRandom()); return sslContext.getSocketFactory();
构建HostnameVerifier
protected static HostnameVerifier getHostnameVerifier(final String[] hostUrls) { HostnameVerifier TRUSTED_VERIFIER = new HostnameVerifier() { public boolean verify(String hostname, SSLSession session) { boolean ret = false; for (String host : hostUrls) { if (host.equalsIgnoreCase(hostname)) { ret = true; } } return ret; } };return TRUSTED_VERIFIER;
}
设置setSocketFactory
okhttpBuilder.socketFactory(HttpsFactroy.getSSLSocketFactory(context, certificates));
certificates 是你raw下证书源ID, int[] certificates = {R.raw.myssl}
设置setNameVerifie
okhttpBuilder.hostnameVerifier(HttpsFactroy.getHostnameVerifier(hosts));
hosts是你的host数据 列如 String hosts[]`= {“https//:aaaa,com”, “https//:bbb.com”}
实现自定义 添加到Retrofit
okHttpClient = okhttpBuilder.build(); Retrofit retrofit = new Retrofit.Builder() .client(okHttpClient) .build();
如果信任所有https请求,
可以直接将OkHttpClient的HostnameVerifier设置为false
OkHttpClient client = new OkHttpClient(); client.setHostnameVerifier(new HostnameVerifier() { @Override public boolean verify(String s, SSLSession sslSession) { return true; } }); TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() { @Override public void checkClientTrusted( java.security.cert.X509Certificate[] x509Certificates, String s) throws java.security.cert.CertificateException { } @Override public void checkServerTrusted( java.security.cert.X509Certificate[] x509Certificates, String s) throws java.security.cert.CertificateException { } @Override public java.security.cert.X509Certificate[] getAcceptedIssuers() { return new java.security.cert.X509Certificate[] {}; } } }; try { SSLContext sc = SSLContext.getInstance("TLS"); sc.init(null, trustAllCerts, new java.security.SecureRandom()); client.setSslSocketFactory(sc.getSocketFactory()); } catch (Exception e) { e.printStackTrace(); } clent.protocols(Collections.singletonList(Protocol.HTTP_1_1)) .build();
常规问题归总
1 url被转义
http://api.myapi.com/http%3A%2F%2Fapi.mysite.com%2Fuser%2Flist
请将@path改成@url
public interface APIService { @GET Call<Users> getUsers(@Url String url);}
或者:
public interface APIService { @GET("{fullUrl}") Call<Users> getUsers(@Path(value = "fullUrl", encoded = true) String fullUrl);}
2Method方法找不到
java.lang.IllegalArgumentException: Method must not be null
请指定具体请求类型@get @post等
public interface APIService { @GET Call<Users> getUsers(@Url String url);}
3Url编码不对,@fieldMap parameters must be use FormUrlEncoded
如果用fieldMap加上FormUrlEncoded编码
@POST()@FormUrlEncodedObservable<ResponseBody> executePost( @FieldMap Map<String, Object> maps);
上层需要转换将自己的map转换为FieldMap
@FieldMap(encoded = true) Map<String, Object> parameters,
4 paht和url一起使用
Using @Path and @Url paramers together with retrofit2
java.lang.IllegalArgumentException: @Path parameters may not be used with @Url. (parameter #4
如果你是这样的:
@GETCall<DataResponse> getOrder(@Url String url, @Path("id") int id);
请在你的url指定占位符.url:
www.myAPi.com/{Id}
总结
看了以上的知识点你发现Retrofit同样支持RxJava,通过以下设置Call适配模式.就可以完美关联RxJava。
retrofit .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
关于 Retrofit+ RxJava的案列, 结尾源码已经结合,以及实际遇到的坑可以看本人的系列文章(Retrofit+Rxjava使用技巧一文)。RxJava也是一款强大的多线程通讯利器,也支持本地线程安全,从以前编程习惯迁移到这种链式风格, 估计入门会让你头痛,但会让你在实际应用开发中无时无刻,随心所欲进行多线程响应式编程开发。一句话 :谁用谁知道!
- Retrofit 2.0 超能实践(一),完美支持加密Https传输
- Retrofit 2.0 超能实践(一),okHttp完美支持Https传输
- Retrofit 2.0 超能实践,完美支持Https传输
- Retrofit 2.0 超能实践,完美支持Https传输
- Retrofit 2.0 超能实践(三),轻松实现文件/多图片上传/Json字符串
- Retrofit 2.0 超能实践(四),完成大文件断点下载
- Retrofit 2.0 超能实践(三),轻松实现多文件/图片上传/Json字符串/表单
- Retrofit 2.0 超能实践(三),轻松实现多文件/图片上传/Json字符串/表单
- Retrofit 2.0 https的应用,以及实践
- retrofit支持https
- Retrofit支持https
- SSL传输加密HTTPS
- python https 加密传输
- HTTPS加密传输过程
- https加密传输详解
- Android https网络加密传输
- RxJava 与 Retrofit 完美结合实践
- RxJava 与 Retrofit 完美结合实践
- Android六个Mnanager(ActivityManager、ConnectivityManager、、Telep、PowerManager、WindowManager、WifiManager)
- 微信支付开发教程JAVA编[005]-签名算法
- 禁止百度转码
- B. Chris and Magic Square
- 编写一个交错合并列表元素的函数。例如:给定的两个列表为[a,B,C]和[1,2,3],函数返回[a,1,B,2,C,3]。
- Retrofit 2.0 超能实践(一),完美支持加密Https传输
- 使用cefSharp的坎坷经历
- java IO字符流和字节流
- 求最大公约数
- 前端开发记录
- &和&&的区别
- ubuntu安装tomcat
- 设计模式基础理解及整理实现
- 百度快照多久更新一次?