由于鸿洋大佬开源接口停用(其他人滥用导致),该教程接口不再可用,有空再重写,抱歉
带实际例子的Android架构MVP简述一「Kotlin,MVP,Retrofit,RxJava」
谷歌推荐的MVP架构,是带有Model层的。但是也有人提出省略Model层的方式,本篇讲解的就是无Model层的实现方式。
本篇文章会使用Retrofit和Rxkotlin请求一个网络学生信息接口,并将请求到的学生数据展示到界面的文本上

教程使用的是kotlin语言,如果对kotlin语言不熟悉的,可以查看我的教程推荐,传送门
本篇文章也有相应的Java版本,传送门,文章源代码已经上传到仓库的kotlinMVP1
为什么要有这个东西
原生的架构是把所有的操作,如网络请求,数据库请求都直接放在Activity里面。这样做的好处是写的时候比较简单,在操作少的时候,这样做是比较方便
但是当操作比较多的时,一个类的代码太多就会使得我们整理起来很费劲,不容易读。比如第一行代码里面最后的天气项目的WeatherActivity就是我们原生的做法,把网络请求直接到Activity里面进行(郭神那时候可能是有其他的考虑,这里不是说这样做就绝对不对)

而MVP架构要做的,就是把Activity中过多的代码,网络请求,数据库请求抽离出来,这体现的其实是一种封装的思想,如果你学习过设计模式,就会发现这种思想在设计模式中随处可见
简而言之,MVP架构就是在一个类的代码太多的时候,把这个类中的代码抽出一部分,放在另外一个,或多个类中,而同时又能不能影响程序原本的功能运行的一种代码整理方式
项目架构的搭建
创建项目,并进行分包
module包是业务包,里面可以有很多不同功能的包,比如这里我们的Info包就是为了获取学生的信息。
bean包用来存放网络请求实体类,net包用来放我们网络请求需要用到的类
另外我们还重新创建了InfoActivity,并且在AndroidManifest.xml中将InfoActivity设置为程序的第一启动项,接下来书写xml布局
接下来开始MVP的代码书写了,我们到presenter包下创建InfoPresenter类,接下来从Activity里面抽取出来的代码就是写在该类里的
/**
* 获取学生信息P层类
*/
class InfoPresenter {
fun getData() {
//在此处进行网络请求,并将请求结果返回给Activity,这里假设我们已经请求成功,想要返回学生数据
}
}
这里思考一下,我们想要在InfoPresenter类里的getData方法执行网络请求方法,然后将请求成功或失败的回调返回给Activity进行展示。
但是我们该怎么做呢?给getData方法一个返回值?这样做事不行的,因为在我们后续的网络请求的方法中,是要结合RxKotlin的方法进行异步请求的,所以我们是没有办法直接返回的(直接报错,不能在方法中返回上一级方法的返回值),如下图
所以靠返回值来和Actvity进行交互是不行了,那我们就得采用另外一种方法,绑定View
我们在view包下创建Info接口,在该类下创建show()方法,该方法就是用于我们上面说到的将P层中网络请求到的数据返回给Activity
/**
* 用于在P层将数据返回给Activity
*/
interface InfoView {
//展示数据
fun show(msg: String)
}
然后回到P层,看看View层在P层中我们该如何操作
/**
* 获取学生信息P层类
*/
class InfoPresenter {
//View接口
private var infoView: InfoView? = null
//绑定View
fun attachView(view: InfoView) {
this.infoView = view
}
//销毁View对象
fun detechView() {
infoView = null
}
fun getData() {
//在此处进行网络请求,并将请求结果返回给Activity,这里假设我们已经请求成功,想要返回学生数据
infoView?.show("数据请求成功")
}
}
我们声明了一个infoView变量,使用attachView方法中完成对view接口的绑定,意思就是当进行该操作后,infoView变量就可以调用Activity里面的show方法来对文本进行赋值。
至于为什么infoView变量能调用Activity的show()方法请接着看下面的InfoActivity
/**
* 获取学生信息Activity
*/
class InfoActivity : AppCompatActivity(), InfoView {
//声明p层接口
private var infoPresenter = InfoPresenter()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_info)
//完成对view接口的绑定
infoPresenter.attachView(this)
btnSend.setOnClickListener {
infoPresenter.getData()
}
}
override fun show(msg: String) {
//将请求到的数据设置到TextView上
txtStudentInfo.text = msg
}
override fun onDestroy() {
super.onDestroy()
infoPresenter.detechView()
}
}
我们将InfoActivity继承于InfoView接口。
调用attachView(this)方法,传入this,相当于把Actiivty本身传递了进去,之后InfoPresenter的InfoView变量调用的show方法就是Actiivty的show方法啦(因为Actiivty继承了InfoView接口)
我们通过InfoView这个接口作为桥梁,通过attachView精心赋值,使得P层里的infoview变量调用的方法就是Activity的方法
运行程序,在按钮点击事件里面调用P层的getData()方法,P层的getData()方法获取到数据后会调用InfoView接口的show()方法,也就是InfoActivity的show()方法,然后在TextView中显示我们获取到的数据。

