android jetpack 提供的包中提供的新工具:WorkManager
//workManager引入
implementation 'androidx.work:work-runtime:2.5.0' //java
implementation 'androidx.work:work-runtime-ktx:2.5.0' //kotlin
除了官方说的持久性工作用途
我发现使用WorkManager + Retrofit可以帮助我们优雅的完成网络请求事件
我们有一个登录的请求操作,需要将账号+密码上传给服务器进行验证,并将结果反馈到主线程。首先在Android环境下,请求网络这种事情是不能在主线程的,所以一般的做法是单独为其开一个子线程来做请求操作,待请求结果完成,再返回给主线程。这中间所有关于线程切换的操作,都需要我们手动完成,而且中途如果子线程抛出异常,我们是很难处理的,而且还会造成大量的代码冗余。
但是结合WorkManager + Retrofit之后情况就完全不同啦,不废话,上代码:
/**
* 登录任务
*/
class LoginWorker(context: Context, workerParams: WorkerParameters) :
Worker(context, workerParams) {
companion object {
//两个Key值
const val LOGIN_NAME = "LOGIN_NAME"
const val LOGIN_PWD = "LOGIN_PWD"
/**
* 在伴生对象中(相当于java的静态方法)用方法创建一个WorkRequest
* 这个request是一个一次性的Work任务
*/
fun start(name: String, pwd: String): WorkRequest {
val workRequest = OneTimeWorkRequestBuilder().apply {
setInputData(Data.Builder().let {
//通过setInputData往内部填入账号+密码
it.putString(LOGIN_NAME, name)
it.putString(LOGIN_PWD, pwd)
it.build()
})
}.build()
return workRequest
}
}
/**
* Worker对象实际执行任务的地方
*/
override fun doWork(): Result {
//首先从inputData中取出账号+密码
val name: String? = inputData.keyValueMap[LOGIN_NAME] as String?
val pwd: String? = inputData.keyValueMap[LOGIN_PWD] as String?
//如果为空,则直接返回任务失败
if (name == null || pwd == null)
return Result.failure(
Data.Builder().apply {
//任务失败也是可以设置数据的,存入失败原因或其它任何你想要配置的数据
putInt(ConstantUtil.WORKER_RESULT_CODE_KEY, -1)
putString(
ConstantUtil.WORKER_RESULT_KEY,
"name == null || pwd == null"
)
}.build()
)
//这里因为是从DataStore中取数据,所以用了协程
//不了解协程的小伙伴可以忽略,不影响
val netId = runBlocking { DataStoreUtil.get(ConstantUtil.LOGIN_NET_ID_KEY) }
LOG.info("Login Worker netId = $netId")
//这里通过Retrofit来实现网络请求
val call =
RetrofitUtil.retrofit.create(LoginService::class.java).login(name = name, pwd = pwd, netId = netId)
call?.execute()?.let {
//执行完成后,根据结果来判断是非登录成功
if (it.body()!!.code != 0) {
LOG.error("LoginWorker -> result code = ${it.body()!!.code}")
LOG.error("LoginWorker -> result msg = ${it.body()!!.msg}")
//登录失败
return Result.failure(
Data.Builder()
.apply {
putInt(ConstantUtil.WORKER_RESULT_CODE_KEY, -2)
putString(ConstantUtil.WORKER_RESULT_KEY, "result code != 0")
}
.build()
)
}
//登录成功后的token保存至DataStore
runBlocking { DataStoreUtil.put(ConstantUtil.LOGIN_TOKEN_KEY, it.body()!!.data) }
//登录成功的结果反馈
return Result.success(Data.Builder().apply {
putInt(ConstantUtil.WORKER_RESULT_CODE_KEY, it.body()!!.code)
putString(ConstantUtil.WORKER_RESULT_KEY, it.body()!!.data)
}.build())
}
return Result.failure(
Data.Builder()
.apply {
putInt(ConstantUtil.WORKER_RESULT_CODE_KEY, -3)
putString(ConstantUtil.WORKER_RESULT_KEY, "cannot get response")
}.build()
)
}
}
retrofit本身就不讲了,网上很多使用教程,我这边贴一下我定义的接口就行了
interface LoginService {
@POST(RetrofitUtil.headUrl)
@FormUrlEncoded
fun login(
@Field("m") m: String = "LOGIN",
@Field("name") name: String,
@Field("pwd") pwd: String,
@Field("netId") netId: Int
): Call?
@POST(RetrofitUtil.headUrl)
@FormUrlEncoded
fun checkLogin(
@Field("m") m: String = "check_token",
@Field("token") token: String
): Call?
@GET(RetrofitUtil.headUrl)
fun getUserInfo(
@Query("m") m: String = "USER_INFO",
@Query("token") token: String
): Call?
}
上面的代码是对于登录这个行为的定义,或者说预设,接下来讲讲使用
//通过伴生方法,直接创建一个workRequest
val workRequest = LoginWorker.start(name, pwd)
//WorkManager是支持lifeCycle的
//所以通过workRequest的id,对任务的执行做观察方法
WorkManager.getInstance(requireContext())
.getWorkInfoByIdLiveData(workRequest.id)
.observe(requireActivity()) {
LOG.e("当前work的执行状态->${it.state}")
if (it.state != WorkInfo.State.SUCCEEDED && it.state != WorkInfo.State.FAILED) {
LOG.e("不关心其它状态")
return@observe
}
//这里就是对结果数据进行解析啦
val data = it.outputData.getString(ConstantUtil.WORKER_RESULT_KEY)
val code = it.outputData.getInt(ConstantUtil.WORKER_RESULT_CODE_KEY, -11)
LOG.info("LoginFragment login Result code->$code")
LOG.info("LoginFragment login Result data->$data")
if (code == 0) {
//登录成功
loginSucceed()
sweetDialogDismiss()
Toast.makeText(requireContext(), "登录成功", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(requireContext(), data, Toast.LENGTH_SHORT).show()
sweetDialog().apply {
titleText = "登录失败"
contentText = data
changeAlertType(SweetAlertDialog.ERROR_TYPE)
}.show()
}
}
//这里表示将workRequest这个任务正式加入WorkManager的队列中,由其决定怎么执行
WorkManager.getInstance(requireContext()).enqueue(workRequest)
总结:
通过WorkManager我们可以非常轻松的对日常开发的任务进行预设置,也就通过继承Worker来对某一行为的预置,然后通过伴生方法或静态方法实现快速构建,这样代码逻辑就变得极其清晰了。特别是针对网络请求这类行为,WorkManager自带的线程管理机制要比我们自己重新写一套线程切换机制简单的多。