0%

简易MVVM网络请求框架(多个请求)

多次API请求之顺序请求

例:先上传用户头像图片,后更新用户信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
suspend fun uploadFile(fileUri: Uri) = withContext(coroutineContext + Dispatchers.IO) {
val pfd = contentResolver.openFileDescriptor(fileUri, "r")
?: return@withContext MissingDataError
var tmpFile: File? = null
val mediaType = contentResolver.getType(fileUri)?.toMediaType()
val requestBody: RequestBody =
if (pfd.statSize in 0..(5 * 1024 * 1024)) {//小于5M
val byteArray = FileInputStream(pfd.fileDescriptor).use {
it.readBytes()
}
byteArray.toRequestBody(mediaType)
} else {
tmpFile = File(cacheDir, System.currentTimeMillis().toString())
FileInputStream(pfd.fileDescriptor).use { fis ->
tmpFile.outputStream().use { fos ->
fis.copyTo(fos)
}
}
tmpFile.asRequestBody(mediaType)
}
pfd.close()

val service = ApiClient.getRetrofit().create<UploadService>()
dataFromNet {
service.uploadImg(template, MultipartBody.Part.createFormData("file", "filename", requestBody))
}.also {
tmpFile?.delete()
}
}

上面是上传图片的代码。suspend函数只能在另一个suspend函数或协程内调用。每一个suspend函数都有一个coroutineContext,coroutineContext是会向下传递的。在协程内被调用,这个coroutineContext就是协程的coroutineContext。在suspend函数内被调用,这个coroutineContext就是suspend函数的coroutineContext。

withContext(coroutineContext + Dispatchers.IO)

我们看看**uploadFile()**用的两个context有什么作用?

第一个coroutineContext是suspend函数的,就是上一级传递下来的,这个函数的调用起点会是ViewModel,coroutineContext可能是viewModelScope.coroutineContext,也可能是viewModelScope的子协程context,所以当ViewModel执行clear()后,coroutineContext会被取消,上传图片的请求也会被取消。

第二个Dispatchers.IO是指定函数体在IO线程执行,因为涉及到文件读写,所以最好在IO线程内执行

suspend函数是可以被挂起的,不会阻塞主线程(除非你使用主线程执行函数)

最后,使用示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
fun updateUserInfo(imageUri: Uri): LiveData<Resource<Any>> {
return liveData(context) {
emit(InProgress)
val result = uploadFile(imageUri)
if (result is Resource.Success) {
if (result.data != null) {
emit(dataFromNet {
service.updateUserInfo(result.data.img_id.toRequestBody())
})
} else {
emit(MissingDataError)
}
} else {
emit(result)
}
}
}

多次请求之并发请求

没有实际用例,下面是一些模拟的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private suspend fun multiCall() = withContext(coroutineContext) {
val deferred0 = async {
dataFromNet {
service.getData0()
}
}
val deferred1 = async {
dataFromNet {
service.getData1()
}
}
val result0 = deferred0.await()
val result1 = deferred1.await()
if (result0 is Resource.Success && result1 is Resource.Success) {
//combine result0 and result1
Resource.Success(result0.data)
} else if (result0 is Resource.Error) {
result0
} else {
result1
}
}

若要发起并发请求,则需要async来实现。看看async.await()的注释

Awaits for completion of this value without blocking a thread and resumes when deferred computation is complete

dataFromNet(基于retrofit)是一个异步的请求,async.await()不会阻塞主线程。

如果需要,可以通过withContext(coroutineContext + Dispatchers.IO)在函数体内或async{}内做一些耗时操作。

最后,使用示例如下:

1
2
3
4
5
6
fun multiCallTest(): LiveData<Resource<List<String>>> {
return liveData(context) {
emit(InProgress)
emit(multiCall())
}
}