使用Kotlin在Android中进行HTTP请求

110

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

我已经拥有了我的上一个项目的URL、服务器用户名和密码。


5
你尝试过使用Retrofit吗?你可以在这里找到Retrofit。 - EpicPandaForce
如果/当Android开始支持Java 11的新HTTP Client API,那也是一个很好的选择。 (目前,据我所知,Java 11的这一部分在Android上不受支持)。请参阅此帖子 - Mahozad
11个回答

88

对于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()

4
在Java Android中,一些Apache组件被标记为已弃用,建议使用Volley库进行HTTP请求。 - Thecave3
2
好的,我没有看到安卓标签,问题只在这里提到了 Kotlin。 - s1m0nw1
11
当然,它可以在Android以外的地方使用。Kotlin最初并不是针对Android开发的;-) - s1m0nw1
6
不要忘记在 AndroidManifest.xml 文件中添加 <uses-permission android:name="android.permission.INTERNET" /> 权限。 - Josh Correia
2
lines() 需要API>=24。 - Al Po
显示剩余3条评论

34

使用 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变量在哪里定义?此代码不完整。 - jungledev
2
BufferedReader 实现了 AutoClosable 接口,因此您不需要显式调用 it.close - kwmt

22

只使用标准库和最少的代码!

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

这会在一个新线程上开始GET请求,让UI线程来响应用户输入。然而,我们只能从主/UI线程修改UI元素,因此我们实际上需要一个runOnUiThread块来向用户显示结果。这样可以将我们的显示代码排队在UI线程上很快运行。

try/catch存在是为了防止您在手机没有联网时进行请求导致应用程序崩溃。您可以根据需要添加自己的错误处理(例如,显示Toast)。

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


为什么不按照下面描述的方式包装函数体,而不仅仅是发出(有用的)警告?<pre> var worker = object : AsyncTask<Void, Void, Void?>() { override fun doInBackground(params: Array<Void>): Void? { runner() return null } override fun onPostExecute(result: Void?) { super.onPostExecute(result) /对结果进行处理/ } } worker.execute() </pre> - Marco Ottina
2
嘿,Marco,我目前也在使用_AsyncTask_,但它将在Android 11中被弃用。而且我还没有尝试过其他方法,所以我不太舒服解释它们,我稍后会编辑我的答案。 - xjcl
2
我已经修改了我的回答,现在使用 thread 而不是 AsyncTask - xjcl

14
请查看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 -> }

他们的文档可以在这里找到。


11

或许最简单的GET请求

对于那些被其他解决方案困扰着NetworkOnMainThreadException的人来说:可以使用AsyncTask,甚至更短的(但仍处于实验阶段)协程:

launch {

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

}
如果您需要使用普通的http进行测试,请不要忘记在manifest中添加以下内容:android:usesCleartextTraffic="true" 对于实验性的协程,您需要在build.gradle中添加以下内容,截至2018年10月10日:
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版本中将取消实验性协程支持”。 - xjcl
看起来协程已经从实验性功能中移除了!此外,现在启动语法有点不同。 - xjcl
抱歉,我又来了。我的IDE告诉我“不适当的阻塞方法调用”针对readText--在协程中使用readText而不是线程可以吗? - xjcl

11

我认为使用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() + ".")
}

1
谢谢。这对我有用。但是RequestBody.created()现在已经过时了。解决方案是什么? - Abhijith Konnayil
你的导入是什么?我无法确定应该使用哪个“get”与我假设的okhttp MediaType相匹配... "$data"和"$token"是什么?还说“无法访问'body':在'Response'中它是私有的”,在“val responseBody = response.body!!.string()”中。 - Raksha
@Raksha导入com.fasterxml.jackson.databind.ObjectMapper 导入okhttp3.OkHttpClient 导入okhttp3.MediaType.Companion.toMediaType 导入okhttp3.Request 导入java.net.URL 导入okhttp3.RequestBody.Companion.toRequestBody你应该尝试使用更新的示例。 - fvildoso
未解决的引用:ObjectMapper 这是自定义类吗? - J A S K I E R
@Oleksandr,您不需要任何自定义类。在我的示例中,您需要使用OkHttp进行请求,并使用Jackson序列化JSON。我已经添加了这些依赖项。 - fvildoso

6
如果您正在使用 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()} - Abhijit Sarkar

4
你可以使用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"
   }
}

你可以在 文档 中找到更多细节。
要使用 gradle 获取它,请执行以下操作:
implementation 'io.github.rybalkinsd:kohttp:0.12.0'

这取决于您的需求。GET 方法被声明为 fun httpGet(client: Call.Factory = defaultHttpClient, init: HttpGetContext.() -> Unit): Response,因此它可以使用与您的项目需求和性能问题特定配置的调用工厂。 - Sergei Rybalkin

3

不需要添加额外的依赖,这个方法可行。 你不需要使用 Volley。这个方法是在Kotlin 1.3.10版本下测试通过的,时间为2018年12月。

如果要在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?) {
    }
}

3

使用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) 文件中。Jackson 是可选的,您可以将其用于对象序列化和反序列化。

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'

(在英语中,它是巴西,而不是你的个人资料中的巴西尔。) - Peter Mortensen
@JASKIER 你添加了依赖项吗? - Mateus Nascimento
@PeterMortensen 谢谢! - Mateus Nascimento

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接