小言_互联网的博客

一文带你玩转安卓Kotlin+Retrofit+RxJava+MVP架构(附Demo)

245人阅读  评论(0)

为什么写

主要是周末闲的。之前公司的代码谈不上架构一说,因为基本都是直接在activity中进行操作,不管是网络请求还是数据库的操作,有时候一个activity甚至能写到好几千行,维护起来真的是。。。那酸爽。

前言

安卓目前的架构无非那几种:MVC 、MVP、MVVM。M和V一直存在,只是后面的不同。都是老生常谈的东西了,这里也就不多赘述了。

最开始学习安卓的时候,使用的是HttpClient、HttpConnection,之后开始使用OKHttp。后来Retrofit出来了,但我一直感觉和OKHttp差不多,尤其是底层也是OKHttp,这更令我丧失了学习的动力和欲望。昨天和今天闲来无事,想着用一下试试吧,用了之后,配合着RxJava和MVP,以及Kotlin优秀的语法糖,写出来的代码简介易懂了不少,下面开始一步一步来,文章结尾会放出源码。

虽然并不是写的View,但还是看一眼实现的效果吧(界面太丑,别嫌弃):

开始

1、添加依赖


  
  1. // Retrofit
  2. implementation 'com.squareup.retrofit2:retrofit:2.5.0'
  3. // Retrofit和jxjava关联
  4. implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
  5. // Retrofit使用Gson转换
  6. implementation 'com.squareup.retrofit2:converter-gson:2.4.0'
  7. // RxJava
  8. implementation 'io.reactivex.rxjava2:rxjava:2.2.13'
  9. // RxAndroid
  10. implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'

2、搭建MVP

      MVP,Model、View、Presenter。

      一个一个来,首先是View,为了高度抽象,搞成了接口,其中写了错误信息的回调以及显示和关闭加载框。


  
  1. /**
  2. * 定义通用的接口方法
  3. */
  4. interface BaseView {
  5. // 出错信息的回调
  6. fun onError(result: String)
  7. // 显示进度框
  8. fun showProgressDialog()
  9. // 关闭进度框
  10. fun hideProgressDialog()
  11. }

      接下来是Presenter,其中进行了View和Presenter的绑定和解绑,以及为了减小开销在每次网络访问之前初始化时进行添加Disposable,解绑View时关闭。


  
  1. /**
  2. * @author jiang zhu on 2019/11/23
  3. */
  4. abstract class BasePresenter<V : BaseView> {
  5. //将所有正在处理的Subscription都添加到CompositeSubscription中。统一退出的时候注销观察
  6. private var mCompositeDisposable: CompositeDisposable? = null
  7. /**
  8. * 获取View
  9. * @return
  10. */
  11. var mvpView: V? = null
  12. private set
  13. fun attachView(baseView: V) {
  14. this.mvpView = baseView
  15. }
  16. /**
  17. * 解绑View,该方法在BaseMvpActivity类中被调用
  18. */
  19. fun detachView() {
  20. mvpView = null
  21. // 在界面退出等需要解绑观察者的情况下调用此方法统一解绑,防止Rx造成的内存泄漏
  22. if (mCompositeDisposable != null) {
  23. mCompositeDisposable!!.dispose()
  24. }
  25. }
  26. /**
  27. * 将Disposable添加,
  28. *
  29. * @param subscription
  30. */
  31. fun addDisposable(subscription: Disposable) {
  32. //csb 如果解绑了的话添加 sb 需要新的实例否则绑定时无效的
  33. if (mCompositeDisposable == null || mCompositeDisposable!!.isDisposed) {
  34. mCompositeDisposable = CompositeDisposable()
  35. }
  36. mCompositeDisposable!!.add(subscription)
  37. }
  38. }

      下面建立BaseActivity:


  
  1. /**
  2. * @author jiang zhu on 2019/11/23
  3. */
  4. abstract class BaseActivity : AppCompatActivity() {
  5. // 设置布局
  6. protected abstract val layoutId: Int
  7. override fun onCreate(savedInstanceState: Bundle?) {
  8. super.onCreate(savedInstanceState)
  9. setContentView(layoutId)
  10. initPresenter()
  11. //初始化控件
  12. initViews()
  13. //获取数据
  14. getDataFromServer()
  15. }
  16. // 初始化界面
  17. protected abstract fun initViews()
  18. // 获取数据
  19. protected fun getDataFromServer() {}
  20. // 实例化presenter
  21. protected open fun initPresenter() {}
  22. }

      接下来是BaseMvpActivity,这里来解释下为什么不把这两个合成一个,首先是可以留出一层,为了以后业务的修改;其次是并不是所有的活动都需要MVP,不能为了写MVP而写MVP,实在是没有必要,如果是简单的页面,只有一个网络请求或者根本没有网络请求和数据库的操作,那么写MVP的话就实在没有必要了,这时就可以继承BaseActivity。下面是BaseMvpActivity代码:


  
  1. /**
  2. * @author jiang zhu on 2019/11/23
  3. */
  4. abstract class BaseMvpActivity<V : BaseView, P : BasePresenter<V>> : BaseActivity() {
  5. protected var presenter: P? = null
  6. private set
  7. override fun initPresenter() {
  8. //实例化Presenter
  9. presenter = createPresenter()
  10. //绑定
  11. if (presenter != null) {
  12. @Suppress("UNCHECKED_CAST")
  13. presenter!!.attachView( this as V)
  14. }
  15. }
  16. // 初始化Presenter
  17. protected abstract fun createPresenter(): P
  18. override fun onDestroy() {
  19. //解绑
  20. if (presenter != null) {
  21. presenter!!.detachView()
  22. }
  23. super.onDestroy()
  24. }
  25. }

