前言:这文章跟上一个文章同用一个demo,为了区分还是分开比较方便说明

参考:大部分参考这位大佬的https://www.jianshu.com/p/f5f7b9750360

1. 前置准备

1.1 加入依赖build.gradle

// 网络请求类
//1.retrofit2.6.0
implementation 'com.squareup.retrofit2:retrofit:2.6.0'
// implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.0'
implementation 'com.squareup.retrofit2:converter-gson:2.6.0'
//2.协程
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'

1.2 权限AndroidManifest.xml

<uses-permission android:name="android.permission.INTERNET"/>

1.3 常量类

/**
 * @ObjectName: Constants
 * @Description:定义的常量,URL等
 * @Author: chemoontheshy
 * @Date: 2021/3/11-13:42
 */
object Constants {
    const val WEATHER_URL_TEST =  "https://api.muxiaoguo.cn/"
    const val WEATHER_URL = "https://api.muxiaoguo.cn/"
}object Constants {
    const val WEATHER_URL_TEST =  "https://api.muxiaoguo.cn/"
    const val WEATHER_URL = "https://api.muxiaoguo.cn/"
}

2. http类封装

2.1 HttpUtils

说明createApi的方法可能跟传统的不一样,这里是用泛型代替了指定好的方法,减少了耦合。

GsonConverterFactory.create()也不可以省略,好像省略会报错,不能解析返回的数据。

/**
 * @ClassName: HttpUtils
 * @Description: 封装网络请求的方法,这里是Retrofit2.60以上的方法
 * @Author: chemoontheshy
 * @Date: 2021/3/11-13:46
 */
object HttpUtils {
    /**
     * @name: isTest
     * @Description: 判断是否是test的API接口,方便可以省略
     */
    private fun isTest(isTest:Boolean):String = if(isTest) WEATHER_URL_TEST else WEATHER_URL

    fun<T>createApi(clazz: Class<T>):T = Retrofit.Builder()
        .baseUrl(isTest(true))
        .addConverterFactory(GsonConverterFactory.create())
        .build()
        .create(clazz)
}

2.2 UserApi

  • 具体看注释
  • 具体内容需要看请求API的结构,这里是用了木小果的API的天气查询。
/**
 * @InterfaceName: UserApi
 * @Description:请求API的接口,可以多个接口,根据请求的不同
 * @Author: chemoontheshy
 * @Date: 2021/3/11-13:46
 */
interface UserApi {
    /**
     * 注意问题:
     * 1. 加入@FormUrlEncoded 只支持x-www-form-urlencoded不支持form-data所致,时加入这个
     *    如果不加人,会报错,正常请求应该不需要加
     * 2. BaseModel<MainModel>:BaseModel<T>是Model的基类,参考另外文章的data class
     * 3. MainModel是activity的MVP里的M层,具体内容是请求api返回的格式
     */
    @FormUrlEncoded
        @POST("api/tianqi")
        suspend fun getWeather(
            @Field("city") city: String,
            @Field("type") type: Int
        ): BaseModel<MainModel>
}

3. MainModel

这里的结构也是根据请求API的结构所编写的,这里是用了木小果的API的天气查询。

返回类容

{"code":"200","msg":"success","data":{"cityname":"长沙","nameen":"changsha","temp":"19","WD":"北风","WS":"1级","wse":"12km/h","SD":"50%","weather":"多云","pm25":"39","limitnumber":"","time":"14:00"}}
/**
 * @ClassName: MainModel
 * @Description:这里是根据API返回值,编写的data class
 * @Author: chemoontheshy
 * @Date: 2021/3/11-14:56
 */
data class MainModel(val cityname:String,
                     val nameen:String,
                     val temp:String,
                     val WD:String,
                     val WS:String,
                     val wse:String,
                     val SD:String,
                     val weather:String,
                     val pm25:String,
                     val limitnumber:String,
                     val time:String)

4. MainPresenter

这里直接调用时不行的,需要引入CoroutineScope by MainScope()但是这里为了方便已经提取到基类里。

分析

  • launch 是协程里子线程的挂起,反正相当于创建一个子线程,不会阻塞主线程,另外还有async
  • exceptionHandler 是请求过程中发生错误,处理异常的方法
  • response:BaseModel<MainModel> 是通过基类BaseModel,和实际的M层组成“返回类容”
  • HttpUtils.createApi(UserApi::class.java).getWeather("长沙",1)UserApi相当于之前封装好的里的<T>
/**
 * @ClassName: MainPresenter
 * @Description:P层,提供不涉及数据M层的方法,和涉及M层的方法
 * @Author: chemoontheshy
 * @Date: 2021/3/11-14:56
 */
class MainPresenter: BasePresenter<MainView>() {
    fun setText(){
        getBaseView()?.setData("test")
    }

    /**
     * launch 是协程里子线程的挂起,反正相当于创建一个子线程,不会阻塞主线程,另外还有async
     * exceptionHandler 是请求过程中发生错误,处理异常的方法
     * response:BaseModel<MainModel> 是通过基类BaseModel,和实际的M层组成“返回类容”
     * HttpUtils.createApi(UserApi::class.java).getWeather("长沙",1)
     */
    fun getWeather(){
        launch (exceptionHandler){
            val response:BaseModel<MainModel> = HttpUtils.createApi(UserApi::class.java).getWeather("长沙",1)
            getBaseView()?.setData(response)
        }
    }
}

附1:BasePresenter更新

这里这样写是为了区分前面MVP文章,不一定是使用协程来请求,但是使用协程请求最好还是提取到基基类里面的意思。

/**
 * @ClassName: BasePresenter
 * @Description:Presenter的基类,主要是绑定,解绑,提供方法接口,增加协程
 * @Author: chemoontheshy
 * @Date: 2021/3/11-14:01
 */
open class BasePresenter<V> : CoroutineScope by MainScope(){
    /**
     * 在基类里,提供协程出现错误的调用
     */
    val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
        Log.e(ContentValues.TAG, "coroutine: error ${throwable.message}")
    }

    private var mBaseView:V? = null
    /**
     * 绑定View
     */
    fun bindView(mBaseView:V){
        this.mBaseView = mBaseView
    }
    /**
     * 解绑View
     */
    fun unbindView(){
        this.mBaseView = null
    }
    /**
     * 返回方法的接口
     */
    fun getBaseView() = mBaseView
}

附2 :关于response的解析和data class的使用

Log.e(TAG,response.toString())这里是直接打印response。

返回:

BaseModel(code=200, message=null, data=MainModel(cityname=长沙, nameen=changsha, temp=19, WD=北风, WS=1级, wse=12km/h, SD=52%, weather=多云, pm25=39, limitnumber=, time=14:20))

可以分析出,根据BaseModel就是网络请求的基本三个要素,code 、message、data,(事实上这个木小果的API,的基本是code 、mesg、data,所以这里返回了null,根据上面的“返回类容”如果基类变成mgs,应该返回success而不是null,data就是MainModel的数据结构。

至于怎样调用呢,就方便,比如需要提取城市名 cityname和当地温度 temp可以这提出

val cityName = response.data.cityname
val temp = response.data.temp
Log.e(TAG,cityName+temp)

这样就可以获取返回的数据了。另外data class有自带的方法,这里就不演示了。

5 总结

到此,就完全使用了MVP,并使用协程+Retrofit2.6.0封装了网络请求,很方便。

思路主要是封装好HttpUtils,至于UserApi和M层基类和M层基本根据请求的API编写,然后根据MVP的思维调用即可。