带有 Kotlin 的 Android 中的 HTTP 请求

Posted

技术标签:

【中文标题】带有 Kotlin 的 Android 中的 HTTP 请求【英文标题】:HTTP Request in Android with Kotlin 【发布时间】:2018-02-20 23:03:06 【问题描述】:

我想使用 POST 方法进行登录验证,并使用 GET 方法获取一些信息。

我已经有了之前项目的 URL、服务器用户名和密码。

【问题讨论】:

你试过using Retrofit吗?你可以找到改造here 如果/当 android 开始支持 Java 11 的新 HTTP Client API 时,这也是一个不错的选择。 (目前,据我所知this part of Java 11 it is not supported on Android)。见this post。 【参考方案1】:

对于 Android,Volley 是一个很好的起点。对于所有平台,您可能还想查看ktor 客户端或http4k,它们都是很好的库。

不过,您也可以使用标准 Java 库,例如 java.net.HttpURLConnection 这是 Java SDK 的一部分:

fun sendGet() 
    val url = URL("http://www.google.com/")

    with(url.openConnection() as HttpURLConnection) 
        requestMethod = "GET"  // optional default is GET

        println("\nSent 'GET' request to URL : $url; Response Code : $responseCode")

        inputStream.bufferedReader().use 
            it.lines().forEach  line ->
                println(line)
            
        
    

或者更简单:

URL("https://google.com").readText()

【讨论】:

部分Apache组件在Java Android中被标记为弃用,HTTP请求最好使用Volley library 好吧,我没有看到android标签,问题在这里只提到了Kotlin 是的,它可以在 Android 之外使用。 Kotlin 最初从未针对 Android ;-) 别忘了将<uses-permission android:name="android.permission.INTERNET" />添加到AndroidManifest.xml lines() 需要 API>=24【参考方案2】:

使用 HttpURLConnection 发送带有参数的 HTTP POST/GET 请求:

POST 带参数:

fun sendPostRequest(userName:String, password:String) 

    var reqParam = URLEncoder.encode("username", "UTF-8") + "=" + URLEncoder.encode(userName, "UTF-8")
    reqParam += "&" + URLEncoder.encode("password", "UTF-8") + "=" + URLEncoder.encode(password, "UTF-8")
    val mURL = URL("<Your API Link>")

    with(mURL.openConnection() as HttpURLConnection) 
        // optional default is GET
        requestMethod = "POST"

        val wr = OutputStreamWriter(getOutputStream());
        wr.write(reqParam);
        wr.flush();

        println("URL : $url")
        println("Response Code : $responseCode")

        BufferedReader(InputStreamReader(inputStream)).use 
            val response = StringBuffer()

            var inputLine = it.readLine()
            while (inputLine != null) 
                response.append(inputLine)
                inputLine = it.readLine()
            
            println("Response : $response")
        
    

带参数的GET:

fun sendGetRequest(userName:String, password:String) 

        var reqParam = URLEncoder.encode("username", "UTF-8") + "=" + URLEncoder.encode(userName, "UTF-8")
        reqParam += "&" + URLEncoder.encode("password", "UTF-8") + "=" + URLEncoder.encode(password, "UTF-8")

        val mURL = URL("<Yout API Link>?"+reqParam)

        with(mURL.openConnection() as HttpURLConnection) 
            // optional default is GET
            requestMethod = "GET"

            println("URL : $url")
            println("Response Code : $responseCode")

            BufferedReader(InputStreamReader(inputStream)).use 
                val response = StringBuffer()

                var inputLine = it.readLine()
                while (inputLine != null) 
                    response.append(inputLine)
                    inputLine = it.readLine()
                
                it.close()
                println("Response : $response")
            
        
    

【讨论】:

urlinputStream 变量在哪里定义?此代码不完整。 BufferedReader 实现了 AutoClosable,所以不需要显式调用it.close【参考方案3】:

查看Fuel 库,一个示例GET 请求

"https://httpbin.org/get"
  .httpGet()
  .responseString  request, response, result ->
    when (result) 
      is Result.Failure -> 
        val ex = result.getException()
      
      is Result.Success -> 
        val data = result.get()
      
    
  

// You can also use Fuel.get("https://httpbin.org/get").responseString  ... 
// You can also use FuelManager.instance.get("...").responseString  ... 

POST 请求示例

Fuel.post("https://httpbin.org/post")
    .jsonBody(" \"foo\" : \"bar\" ")
    .also  println(it) 
    .response  result -> 

他们的文档可以在here找到 ​

【讨论】:

【参考方案4】:

也许是最简单的 GET

对于所有坚持使用 NetworkOnMainThreadException 的其他解决方案的人:使用 AsyncTask 或更短的(但仍处于试验阶段)协程:

launch 

    val jsonStr = URL("url").readText()



如果您需要使用纯 http 进行测试,请不要忘记添加到您的清单中: android:usesCleartextTraffic="true"


对于实验性协程,您必须从 2018 年 10 月 10 日起添加到 build.gradle:

kotlin 
    experimental 
        coroutines 'enable'
    

dependencies 
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:0.24.0"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.24.0"
    ...

【讨论】:

嗯,Android Studio 说“实验协程支持将在 1.4 中删除” 看起来协程已经从实验中移除了!现在启动语法也有点不同了。 对不起,我又来了。我的 IDE 告诉我 readText 的“不适当的阻塞方法调用”——可以将 readText 与协程而不是线程一起使用吗?【参考方案5】:

我认为使用 okhttp 是最简单的解决方案。在这里您可以看到一个 POST 方法的示例,发送一个 json,并使用身份验证。

val url = "https://example.com/endpoint"

val client = OkHttpClient()

val JSON = MediaType.get("application/json; charset=utf-8")
val body = RequestBody.create(JSON, "\"data\":\"$data\"")
val request = Request.Builder()
        .addHeader("Authorization", "Bearer $token")
        .url(url)
        .post(body)
        .build()

val  response = client . newCall (request).execute()

println(response.request())
println(response.body()!!.string())

记得把这个依赖添加到你的项目https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp

更新日期:2019 年 7 月 7 日 我将使用最新的 Kotlin (1.3.41)、OkHttp (4.0.0) 和 Jackson (2.9.9) 给出两个示例。

更新日期:2021 年 1 月 25 日 最新版本一切正常。

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.module/jackson-module-kotlin -->
        <dependency>
            <groupId>com.fasterxml.jackson.module</groupId>
            <artifactId>jackson-module-kotlin</artifactId>
            <version>2.12.1</version>
        </dependency>

<!-- https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp -->
        <dependency>
            <groupId>com.squareup.okhttp3</groupId>
            <artifactId>okhttp</artifactId>
            <version>4.9.0</version>
        </dependency>

获取方法

fun get() 
    val client = OkHttpClient()
    val url = URL("https://reqres.in/api/users?page=2")

    val request = Request.Builder()
            .url(url)
            .get()
            .build()

    val response = client.newCall(request).execute()

    val responseBody = response.body!!.string()

    //Response
    println("Response Body: " + responseBody)

    //we could use jackson if we got a JSON
    val mapperAll = ObjectMapper()
    val objData = mapperAll.readTree(responseBody)

    objData.get("data").forEachIndexed  index, jsonNode ->
        println("$index $jsonNode")
    

POST方法

fun post() 
    val client = OkHttpClient()
    val url = URL("https://reqres.in/api/users")

    //just a string
    var jsonString = "\"name\": \"Rolando\", \"job\": \"Fakeador\""

    //or using jackson
    val mapperAll = ObjectMapper()
    val jacksonObj = mapperAll.createObjectNode()
    jacksonObj.put("name", "Rolando")
    jacksonObj.put("job", "Fakeador")
    val jacksonString = jacksonObj.toString()

    val mediaType = "application/json; charset=utf-8".toMediaType()
    val body = jacksonString.toRequestBody(mediaType)

    val request = Request.Builder()
            .url(url)
            .post(body)
            .build()

    val response = client.newCall(request).execute()

    val responseBody = response.body!!.string()

    //Response
    println("Response Body: " + responseBody)

    //we could use jackson if we got a JSON
    val objData = mapperAll.readTree(responseBody)

    println("My name is " + objData.get("name").textValue() + ", and I'm a " + objData.get("job").textValue() + ".")

【讨论】:

谢谢。这对我有用。但 RequestBody.created() 现在已弃用。解决办法是什么? 你进口的是什么?我无法弄清楚它应该是哪个“get”与我假设的 okhttp MediaType 相匹配......“$data”和“$token”是什么?在“val responseBody = response.body!!.string()”中还说“无法访问'body':它在'Response'中是私有的” @Raksha 导入 com.fasterxml.jackson.databind.ObjectMapper 导入 o​​khttp3.MediaType.Companion.toMediaType 导入 o​​khttp3.OkHttpClient 导入 o​​khttp3.Request 导入 java.net.URL 导入 o​​khttp3.RequestBody.Companion.toRequestBody您应该尝试使用更新的示例。 未解析的参考:ObjectMapper 是自定义类吗? @Oleksandr 你不需要任何自定义类。在我的示例中,您必须使用 OkHttp 来执行请求,并使用 Jackson 来序列化 JSON。我已经添加了依赖项。【参考方案6】:

