网络 - Retrofit

一、概念

HttpClientAndroid 6中移除(API数量多扩展困难)。
HttpURLConnection目前官方集成的。
OKHttpSquare公司出品,底层通讯的实现。
RetrofitSquare公司出品,上层接口的封装(注解代替代码)更方便进行网络请求。

二、基本使用

Retrofit把网络请求的 URL 分成了两部分设置:创建Retrofit实例时通过 .baseUrl("...") 设置的 + 网络访问接口的函数注解 @GET("...") 设置的。

2.1 添加依赖

查看最新版本

implementation 'com.squareup.retrofit2:retrofit:2.9.0'    //会连带下载 OkHttp和Okio
implementation 'com.squareup.retrofit2:converter-gson:2.k6.1'    //会连带下载 GSON

2.2 构建Retrofit对象

val retrofit = Retrofit.Builder()
    //配置重复的根路径
    .baseUrl("https://www.baidu.com/")
    //指定解析数据使用的转换库(这里是Gson),使来自接口的json字符串解析成我们定义好的对应bean类
    .addConverterFactory(GsonConverterFactory.create())
    .build()

2.3 定义实体类

根据 JSON 内容,编写对应的实体类。

data class Person(    //类名后缀加不加Bean随意
    var name: String,
    var age: Int
)

2.4 定义网络访问接口

  • 根据 API 编写网络访问接口。Retrofit 将 Http 请求抽象成接口,并在接口里面采用注解来配置网络请求参数(每个形参都要注解),用动态代理将该接口“翻译”成一个 Http 请求再执行。
  • 命名通常以功能名称开头+Service结尾。返回值类型必须声明成 Retrofit 内置的 Call 类型,通过泛型指定服务器返回的具体数据类型(使用Call<ResponseBody>则返回没经过Gson转换的原始数据类即json字符串)(使用RxJava声明的是Observable<Person>类型)(使用协程可直接返回对象类型,见下文Android封装用法)。
函数注解说明

@GET

从服务器获取数据。
@POST向服务器提交数据。
@DELETE删除服务器上的数据。

@PUT

@PATCH

修改服务器上的数据。
@Headers添加固定请求头
形参注解说明
@Header动态添加请求头。
@Path替换路径。
@Query替代参数值(通常结合get请求)。
@Feild替换参数值(通常结合post请求)。
@FormUrlEncoded用于表单数据数据提交。

2.4.1 GET 示例

interface GetService {
    //接口1:https://www.baidu.com/person.json
    @GET("person.json")    //表示发起的是GET请求,传入请求的地址(相对路径,重复根路径在后面配置)
    fun getPerson(): Call<list<Person>>
    //接口2:https://www.baidu.com/<page>/person.json
    @GET("{page}/get_data.json")    //使用 {page} 占位
    fun getData(@Path("page") page: Int): Call<Data>    //使用 @Path("page")注解来声明对应参数
    //接口3:https://www.baidu.com/person.json?u=<user>&t=<token>
    @GET("person.json")
    fun getData(@Query("u") user: String, @Query("t") token: String): Call<Data>
    //接口4:https://api.caiyunapp.com/v2/place?query=北京&token={token}&lang=zh_CN
    @GET("v2/place?token=${GlobalApplication.TOKEN}&lang=zh_CN")    //不变的参数固定写在GET里
    fun searchPlaces(@Query("query") query: String): Call<PlaceResponse>
}

2.4.2 POST 示例

interface PostService {
    //接口6:https://www.baidu.com/data/create{"id": 1, "content": "The description for this data."}
    @POST("data/create")
    fun createData(@Body data: Data): Call<ResponseBody>    //将Data对象中的数据转换成JSON格式的文本,并放到HTTP请求的body部分
}

2.4.3 DELETE 示例

interface DeleteService {
    //接口5:https://www.baidu.com/data/<id>
    @DELETE("data/{id}")
    fun deleteData(@Path("id") id: String): Call<ResponseBody>    //该泛型表示能接受任意类型切不会进行解析
}

2.4.4 Headers 示例

interface PersonService {
    /*接口7:http://example.com/get_data.json
    User-Agent: okhttp    //header参数就是键值对
    Cache-Control: max-age=0
    */

    //静态声明(添加固定请求头)
    @Headers("User-Agent: okhttp", "Cache-Control: max-age=0")
    @GET("get_data.json")
    fun getData(): Call<Data>
    //动态声明(动态添加请求头)
    @GET("get_data.json")
    fun getData(@Header("User-Agent") userAgent: String, @Header("Cache-Control") cacheControl: String): Call<Data>
}

