前言
安卓目前的架构无非那几种:MVC 、MVP、MVVM。M和V一直存在,只是后面的不同。都是老生常谈的东西了,这里也就不多赘述了。
最开始学习安卓的时候,使用的是HttpClient、HttpConnection,之后开始使用OKHttp。后来Retrofit出来了,但我一直感觉和OKHttp差不多,尤其是底层也是OKHttp,这更令我丧失了学习的动力和欲望。昨天和今天闲来无事,想着用一下试试吧,用了之后,配合着RxJava和MVP,以及Kotlin优秀的语法糖,写出来的代码简介易懂了不少,下面开始一步一步来,文章结尾会放出源码。
虽然并不是写的View,但还是看一眼实现的效果吧(界面太丑,别嫌弃):
开始
1、添加依赖
// Retrofit implementation 'com.squareup.retrofit2:retrofit:2.5.0' // Retrofit和jxjava关联 implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' // Retrofit使用Gson转换 implementation 'com.squareup.retrofit2:converter-gson:2.4.0' // RxJava implementation 'io.reactivex.rxjava2:rxjava:2.2.13' // RxAndroid implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
2、搭建MVP
MVP,Model、View、Presenter。
一个一个来,首先是View,为了高度抽象,搞成了接口,其中写了错误信息的回调以及显示和关闭加载框。
/** * 定义通用的接口方法 */ interface BaseView { // 出错信息的回调 fun onError(result: String) // 显示进度框 fun showProgressDialog() // 关闭进度框 fun hideProgressDialog() }
接下来是Presenter,其中进行了View和Presenter的绑定和解绑,以及为了减小开销在每次网络访问之前初始化时进行添加Disposable,解绑View时关闭。
/** * @author jiang zhu on 2019/11/23 */ abstract class BasePresenter<V : BaseView> { //将所有正在处理的Subscription都添加到CompositeSubscription中。统一退出的时候注销观察 private var mCompositeDisposable: CompositeDisposable? = null /** * 获取View * @return */ var mvpView: V? = null private set fun attachView(baseView: V) { this.mvpView = baseView } /** * 解绑View,该方法在BaseMvpActivity类中被调用 */ fun detachView() { mvpView = null // 在界面退出等需要解绑观察者的情况下调用此方法统一解绑,防止Rx造成的内存泄漏 if (mCompositeDisposable != null) { mCompositeDisposable!!.dispose() } } /** * 将Disposable添加, * * @param subscription */ fun addDisposable(subscription: Disposable) { //csb 如果解绑了的话添加 sb 需要新的实例否则绑定时无效的 if (mCompositeDisposable == null || mCompositeDisposable!!.isDisposed) { mCompositeDisposable = CompositeDisposable() } mCompositeDisposable!!.add(subscription) } }
下面建立BaseActivity:
/** * @author jiang zhu on 2019/11/23 */ abstract class BaseActivity : AppCompatActivity() { // 设置布局 protected abstract val layoutId: Int override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(layoutId) initPresenter() //初始化控件 initViews() //获取数据 getDataFromServer() } // 初始化界面 protected abstract fun initViews() // 获取数据 protected fun getDataFromServer() {} // 实例化presenter protected open fun initPresenter() {} }
接下来是BaseMvpActivity,这里来解释下为什么不把这两个合成一个,首先是可以留出一层,为了以后业务的修改;其次是并不是所有的活动都需要MVP,不能为了写MVP而写MVP,实在是没有必要,如果是简单的页面,只有一个网络请求或者根本没有网络请求和数据库的操作,那么写MVP的话就实在没有必要了,这时就可以继承BaseActivity。下面是BaseMvpActivity代码:
/** * @author jiang zhu on 2019/11/23 */ abstract class BaseMvpActivity<V : BaseView, P : BasePresenter<V>> : BaseActivity() { protected var presenter: P? = null private set override fun initPresenter() { //实例化Presenter presenter = createPresenter() //绑定 if (presenter != null) { @Suppress("UNCHECKED_CAST") presenter!!.attachView(this as V) } } // 初始化Presenter protected abstract fun createPresenter(): P override fun onDestroy() { //解绑 if (presenter != null) { presenter!!.detachView() } super.onDestroy() } }
3、Retrofit
之前没用过,也不了解,这里两天用的感觉是:挺舒服,来吧,记录下怎么使用:
首先准备下网络请求的BaseUrl和网址吧:
/** * @author jiang zhu on 2019/11/23 */ internal object UrlConstant { //base const val BASE_URL = "http://192.168.3.37:8080/pet/" //base DATA const val BASE_DATA = "data" //登录接口 const val GET_LOGIN = "user/getLogin" //获取动态接口 const val GET_DYNAMIC = "dynamic/getDynamics" }
然后来写一个Retrofit的帮助类,由于该类会被经常调用,所以写成单例,里面并没有什么内容,只是将Retrofit进行了初始化:
/** * @author jiang zhu on 2019/11/23 */ class RetrofitHelper private constructor() { private val client = OkHttpClient() // 声明Retrofit对象 private var mRetrofit: Retrofit? = null internal val server: RetrofitService get() = mRetrofit!!.create(RetrofitService::class.java) init { initRetrofit() } /** * 初始化 retrofit */ private fun initRetrofit() { mRetrofit = Retrofit.Builder() .baseUrl(UrlConstant.BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .build() } companion object { //单例模式 @Volatile private var instance: RetrofitHelper? = null fun getInstance(): RetrofitHelper? { if (instance == null) { synchronized(RetrofitHelper::class.java) { if (instance == null) { instance = RetrofitHelper() } } } return instance } } }
上面代码中的RetrofitService中定义了网络接口,返回值为Observable<T>,方便之后的数据操作:
/** * @author jiang zhu on 2019/11/23 */ interface RetrofitService { @POST(UrlConstant.GET_LOGIN) fun getLogin(@Query(UrlConstant.BASE_DATA) data: String): Observable<UserBean> @GET(UrlConstant.GET_DYNAMIC) fun getDynamic(@Query(UrlConstant.BASE_DATA) data: String): Observable<DynamicBean> }
简单看一下支持的网络请求以及所有的注解:
之后定义DataManager,同样设置成单例,用来管理RetrofitService中定义的网络接口,当做Presenter和Retrofit的桥梁:
class DataManager private constructor() { private val mRetrofitService: RetrofitService = RetrofitHelper.getInstance()!!.server // 将retrofit的业务方法映射到DataManager中,统一用该类来调用业务方法 fun getLogin(data: String): Observable<UserBean> { return mRetrofitService.getLogin(data) } fun getDynamic(data: String): Observable<DynamicBean> { return mRetrofitService.getDynamic(data) } companion object { //单例 @Volatile private var instance: DataManager? = null fun getInstance(): DataManager? { if (instance == null) { synchronized(DataManager::class.java) { if (instance == null) { instance = DataManager() } } } return instance } } }
4、使用样例
样例就以登录作为样例吧。首先来建立网络请求的实体类,由于这里为GsonFormat直接生成的代码,就不改Kotlin了,代码太多,get、set代码直接省略:
public class UserBean { /** * msg : 查询成功 * success : true * rows : {"uid":"111111111","account":"123456","password":"123456","name":"??"} */ private String msg; private boolean success; private RowsBean rows; public static class RowsBean { /** * uid : 111111111 * account : 123456 * password : 123456 * name : 爱你 */ private String uid; private String account; private String password; private String name; private String photo; } }
下面是LoginView,直接继承BaseView,里面只定义了一个回调,之后如果有需要可以直接进行添加:
/** * @author jiang zhu on 2019/11/23 */ interface LoginView : BaseView { // 当前页面比较简单仅仅是获取接口数据进行展示, // 业务比较复杂的时候,可能一个页面需要不同的接口得到不同的数据类型 fun onSuccess(mUser: UserBean) }
然后是LoginPresenter,继承自BasePresenter<BaseView>,里面直接对Observable进行解析,和LoginView以及BaseView中的接口进行关联,数据进行回调:
/** * @author jiang zhu on 2019/11/23 */ class LoginPresenter : BasePresenter<LoginView>() { private val dataManager: DataManager? = DataManager.getInstance() private var mUser: UserBean? = null /** * 登录 * @param username 账号 * @param password 密码 */ fun getLogin(username: String, password: String) { if (mvpView != null) { val hashMap = java.util.LinkedHashMap<String, String>() hashMap["account"] = username hashMap["password"] = password val gao = Gson() val data = gao.toJson(hashMap) // 进行网络请求 dataManager?.getLogin(data)?.doOnSubscribe { disposable -> //请求加入管理,统一管理订阅,防止内存泄露 addDisposable(disposable) // 显示进度提示 mvpView!!.showProgressDialog() }?.subscribeOn(Schedulers.io())?.observeOn(AndroidSchedulers.mainThread())?.subscribe(object : Observer<UserBean> { override fun onSubscribe(d: Disposable) { } override fun onNext(userBean: UserBean) { mUser = userBean } override fun onError(e: Throwable) { // 在事件处理过程中出异常时,onError() 会被触发,同时队列自动终止,不允许再有事件发出 e.printStackTrace() mvpView!!.onError("请求失败!!") mvpView!!.hideProgressDialog() } override fun onComplete() { // onComplete方法和onError方法是互斥的, // RxJava 规定,当不会再有新的 onNext() 发出时,需要触发 onCompleted() 方法作为标志。 if (mUser != null) { mvpView!!.onSuccess(mUser!!) } // 隐藏进度 mvpView!!.hideProgressDialog() } }) } } }
直接在Activity中对Presenter进行调用,传入用户名密码:
private fun submit() { // validate val username = loginEtUsername.text.toString().trim { it <= ' ' } if (TextUtils.isEmpty(username)) { Toast.makeText(this, "账号不能为空", Toast.LENGTH_SHORT).show() return } val password = loginEtPassword.text.toString().trim { it <= ' ' } if (TextUtils.isEmpty(password)) { Toast.makeText(this, "密码不能为空", Toast.LENGTH_SHORT).show() return } // 执行登录操作 presenter?.getLogin(username, password) }
最后可以在显示隐藏等待框的回调中进行操作:
override fun showProgressDialog() { runOnUiThread { loginBtnLoading.visibility = View.VISIBLE } } override fun hideProgressDialog() { loginBtnLoading.visibility = View.GONE }