前言
作为Android开发者,你是不是也遇到过这样的场景:写个登录功能,复制粘贴了300行网络请求代码;改个接口参数,全项目搜"HttpURLConnection"改到崩溃;用户说"没网的时候APP卡死了",你对着满屏的try-catch陷入沉思...
其实,这些问题的根源都一样:没有一套靠谱的网络框架。
就像盖房子没搭脚手架,砌墙全靠手抓砖------不是不能盖,就是容易塌。今天咱们就来手把手搭一套"抗揍又好改"的Android网络框架,从基础组件到进阶技巧,保证让你的网络请求代码从"一锅粥"变成"精装房"。
先搞懂:网络框架到底要解决啥问题?
在开始敲代码前,咱们得先明确目标:一套好的网络框架,不是为了炫技,而是为了帮你少踩坑。它至少要搞定这几件事:
- 别重复造轮子:同一个项目里,登录、注册、获取列表的网络请求逻辑不该重复写
- 主线程不能卡:网络请求是耗时操作,必须扔到子线程,不然ANR警告分分钟找上门
- 数据解析不头疼:JSON字符串手动转对象?这种体力活就该交给框架
- 异常能优雅处理:没网、服务器500、数据格式错了,总不能直接抛个红框给用户看吧
- 缓存要聪明:用户地铁里没网,至少能看到上次加载的内容
- 扩展性要强:以后想加个"请求重试"、"Token自动刷新",不能改得翻天覆地
带着这些目标,咱们开始"搭骨架"。目前Android网络框架的"黄金组合"是:OkHttp(打工人)+ Retrofit(翻译官)+ 协程(调度员)+ Gson(翻译器)。这四个工具各司其职,配合起来堪称完美。
核心组件:认识这几个"打工人"
1. OkHttp:最靠谱的"快递小哥"
OkHttp是Square公司出的网络库,就像一个经验丰富的快递小哥:能帮你发快递(发起请求)、收快递(处理响应)、遇到堵车绕个路(自动重连)、还能攒一波快递一起送(连接池复用)。
基本用法:先添加依赖(现在都用AndroidX,直接用最新版)
gradle
dependencies {
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
}
然后就能发个简单的GET请求了:
kotlin
// 创建OkHttpClient实例(建议全局单例,不然浪费资源)
val okHttpClient = OkHttpClient.Builder().build()
// 创建请求
val request = Request.Builder()
.url("https://api.example.com/user") // 地址
.get() // 请求方法
.build()
// 发请求(注意:不能在主线程!)
okHttpClient.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// 失败了(没网、超时等)
}
override fun onResponse(call: Call, response: Response) {
// 成功了,response.body?.string()就是返回的字符串
}
})
但直接用OkHttp太"原始"了:每次请求都要写回调,解析数据还得自己转,所以咱们需要个"翻译官"------Retrofit。
2. Retrofit:OkHttp的"高级秘书"
Retrofit也是Square家的,它的作用是把HTTP接口"翻译"成Java/Kotlin接口,让你用调用函数的方式发请求,不用再手动拼URL、加参数。
添加依赖:
gradle
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// 配合Gson解析JSON
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
}
基本用法:三步搞定:
第一步:定义接口(告诉Retrofit"怎么发请求")
kotlin
interface ApiService {
// GET请求:获取用户信息,路径里带个userId参数
@GET("user/{userId}")
fun getUserInfo(@Path("userId") userId: String): Call<User>
// POST请求:登录,参数放请求体里
@POST("login")
fun login(@Body loginRequest: LoginRequest): Call<LoginResponse>
}
第二步:创建Retrofit实例(关联OkHttp和解析器)
kotlin
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/") // 基础地址,接口里的路径会拼在后面
.client(okHttpClient) // 关联OkHttp
.addConverterFactory(GsonConverterFactory.create()) // 用Gson解析JSON
.build()
// 创建接口实例
val apiService = retrofit.create(ApiService::class.java)
第三步:调用接口(发请求)
kotlin
// 调用登录接口
val loginCall = apiService.login(LoginRequest("username", "password"))
loginCall.enqueue(object : Callback<LoginResponse> {
override fun onFailure(call: Call<LoginResponse>, t: Throwable) {
// 失败处理
}
override fun onResponse(call: Call<LoginResponse>, response: Response<LoginResponse>) {
// 成功了,response.body()就是解析好的LoginResponse对象
}
})
是不是比直接用OkHttp清爽多了?但回调嵌套还是有点烦,这时候就需要"调度员"------协程登场了。
3. 协程:让异步代码像同步一样好写
如果用Retrofit的回调,多层请求嵌套会写成"回调地狱"(比如先登录,再用token获取用户信息,再获取用户订单...)。而协程能让异步代码线性执行,告别嵌套。
怎么用 :给Retrofit接口加个suspend关键字,就能在协程里直接调用:
kotlin
interface ApiService {
// 加个suspend,变成挂起函数
@POST("login")
suspend fun login(@Body loginRequest: LoginRequest): LoginResponse
}
// 在ViewModel里用协程调用
viewModelScope.launch {
try {
val loginResponse = apiService.login(LoginRequest("username", "password"))
// 登录成功,接着做其他事(比如存token)
val userInfo = apiService.getUserInfo(loginResponse.userId)
// 获取用户信息成功...
} catch (e: Exception) {
// 统一处理异常(网络错、解析错都在这)
}
}
一行try-catch搞定所有异常,代码从头到尾像同步一样写,这才是开发者该有的待遇!
框架封装:从"能用"到"好用"
光把库串起来还不够,咱们得封装一层,让它更符合项目需求。这部分是框架的"灵魂",直接决定了后续好不好维护。
1. 统一响应格式:别让服务器返回"惊喜"
几乎所有后端接口都会返回统一格式,比如:
json
{
"code": 200, // 状态码:200成功,其他失败
"message": "success", // 提示信息
"data": { ... } // 实际数据(成功时才有)
}
如果每个接口都手动判断code,会写吐的。咱们可以封装一个BaseResponse:
kotlin
// 泛型T表示data的类型
data class BaseResponse<T>(
val code: Int,
val message: String,
val data: T?
)
然后定义一个"成功判断规则",比如code == 200才算成功:
kotlin
// 密封类:用于包装请求结果(成功/失败)
sealed class Result<out T> {
data class Success<out T>(val data: T) : Result<T>()
data class Failure(val code: Int, val message: String) : Result<Nothing>()
data class Error(val exception: Exception) : Result<Nothing>()
}
// 扩展函数:把BaseResponse转成Result
suspend fun <T> ApiService.wrapRequest(request: suspend () -> BaseResponse<T>): Result<T> {
return try {
val response = request()
if (response.code == 200) {
// 成功:如果data为null,可能返回默认值(根据后端情况定)
Result.Success(response.data ?: throw NullPointerException("data is null"))
} else {
// 失败:比如code=401(未登录)、500(服务器错)
Result.Failure(response.code, response.message)
}
} catch (e: Exception) {
// 异常:网络错、解析错等
Result.Error(e)
}
}
这样用起来就爽了:
kotlin
// 接口定义时,返回BaseResponse
interface ApiService {
@POST("login")
suspend fun login(@Body request: LoginRequest): BaseResponse<LoginData>
}
// 调用时
viewModelScope.launch {
val result = apiService.wrapRequest {
apiService.login(LoginRequest("username", "password"))
}
when (result) {
is Result.Success -> {
// 成功,result.data就是LoginData
}
is Result.Failure -> {
// 后端返回的失败,比如"密码错误"
Toast.makeText(context, result.message, Toast.LENGTH_SHORT).show()
}
is Result.Error -> {
// 异常,比如没网
if (result.exception is IOException) {
Toast.makeText(context, "网络不给力", Toast.LENGTH_SHORT).show()
}
}
}
}
从此判断结果只需要一个when,再也不用到处写if (code == 200)了!
2. OkHttp拦截器:给请求"加戏"
拦截器(Interceptor)是OkHttp的"黑科技",能在请求发出去前、响应回来后做些额外操作。比如打印日志、加Token、处理缓存,都靠它。
(1)日志拦截器:调试时的"监控摄像头"
开发时想知道请求参数、响应数据?用HttpLoggingInterceptor:
kotlin
val loggingInterceptor = HttpLoggingInterceptor { message ->
Log.d("HttpLog", message) // 打印日志
}.apply {
level = HttpLoggingInterceptor.Level.BODY // 打印详细日志(包含请求体、响应体)
}
// 给OkHttp添加拦截器
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
这样Logcat里就会输出完整的请求响应信息,调试时再也不用猜"参数传对了没"。
(2)Token拦截器:自动给请求"戴帽子"
很多接口需要在Header里带Token(比如Authorization: Bearer xxxx),总不能每个接口都手动加吧?用拦截器自动加:
kotlin
class TokenInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
// 1. 获取原始请求
val originalRequest = chain.request()
// 2. 从本地获取Token(比如从SharedPreferences里读)
val token = SPUtils.getString("token", "")
// 3. 如果有Token,给请求加Header
val newRequest = if (token.isNotEmpty()) {
originalRequest.newBuilder()
.header("Authorization", "Bearer $token")
.build()
} else {
originalRequest // 没Token就用原始请求
}
// 4. 继续处理请求
return chain.proceed(newRequest)
}
}
// 添加到OkHttp
val okHttpClient = OkHttpClient.Builder()
.addInterceptor(TokenInterceptor())
.addInterceptor(loggingInterceptor) // 注意顺序:Token拦截器先加,日志才能打印出带Token的请求
.build()
更牛的是,还能处理Token过期:如果接口返回401(Token失效),自动刷新Token后重试请求。这个逻辑稍微复杂点,需要用"拦截器+协程"配合,核心思路是:
- 拦截到401响应
- 用刷新Token的接口获取新Token
- 用新Token重新构建请求,再次发起
- 把新Token存到本地
(代码太长,这里就不贴了,核心是用chain.proceed(newRequest)重试)
3. 缓存策略:没网也能"看历史"
用户没网的时候,能显示上次加载的数据,体验会好很多。OkHttp的缓存机制可以帮我们实现这个功能。
配置缓存:
kotlin
// 1. 指定缓存目录和大小(比如50MB)
val cacheDir = context.cacheDir // 应用缓存目录
val cache = Cache(cacheDir, 50 * 1024 * 1024L) // 50MB
// 2. 写个缓存拦截器,定义缓存规则
class CacheInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
// 如果没网,强制使用缓存(缓存有效期60秒,可自定义)
if (!NetworkUtils.isConnected(context)) {
request = request.newBuilder()
.cacheControl(CacheControl.FORCE_CACHE)
.build()
}
val response = chain.proceed(request)
// 如果有网,缓存有效期设为0(不缓存);没网则延长缓存时间
if (NetworkUtils.isConnected(context)) {
val maxAge = 0 // 0秒内不缓存,直接请求网络
response.newBuilder()
.header("Cache-Control", "public, max-age=$maxAge")
.removeHeader("Pragma") // 清除旧的缓存头
.build()
} else {
val maxStale = 60 * 60 * 24 // 没网时,缓存有效期24小时
response.newBuilder()
.header("Cache-Control", "public, only-if-cached, max-stale=$maxStale")
.removeHeader("Pragma")
.build()
}
return response
}
}
// 3. 给OkHttp添加缓存和拦截器
val okHttpClient = OkHttpClient.Builder()
.cache(cache)
.addInterceptor(CacheInterceptor()) // 应用拦截器:处理缓存逻辑
.addNetworkInterceptor(CacheInterceptor()) // 网络拦截器:配合缓存使用
.build()
这里要注意:addInterceptor(应用拦截器)和addNetworkInterceptor(网络拦截器)的区别:前者不管有没有网都会执行,后者只在有网络请求时执行。缓存逻辑需要两者配合。
4. 单例管理:别让实例"满天飞"
OkHttp、Retrofit这些实例创建成本很高,最好全局单例。可以用Dagger或Hilt(Google推荐的依赖注入库)来管理,这里用Hilt举个例子:
kotlin
// 1. 定义OkHttp单例
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
@Singleton
@Provides
fun provideOkHttpClient(context: Context): OkHttpClient {
return OkHttpClient.Builder()
.cache(Cache(context.cacheDir, 50 * 1024 * 1024L))
.addInterceptor(TokenInterceptor())
.addInterceptor(loggingInterceptor)
.addInterceptor(CacheInterceptor())
.addNetworkInterceptor(CacheInterceptor())
.connectTimeout(10, TimeUnit.SECONDS) // 连接超时
.readTimeout(10, TimeUnit.SECONDS) // 读取超时
.build()
}
// 2. 定义Retrofit单例
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(okHttpClient)
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(CoroutineCallAdapterFactory()) // 支持协程
.build()
}
// 3. 提供ApiService
@Singleton
@Provides
fun provideApiService(retrofit: Retrofit): ApiService {
return retrofit.create(ApiService::class.java)
}
}
然后在需要用的地方直接注入:
kotlin
class UserRepository @Inject constructor(
private val apiService: ApiService
) {
// 直接用apiService发请求
suspend fun login(username: String, password: String) = apiService.wrapRequest {
apiService.login(LoginRequest(username, password))
}
}
用Hilt管理后,再也不用手动new实例了,还能保证全局只有一个实例,内存更省。
进阶技巧:让框架更"抗揍"
1. 请求重试:失败了别轻易放弃
网络偶尔抽风很正常,给重要请求加个重试机制很有必要。可以用拦截器实现:
kotlin
class RetryInterceptor(private val maxRetry: Int = 3) : Interceptor {
private var retryCount = 0 // 已重试次数
override fun intercept(chain: Interceptor.Chain): Response {
val request = chain.request()
var response: Response? = null
try {
response = chain.proceed(request)
} catch (e: Exception) {
// 如果失败,且没到最大重试次数,就重试
while (retryCount < maxRetry) {
retryCount++
response = chain.proceed(request)
if (response.isSuccessful) { // 成功了就退出循环
break
}
}
}
return response ?: throw IOException("重试$maxRetry次后仍失败")
}
}
注意:不是所有请求都适合重试(比如POST提交订单,重试可能导致重复下单),所以最好在拦截器里加个判断,比如只重试GET请求。
2. 大文件下载:进度监听不能少
下载文件时,用户肯定想知道进度。OkHttp可以通过ResponseBody的source()方法监听进度:
kotlin
// 定义下载进度回调
interface DownloadListener {
fun onProgress(progress: Int) // 0-100
fun onSuccess(file: File)
fun onFailure(e: Exception)
}
// 下载接口
interface ApiService {
@GET
@Streaming // 流式下载(大文件必须加,不然会把整个文件读到内存)
suspend fun downloadFile(@Url url: String): ResponseBody
}
// 下载实现
suspend fun downloadFile(url: String, savePath: String, listener: DownloadListener) {
try {
val responseBody = apiService.downloadFile(url)
val totalLength = responseBody.contentLength() // 总大小
var currentLength = 0L // 已下载大小
val inputStream = responseBody.byteStream()
val file = File(savePath)
val outputStream = FileOutputStream(file)
val buffer = ByteArray(1024 * 8)
var len: Int
while (inputStream.read(buffer).also { len = it } != -1) {
outputStream.write(buffer, 0, len)
currentLength += len
// 计算进度并回调
val progress = (currentLength * 100 / totalLength).toInt()
listener.onProgress(progress)
}
outputStream.flush()
inputStream.close()
outputStream.close()
listener.onSuccess(file)
} catch (e: Exception) {
listener.onFailure(e)
}
}
关键是加@Streaming注解,不然大文件会直接塞满内存,导致OOM。
总结
到这里,咱们的网络框架就搭得差不多了。总结一下,它包含这些部分:
- 底层引擎:OkHttp负责实际的网络通信,处理连接、缓存、拦截器
- 接口封装:Retrofit把HTTP接口转成Kotlin接口,配合Gson自动解析JSON
- 线程调度:协程解决异步回调问题,让代码更清爽
- 统一处理:BaseResponse+Result封装响应,统一判断成功失败
- 功能增强:拦截器处理Token、日志、缓存;重试机制提高稳定性
- 依赖管理:Hilt管理单例,避免资源浪费
这套框架就像一个"网络请求管家":你只需要告诉它"我要登录,参数是xxx",它就会自动帮你发请求、处理异常、刷新Token、缓存数据,最后把整理好的结果给你。
最后说句大实话:框架不是越复杂越好,适合自己项目的才是最好的。
刚开始可以先搭个基础版,随着项目需求增加再慢慢加功能(比如加RxJava支持、加证书校验)。
毕竟,能解决问题的框架,才是好框架~
祝你从此告别"网络请求恐惧症",代码越写越顺!