Skip to content

一款基于 kotlin + mvp + 协程 + retrofit 的android快速开发框架

Notifications You must be signed in to change notification settings

T-bright/ktBaseLibrary

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ffeadac · Aug 13, 2020

History

61 Commits
Jun 29, 2020
Mar 24, 2020
Aug 13, 2020
Jun 15, 2020
Feb 16, 2020
Jun 15, 2020
Apr 10, 2020
Feb 16, 2020
Feb 16, 2020
Feb 16, 2020
Apr 28, 2020
Apr 28, 2020
Feb 16, 2020

Repository files navigation

ktBaseLibrary

ktBaseLibrary是基于 MVP + Retrofit + 协程 的android快速开发框架。

之前一直使用rxjava + retrofit。在使用了kotlin之后,接触了协程,现在retrofit已经支持了协程,在使用retrofit+协程之后,果断的抛弃了rxjava。

之所以抛弃了rxjava,主要有以下两个方面:

  • 可以以同步的方式写出异步的代码。
  • 减少一个rxjava的依赖,可以减小apk的体积

下面是以同步方式写出异步代码的实例:

mainScope.launch {
    //网络请求
    val singlePoetry = ApiServices.instance.singlePoetry().response()
    
    //singlePoetry是网络请求的结果
    if (singlePoetry != null) {
        mView?.showResult(singlePoetry)
    }
    
    mView?.hideLoading()
}

以上代码简洁,逻辑清晰。

框架简介

框架主体使用 kotlin + mvp + retrofit + 协程 的模式。另外引用了一些好用的第三方库以及自己封装的一些基础库。具体如下图: image

如何使用

1、依赖

在主项目app的build.gradle中依赖

dependencies {
    ...
   implementation 'com.tbright:ktbaselibrary:1.1.3'
}

在project的build.gradle中配置

allprojects {
    repositories {
        maven { url 'https://dl.bintray.com/tongsiwei49/ktbaselibrary/' }
    }
}

或者 下载到本地导入Module.

2、使用

2.1、配置http请求

需继承 HttpConfigProxy,这里主要是统一处理错误的请求结果。下面是demo里面实现的一个,具体的大家可以根据自己的项目作修改,详细请看: HttpConfig

class HttpConfig : HttpConfigProxy() {

    private var mRetrofit: Retrofit? = null

    private var mRetrofitBuilder: Retrofit.Builder? = null

    private var mOkHttpClientBuilder: OkHttpClient.Builder? = null

    //
    override var baseUrl: String = mBaseUrl

    override var baseUrls: Map<String, String>
        set(value) {}
        get() {
            var urls = linkedMapOf<String, String>()
            if (urls.isNotEmpty()) return urls
            if (GlobalConfig.isDebug) {//可以在这里动态切换服务
                urls[BASE_URL] = mBaseUrl
                urls[GANK_URL] = mGankUrl
            } else {
                urls[BASE_URL] = mBaseUrl
                urls[GANK_URL] = mGankUrl
            }
            return urls
        }

    override suspend fun <T> parseResponseData(responseData: Deferred<BaseResponse<T>>): T? {
        try {
            var response = responseData.await()
            if (response.isResponseSuccess()) {
                return response.getResponseData()
            } else {
                MessageEvent( EVENTCODE_RESPONSE_FAIL,response.getResponseMessage()).send()
                return null
            }
        } catch (e: Throwable) {
            var errMsg = "网络异常"
            when (e) {
                is UnknownHostException -> errMsg = "连接失败"
                is ConnectException -> errMsg = "连接失败"
                is SocketTimeoutException -> errMsg = "连接超时"
                is InterruptedIOException -> errMsg = "连接中断"
                is SSLHandshakeException -> errMsg = "证书验证失败"
                is JSONException -> errMsg = "数据解析错误"
                is JsonSyntaxException -> errMsg = "数据解析错误"
                is NoNetworkException -> errMsg = "无可用网络"
                is HttpException -> {
                    when (e.code()) {
                        UNAUTHORIZED, FORBIDDEN -> {//这两个一般会要求重新登录
                            MessageEvent(EVENTCODE_RELOGIN,errMsg).send()
                            return null
                        }
                    }
                }
                else -> errMsg = e.message.toString()
            }
            MessageEvent(EVENTCODE_RESPONSE_FAIL, errMsg).send()
        }
        return null
    }

    override suspend fun <T> parseResponseWrapperData(responseData: Deferred<BaseResponse<T>>,vararg needDisposeError:Any): BaseResponse<T>? {
        try {
            var response = responseData.await()
            if (response.isResponseSuccess()) {
                return response
            } else {
                if(needDisposeError.contains(response.getResponseStatus())){//如果包含,不统一处理,在相应的页面特殊处理
                    return response
                }else{
                    MessageEvent( EVENTCODE_RESPONSE_FAIL,response.getResponseMessage()).send()
                    return null
                }
            }
        } catch (e: Throwable) {
            var errMsg = "网络异常"
            when (e) {
                is UnknownHostException -> errMsg = "连接失败"
                is ConnectException -> errMsg = "连接失败"
                is SocketTimeoutException -> errMsg = "连接超时"
                is InterruptedIOException -> errMsg = "连接中断"
                is SSLHandshakeException -> errMsg = "证书验证失败"
                is JSONException -> errMsg = "数据解析错误"
                is JsonSyntaxException -> errMsg = "数据解析错误"
                is NoNetworkException -> errMsg = "无可用网络"
                is HttpException -> {
                    when (e.code()) {
                        UNAUTHORIZED, FORBIDDEN -> {//这两个一般会要求重新登录
                            MessageEvent(EVENTCODE_RELOGIN,errMsg).send()
                            return null
                        }
                    }
                }
                else -> errMsg = e.message.toString()
            }
            MessageEvent(EVENTCODE_RESPONSE_FAIL, errMsg).send()
        }
        return null
    }


