基于Kotlin+RxJAVA2+Retorfit2+OkHttp+MVP简单封装

基于Kotlin+RxJAVA2+Retorfit2+OkHttp+MVP串行请求简单封装

前言

从毕业到至今已经工作几个月,一直以来把cv工程师,面向百度编程发挥的淋漓尽致,各种框架控件的使用以及封装都是直接拿网上的用。但这样下去总不是办法,现在是时候慢慢学会自己封装了,恰逢最近正在学习kotlin,而Retrofit2+Rxjava2这个网络请求框架也是一套热门的组合,所以从这个开始入手。

效果图

在这里插入图片描述
界面很简单,只有两个输入框和按钮,登录成功返回手机号码、昵称及头像。注意 这是两个接口,返回手机号码以及昵称是一个接口,头像是第二个接口,当点击登录时会按顺序进行登录以及获取头像的请求

准备工作

导入依赖

  	implementation 'io.reactivex.rxjava2:rxjava:2.1.8'
    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
    implementation 'io.reactivex.rxjava2:rxkotlin:2.2.0'
    implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
    implementation 'com.google.code.gson:gson:2.7'
    implementation 'com.squareup.okhttp3:logging-interceptor:3.10.0'//okhttp提供的请求日志拦截器
    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0'
    implementation 'com.squareup.retrofit2:retrofit:2.5.0'
    implementation 'com.squareup.retrofit2:converter-scalars:2.0.0'
主体代码

由于我们采用mvp架构,所以需要先配置MVP的接口,利用contract管理接口,注释很详细,聪明的你应该能看懂

package com.midai.kotlinapplication.mvp.contarct

import com.midai.kotlinapplication.mvp.model.bean.HeadBean
import com.midai.kotlinapplication.mvp.model.bean.UserBean
 interface LoginContract {
    interface Model{
        fun getPresenter(presenter: Presenter)//关联Presenter,Model

        //发送请求
        fun SendLoginRequest(loginJson:String,headJson:String)
    }
    interface Presenter{
        fun getView(view: View)//关联Presenter,view

        //发送请求
        fun Login(strPhone:String,strPwd:String)//登录
        fun getHead(strUid:Int)//获取头像
        fun SendRequest()//发送网络请求
        //回调结果
        fun LoginSuccess(userBean: UserBean)//登录成功
        fun LoginFault(message:String)//登录失败

        fun getHeadSuccess(headBean: HeadBean)//获取头像成功
        fun getHeadFault(message:String)//获取头像失败
    }
    interface View{
        //回调结果
        fun LoginSuccess(userBean: UserBean)//登录成功
        fun LoginFault(message:String)//登录失败

        fun getHeadSuccess(headBean: HeadBean)//获取头像成功
        fun getHeadFault(message:String)//获取头像失败
    }
}

View层代码(Activity),Activity主要用于显示页面以及获取输入框参数并发送给presenter层,注释很详细,我就不说了

class LoginActivity : AppCompatActivity(), View.OnClickListener, LoginContract.View {

    var strPhone = ""
    var strPwd = ""
    var uid = 92
    var presenter: LoginPresenter? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)

        bt_Login.setOnClickListener(this)
    }

    override fun onClick(p0: View?) {
        when (p0!!.id) {
            R.id.bt_Login -> Login()
        }
    }
    //登录
    private fun Login() {
        strPhone = et_Phone.text.toString()//获取手机号码
        strPwd = et_pwd.text.toString()//获取密码

        //关联view ,presenter
        presenter = LoginPresenter()
        presenter!!.getView(this)

        presenter!!.Login(strPhone, strPwd)//获取登录信息请求
        presenter!!.getHead(uid)//获取头像请求
        presenter!!.SendRequest()//将登陆请求和头像请求合并进行

    }

    //回调数据
    //登录成功
    override fun LoginSuccess(userBean: UserBean) {
        tv_Phone.text = userBean.data.phone//显示手机号码
        tv_NickName.text = userBean.data.nickname//显示昵称
    }

    //登录失败
    override fun LoginFault(message:String) {
        ToastUtils.ShowToast(this,message)
//        Toast.makeText(this,message,Toast.LENGTH_SHORT).show()
    }

    //获取头像成功
    override fun getHeadSuccess(headBean: HeadBean) {
        //显示头像
        Glide.with(this).load(URLConstant.SPLICE_URL2+headBean.data.pic).into(iv_Head)
    }

    //获取头像失败
    override fun getHeadFault(message:String) {
        ToastUtils.ShowToast(this,message)
    }
    //解绑presenter
    override fun onStop() {
        super.onStop()
        if (presenter!=null){
            presenter = null
        }
    }

