最近学习完flutter基础后,心血来潮,把kotlin的协程学习了下,又把项目的网络框架用协程重写了,分享给你大家
一、gradel插件相关配置
因为协程,需要用到kotlin
(1)根gradel配置
buildscript {
ext {
kotlin_version = '1.3.72'
}
repositories {
google()
jcenter()
mavenCentral()
ext {
kotlin_version = '1.3.72'
}
}
dependencies {
classpath 'com.android.tools.build:gradle:4.1.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.3.72"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
jcenter()
maven { url 'https://jitpack.io' }
mavenCentral()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
(2)module gradel配置
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
dependencies {
/*viewmodle 绑定*/
api 'androidx.lifecycle:lifecycle-extensions:2.2.0'
//kt协程
api "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0-rc01"
api 'com.squareup.retrofit2:retrofit:2.7.0'
api 'com.squareup.okhttp3:logging-interceptor:4.4.0'
api 'com.squareup.retrofit2:converter-gson:2.7.0'
api 'com.squareup.retrofit2:adapter-rxjava2:2.7.0'
}
二、kotlin写Retrofit
(1)基础的RetrofitClient
object RetrofitClient {
//请求的地址
private const val BASE_URL = "http://xxxx.xxxx.net.cn/"
//retrofit对象
private var retrofit: Retrofit? = null
//请求的api,可以根据不同的场景设置多个
val service: ApiService by lazy {
getRetrofit().create(ApiService::class.java)
}
private fun getRetrofit(): Retrofit {
if (retrofit == null) {
retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(getOkHttpClient())
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit!!
}
/**
* 获取 OkHttpClient
*/
private fun getOkHttpClient(): OkHttpClient {
val builder = OkHttpClient().newBuilder()
val loggingInterceptor: HttpLoggingInterceptor
if (BuildConfig.DEBUG) {
val logInterceptor = HttpLoggingInterceptor(HttpLogger())
logInterceptor.level = HttpLoggingInterceptor.Level.BODY
builder.addNetworkInterceptor(logInterceptor)
}
builder.run {
connectTimeout(HttpConstant.DEFAULT_TIMEOUT, TimeUnit.SECONDS)
readTimeout(HttpConstant.DEFAULT_TIMEOUT, TimeUnit.SECONDS)
writeTimeout(HttpConstant.DEFAULT_TIMEOUT, TimeUnit.SECONDS)
retryOnConnectionFailure(true) // 错误重连
// cookieJar(CookieManager())
}
return builder.build()
}
(2)ApiService
因为用到了协程,接口需要挂在任务中去,一般用suspend与CoroutineScope一起使用
interface ApiService {
companion object {
const val JOIN_URL = "?c=600&v=100"
}
/**
* 商城首页轮播
*/
@GET("xxx/xxx/xxx/banner/list$JOIN_URL")
suspend fun getBanners(@QueryMap map: Map<String, String>?): ApiFrameResult<BasePageEntity<MallBannerBean>>
/**
* 订单状态数量
*/
@GET("xxx/xxx/xxx/order/orderCount$JOIN_URL")
suspend fun getMallOrderCount(@QueryMap map: Map<String, String>?):ApiFrameResult<MallOrderCount>
}
(3)是否需要加密
这里的加密规则按照后台规范要求来操作,我这里采取了项目的一些代码块,EncryptionUtil,JsonUtil工具类问度娘
open class HttpClient {
/**
* 是否需要加密
*
* @return 加密
*/
protected val isNeedEncrypt: Boolean
protected get() = false
/**
* 组装query形式的请求参数
* @param parameter 业务参数
* @return query 参数
*/
protected open fun composeQueryParameter(parameter: Any?): Map<String, String>? {
val uid: String = BaseCacheHelper.getUid()
val map: MutableMap<String, String> = HashMap(6)
map["uid"] = uid
val json = handelParameterQ(parameter)
map["q"] = json
if (isNeedEncrypt) {
map["sign"] = EncryptionUtil.encryptSgin(json)
}
return map
}
/**
* 组装post请求体中业务参数
* @param parameter 业务参数
* @return 请求体
*/
protected open fun composePostBody(parameter: Any?): RequestBody? {
val mapOut: MutableMap<String, Any> = HashMap(1)
mapOut["q"] = handelParameterQ(parameter)
return create("application/json; charset=utf-8".toMediaTypeOrNull(), JsonUtil.string(mapOut))
}
/**
* 处理请求参数
*
* @param parameter 业务参数
*/
protected open fun handelParameterQ(parameter: Any?): String {
var json = if (parameter == null) "{}" else JsonUtil.string(parameter)
if (isNeedEncrypt) {
json = EncryptionUtil.encrypt(json)
}
return json
}
}
(4)业务逻辑请求类
协程中执行的任务,挂起通过挂起函数,实现非阻塞挂起,suspend用来修饰函数,作用是提醒这是一个挂起函数,协程运行到该函数的时候,会挂起
class CommonClient : HttpClient() {
companion object {
@Volatile
private var INSTANCE: CommonClient? = null
fun getInstance(): CommonClient = INSTANCE ?: synchronized(this) {
INSTANCE ?: CommonClient()
}
}
/**
* @description 获取banner数据
*/
suspend fun getBanner(params: Map<String?, Any?>?): ApiFrameResult<BasePageEntity<MallBannerBean>> {
return RetrofitClient.service.getBanners(composeQueryParameter(params))
}
/**
* @description 返回订单数量
*/
suspend fun getMallOrderCount(params: Map<String?, Any?>?): ApiFrameResult<MallOrderCount> {
return RetrofitClient.service.getMallOrderCount(composeQueryParameter(params))
}
三、MVVM封装
(1)BaseViewModel对协程的封装
此处launchData放在基类,让子类去实现,参数详解
api: suspend CoroutineScope.() -> ApiFrameResult<T>这个参数,是一个协程接口方法,需要用suspend 来修饰
withContext 任务是串行的, 且 withContext可直接返回耗时任务的结果,直到withConext执行完成后再执行下面,在子线程和主线程来回切换
liveData.value,error.value,熟悉mvvm原理的,我不赘述,会在view层做监听处理,后面有贴代码
ApiRequestSubscriber这个根据后台的返回code自行封装
open class BaseViewModel : ViewModel(), LifecycleObserver {
@JvmField
val loadingShowLD = MutableLiveData<Boolean>() //请求接口的loading
val error = MutableLiveData<Int>()
/**
* @description 处理异常
*/
fun handleException(status: Int) {
error.value = status
}
/**
* 注意此方法传入的参数:api是以函数作为参数传入
* api:即接口调用方法
* error:可以理解为接口请求失败回调
* ->数据类型,表示方法返回该数据类型
* ->Unit,表示方法不返回数据类型
*/
fun <T> launchData(
api: suspend CoroutineScope.() -> ApiFrameResult<T>,//请求接口方法,T表示data实体泛型,调用时可将data对应的bean传入即可
liveData: MutableLiveData<T>,
isShowLoading: Boolean = false) {
if (isShowLoading) {
//loading
loadingShowLD.value = true
}
viewModelScope.launch {
try {
withContext(Dispatchers.IO) {//异步请求接口
val result = api()
withContext(Dispatchers.Main) {
if (result.status == ApiRequestSubscriber.SERVER_SUCCESS) {//请求成功
LogUtils.d("http 成功--- " + result.status)
liveData.value = result.content.data
} else {
LogUtils.d("http 服务器返回异常--- ")
handleException(result.status)
}
}
}
} catch (e: Throwable) {//接口请求失败
LogUtils.e(e.message)
} finally {//请求结束
LogUtils.d("http 请求结束--- ")
//解除loading
loadingShowLD.value = false
}
}
}
}
贴出数据ApiFrameResult类
data class ApiFrameResult<T>(
val status: Int,
val msg: String,
val uid: String,
val content: ApiBusinessResult<T>
)
/**
* 业务返回的数据
*/
data class ApiBusinessResult<T>(
val code: Int,
val codeDesc: String,
val data: T
)
data class BasePageEntity<T>(
val list: List<T>?,
val total: Int,
val totalPage: Int,
val pageNo: Int,
val pageSize: Int,
val isLastPage: Boolean
)
data class MallBannerBean(
val bannerId: String,
val bannerType: Int,
val imgUrl: String,//图片地址
val imgLink: String//图片跳转链接
)
data class MallOrderCount(
val toBeDeliveredCount: Int,
val toPayCount: Int,
val toSendCount: Int
)
(2)View层的BaseActivity
public abstract class BaseActivity extends AppCompatActivity {
protected BaseViewModel viewModel;
protected ViewModelProvider viewModelProvider;
protected ViewModelProvider.Factory factory;
private LoadingDialog loadingDialog;
protected abstract void initView();
protected abstract void initLogic();
protected BaseViewModel createViewModel() {
return null;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = createViewModel();
initView();
observeLiveData();
initLogic();
}
/**
*请求网络数据的错误回调
*/
protected void observeLiveData() {
if (viewModel != null) {
viewModel.loadingShowLD.observe(this, this::showLoading);
viewModel.getError().observe(this, status -> {
switch (status) {
case ApiRequestSubscriber.TOKEN_FAILURE:
ToastUtils.showShort("登录失效");
break;
case ApiRequestSubscriber.SERVER_ERROR:
ToastUtils.showShort("服务器异常");
break;
case ApiRequestSubscriber.NET_ERROR:
ToastUtils.showShort("网络错误");
break;
case ApiRequestSubscriber.TIME_OUT:
ToastUtils.showShort("网络超时");
break;
case ApiRequestSubscriber.LOGIN_FAILURE:
ARouter.getInstance().build(PageRouter.PAGE_lOGIN).navigation();
ToastUtils.showShort("请重新登录");
break;
}
});
}
}
/**
* 是否显示loading
*/
protected void showLoading(boolean showLoading) {
if (showLoading) {
if (loadingDialog == null) {
loadingDialog = new LoadingDialog(this);
loadingDialog.setCanceledOnTouchOutside(false);
}
if (!loadingDialog.isShowing() && !isFinishing()) {
loadingDialog.show();
}
} else {
if (loadingDialog != null) {
if (loadingDialog.isShowing()) {
loadingDialog.dismiss();
loadingDialog = null;
}
}
}
}
protected ViewModelProvider getViewModelProvider() {
if (viewModelProvider == null) {
viewModelProvider = new ViewModelProvider(this, getFactory());
}
return viewModelProvider;
}
private ViewModelProvider.Factory getFactory() {
Application application = getApplication();
if (application == null) {
throw new IllegalStateException("Your activity/fragment is not yet attached to "
+ "Application. You can't request ViewModel before onCreate call.");
}
if (factory == null) {
factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application);
}
return factory;
}
(3)model层的业务实现CommonViewModel
class CommonViewModel : BaseViewModel() {
/**
* 轮播图数据监听
*/
val bannerLivaData: MutableLiveData<BasePageEntity<MallBannerBean>> = MutableLiveData()
val mallOrderCount: MutableLiveData<MallOrderCount> = MutableLiveData()
fun getBannerData(): MutableLiveData<BasePageEntity<MallBannerBean>> {
return bannerLivaData
}
/**
* @description 轮播图
* @time 2021/4/14 13:33
*/
fun getBanner() {
launchData({ CommonClient.getInstance().getBanner(null) }, bannerLivaData, true)
}
/**
* @description 获取配送的数量
* @time 2021/4/14 13:50
*/
fun getMallOrderCount() {
launchData({ CommonClient.getInstance().getMallOrderCount(HashMap()) }, mallOrderCount, true)
}
}
(4)View层的页面实现
class CoroutineActivity : BaseActivity() {
....省略一些代码....
override fun createViewModel(): BaseViewModel {
commonViewModel = getViewModelProvider().get(CommonViewModel::class.java)
return commonViewModel
}
//回调数据
override fun observeLiveData() {
super.observeLiveData()
commonViewModel.getBannerData().observe(this, Observer {
binding.kotlin.text = "banner数据"
LogUtils.d("http bannerLivaData-- " + it.list?.size)
})
}
}
调试
QQ图片20210430113606.png
四、协程相关知识
创建协程的方式就有了五种:
GlobalScope.launch{} :非阻塞的
launch{}
runBlocking{} : 是阻塞的
coroutineScope{}
async{}
//Dispatchers.Main:使用这个调度器在 Android 主线程上运行一个协程。可以用来更新UI 。在UI线程中执行
//Dispatchers.IO:这个调度器被优化在主线程之外执行磁盘或网络 I/O。在线程池中执行
/**
* @description ,协程体里的任务时就会先挂起(suspend),让CoroutineScope.launch后面的代码继续执行,
* 直到协程体内的方法执行完成再自动切回来所在的上下文回调结果。
*/
fun lauch() {
CoroutineScope(Dispatchers.IO).launch {
delay(500) //延时500ms
LogUtils.d(LogCat.COROUT_LOG + "1.执行CoroutineScope.... [当前线程为:${Thread.currentThread().name}]")
}
LogUtils.d(LogCat.COROUT_LOG + "2.BtnClick.... [当前线程为:${Thread.currentThread().name}]")
}
/**
* @description 实际开发中很少会用到runBlocking,阻塞主线程
* 等协程体完成才会执行下一个
*/
fun runBlocking() {
runBlocking {
delay(500) //延时500ms
LogUtils.d(LogCat.COROUT_LOG + "1.执行CoroutineScope.... [当前线程为:${Thread.currentThread().name}]")
}
LogUtils.d(LogCat.COROUT_LOG + "2.BtnClick.... [当前线程为:${Thread.currentThread().name}]")
}
/**
* @description 返回耗时任务的执行结果
多个 withContext 任务是串行的, 且 withContext可直接返回耗时任务的结果,直到withConext执行完成后再执行下面
用在一个请求结果依赖另一个请求结果的这种情况
*/
fun withContext() {
CoroutineScope(Dispatchers.IO).launch {
val time1 = System.currentTimeMillis()
val task1 = withContext(Dispatchers.IO) {
delay(2000)
LogUtils.d(LogCat.COROUT_LOG + "1.执行task1.... [当前线程为:${Thread.currentThread().name}]")
"one" //返回结果赋值给task1
}
val task2 = withContext(Dispatchers.IO) {
delay(1000)
LogUtils.d(LogCat.COROUT_LOG + "2.执行task2.... [当前线程为:${Thread.currentThread().name}]")
"two" //返回结果赋值给task2
}
LogUtils.d(LogCat.COROUT_LOG + "task1 = $task1 , task2 = $task2 , 耗时 ${System.currentTimeMillis() - time1} ms [当前线程为:${Thread.currentThread().name}]")
}
}
/**
* @description 处理多个耗时任务,且这几个任务都无相互依赖时,可以使用 async ...
*/
fun async() {
CoroutineScope(Dispatchers.IO).launch {
val time1 = System.currentTimeMillis()
val task1 = async(Dispatchers.IO) {
delay(2000)
LogUtils.d(LogCat.COROUT_LOG + "1.执行task1.... [当前线程为:${Thread.currentThread().name}]")
"one" //返回结果赋值给task1
}
val task2 = async(Dispatchers.IO) {
delay(1000)
LogUtils.d(LogCat.COROUT_LOG + "2.执行task2.... [当前线程为:${Thread.currentThread().name}]")
"two" //返回结果赋值给task2
}
LogUtils.d(LogCat.COROUT_LOG + "task1 = $task1 , task2 = $task2 , 耗时 ${System.currentTimeMillis() - time1} ms [当前线程为:${Thread.currentThread().name}]")
}
}
``
网友评论