3、Retrofit

      之前没用过,也不了解,这里两天用的感觉是:挺舒服,来吧,记录下怎么使用:

      首先准备下网络请求的BaseUrl和网址吧:


  
  1. /**
  2. * @author jiang zhu on 2019/11/23
  3. */
  4. internal object UrlConstant {
  5. //base
  6. const val BASE_URL = "http://192.168.3.37:8080/pet/"
  7. //base DATA
  8. const val BASE_DATA = "data"
  9. //登录接口
  10. const val GET_LOGIN = "user/getLogin"
  11. //获取动态接口
  12. const val GET_DYNAMIC = "dynamic/getDynamics"
  13. }

      然后来写一个Retrofit的帮助类,由于该类会被经常调用,所以写成单例,里面并没有什么内容,只是将Retrofit进行了初始化:


  
  1. /**
  2. * @author jiang zhu on 2019/11/23
  3. */
  4. class RetrofitHelper private constructor() {
  5. private val client = OkHttpClient()
  6. // 声明Retrofit对象
  7. private var mRetrofit: Retrofit? = null
  8. internal val server: RetrofitService
  9. get() = mRetrofit!!.create(RetrofitService:: class.java)
  10. init {
  11. initRetrofit()
  12. }
  13. /**
  14. * 初始化 retrofit
  15. */
  16. private fun initRetrofit() {
  17. mRetrofit = Retrofit.Builder()
  18. .baseUrl(UrlConstant.BASE_URL)
  19. .client(client)
  20. .addConverterFactory(GsonConverterFactory.create())
  21. .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
  22. .build()
  23. }
  24. companion object {
  25. //单例模式
  26. @Volatile
  27. private var instance: RetrofitHelper? = null
  28. fun getInstance(): RetrofitHelper? {
  29. if (instance == null) {
  30. synchronized(RetrofitHelper:: class.java) {
  31. if (instance == null) {
  32. instance = RetrofitHelper()
  33. }
  34. }
  35. }
  36. return instance
  37. }
  38. }
  39. }

      上面代码中的RetrofitService中定义了网络接口,返回值为Observable<T>,方便之后的数据操作:


  
  1. /**
  2. * @author jiang zhu on 2019/11/23
  3. */
  4. interface RetrofitService {
  5. @POST(UrlConstant.GET_LOGIN)
  6. fun getLogin(@Query(UrlConstant.BASE_DATA) data: String): Observable<UserBean>
  7. @GET(UrlConstant.GET_DYNAMIC)
  8. fun getDynamic(@Query(UrlConstant.BASE_DATA) data: String): Observable<DynamicBean>
  9. }

      简单看一下支持的网络请求以及所有的注解:

      之后定义DataManager,同样设置成单例,用来管理RetrofitService中定义的网络接口,当做Presenter和Retrofit的桥梁:


  
  1. class DataManager private constructor() {
  2. private val mRetrofitService: RetrofitService = RetrofitHelper.getInstance()!!.server
  3. // 将retrofit的业务方法映射到DataManager中,统一用该类来调用业务方法
  4. fun getLogin(data: String): Observable<UserBean> {
  5. return mRetrofitService.getLogin( data)
  6. }
  7. fun getDynamic(data: String): Observable<DynamicBean> {
  8. return mRetrofitService.getDynamic( data)
  9. }
  10. companion object {
  11. //单例
  12. @Volatile
  13. private var instance: DataManager? = null
  14. fun getInstance(): DataManager? {
  15. if (instance == null) {
  16. synchronized(DataManager:: class.java) {
  17. if (instance == null) {
  18. instance = DataManager()
  19. }
  20. }
  21. }
  22. return instance
  23. }
  24. }
  25. }