2.5 发起网络请求

//创建网络请求接口的实例
val personService = retrofit.create(PersonService::class.java)
//获取Call对象(对发送请求进行封装)
Call<list<Person>> personCall = personService.getPerson()
//发送网络请求(异步是.enqueue(),同步是.excute())
personCall.enqueue(object : Callback<List<person>> {    //接口回调
    override fun onResponse(call: Call<List<person>>, response: Response<List<person>>) {
        //对response做判断
        val list = response.body()    //得到解析后的对象
    }
    override fun onFailure(call: Call<List<person>>, t: Trouble) {
        t.printStackTrace()
    }
})

2.6 Android写法

2.6.1 Retrofit客户端

object RetrofitClient {
    //Cookie
    private val cookieJar by lazy {
        PersistentCookieJar(SetCookieCache(), SharedPrefsCookiePersistor(APP.context))
    }
    //OkHttpClient
    private val okHeepClient by lazy {
        OkHttpClient.Builder()
            .cookieJar(cookieJar)
            .build()
    }
    //Retrofit
    private val retrofit by lazy {
        Retrofit.Builder()
            .client(okHeepClient)
            .baseUrl(ApiService.BASE_URL)
            .addConverterFactory(GsonConverterFactory.create())
            .build()
    }
    //ApiService
    val apiService: ApiService by lazy {
        retrofit.create(ApiService::class.java)
    }
}

2.6.2 返回数据的基类

//返回数据的基类
data class ApiResponse<T>(
    val errorCode: Int,
    val errorMsg: String,
    val data: T?
) {
    fun getResult(): Result<T> {    //使用Result类包装返回结果(数据或异常)
        return if (errorCode == 0 && data != null) {
            Result.success(data)
        } else {
            Result.failure(ApiException(errorMsg))
        }
    }
}

//异常封装
class ApiException(
    errorMsg: String
) : Exception(errorMsg)

2.6.3 API接口

interface ApiService {
    companion object {
        const val BASE_URL = "https://www.wanandroid.com/"
    }
    //登录
    @FormUrlEncoded
    @POST("user/login")
    suspend fun login(
        @Field("username") userName: String,
        @Field("password") password: String
    ): ApiResponse<LoginBean>
}

2.6.4 协程使用

//数据源
//为了“静态”调用,该类无需其他功能就定义成 object 类,有的话使用伴生对象写联网方法
object LoginRemoteDataResource {
    suspend fun login(userName: String, password: String) =
        withContext(Dispatchers.IO) {
            RetrofitClient.apiService.login(userName, password).getResult()
        }
}

//仓库
interface IRepository {
    suspend fun login(userName: String, password: String): Result<LoginBean>
}
class Repository : IRepository {
    override suspend fun login(userName: String, password: String) =
        LoginRemoteDataResource.login(userName, password)
}

//ViewModel
class SplashViewModel : ViewModel() {
    private val repository: IRepository = Repository()
    private val _loginData = MutableLiveData<Result<LoginBean>>()
    val loginData =  _loginData as LiveData<Result<LoginBean>> //幕后属性,提供不可变版本供外部访问

    suspend fun login(userName: String, password: String){
        val result = repository.login(userName, password)
        _loginData.value = result
    }
}

//UI
viewModel.loginData.observe(this) { it ->    //对Result包装的返回结果是数据还是异常做判断
        it.onSuccess {
            activity.switchFragment(R.id.action_splashLoginFragment_to_mainActivity)
            activity.finish()
        }.onFailure {
            showToast(it.message.orEmpty())
        }
    }
}

三、解决 http 链接无法访问

Android 9开始默认只允许使用 HTTPS 类型的网络请求,HTTP明文传输因为有安全隐患不再支持。net:ERR_CLEARTEXT_NOT_PERMITTED

3.1 方式一

直接在 AndroidManifest 的 <application> 中添加如下代码:

android:usesCleartextTraffic="true"

3.2 方式二

右键res目录→New→Directory→创建一个xml目录,右键xml目录→New→File→创建一个network_security_config.xml文件,修改内容如下:

<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
    <domain-config cleartextTrafficPermitted="false">
        <domain includeSubdomains="true">www.baidu.com</domain>
    </domain-config>
</network-security-config>
Manifest {
    //添加网络访问权限
    <uses-permission android:name="android.permission.INTERNET" />
    //允许HTTP访问
    <application
        android:networkSecurityConfig="@xml/network_security_config"
    </application>
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值