presenter层代码,由于我的后台是接收json格式的,所以需要将请求参数拼接成json,如果是需要表单形式提交不需要拼接

class LoginPresenter : LoginContract.Presenter {
    var view:LoginContract.View? = null
    var loginModel:LoginModel? = null
    var loginJson :String=""
    var headJson:String=""

    //关联Model Presenter View
    override fun getView(view: LoginContract.View) {
        this.view = view
        loginModel = LoginModel()
        loginModel!!.getPresenter(this)
    }
    //登录
    override fun Login(strPhone:String,strPwd:String) {
        //拼接登录参数json
        loginJson = "{\"phone\":\""+strPhone+"\",\"pwd\":\""+strPwd+"\"}"
    }
    //头像
    override fun getHead(strUid:Int) {
        //拼接头像参数json
        headJson = "{\"uid\":"+strUid+"}"
    }
    //发送串行请求
    override fun SendRequest() {
        //合并登录请求以及头像请求,可自由决定进行几个请求
        loginModel!!.SendLoginRequest(loginJson,headJson)
    }

    /*
    * 回调成功
    * */
    //登录成功
    override fun LoginSuccess(userBean: UserBean) {
        view!!.LoginSuccess(userBean)
    }
    //登录失败
    override fun LoginFault(message:String) {
       view!!.LoginFault(message)
    }
    //获取头像成功
    override fun getHeadSuccess(headBean: HeadBean) {
        view!!.getHeadSuccess(headBean)
    }
    //获取头像失败
    override fun getHeadFault(message:String) {
        view!!.getHeadFault(message)
    }

}

Model层代码,model才是这里的核心,因为进行网络请求是在model成完成的,并将相关的回调数据返回presenter进行处理

class LoginModel : LoginContract.Model {

    var presenter: LoginContract.Presenter? = null
    var position:Int = 0//用于标志返回是第几个回调,并根据需要进行各接口返回数据处理
    val LOGINCODE:Int = 1//返回登录数据
    val HEADCODE:Int = 2//返回头像数据

    //关联Model Presenter
    override fun getPresenter(presenter: LoginContract.Presenter) {
        this.presenter = presenter
    }
    //发送请求
    override fun SendLoginRequest(loginJson: String, headJson: String) {
        //发送请求 SendRequest.login()用于合并登陆以及头像请求
        SendRequest.Login(loginJson, headJson)
            /*
      *回调数据,有于我使用的是串行请求,
      * 所有返回的数据是按照顺序返回,因此定义一个position记录返回的是第几个接口数据,
       */
            .subscribe(object : MyObserable<ResponseBody>() {
                override fun onNext(t: ResponseBody) {
                    position++
                    when(position){
                        LOGINCODE ->callLogin(t.string())//返回的是登录的数据
                        HEADCODE ->callHead(t.string())//返回的是头像数据
                    }
                }
            })

    }
    //返回登录信息
    private fun callLogin(result: String) {
        var userBean:UserBean? = null
        /*将数据解析成实体类,
        try catch 是由于我登录成功以及失败时后台返回的数据格式会不一样,
        但是返回数据格式不一样会导致Gson解析失败而闪退
        所以要进行异常捕获
        */
        try {
            userBean = GsonUtils.fromJson(result,UserBean::class.java)
            if (userBean!!.status==4000){
                presenter!!.LoginSuccess(userBean!!)
                Log.i("userBean","登录成功")
            }else{
                presenter!!.LoginFault(userBean!!.message)
            }
        }catch (e: Exception){
            Log.i("userBean","登录失败")
            presenter!!.LoginFault("登录失败")
        }
    }
    //返回头像
    private fun callHead(result: String) {
        var headBean = GsonUtils.fromJson(result,HeadBean::class.java)
        if (headBean!!.status==4000){
            presenter!!.getHeadSuccess(headBean!!)
        }else{
            presenter!!.getHeadFault(headBean.message)
        }
    }
}

但我们接收到presenter传过来的参数后调用 SendRequest.Login(loginJson, headJson)对参数进行处理以及发送请求处理