仅使用标准库,代码最少!

thread 
    val json = try  URL(url).readText()  catch (e: Exception)  return@thread 
    runOnUiThread  displayOrWhatever(json) 

在新线程上启动 GET 请求,让 UI 线程响应用户输入。但是,我们只能从 main/UI 线程修改 UI 元素,所以我们实际上需要一个 runOnUiThread 块来向我们的用户显示结果。这会将我们的显示代码排入队列,以便很快在 UI 线程上运行。

try/catch 在那里,因此如果您在手机互联网关闭的情况下发出请求,您的应用就不会崩溃。随意添加您自己的错误处理(例如显示 Toast)。

.readText() 不是 java.net.URL 类的一部分,而是 Kotlin extension method,Kotlin 将此方法“粘合”到 URL 上。这对于普通的 GET 请求已经足够了,但是对于更多的控制和 POST 请求,您需要像 Fuel 库这样的东西。

【讨论】:

你为什么不按照下面的描述包装函数体而不是只发出(有用的)警告?
 var worker = object : AsyncTask()  override fun doInBackground(params: Array): Void?  runner() return null  override fun onPostExecute(result: Void?)  super.onPostExecute(result) /*do something with result*/   worker.execute() 
嗨,Marco,我目前也在使用 AsyncTask,但它会在 Android 11 中被弃用。而且我还没有尝试过其他方法,所以我还不愿意解释它们,我稍后会编辑我的答案。 我已将答案编辑为现在使用 thread 而不是 AsyncTask【参考方案7】:

如果你使用Kotlin,你最好让你的代码尽可能简洁。 run 方法将接收者变成this 并返回块的值。 this as HttpURLConnection 创建一个智能演员表。 bufferedReader().readText() 避免了一堆样板代码。

return URL(url).run 
        openConnection().run 
            this as HttpURLConnection
            inputStream.bufferedReader().readText()
        

你也可以把它包装成一个扩展函数。

fun URL.getText(): String 
    return openConnection().run 
                this as HttpURLConnection
                inputStream.bufferedReader().readText()
            

然后这样称呼它

return URL(url).getText()

最后,如果你超级懒惰,你可以扩展 String 类。

fun String.getUrlText(): String 
    return URL(this).run 
            openConnection().run 
                this as HttpURLConnection
                inputStream.bufferedReader().readText()
            
    

然后这样称呼它

return "http://somewhere.com".getUrlText()

【讨论】:

别忘了关闭InputStream,比如inputStream.bufferedReader().use readText()【参考方案8】:

您可以使用kohttp 库。它是一个 Kotlin DSL HTTP 客户端。它支持 square.okhttp 的特性,并为它们提供清晰的 DSL。 KoHttp 异步调用由协程提供支持。

httpGet扩展功能

val response: Response = "https://google.com/search?q=iphone".httpGet()

你也可以在协程中使用异步调用

val response: Deferred<Response> = "https://google.com/search?q=iphone".asyncHttpGet()

或用于更复杂请求的 DSL 函数

val response: Response = httpGet 
    host = "google.com"
    path = "/search"
    param 
       "q" to "iphone"
       "safe" to "off"
   

您可以在docs找到更多详细信息

使用 gradle 获取它

implementation 'io.github.rybalkinsd:kohttp:0.12.0'

【讨论】:

这取决于您的需求。 GET 方法被声明为fun httpGet(client: Call.Factory = defaultHttpClient, init: HttpGetContext.() -&gt; Unit): Response,因此它可以使用具有特定于您的项目需求和性能问题的配置的调用工厂。【参考方案9】:

无需添加额外的依赖项,就可以了。你不需要 Volley。这适用于截至 2018 年 12 月的当前版本的 Kotlin:Kotlin 1.3.10

如果使用 Android Studio,您需要在 AndroidManifest.xml 中添加此声明:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

您应该在此处手动声明导入。自动导入工具给我带来了很多冲突。:

import android.os.AsyncTask
import java.io.BufferedReader
import java.io.InputStreamReader
import java.io.OutputStream
import java.io.OutputStreamWriter
import java.net.URL
import java.net.URLEncoder
import javax.net.ssl.HttpsURLConnection

您不能在后台线程上执行网络请求。你必须继承AsyncTask

调用方法:

NetworkTask().execute(requestURL, queryString)

声明:

private class NetworkTask : AsyncTask<String, Int, Long>() 
    override fun doInBackground(vararg parts: String): Long? 
        val requestURL = parts.first()
        val queryString = parts.last()

        // Set up request
        val connection: HttpsURLConnection = URL(requestURL).openConnection() as HttpsURLConnection
        // Default is GET so you must override this for post
        connection.requestMethod = "POST"
        // To send a post body, output must be true
        connection.doOutput = true
        // Create the stream
        val outputStream: OutputStream = connection.outputStream
        // Create a writer container to pass the output over the stream
        val outputWriter = OutputStreamWriter(outputStream)
        // Add the string to the writer container
        outputWriter.write(queryString)
        // Send the data
        outputWriter.flush()

        // Create an input stream to read the response
        val inputStream = BufferedReader(InputStreamReader(connection.inputStream)).use 
            // Container for input stream data
            val response = StringBuffer()
            var inputLine = it.readLine()
            // Add each line to the response container
            while (inputLine != null) 
                response.append(inputLine)
                inputLine = it.readLine()
            
            it.close()
            // TODO: Add main thread callback to parse response
            println(">>>> Response: $response")
        
        connection.disconnect()

        return 0
    

    protected fun onProgressUpdate(vararg progress: Int) 
    

    override fun onPostExecute(result: Long?) 
    

【讨论】:

【参考方案10】:

使用 OkHttp 的 GET 和 POST

private const val CONNECT_TIMEOUT = 15L
private const val READ_TIMEOUT = 15L
private const val WRITE_TIMEOUT = 15L

private fun performPostOperation(urlString: String, jsonString: String, token: String): String? 
    return try 
        val client = OkHttpClient.Builder()
            .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
            .build()

        val body = jsonString.toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())

        val request = Request.Builder()
            .url(URL(urlString))
            .header("Authorization", token)
            .post(body)
            .build()

        val response = client.newCall(request).execute()
        response.body?.string()
    
    catch (e: IOException) 
        e.printStackTrace()
        null
    


private fun performGetOperation(urlString: String, token: String): String? 
    return try 
        val client = OkHttpClient.Builder()
            .connectTimeout(CONNECT_TIMEOUT, TimeUnit.SECONDS)
            .writeTimeout(WRITE_TIMEOUT, TimeUnit.SECONDS)
            .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
            .build()

        val request = Request.Builder()
            .url(URL(urlString))
            .header("Authorization", token)
            .get()
            .build()

        val response = client.newCall(request).execute()
        response.body?.string()
    
    catch (e: IOException) 
        e.printStackTrace()
        null
    

对象序列化和反序列化

@Throws(JsonProcessingException::class)
fun objectToJson(obj: Any): String 
    return ObjectMapper().writeValueAsString(obj)


@Throws(IOException::class)
fun jsonToAgentObject(json: String?): MyObject? 
    return if (json == null)  null  else 
        ObjectMapper().readValue<MyObject>(json, MyObject::class.java)
    

依赖关系

将以下行放入您的 gradle (app) 文件中。杰克逊是可选的。您可以将其用于对象序列化和反序列化。

implementation 'com.squareup.okhttp3:okhttp:4.3.1'
implementation 'com.fasterxml.jackson.core:jackson-core:2.9.8'
implementation 'com.fasterxml.jackson.core:jackson-annotations:2.9.8'
implementation 'com.fasterxml.jackson.core:jackson-databind:2.9.8'

【讨论】:

(英文是 Brazil,而不是 Brasil(您的个人资料)。) @JASKIER 你添加依赖了吗? @PeterMortensen 谢谢!

以上是关于带有 Kotlin 的 Android 中的 HTTP 请求的主要内容,如果未能解决你的问题,请参考以下文章

Xamarin 是不是支持 Xamarin Android 绑定库中的 Kotlin 协程?

无法在带有 Kotlin 的 Android 中使用 Autodispose

带有 Java 和 Kotlin 文件、kapt 或 annotationProcessor 的 Android 项目?

Android kotlin静态属性静态方法

带有 Kotlin 的 Android - 将数据传回之前的 Activity

DiffUtil 不刷新观察者调用android kotlin中的视图