    override fun initRetrofit() {
        initClient()
        mRetrofitBuilder = Retrofit.Builder()
        mRetrofit = mRetrofitBuilder?.run {
            baseUrl(GlobalConfig.httpConfigProxy?.baseUrl ?: GlobalConfig.httpConfigProxy?.baseUrls!!.values.first())//如果项目就一个域名,可以直接使用baseUrl,baseUrls可以不用管
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(CoroutineCallAdapterFactory())
                .client(mOkHttpClientBuilder!!.build())
                .build()
            build()
        }
    }

    private var mRetrofitServices = hashMapOf<String, Any>()

    @Suppress("UNCHECKED_CAST")
    override fun <T> create(clazz: Class<T>): T {
        var key = clazz.canonicalName
        var mRetrofitService = mRetrofitServices[key]
        if (mRetrofitService == null) {
            mRetrofitService = mRetrofit!!.create(clazz)
            mRetrofitServices[key!!] = mRetrofitService!!
        }
        return mRetrofitService as T
    }

    private fun initClient() {
        mOkHttpClientBuilder = OkHttpClient.Builder()
        mOkHttpClientBuilder?.run {
            if (GlobalConfig.isDebug) {
                val loggingInterceptor = HttpLoggingInterceptor()
                loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
                //设置 Debug Log 模式
                addInterceptor(loggingInterceptor)
            }
            connectTimeout(TIME_OUT, TimeUnit.SECONDS)
            readTimeout(TIME_OUT, TimeUnit.SECONDS)
            writeTimeout(TIME_OUT, TimeUnit.SECONDS)

            //错误重连
            retryOnConnectionFailure(true)
            addInterceptor(CacheInterceptor())
            addInterceptor(HeaderInterceptor())
            addInterceptor(MultiUrlInterceptor())

        }
    }
}

2.2、配置默认loading框

需继承 ShowUIProxy。它的作用主要是统一显示请求网络是需要显示的loading框。下面是demo里面实现的一个,具体的大家可以根据自己的项目作修改,详细请看: ShowUIConfig

class ShowUIConfig : ShowUIProxy {
    override fun <T> parseResponseFailMessage(messageEvent: MessageEvent<T>) {
        when (messageEvent.code) {
            EVENTCODE_RESPONSE_FAIL -> {
                hideLoading()
                if (messageEvent.data != null) {
                    var errorMessage = messageEvent.data as String
                    showError(errorMessage)
                } else {
                    showError("网络错误")
                }
            }
            EVENTCODE_RELOGIN -> {//重新登录
                hideLoading()
                getTopActivity().reLogin()
            }
        }
    }

    override fun showLoading() {
        getTopActivity().showLoadingDialog()
    }

    override fun hideLoading() {
        getTopActivity().hideLoadingDialog()
    }

    override fun showError(errorMessage: String) {
        errorMessage.showToast()
    }
}

2.3、在Application中设置

这个是必须的

GlobalConfig.init(this,httpConfigProxy = HttpConfig(),showUIProxy = ShowUIConfig())

2.4、Activity

继承BaseMvpActivity

class MainActivity : BaseMvpActivity<MainPresenter>(),MainContract.MainView {
    override fun getLayoutId(): Int {
        return R.layout.activity_main
    }

    override fun initView(savedInstanceState: Bundle?) {

    }

    override fun initData() { 
    
    }
}

当然如果页面很简单,没有什么逻辑要处理,也可以继承 BaseActivity

class MainActivity : BaseActivity() {
    override fun getLayoutId(): Int {
        return R.layout.activity_main
    }

    override fun initView(savedInstanceState: Bundle?) {
        
    }

    override fun initData() {
        
    }
}

2.5、Fragment

Fragment 的使用方式和 Activity 差不多,继承 BaseMvpFragment

class MainFragment : BaseMvpFragment<MainFragmentPresenter>(),MainFragmentContract.MainView {

    override fun getLayoutId(): Int {
        return R.layout.activity_main
    }

    override fun initView(savedInstanceState: Bundle?) {

    }

    override fun initData() { 
    
    }
    
}

当然也可以直接继承 BaseFragment ,与直接继承 Activity 一样。

2.6、Presenter和View

Presenter 需继承 BasePresenter ,View 需继承 BaseView。下面是demo的契约类,可以参考:

interface MainContract {

    abstract class MainPresenter : BasePresenter<BaseModel, MainView>(){

        abstract fun singlePoetry()
        
        ...
    }

    interface MainView : BaseView{
        fun showResult(result : String)
    }
}

下面是MainPresenter的实例代码:

class MainPresenter : MainContract.MainPresenter() {

    override fun singlePoetry() {
        mView?.showLoading()
        mainScope.launch {
            //网络请求
            val singlePoetry = ApiServices.instance.singlePoetry().response()
    
            //singlePoetry是网络请求的结果
            if (singlePoetry != null) {
                mView?.showResult(singlePoetry)
            }
    
            mView?.hideLoading()
        }
    }
    
}

可以看见,网络请求的逻辑非常清晰。

具体的实例,可以参考demo。

关于框架

后续会进一步完善,欢迎大家多都提意见

QQ群(1105357684)

About

一款基于 kotlin + mvp + 协程 + retrofit 的android快速开发框架

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages