前言
从毕业到至今已经工作几个月,一直以来把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