断断续续耗费了快一天时间,终于把 Android 拍照并使用 OkHttp3 上传图片的功能实现。 整体感受:
- Kotlin 相关的资料还是少,即便是英文的资料也不足,特别是三方库。大部分都是 Java 的代码
- Android 这些类库相关接口废弃得过于频繁,就算找到了示例代码,经常是已经废弃的接口。对新手很不友好
- 无聊的概念太多,必须沉住气,要不很容易掀桌子
- 家里有孩子需要看,不可能一直坐在电脑边,身边有本纸质参考书翻翻,即便是过时的实现方法,还是有启发的,也可以了解基础知识
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 为何更流行
- 实际上 Retrofit 是基于 OkHttp 开发的,并内置了 GSON
- 发起请求时,Retrofit 会自动开启子线程,在 Callback 里又会自动切换回主线程。省去了手动切换线程的麻烦。
但是,我感觉简单的单页 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.
参考
- https://handyopinion.com/upload-file-to-server-in-android-kotlin/
- https://cloud.tencent.com/developer/article/1735910
- https://stackoverflow.com/questions/45828401/how-to-post-a-bitmap-to-a-server-using-retrofit-android
微信关注我哦 👍
我是来自山东烟台的一名开发者,有感兴趣的话题,或者软件开发需求,欢迎加微信 zhongwei 聊聊, 查看更多联系方式
谈笑风生
abcjeff (来自: 俄罗斯) 1年前