本文使用登录场景来简单介绍 Android 应用中使用 OkHttp 访问网络的用法。
- 数据交换协议 HTTP
- 数据交换格式 JSON
- HTTP 请求方法 POST
访问网络的准备工作
声明使用网络访问权限
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
启用明文通信
AndroidManifest.xml
<application
...
android:usesCleartextTraffic="true">
...
</application>
出于安全性的考虑,在 Android 9 (API 级别 28)及以上版本的系统上,默认禁止应用明文通信,即禁止使用 HTTP 交换数据,需要使用 HTTPS 。
如果没有启用明文通信,应用在使用 HTTP 访问网络时会引发异常。
HTTP FAILED : java.net.UnknownServiceException : CLEARTEXT communication to 192.168.43.218 not permitted by network security policy
译:网络安全策略不允许和 192.168.43.218 进行明文通信
关于 Android 9 中默认禁用 HTTP 通信的详细信息,可以参阅行为变更:以 API 级别 28 及更高级别为目标的应用一文的 框架安全性变更 部分。
使用 OkHttp 访问网络
1.引入依赖
build.gradle(:app)
dependencies {
...
implementation 'com.squareup.okhttp3:okhttp:4.10.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.10.0'
implementation 'com.google.code.gson:gson:2.10.1'
}
2. 创建 OkHttpClient 。添加 Http 日志拦截器,以便调试。
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.build();
3. 将请求参数转换为 JSON 字符串,创建 Request。
- 将请求参数转换为 JSON 格式的字符串
JsonObject requestParamJsonObject = new JsonObject();
requestParamJsonObject.addProperty("userName", userName);
requestParamJsonObject.addProperty("password", password);
String requestParam = requestParamJsonObject.toString();
- 创建 RequestBody,并将其作为请求体创建 Request
Request 用来描述一个请求的相关信息,包括 url,请求方法Post/Get, 请求头,请求体等信息。
MediaType mediaTypeJson = MediaType.parse("application/json; charset=utf-8");
Request request = new Request.Builder()
.url("http://192.168.43.218:8080/user/login")
.post(RequestBody.create(requestParam, mediaTypeJson))
.build();
4. 创建 Call。
然后可以使用同步execute()
或异步enqueue(Callback)
方式执行请求。本文中使用的是异步方式。
Call call = client.newCall(request);
由于这一步比较简单,通常的写法是和下一步连起来,将 Call 的实例作为匿名对象来使用。
client.newCall(request).enqueue(new Callback(){...});
5. 将 Call 加入队列,创建 Callback 用来处理网络访问的结果。
- 将 Call 加入队列,使用 Callback 实例作为 enqueue() 方法的参数
call.enqueue(new Callback() {
@Override
public void onFailure(@NonNull Call call, @NonNull IOException e) {
}
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
}
});
- 为了方便对响应数据进行预处理,通常会定义统一的接口响应数据格式。
响应数据示例
// 登录失败的接口响应数据
{
"code": 701,
"message": "密码错误",
"data": ""
}
// 登录成功的接口响应数据
{
"code": 600,
"message": "登录成功",
"data": "token"
}
我们可以定义一个 Result 类型来描述响应数据。
public class Result<DATA> {
private static final int HTTP_REQUEST_SUCCESS_CODE = 600;
public int code;
public String message;
public DATA data;
public boolean isSuccessful() {
return code == HTTP_REQUEST_SUCCESS_CODE;
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", message='" + message + '\'' +
", data=" + data +
'}';
}
}
- 在 Callback 的 onResponse() 中将响应体的 JSON 字符串转换为期望的类型。
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
ResponseBody body = response.body();
if (body == null) {
onFailure(call, new IOException("body is null"));
return;
}
Gson gson = new Gson();
Result<String> result = gson.fromJson(body.string(), new TypeToken<Result<String>>() {
}.getType());
}
6. 在 Callback 的回调方法中切换到主线程,进行后续处理。
比较常用的线程切换方案如下。
- Handler
- LiveData
- Retrofit
后两种的实现还是使用了 Handler ,由于进行了封装,所以用起来相对简单。本文 采用 LiveData 方案进行线程切换。
使用 LiveData 切换线程
1. 引入依赖
dependencies {
...
implementation "androidx.lifecycle:lifecycle-livedata:2.5.1"
}
2. 定义 MutableLiveData 变量
定义 loginResult 变量,用于描述登录结果。
private final MutableLiveData<Result<String>> loginResult = new MutableLiveData<>();
3. 为 MutableLiveData 变量添加观察者
调用 loginResult 的 observe() 方法,添加观察者。当 loginResult 的值改变后,此观察者会在主线程进行后续处理。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
subscribeToLiveData();
}
private void subscribeToLiveData() {
loginResult.observe(this, loginResult -> {
Toast.makeText(LoginActivity.this, loginResult.message, Toast.LENGTH_SHORT).show();
if (loginResult.isSuccessful()) {
handleLoginResultSuccess(loginResult);
}
});
}
private void handleLoginResultSuccess(Result<String> loginResult) {
Log.i(TAG, "token : " + loginResult.data);
// save token
// start activity
}
4. 在任务线程中调用 postValue() 方法,更改该变量的值
在 Callback 的 onResponse() 方法中调用 loginResult 的 postValue() 方法。
@Override
public void onResponse(@NonNull Call call, @NonNull Response response) throws IOException {
ResponseBody body = response.body();
if (body == null) {
onFailure(call, new IOException("body is null"));
return;
}
Gson gson = new Gson();
Result<String> result = gson.fromJson(body.string(), new TypeToken<Result<String>>() {
}.getType());
loginResult.postValue(result);
}
附
测试设备参数
- 测试设备1:
- 型号:Mi 10 Lite Zoom
- 操作系统:MIUI 12.0.6 稳定版 (Android 10)
- 测试设备2:
- 型号:vivo Y66L
- 操作系统:Funtouch OS 3.0(Android 6.0.1)
网友评论