Android Kotlin 使用 OkHttp3 上传拍照获取的 Bitmap 图片

发布时间: 2022-04-04 21:28:07 作者: 大象笔记

断断续续耗费了快一天时间,终于把 Android 拍照并使用 OkHttp3 上传图片的功能实现。 整体感受:

OkHttp 官方文档

https://square.github.io/okhttp/

只不过文档是基于 Java 的,如果要参考 Koltin 的使用方法,还是得 Google。

OkHttp 与 OkHttp 3 的关系

其实就类似 vue3 与 vue2 的关系,版本不同罢了。还是同一个项目。

只不过当前的最新版本是 OkHttp 3 罢了。不过诡异的是 okhttp 有自己的版本号,现在居然是 4 开头,不能理解。

调试过程中经常能收到之前版本的 OkHttp 代码,完全不能用,兼容性炸裂。

添加依赖

build.gradle 里新增:

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

修改后,点击 Sync。

网络权限

如果不申请网络权限,会报错:

2022-04-04 10:43:10.885 3570-3647/com.sunzhongwei.androidwheatcv E/AndroidRuntime: FATAL EXCEPTION: Thread-2
    Process: com.sunzhongwei.androidwheatcv, PID: 3570
    java.lang.SecurityException: Permission denied (missing INTERNET permission?)

在 AndroidManifest.xml 中 application 上方添加网络权限。

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

一个简单的 OkHttp 调用服务器接口实现

先写个简单 http 请求,建立一下信心。例如在一个按钮的点击事件中:

import okhttp3.OkHttpClient
import okhttp3.Request
import kotlin.concurrent.thread

thread {
	val client = OkHttpClient()
	val request = Request.Builder()
		.url("https://www.sunzhongwei.com/test")
		.build()
	val response = client.newCall(request).execute()
	val responseData = response.body?.string()
	if (responseData != null) {
		Log.d("tag1", responseData)
	}
}

加上 try catch 更安全一点。

确认后台 fastapi 接口 content type: application/octet-stream 还是 multipart/form-data

我服务端后台接收文件上传的接口是用 python 的 fastapi 框架写的。 从官方文档看,fastapi 接收文件的方式是 form data。

https://fastapi.tiangolo.com/tutorial/request-files/

uploaded files are sent as "form data"

Data from forms is normally encoded using the "media type" application/x-www-form-urlencoded when it doesn't include files. But when the form includes files, it is encoded as multipart/form-data.

类似的 HTML 前端代码为:

<form action="/files/" enctype="multipart/form-data" method="post">
	<input name="files" type="file" multiple>
	<input type="submit">
</form>

类似的,Spring Boot 默认也是支持 form data 上传文件

OkHttp 上传 Bitmap 图片到服务器

试了差不多一天,终于拼凑出一段可以用的代码。

thread {
	try {
		val client = OkHttpClient()
		val byteArrayOutputStream = ByteArrayOutputStream()
		bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)
		val byteArray = byteArrayOutputStream.toByteArray()
		val requestBody = MultipartBody.Builder()
			.setType(MultipartBody.FORM)
			.addFormDataPart("file", "image.png", byteArray.toRequestBody("multipart/form-data".toMediaTypeOrNull(), 0, byteArray.size))
			.build()
		val request = Request.Builder()
			.url("https://www.sunzhongwei.com/test")
			.post(requestBody)
			.build()
		val response = client.newCall(request).execute()
		val responseData = response.body?.string()
		Log.d("test1", "test2")
		if (responseData != null) {
			Log.d("test", responseData)
		}
	} catch (e: Exception) {
		Log.d("tag", e.toString())
	}
}

里面 byteArray.toRequestBody 那行感觉有问题,虽然能正常执行。 搞完这个 app 再回头看看,怎么改才合理。

什么是 bitmap

Bit即比特,是目前计算机系统里边数据的最小单位,8个bit即为一个Byte。一个bit的值,或者是0,或者是1;也就是说一个bit能存储的最多信息是2。Bitmap可以理解为通过一个bit数组来存储特定数据的一种数据结构;由于bit是数据的最小单位,所以这种数据结构往往是非常节省存储空间。

在 Android 系统中,可以通过 api 将图像的文件路径,或资源 ID,转换为 Bitmap 对象。然后通过 getWidth, getHeight 等函数获取图像的基本信息。

需要注意的是,为了防止内存问题,在加载为 Bitmap 之前,需要先判定图像的大小,然后根据大小进行适当的降采样。

参考代码

虽然不是 bitmap 的处理,但是也帮了大忙。

val file = File("/sdcard/image.png")
val fileBody = RequestBody.create(MediaType.parse("application/octet-stream"), file)
val requestBody = MultipartBody.Builder()
  .setType(MultipartBody.FORM)
  .addFormDataPart("uploadfile", "image.png", fileBody)
  .build()
val request = Request.Builder()
  .url(url)
  .post(requestBody)
  .build()

界面刷新

runOnUiThread { ttviewResponse.text = responseStr }

为何网上很多示例都是用 Retrofit 写的

Retrofit 为何更流行

但是,我感觉简单的单页 APP 用 OkHttp 加 GSON 已经足够方便了,暂时没有使用 Retrofit 的场景。

RequestBody' is deprecated

Kotlin Solution: Use the extension function content.toRequestBody(contentType); for the File type file.asRequestBody(contentType)

{"detail":[{"loc":["body","file"],"msg":"field required","type":"value_error.missing"}]}

后台 fastapi 报错,实际上是 content type 设置问题:

val requestBody = MultipartBody.Part.createFormData(
	"file", "test.png",
	byteArray.toRequestBody("image/*".toMediaTypeOrNull(), 0, byteArray.size)
).body

修改为:

val requestBody = MultipartBody.Part.createFormData(
	"file", "test.png",
	byteArray.toRequestBody("multipart/form-data".toMediaTypeOrNull(), 0, byteArray.size)
).body

{"detail":"There was an error parsing the body"}

实际上,上面那个也不对,改成最终的代码才正常执行。

java.net.UnknownServiceException: CLEARTEXT communication to 127.0.0.1 not permitted by network security policy

本想用本地开发环境的后台接口测试,发现报错,需要配置

Starting with Android 9 (API level 28), cleartext support is disabled by default.

参考

我是一名山东烟台的开发者,联系作者