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
// Retrofit库
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
//添加retrofit gson转换会自动下载gson
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个文件进行阐述:

  • ApiService
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/";
/**
* @param fileId 文件id
* @return
* @Streaming 是注解大文件的
*/
@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);


}

  • ApiRetrofit
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
/**
* @author zj970
* @Description api请求管理工具
* @create 2023-09-16 2:34 PM
*/
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; //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) {
//请求日志打印
//记得一定要把okhttpclient配置的日志拦截器去掉,否则所有数据流都会加载进内存
HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> {
try {
LogUtil.e("initClient--->", URLDecoder.decode(message, "utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
});
loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
//注释1:创建OkHttpClient
okHttpClient = new OkHttpClient.Builder()
.connectTimeout(TIME_OUT, TimeUnit.SECONDS)
//.addInterceptor(loggingInterceptor)//所有数据流都会加载进内存触发GC
.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) {
//注释2:创建Retrofit
retrofit = new Retrofit.Builder()
.client(initClient())
/// 设置基址
.baseUrl(ApiService.SERVER_DEBUG)//必填
// 适配rxjava,目的在于使用观察者模式,分解上层请求的过程,便于我们横加干预(比如请求嵌套)
.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);
}

}

  • 自定义的Gson工厂转换
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
/**
* @author zj970
* @Description 自定义gson转换工厂,需要对data进行加密解密
* @create 2023-09-16 6:23 PM
*/
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);
}
}

/**
* @author zj970
* @Description 自定义解析GsonResponseBoay
* @create 2023-09-16 6:23 PM
*/
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 +
'}';
}
}
}
  • AES 加密
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
/**
* @author zj970
* @Description 加密解密工具类
* @create 2023-09-16 5:24 PM
*/
public enum CiphertextUtils {
INSTANCE;

private CiphertextUtils(){}

/**
* AES 加密
* @return
*/
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 "";

}

/**
* AES 解密
* @param encryptedText
* @param secretKey
* @param ivKey
* @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");

// 创建 IV 对象
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);
}
}

   实体类的定义根据网络数据来定义即可。