本文使用登錄場景來簡單介紹 Android 應(yīng)用中使用 OkHttp 訪問網(wǎng)絡(luò)的用法鸟妙。
- 數(shù)據(jù)交換協(xié)議 HTTP
- 數(shù)據(jù)交換格式 JSON
- HTTP 請求方法 POST
訪問網(wǎng)絡(luò)的準(zhǔn)備工作
聲明使用網(wǎng)絡(luò)訪問權(quán)限
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
啟用明文通信
AndroidManifest.xml
<application
...
android:usesCleartextTraffic="true">
...
</application>
出于安全性的考慮勃痴,在 Android 9 (API 級別 28)及以上版本的系統(tǒng)上,默認(rèn)禁止應(yīng)用明文通信,即禁止使用 HTTP 交換數(shù)據(jù)嫡纠,需要使用 HTTPS 闰歪。
如果沒有啟用明文通信肚逸,應(yīng)用在使用 HTTP 訪問網(wǎng)絡(luò)時會引發(fā)異常。
HTTP FAILED : java.net.UnknownServiceException : CLEARTEXT communication to 192.168.43.218 not permitted by network security policy
譯:網(wǎng)絡(luò)安全策略不允許和 192.168.43.218 進行明文通信
關(guān)于 Android 9 中默認(rèn)禁用 HTTP 通信的詳細(xì)信息聘萨,可以參閱行為變更:以 API 級別 28 及更高級別為目標(biāo)的應(yīng)用一文的 框架安全性變更 部分竹椒。
使用 OkHttp 訪問網(wǎng)絡(luò)
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. 創(chuàng)建 OkHttpClient 。添加 Http 日志攔截器米辐,以便調(diào)試胸完。
OkHttpClient client = new OkHttpClient.Builder()
.addInterceptor(new HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
.build();
3. 將請求參數(shù)轉(zhuǎn)換為 JSON 字符串,創(chuàng)建 Request翘贮。
- 將請求參數(shù)轉(zhuǎn)換為 JSON 格式的字符串
JsonObject requestParamJsonObject = new JsonObject();
requestParamJsonObject.addProperty("userName", userName);
requestParamJsonObject.addProperty("password", password);
String requestParam = requestParamJsonObject.toString();
- 創(chuàng)建 RequestBody赊窥,并將其作為請求體創(chuàng)建 Request
Request 用來描述一個請求的相關(guān)信息,包括 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. 創(chuàng)建 Call芍耘。
然后可以使用同步execute()
或異步enqueue(Callback)
方式執(zhí)行請求址遇。本文中使用的是異步方式。
Call call = client.newCall(request);
由于這一步比較簡單斋竞,通常的寫法是和下一步連起來倔约,將 Call 的實例作為匿名對象來使用。
client.newCall(request).enqueue(new Callback(){...});
5. 將 Call 加入隊列坝初,創(chuàng)建 Callback 用來處理網(wǎng)絡(luò)訪問的結(jié)果浸剩。
- 將 Call 加入隊列,使用 Callback 實例作為 enqueue() 方法的參數(shù)
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 {
}
});
- 為了方便對響應(yīng)數(shù)據(jù)進行預(yù)處理鳄袍,通常會定義統(tǒng)一的接口響應(yīng)數(shù)據(jù)格式绢要。
響應(yīng)數(shù)據(jù)示例
// 登錄失敗的接口響應(yīng)數(shù)據(jù)
{
"code": 701,
"message": "密碼錯誤",
"data": ""
}
// 登錄成功的接口響應(yīng)數(shù)據(jù)
{
"code": 600,
"message": "登錄成功",
"data": "token"
}
我們可以定義一個 Result 類型來描述響應(yīng)數(shù)據(jù)。
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() 中將響應(yīng)體的 JSON 字符串轉(zhuǎn)換為期望的類型拗小。
@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 的回調(diào)方法中切換到主線程重罪,進行后續(xù)處理。
比較常用的線程切換方案如下。
- Handler
- LiveData
- Retrofit
后兩種的實現(xiàn)還是使用了 Handler 蛆封,由于進行了封裝唇礁,所以用起來相對簡單。本文 采用 LiveData 方案進行線程切換惨篱。
使用 LiveData 切換線程
1. 引入依賴
dependencies {
...
implementation "androidx.lifecycle:lifecycle-livedata:2.5.1"
}
2. 定義 MutableLiveData 變量
定義 loginResult 變量盏筐,用于描述登錄結(jié)果。
private final MutableLiveData<Result<String>> loginResult = new MutableLiveData<>();
3. 為 MutableLiveData 變量添加觀察者
調(diào)用 loginResult 的 observe() 方法砸讳,添加觀察者琢融。當(dāng) loginResult 的值改變后,此觀察者會在主線程進行后續(xù)處理簿寂。
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. 在任務(wù)線程中調(diào)用 postValue() 方法漾抬,更改該變量的值
在 Callback 的 onResponse() 方法中調(diào)用 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);
}
附
測試設(shè)備參數(shù)
- 測試設(shè)備1:
- 型號:Mi 10 Lite Zoom
- 操作系統(tǒng):MIUI 12.0.6 穩(wěn)定版 (Android 10)
- 測試設(shè)備2:
- 型號:vivo Y66L
- 操作系統(tǒng):Funtouch OS 3.0(Android 6.0.1)