美文网首页
jetpack+mvvm+协程封装网络框架

jetpack+mvvm+协程封装网络框架

作者: 书生也很芍 | 来源:发表于2021-04-30 12:12 被阅读0次

    最近学习完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}]")
            }
        }
    ``

    相关文章

      网友评论

          本文标题:jetpack+mvvm+协程封装网络框架

          本文链接:https://www.haomeiwen.com/subject/ezknrltx.html