4、使用样例

      样例就以登录作为样例吧。首先来建立网络请求的实体类,由于这里为GsonFormat直接生成的代码,就不改Kotlin了,代码太多,get、set代码直接省略:


  
  1. public class UserBean {
  2. /**
  3. * msg : 查询成功
  4. * success : true
  5. * rows : { "uid": "111111111", "account": "123456", "password": "123456", "name": "??"}
  6. */
  7. private String msg;
  8. private boolean success;
  9. private RowsBean rows;
  10. public static class RowsBean {
  11. /**
  12. * uid : 111111111
  13. * account : 123456
  14. * password : 123456
  15. * name : 爱你
  16. */
  17. private String uid;
  18. private String account;
  19. private String password;
  20. private String name;
  21. private String photo;
  22. }
  23. }

      下面是LoginView,直接继承BaseView,里面只定义了一个回调,之后如果有需要可以直接进行添加:


  
  1. /**
  2. * @author jiang zhu on 2019/11/23
  3. */
  4. interface LoginView : BaseView {
  5. // 当前页面比较简单仅仅是获取接口数据进行展示,
  6. // 业务比较复杂的时候,可能一个页面需要不同的接口得到不同的数据类型
  7. fun onSuccess(mUser: UserBean)
  8. }

      然后是LoginPresenter,继承自BasePresenter<BaseView>,里面直接对Observable进行解析,和LoginView以及BaseView中的接口进行关联,数据进行回调:


  
  1. /**
  2. * @author jiang zhu on 2019/11/23
  3. */
  4. class LoginPresenter : BasePresenter<LoginView>() {
  5. private val dataManager: DataManager? = DataManager.getInstance()
  6. private var mUser: UserBean? = null
  7. /**
  8. * 登录
  9. * @param username 账号
  10. * @param password 密码
  11. */
  12. fun getLogin(username: String, password: String) {
  13. if (mvpView != null) {
  14. val hashMap = java.util.LinkedHashMap<String, String>()
  15. hashMap[ "account"] = username
  16. hashMap[ "password"] = password
  17. val gao = Gson()
  18. val data = gao.toJson(hashMap)
  19. // 进行网络请求
  20. dataManager?.getLogin( data)?.doOnSubscribe { disposable ->
  21. //请求加入管理,统一管理订阅,防止内存泄露
  22. addDisposable(disposable)
  23. // 显示进度提示
  24. mvpView!!.showProgressDialog()
  25. }?.subscribeOn(Schedulers.io())?.observeOn(AndroidSchedulers.mainThread())?.subscribe( object : Observer<UserBean> {
  26. override fun onSubscribe(d: Disposable) {
  27. }
  28. override fun onNext(userBean: UserBean) {
  29. mUser = userBean
  30. }
  31. override fun onError(e: Throwable) {
  32. // 在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出
  33. e.printStackTrace()
  34. mvpView!!.onError( "请求失败!!")
  35. mvpView!!.hideProgressDialog()
  36. }
  37. override fun onComplete() {
  38. // onComplete方法和onError方法是互斥的,
  39. // RxJava 规定,当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。
  40. if (mUser != null) {
  41. mvpView!!.onSuccess(mUser!!)
  42. }
  43. // 隐藏进度
  44. mvpView!!.hideProgressDialog()
  45. }
  46. })
  47. }
  48. }
  49. }

      直接在Activity中对Presenter进行调用,传入用户名密码:


  
  1. private fun submit() {
  2. // validate
  3. val username = loginEtUsername.text.toString().trim { it <= ' ' }
  4. if (TextUtils.isEmpty(username)) {
  5. Toast.makeText( this, "账号不能为空", Toast.LENGTH_SHORT).show()
  6. return
  7. }
  8. val password = loginEtPassword.text.toString().trim { it <= ' ' }
  9. if (TextUtils.isEmpty(password)) {
  10. Toast.makeText( this, "密码不能为空", Toast.LENGTH_SHORT).show()
  11. return
  12. }
  13. // 执行登录操作
  14. presenter?.getLogin(username, password)
  15. }

      最后可以在显示隐藏等待框的回调中进行操作:


  
  1. override fun showProgressDialog() {
  2. runOnUiThread {
  3. loginBtnLoading.visibility = View.VISIBLE
  4. }
  5. }
  6. override fun hideProgressDialog() {
  7. loginBtnLoading.visibility = View.GONE
  8. }

总结

      到这里本篇文章基本技术,总结下:周末两天摸鱼。。。。努力,共勉。

      本文所写所有代码已上传到Githubhttps://github.com/zhujiang521/Retrofit

欢迎大家关注我的个人公众号,会定期发布安卓、Java学习及搞笑文章。


转载:https://blog.csdn.net/haojiagou/article/details/103222811
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场