返回

Android HTTPS 获取 JSON:协程与OkHttp实战

Android

Android 应用中通过 HTTPS 获取公开 JSON 数据的实践

在 Android 开发中,经常需要从网络 API 获取 JSON 数据。 使用 java.net.URL 来进行简单的 HTTP GET 请求是一种方法,但是它会引发一个常见的问题:NetworkOnMainThreadException。该异常提醒开发者,网络操作不应在主线程(UI 线程)上执行。 让我们探讨一下这个问题,以及几种有效的解决方法。

为什么直接使用 java.net.URL 会出现问题?

直接在主线程调用 URL().readText() 这类耗时操作会导致界面冻结,用户体验下降。Android 强制要求网络请求在后台线程中完成。 因此,问题的关键不在于能否使用 java.net.URL,而在于如何正确地管理线程。

提供代码示例中存在一个典型错误:在后台线程完成之前就尝试返回结果。 主线程调用 getJsonFromUrl 后,立即返回了一个空字符串 "", 而实际的数据获取还在后台进行, 这就是为何你会总是获取到空字符的原因。

解决方案:使用协程和 withContext

Kotlin 协程提供了一种优雅的方式来处理异步任务,如网络请求。它们避免了线程的复杂性,同时又易于阅读和理解。使用 kotlinx.coroutines 包的 withContext(Dispatchers.IO) 可以在后台线程中执行代码,并返回结果。

以下是如何修改 getJsonFromUrl 函数的代码:

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.net.URL

suspend fun getJsonFromUrl(myUrl: String): String {
    return withContext(Dispatchers.IO) {
        URL(myUrl).readText()
    }
}

这段代码将 URL(myUrl).readText() 放到 Dispatchers.IO 这个专门用于 I/O 操作的调度器下执行。 这保证了网络请求不会阻塞 UI 线程。 withContext 函数会暂停协程的执行,直到后台操作完成,并将结果返回。 函数签名中的 suspend 说明了这个函数只能在协程的作用域中被调用。

使用方法

  1. 引入协程库
    首先, 确保在你的 build.gradle(:app) 文件中添加以下依赖项:
     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")
    
  2. 在协程作用域中使用 :
    例如在 Activity 或者 Fragment 中,可以通过 lifecycleScope 创建一个协程。
import kotlinx.coroutines.launch
import androidx.lifecycle.lifecycleScope
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import androidx.activity.ComponentActivity
import androidx.core.view.LayoutInflaterCompat


 class MainActivity : ComponentActivity() {
  private lateinit var textView: TextView


   override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

          textView = findViewById(R.id.text_view)
          val apiUrl = "https://api.deezer.com/search/playlist?q=test"
        lifecycleScope.launch {
             try {
                val jsonResult = getJsonFromUrl(apiUrl)
                textView.text = jsonResult
             } catch (e: Exception) {
               textView.text = "Error: ${e.message}"
             }

           }
   }
 }

以上示例演示了如何在 Activity 中使用 lifecycleScope.launch 来调用 getJsonFromUrl 并展示结果。注意要使用 try/catch 来捕获网络请求可能抛出的异常。

方案:使用 OkHttp

虽然文中提到不想用 OkHttp, 仍在此提供 OkHttp 的简单用法, 仅供参考。OkHttp 是一个强大的 HTTP 客户端库,在 Android 开发中被广泛使用。 它拥有比 java.net.URL 更丰富的特性,比如连接池、拦截器等等。 它还可以使用协程更加方便处理网络请求。

添加 OkHttp 的依赖:

 implementation("com.squareup.okhttp3:okhttp:4.10.0")

OkHttp 的用法示例:

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import okhttp3.Request

suspend fun getJsonFromUrlOkHttp(myUrl: String): String {
   return withContext(Dispatchers.IO) {
       val client = OkHttpClient()
       val request = Request.Builder().url(myUrl).build()
       client.newCall(request).execute().use { response ->
            if (!response.isSuccessful) throw Exception("HTTP error, ${response.code}")
            response.body?.string() ?: ""
       }
   }
}

OkHttpjava.net.URL 有更多的灵活性。 你可以在构建 Request 对象时设置各种头部信息。

安全性建议

从公共 API 获取数据是常见的做法,但是应该考虑以下几个安全问题:

  • HTTPS : 始终使用 HTTPS 协议。确保数据传输过程中被加密,防止中间人攻击。

  • 异常处理 : 网络请求可能会因为各种原因失败。你需要加入恰当的 try/catch 块,以便可以妥善地处理错误,给用户友好的反馈,而避免程序崩溃。

  • API 速率限制 : 公共 API 往往有访问限制。你需要注意 API 文档中关于请求频率的规定。 过度频繁的访问可能会导致你的请求被服务器屏蔽。

  • 权限 : 确保你的 Android 应用正确请求 INTERNET 权限。

结论

在 Android 开发中,网络请求应该在后台线程中完成。协程 (Coroutine) 是一个有效的解决方案, 可以简化异步代码的编写。 OkHttp 是更加专业的网络请求库。 可以根据实际的需求,选择合适的方案来获取 JSON 数据。请始终注意数据安全以及做好相应的异常处理。