Android第三方网络框架封装-Java
本文中的所有内容大部分来源于网络资料,如有侵权请联系本人修改或删除,请大家多多支持原创!非常感谢!
1. 概述 在Android开发中,网络请求是必不可少的一环。最近做了一个需求,类似于应用宝的功能,需要从服务器获取数据,然后展示到界面上。并下载apk文件,实现静默安装。本篇主要介绍自己是如何使用Rxjava、Retrofit、Okhttp等框架,实现网络请求和下载apk的功能。
1.1 OKHttp OkHttp是一个用于处理HTTP请求的开源Java库,由Square公司开发。对于网络框架,更多的人会想到volley,volley是Android系统自带的网络请求框架。但是volle在Android 5.0之后被Google抛弃了,所以OkHttp逐渐成为Android开发中首选的网络请求框架。
优点
支持HTTP/2,HTTPS(有效使用套接字)
连接池(在没有HTTTP/2的情况下,多个请求使用同一个TCP连接,减少请求延迟)
GZiP压缩 (缩小下载大小)
响应缓存 (减少重复的网络请求)
拦截器 (修改请求和响应)
从常见的连接问题中自动恢复
替代IP的DNS解析(在IPv4和IPv6的网络中)
支持现代TLS功能(TLS 1.3, ALPN, Certificate Compression 证书钩子)
支持同步和异步调用
使用
1 implementation("com.squareup.okhttp3:okhttp:3.6.0" )
当导入okhttp时,会自动导入一个高性能的I/O库okio ,以及koltin标准库 。
1.2 Retrofit2 Retrofit可以理解为okhttp的加强版,底层封装了OkHttp。Retrofit是一个RESTful的http网络请求框架的封装 。本质过程:App应用程序通过Retrofit请求网络,实质上是使用Retrofit接口层封装请求参数、Header、Url等信息,之后由okhttp来完成后续的请求工作。在服务端返回数据后,okhttp将原始数据交给Retrofit,Retrofit根据用户需求解析。
优点
超级解耦,接口定义、接口参数、接口回调不在耦合在一起
可以配置不同的httpClient来实现网络请求,如okhttp、httpClient等
支持同步、异步、RxJava
可以配置不同反序列化工具类来解析不同的数据,如json、xml等
请求速度快,使用方便灵活
使用
1 2 3 4 implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
1.2 RxJava RxJava在GitHub上《RxJava》的自我介绍是:a library for composing asynchronous and event-based programs using observable sequences for the Java VM.(一个在Java VM 上使用可观测的序列来组成异步的,基于事件的程序的库),有些人可能感到疑惑,其实本质上可以用一词来概括——“异步”,它就是一个异步的操作库,而别的定语都基于这之上的。
优点
异步和并发: RxJava 提供了简单而强大的异步编程模型。通过使用观察者和可观察者,你可以轻松地处理异步任务、事件和并发操作。
错误处理: RxJava提供了强大的错误处理机制,允许开发者在异步操作中更好地处理错误,而不是简单地嵌套回调中。
多线程支持: RxJava通过调度器(Schedulers)支持多线程操作,可以在不同的线程上执行任务,从而更好地管理并发。
响应式思维: RxJava通过引入响应式编程的思想,使得程序更容易理解、扩展和维护。它让开发者从“怎么做”(How to do)转变为“想要什么”(What to do)
组合和变换: RxJava提供了丰富的操作符,使得可以轻松组合、转换和过滤事件序列。这种组合性质使得处理复杂的业务逻辑变得更加简单。
使用
1 2 implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0' implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
RxJava最核心的两个东西Observable(被观察者、事件源)和Observer(观察者),Observable发出一系列的事件,Observer处理这些事件。在Observer接收到事件处理之前我们很方便地对结果做出各种拦截处理等。
2. 封装 这里只对4个文件进行阐述:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public interface ApiService { String SERVER_DEBUG = "http://192.168.0.241:8088/" ; @Streaming @GET("appmanager/android/download/{fileId}") Observable<ResponseBody> downloadApkFile (@Header("apiKey") String apikey, @Path("fileId") String fileId) ; @GET("appmanager/android/getApps") Observable<AppResponse> getAllApkInfo (@Header("apiKey") String apikey, @QueryMap Map<String, Object> map) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 public class ApiRetrofit { private static volatile ApiRetrofit instances; private static volatile OkHttpClient okHttpClient; private static volatile Retrofit retrofit; private static volatile ApiService API; private static final String TAG = ApiRetrofit.class.getSimpleName(); private static int TIME_OUT = 10 ; public static ApiRetrofit getInstance () { if (instances == null ) { synchronized (ApiRetrofit.class) { if (instances == null ) { instances = new ApiRetrofit (); } } } return instances; } private OkHttpClient initClient () { if (okHttpClient == null ) { synchronized (ApiRetrofit.class) { if (okHttpClient == null ) { HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor (message -> { try { LogUtil.e("initClient--->" , URLDecoder.decode(message, "utf-8" )); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } }); loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); okHttpClient = new OkHttpClient .Builder() .connectTimeout(TIME_OUT, TimeUnit.SECONDS) .readTimeout(TIME_OUT, TimeUnit.SECONDS) .writeTimeout(TIME_OUT, TimeUnit.SECONDS) .build(); } } } return okHttpClient; } private Retrofit initRetrofit () { if (retrofit == null ) { synchronized (ApiRetrofit.class) { if (retrofit == null ) { retrofit = new Retrofit .Builder() .client(initClient()) .baseUrl(ApiService.SERVER_DEBUG) .addConverterFactory(MGsonConverterFactory.create(new Gson ())) .addCallAdapterFactory(RxJava3CallAdapterFactory.create()) .build(); } } } return retrofit; } public void initAPI () { if (API == null ){ synchronized (ApiRetrofit.class) { if (API == null ) { API = initRetrofit().create(ApiService.class); } } } } public Observable<AppResponse> getAllApkInfo (Map<String, Object> map) { return API.getAllApkInfo(Constant.getApiKey(),map); } public Observable<ResponseBody> downloadApkFile (String fileId) { return API.downloadApkFile(Constant.getApiKey(),fileId); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 public class MGsonConverterFactory extends Converter .Factory { private static final String TAG = "ConverterFactory" ; private final Gson mGson; public static MGsonConverterFactory create () { return create(new Gson ()); } public static MGsonConverterFactory create (Gson gson) { return new MGsonConverterFactory (gson); } private MGsonConverterFactory (Gson mGson) { this .mGson = mGson; } @Override public Converter<ResponseBody, ?> responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { TypeAdapter<?> adapter = mGson.getAdapter(TypeToken.get(type)); LogUtil.d(TAG, "responseBodyConverter-->" + TypeToken.get(type).toString()); return new MGsonResponseBodyConverter <>(mGson, adapter); } @Override public Converter<?, RequestBody> requestBodyConverter(Type type, Annotation[] parameterAnnotations, Annotation[] methodAnnotations, Retrofit retrofit) { TypeAdapter<?> adapter = mGson.getAdapter(TypeToken.get(type)); LogUtil.d(TAG, "requestBodyConverter-->" + TypeToken.get(type).toString()); return (Converter<?, RequestBody>) new MGsonResponseBodyConverter <>(mGson, adapter); } } final class MGsonResponseBodyConverter <T> implements Converter <ResponseBody,T> { private static final String TAG = "Conver" ; private final Gson gson; private final TypeAdapter<T> adapter; MGsonResponseBodyConverter(Gson gson, TypeAdapter<T> adapter) { this .gson = gson; this .adapter = adapter; } @Override public T convert (ResponseBody value) throws IOException { String str = value.string(); LogUtil.d(TAG,"start ---> " + str); try { BaseResponse baseResponse = gson.fromJson(str,BaseResponse.class); String data = baseResponse.getData(); if (data != null && data.length() > 16 ) { String body = CiphertextUtils.INSTANCE.decryptAES128( data.substring(16 ), Constant.APP_KEY,data.substring(0 ,16 )); baseResponse.setData(body); str = baseResponse.toJson(); } }catch (Exception e){ LogUtil.e(TAG,"convert is failed" ); e.printStackTrace(); } finally { value.close(); } LogUtil.d(TAG,"convert end -->" +str); T result = adapter.fromJson(str); return result; } private class BaseResponse { private int code; public int getCode () { return code; } public void setCode (int code) { this .code = code; } private String msg; private String data; public String getMsg () { return msg; } public void setMsg (String msg) { this .msg = msg; } public String getData () { return data; } public void setData (String data) { this .data = data; } @Override public String toString () { return "BaseResponse{" + "code=" + code + ", msg='" + msg + '\'' + ", data='" + data + '\'' + '}' ; } public String toJson () { return "{\"code\":" + code + ", \"msg\":\"" + msg + '\"' + ", \"data\":" + data + '}' ; } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 public enum CiphertextUtils { INSTANCE; private CiphertextUtils () {} public String encryptAES (String plainText,String secretKey,String ivKey) { try { Cipher cipher = Cipher.getInstance(Constant.ALGORITHM); SecretKeySpec spec = new SecretKeySpec (secretKey.getBytes(StandardCharsets.UTF_8),"AES" ); IvParameterSpec ivParameterSpec = new IvParameterSpec (Constant.IV_KEY.getBytes(StandardCharsets.UTF_8)); cipher.init(Cipher.ENCRYPT_MODE,spec,ivParameterSpec); byte [] encryptedBytes = cipher.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); LogUtil.d("encryptAES" ,ivKey + Base64.encodeToString(encryptedBytes,Base64.DEFAULT)); return ivKey + Base64.encodeToString(encryptedBytes,Base64.DEFAULT); } catch (Exception e){ e.printStackTrace(); } return "" ; } public String decryptAES128 (String encryptedText, String secretKey,String ivKey) throws NoSuchPaddingException, NoSuchAlgorithmException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException, InvalidKeyException { byte [] encryptedBytes = Base64.decode(encryptedText,Base64.DEFAULT); Cipher cipher = Cipher.getInstance(Constant.ALGORITHM); SecretKeySpec SecretKey = new SecretKeySpec (secretKey.getBytes(StandardCharsets.UTF_8), "AES" ); IvParameterSpec ivParameterSpec = new IvParameterSpec (ivKey.getBytes(StandardCharsets.UTF_8)); cipher.init(Cipher.DECRYPT_MODE, SecretKey, ivParameterSpec); byte [] decryptedBytes = cipher.doFinal(encryptedBytes); return new String (decryptedBytes, StandardCharsets.UTF_8); } }
实体类的定义根据网络数据来定义即可。