detechView方法用来销毁掉View,因为P层中进行的是网络,数据库请求,如果不在onDestroy中把view销毁。当遇到这种情况,Activity已经被销毁,但P层中的请求仍在进行的时候,App就有可能发生内存泄漏,为了避免这种情况,我们就在Activity销毁时,一起把p层给销毁掉
网络请求
添加异步请求框架Rxkotlin,RxAndroid,网络请求库okhttp,retrofit框架依赖
// Retrofit库
compile 'com.squareup.retrofit2:retrofit:2.4.0'
compile 'com.squareup.retrofit2:converter-gson:2.4.0' // 支持Gson解析
compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'
compile 'com.squareup.okhttp3:okhttp:3.11.0'
compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0'
// RxKotlin and RxAndroid
compile "io.reactivex:rxkotlin:1.0.0"
compile "io.reactivex:rxandroid:1.2.1"
在实际接入之前,我们先用后端接口调试工具Postman,测试一下我们的等会要请求的学生数据信息接口,接入后端网络接口之前先使用调试工具调试,这是个好习惯
我们先在module/info业务包下得bean实体类接口中创建网络请求实体类
//加入data后该类为数据类,会为我们自动生成toString等方法
data class Student(var name: String, var age: String)
除了toString方法外,关于数据类data为我们生成的其他方法,可以参考官方文档中的讲解
因为要进行网络请求,所以要在AndroidManifest.xml中声明我们网络权限
然后我们在net包下创建Api接口,RetrofitFactory类,RetrofitFactory用来存放我们Retrofit框架的一些配置,我们需要关注的,只是.baseUrl()方法的参数,该参数就是我们接口的前半段
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Retrofit
import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory
import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit
/**
* Created by tonjies on 2018/10/20.
*/
class RetrofitFactory private constructor() {
companion object {
val instance: RetrofitFactory by lazy {
RetrofitFactory()
}
}
private val retrofit: Retrofit
private val interceptor: Interceptor
init {
interceptor = Interceptor { chain ->
val request = chain.request()
.newBuilder()
.addHeader("Content-Type", "application/json")
.addHeader("charset", "utf-8")
.build();
chain.proceed(request)
}
retrofit = Retrofit.Builder()
//接口地址
.baseUrl("http://www.wanandroid.com/tools/mockapi/")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.client(initClient())
.build()
}
private fun initClient(): OkHttpClient? {
return OkHttpClient.Builder()
.addInterceptor(interceptor)
.addInterceptor(initLogInterceptor())
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(10, TimeUnit.SECONDS)
.build()
}
private fun initLogInterceptor(): Interceptor? {
var interceptor: HttpLoggingInterceptor = HttpLoggingInterceptor()
interceptor.level = HttpLoggingInterceptor.Level.BODY
return interceptor
}
fun create(server: Class): T {
return retrofit.create(server)
}
}
接下来是我们Api接口,该接口负责声明我们具体要调用的接口地址
interface Api {
//获取学生信息
//查询天气信息
@GET("2872/student")//接口的后段部分
fun getData(): Observable
}
最后我们回到我们的P层,进行数据请求
/**
* 获取学生信息P层类
*/
class InfoPresenter {
//View接口
private var infoView: InfoView? = null
//声明Api接口api
private var api: Api? = null
constructor() {
//初始化RetrofitFactory
api = RetrofitFactory.instance.create(Api::class.java);
}
//绑定View
fun attachView(view: InfoView) {
this.infoView = view
}
//销毁View对象
fun detechView() {
infoView = null
}
fun getData() {
//在此处进行网络请求,并将请求结果返回给Activity,这里假设我们已经请求成功,想要返回学生数据
// infoView?.show("数据请求成功")
//进行网络请求
api!!.getData()
.observeOn(AndroidSchedulers.mainThread()) //
.subscribeOn(Schedulers.io())
.subscribe(object : Observer {
override fun onError(e: Throwable) {
Log.d("tonjies", e.message)
}
override fun onNext(t: Student) {
var studentName = t.name //学生姓名
var studentAge = t.age //学生年龄
infoView!!.show("学生的姓名是:" + studentName + "学生的年龄是:" + studentAge)
}
override fun onCompleted() {
}
})
}
}
我们声明了Api接口变量,利用之前写好的RetrofitFactory进行初始化,最后使用Rxkotlin进行请求,把拿到的具体数据显示到界面上
运行程序,验证我们的代码

ok,这一小节就到这里啦,文章还是有些不足的,对于Retrofit和Rxkotlin的介绍略显不足,所以如果你想要更加详细的学习这两个,这里我推荐以下几个教程
- 给初学者的RxJava https://www.jianshu.com/p/464fa025229e
- 这是一份很详细的 Retrofit 2.0 使用教程(含实例讲解) https://blog.csdn.net/carson_ho/article/details/73732076
- Android Rxjava:这是一篇 清晰 & 易懂的Rxjava 入门教程 https://www.jianshu.com/p/a406b94f3188
- Retrofit2 和 RxJava 配合使用时的错误处理 https://juejin.im/entry/572fec5471cfe4006cac80e7#comment
当然不代表本篇文章没有意义,当我最初看到其他文章复杂的图示和理论时,都很头疼,想着还不如给我一个简单的例子,本篇文章正是因此而存在的
如果你喜欢本篇文章,希望能给我一个喜欢,这对我来说是很好的鼓励