前言:这文章跟上一个文章同用一个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/"
}
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的思维调用即可。