HttpClient | Android 6中移除(API数量多扩展困难)。 |
HttpURLConnection | 目前官方集成的。 |
OKHttp | Square公司出品,底层通讯的实现。 |
Retrofit | Square公司出品,上层接口的封装(注解代替代码)更方便进行网络请求。 |
Retrofit把网络请求的 URL 分成了两部分设置:创建Retrofit实例时通过 .baseUrl("...") 设置的 + 网络访问接口的函数注解 @GET("...") 设置的。
查看最新版本
implementation 'com.squareup.retrofit2:retrofit:2.9.0' //会连带下载 OkHttp和Okio
implementation 'com.squareup.retrofit2:converter-gson:2.k6.1' //会连带下载 GSON
val retrofit = Retrofit.Builder()
//配置重复的根路径
.baseUrl("https://www.baidu.com/")
//指定解析数据使用的转换库(这里是Gson),使来自接口的json字符串解析成我们定义好的对应bean类
.addConverterFactory(GsonConverterFactory.create())
.build()
根据 JSON 内容,编写对应的实体类。
data class Person( //类名后缀加不加Bean随意
var name: String,
var age: Int
)
根据 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>
}