//登录
    fun Login(loginJson:String,headJson:String):Observable<ResponseBody>{
        //分别将登录参数json,头像参数json转化为 RequestBody
        var LoginBody = RequestBodyUntils.JsonToRequestBody(loginJson)
        var HeadBody = RequestBodyUntils.JsonToRequestBody(headJson)
        //分别调用Retrofit的API
        var loginObservable = RetrofitUtils.apiService.login(LoginBody)
        var headObservable = RetrofitUtils.apiService.getHead(HeadBody)
        return Observable.concat(loginObservable,headObservable).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())//返回串行请求,Observable.concat合并请求,并且依次将元素发出
//        return Observable.merge(loginObservable,headObservable)//Observable.merge,但是发送顺序可能会交叉,自行根据需要选择
    }

同样,注释也很详细,由于采用json进行发送,所有我们需要将json字符串转化为
RequestBody类型,当然如果你采用的是表单形式是不需要这一步的,而且也不需要将参数拼接成json。

object RequestBodyUntils {
    fun JsonToRequestBody(json:String):RequestBody{
        //将请求的参数json转化为RequestBody
        return RequestBody.create(MediaType.parse("application/json; charset=utf-8"),json)
    }
}

Observable.concat是将请求接口进行合并,并且按照顺序将元素发出,当然也可以采用Observable.merge合并接口,但是这种发出元素可能会交叉,不按照顺序发送,需要合并多少个数据括号中写多少个请求接口即可

我们知道使用Retrofit时,我们需要对Retrofit进行初始化,但我们不可能每次请求的时候都去写初始化,所以需要对Retrofit初始化过程进行封装

object RetrofitUtils {
    //初始化Retrofit
    var apiService = intiRetrofit()
    fun intiRetrofit():ApiService{
        apiService = Retrofit.Builder()
            .client(OkhttpUtils.okhttpUnitls())
            .baseUrl(URLConstant.SENDR_EQUEST_URL)
            .addConverterFactory(ScalarsConverterFactory.create())//增加返回值为String的支持
                /*如果使用GsonConverterFactory时,onNext会对返回的json自动解析为实体类,但是
                如果返回的json数据如果不能保证一致,会导致错误,不会操作,所以先返回json字符串
                在根据情况去处理数据
                */
//            .addConverterFactory(GsonConverterFactory.create())//增加返回值为Gson的支持
//            .addConverterFactory(JsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .build().create(ApiService::class.java)
        return apiService
    }

}

其中OkhttpUtils.okhttpUnitls()是封装Okhttp的一些配置

object OkhttpUtils {


    fun okhttpUnitls():OkHttpClient{
        var httpLoggingInterceptor = HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
        //Okhttp对象
         var okHttpClient = OkHttpClient.Builder()
            .addInterceptor(httpLoggingInterceptor)
             .addInterceptor(headerInterceptor)
            .retryOnConnectionFailure(true)//失败重连
            .connectTimeout(30, TimeUnit.SECONDS)//网络请求超时时间单位为秒
            .build()
        return okHttpClient
    }
//添加头部信息拦截器
object headerInterceptor:Interceptor{
    override fun intercept(chain: Interceptor.Chain): Response {
        val originalRequest = chain.request()
        val requestBuilder = originalRequest.newBuilder()
            //                        .addHeader("Accept-Encoding", "gzip")
            .addHeader("Accept", "application/json")
            .addHeader("Content-Type", "application/json; charset=utf-8")
            .method(originalRequest.method(), originalRequest.body())
        requestBuilder.addHeader("Authorization","")//添加请求头信息,服务器进行token有效性验证
        val request = requestBuilder.build()
        return chain.proceed(request)
    }

}
}

ApiService 存放的是我们的请求接口

interface ApiService {
    //管理所有的请求
    //登录
    @POST("android/v1/login/index")
    fun login(@Body body: RequestBody) : Observable<ResponseBody>
    //头像
    @POST("together/v1/me/img")
    fun getHead(@Body body: RequestBody) : Observable<ResponseBody>
}

Observable中使用ResponseBody不使用实体类是因为如果有多个请求时,参数不一致,回调的时候还需要先判断返回的数据是属于哪个实体类,处理起来比较麻烦,所以使用ResponseBody先返回json字符串,在根据情况去解析。注释写的很详细,思路应该很清晰很容易看懂。
最后附上项目链接
https://github.com/WOSLRQ/KotlinApplication.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值