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

简介: 一文带你玩转安卓Kotlin+Retrofit+RxJava+MVP架构

前言

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

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

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

20191124172532647.gif

开始

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>
}

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

2019112417165520.png



     之后定义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
    }





目录
相关文章
|
10天前
|
安全 Java Android开发
安卓开发中的新趋势:Kotlin与Jetpack的完美结合
【6月更文挑战第20天】在不断进化的移动应用开发领域,Android平台以其开放性和灵活性赢得了全球开发者的青睐。然而,随着技术的迭代,传统Java语言在Android开发中逐渐显露出局限性。Kotlin,一种现代的静态类型编程语言,以其简洁、安全和高效的特性成为了Android开发中的新宠。同时,Jetpack作为一套支持库、工具和指南,旨在帮助开发者更快地打造优秀的Android应用。本文将探讨Kotlin与Jetpack如何共同推动Android开发进入一个新的时代,以及这对开发者意味着什么。
|
13天前
|
安全 Java 编译器
Android面试题之Java 泛型和Kotlin泛型
**Java泛型是JDK5引入的特性,用于编译时类型检查和安全。泛型擦除会在运行时移除类型参数,用Object或边界类型替换。这导致几个限制:不能直接创建泛型实例,不能使用instanceof,泛型数组与协变冲突,以及在静态上下文中的限制。通配符如<?>用于增强灵活性,<? extends T>只读,<? super T>只写。面试题涉及泛型原理和擦除机制。
18 3
Android面试题之Java 泛型和Kotlin泛型
|
3天前
|
安全 Android开发 Kotlin
Android面试题之Kotlin协程并发问题和互斥锁
Kotlin的协程提供轻量级并发解决方案,如`kotlinx.coroutines`库。`Mutex`用于同步,确保单个协程访问共享资源。示例展示了`withLock()`、`lock()`、`unlock()`和`tryLock()`的用法,这些方法帮助在协程中实现线程安全,防止数据竞争。
10 1
|
9天前
|
前端开发 JavaScript 测试技术
安卓应用开发中的架构模式解析
【6月更文挑战第21天】在软件开发领域,架构模式是设计优雅、高效、可维护应用程序的基石。本文深入探讨了安卓应用开发中常见的架构模式,包括MVC、MVP、MVVM和Clean Architecture,旨在为开发者提供一个清晰的指导,帮助他们选择最适合自己项目的架构风格。通过对比分析这些架构模式的特点、优势以及适用场景,文章揭示了如何根据项目需求和团队能力来采用恰当的架构模式,以实现代码的可维护性、可扩展性和可测试性。
27 7
|
14天前
|
安全 JavaScript 前端开发
kotlin开发安卓app,JetPack Compose框架,给webview新增一个按钮,点击刷新网页
在Kotlin中开发Android应用,使用Jetpack Compose框架时,可以通过添加一个按钮到TopAppBar来实现WebView页面的刷新功能。按钮位于右上角,点击后调用`webViewState?.reload()`来刷新网页内容。以下是代码摘要:
|
15天前
|
JavaScript Java Android开发
kotlin安卓在Jetpack Compose 框架下跨组件通讯EventBus
**EventBus** 是一个Android事件总线库,简化组件间通信。要使用它,首先在Gradle中添加依赖`implementation &#39;org.greenrobot:eventbus:3.3.1&#39;`。然后,可选地定义事件类如`MessageEvent`。在活动或Fragment的`onCreate`中注册订阅者,在`onDestroy`中反注册。通过`@Subscribe`注解方法处理事件,如`onMessageEvent`。发送事件使用`EventBus.getDefault().post()`。
|
15天前
|
Android开发 Kotlin
Android面试题 之 Kotlin DataBinding 图片加载和绑定RecyclerView
本文介绍了如何在Android中使用DataBinding和BindingAdapter。示例展示了如何创建`MyBindingAdapter`,包含一个`setImage`方法来设置ImageView的图片。布局文件使用`&lt;data&gt;`标签定义变量,并通过`app:image`调用BindingAdapter。在Activity中设置变量值传递给Adapter处理。此外,还展示了如何在RecyclerView的Adapter中使用DataBinding,如`MyAdapter`,在子布局`item.xml`中绑定User对象到视图。关注公众号AntDream阅读更多内容。
20 1
|
15天前
|
JavaScript 前端开发 Android开发
kotlin安卓在Jetpack Compose 框架下使用webview , 网页中的JavaScript代码如何与native交互
在Jetpack Compose中使用Kotlin创建Webview组件,设置JavaScript交互:`@Composable`函数`ComposableWebView`加载网页并启用JavaScript。通过`addJavascriptInterface`添加`WebAppInterface`类,允许JavaScript调用Android方法如播放音频。当页面加载完成时,执行`onWebViewReady`回调。
|
3天前
|
安全 Android开发 C++
在 Android 中使用 Kotlin 调用动态库
在 Android 中使用 Kotlin 调用动态库
11 0
|
9天前
|
Java Android开发 Kotlin
Android面试题:App性能优化之Java和Kotlin常见的数据结构
Java数据结构摘要:ArrayList基于数组,适合查找和修改;LinkedList适合插入删除;HashMap1.8后用数组+链表/红黑树,初始化时预估容量可避免扩容。SparseArray优化查找,ArrayMap减少冲突。 Kotlin优化摘要:Kotlin的List用`listOf/mutableListOf`,Map用`mapOf/mutableMapOf`,支持操作符重载和扩展函数。序列提供懒加载,解构用于遍历Map,扩展函数默认参数增强灵活性。
14 0