Android中Kotlin用khttp发POST请求遇NetworkOnMainThreadException问题解决
解决Android中Kotlin POST请求的NetworkOnMainThreadException问题
咱们先把这个问题拆解开来看:
一、NetworkOnMainThreadException是什么?
这是Android系统强制的主线程规则导致的异常:Android不允许在*主线程(UI线程)*中执行网络请求这类耗时操作。因为主线程负责处理UI渲染和用户交互,如果被网络请求阻塞,会导致App卡顿、无响应,甚至触发ANR(Application Not Responding)错误。你的khttp post请求是同步执行的,直接在主线程调用就触发了这个限制。
二、针对khttp的修复方案:把请求移到后台线程
khttp本身是同步网络库,所以你需要把它放到后台线程执行,推荐用Kotlin协程(Android官方推荐的异步处理方式),示例代码如下:
// 假设代码在Activity/Fragment中,使用lifecycleScope绑定生命周期 lifecycleScope.launch(Dispatchers.IO) { // 先把EditText的内容转成String,避免直接传递Editable对象 val payload = mapOf( "review" to addReviewET.text.toString(), "rating" to quantity.toString(), "id_user" to "1", "id_movie" to id.toString() ) try { // 在IO线程执行网络请求 val r = post(localhost.insertReview(), data = payload) // 请求完成后切回主线程处理结果(比如更新UI) withContext(Dispatchers.Main) { println(r.text) // 这里可以添加成功提示、刷新页面等UI操作 } } catch (e: Exception) { // 处理网络异常,比如无网络、请求超时等 withContext(Dispatchers.Main) { e.printStackTrace() // 显示错误提示给用户 } } }
如果你的代码在ViewModel中,可以替换成viewModelScope,效果是一样的,都是自动管理协程生命周期,避免内存泄漏。
三、更适合Android的POST请求替代方案
虽然khttp能用,但Android生态中有更成熟、更适配的网络库,推荐以下两种:
1. Retrofit(官方推荐,最常用)
Retrofit是Square出品的网络库,配合Kotlin协程使用非常流畅,步骤如下:
第一步:添加依赖(在app模块的build.gradle中)
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
第二步:定义API接口和数据实体
// API接口,定义POST请求的路径和参数 interface MovieReviewApi { @POST("insertReview") // 替换成你的接口相对路径 suspend fun submitReview(@Body request: ReviewRequest): Response<ReviewResponse> } // 请求参数实体类,对应接口需要的字段 data class ReviewRequest( val review: String, val rating: String, val id_user: String, val id_movie: String ) // 响应实体类,根据你的API返回结构调整 data class ReviewResponse( val status: String, val message: String )
第三步:创建Retrofit实例并发起请求
// 在合适的地方初始化Retrofit(比如Application类或ViewModel) val retrofit = Retrofit.Builder() .baseUrl("http://你的服务器地址/") // 替换成你的baseUrl .addConverterFactory(GsonConverterFactory.create()) .build() val reviewApi = retrofit.create(MovieReviewApi::class.java) // 在协程中发起请求 lifecycleScope.launch(Dispatchers.IO) { val request = ReviewRequest( review = addReviewET.text.toString(), rating = quantity.toString(), id_user = "1", id_movie = id.toString() ) try { val response = reviewApi.submitReview(request) withContext(Dispatchers.Main) { if (response.isSuccessful) { println(response.body()?.message) // 处理成功逻辑,比如弹窗提示 } else { println("请求失败:${response.errorBody()?.string()}") } } } catch (e: Exception) { withContext(Dispatchers.Main) { e.printStackTrace() // 处理网络异常 } } }
2. OkHttp(Retrofit的底层依赖,可直接使用)
如果你不想用Retrofit的封装,也可以直接用OkHttp:
val okHttpClient = OkHttpClient() lifecycleScope.launch(Dispatchers.IO) { // 构造JSON请求体 val requestBody = JSONObject().apply { put("review", addReviewET.text.toString()) put("rating", quantity.toString()) put("id_user", "1") put("id_movie", id.toString()) }.toString() val request = Request.Builder() .url(localhost.insertReview()) .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), requestBody)) .build() try { val response = okHttpClient.newCall(request).execute() withContext(Dispatchers.Main) { println(response.body()?.string()) // 处理响应 } } catch (e: Exception) { withContext(Dispatchers.Main) { e.printStackTrace() } } }
总结
核心问题就是不能在主线程执行网络请求,不管用哪个库,都要把网络操作放到后台线程。推荐优先用Retrofit+协程,因为它的封装更规范,能减少重复代码,也更符合Android的最佳实践。
内容的提问来源于stack exchange,提问作者Qube
相关产品推荐
相关产品推荐

