Android架构——MvpClean(kotlin)!!!

484 阅读7分钟
原文链接: blog.csdn.net

概述

第一次知道MvpClean还是在搜寻架构组件的博客时,出于好奇便学习了下。MvpClean给我的第一感觉是烦,写一个功能要创建n个文件,但不可否认对于大型项目和多人合作开发有很大的优势。

Clean架构

在Clean架构中,代码被分层成洋葱形,层层包裹,其中有一个依赖性规则:内层不能依赖外层,即内层不知道有关外层的任何事情,所以这个架构是向内依赖的。看个图感受一下:
图片由Bob大叔提供

Clean架构可以使代码有如下特性:
1. 独立于架构
2. 易于测试
3. 独立于UI
4. 独立于数据库
5. 独立于任何外部类库

MvpClean在Android中的表现

普通android应用一般需要如下三层:

  • 外层:实现层——接口实现层是体现架构细节的地方。实现架构的代码是所有不用来解决问题的代码,这包括所有与安卓相关的东西,比如创建Activity和Fragment,发送Intent以及其他联网与数据库的架构相关的代码。

  • 中层:接口适配层——接口适配层的目的就是桥接逻辑层和架构层的代码。

  • 内层:逻辑层——逻辑层包含了真正解决问题的代码。这一层不包含任何实现架构的代码,不用模拟器也应能运行这里的代码。这样一来逻辑代码就有了易于测试、开发和维护的优点。

每一个位于核心层外部的层都应能将外部模型转成可以被内层处理的内部模型。内层不能持有属于外层的模型类的引用。举个例子,当逻辑层的模型不能直接优雅地展现给用户,或是需要同时展示多个逻辑层的模型时,最好创建一个ViewModel类来更好的进行UI展示。这样一来,就需要一个属于外层的Converter类来将逻辑层模型转换成合适的ViewModel。

项目结构
一般来说一个安卓应用的结构如下:
外层项目包:UI,Storage,Network等等。
中层项目包:Presenter,Converter。
内层项目包:Interactor,Model,Repository,Executor。

外层

外层体现了框架的细节:

  • UI – 包括所有的Activity,Fragment,Adapter和其他UI相关的Android代码。
  • Storage – 用于让交互类获取和存储数据的接口实现类,包含了数据库相关的代码。包括了如ContentProvider或DBFlow等组件。
  • Network – 网络操作。

中层

桥接实现代码与逻辑代码的Glue Code:

  • Presenter – presenter处理UI事件,如单击事件,通常包含内层Interactor的回调方法。
  • Converter – 负责将内外层的模型互相转换。

内层

内层包含了最高级的代码,里面都是POJO类,这一层的类和对象不知道外层的任何信息,且应能在任何JVM下运行:

  • Interactor – Interactor中包含了解决问题的逻辑代码。这里的代码在后台执行,并通过回调方法向外层传递事件。在其他项目中这个模块被称为用例Use Case。一个项目中可能有很多小Interactor,这符合单一职责原则,而且这样更容易让人接受。
  • Model – 在业务逻辑代码中操作的业务模型。
  • Repository – 包含接口让外层类实现,如操作数据库的类等。Interactor用这些接口的实现类来读取和存储数据。这也叫资源库模式Repository Pattern。
  • Executor – 通过Thread Executor让Interactor在后台执行。一般不需要修改这个包里的代码。

use case个人感觉可以理解为一次事务

网上Clean代码

从逻辑层开始编写,有利于提前测试

  • domain包

创建初期基类:

interface Interactor {
    fun execute()
}

实现在后台运行Interactor:

abstract class AbstractInteractor constructor(val threadExecutor: Executor, val mainThread: MainThread) : Interactor {

    protected var mIsCanceled: Boolean = false
    protected var mIsRunning: Boolean = false

    abstract fun run()

    fun cancel() {
        mIsCanceled = true
        mIsRunning = false
    }

    fun isRunning() = mIsRunning

    fun onFinished() {
        mIsRunning = false
        mIsCanceled = false
    }

    override fun execute() {
        // mark this interactor as running
        mIsRunning = true

        // start running this interactor in a background thread
        threadExecutor.execute(this)
    }
}
interface MainThread {
    fun post(runnable: Runnable)
}

实现确保runnable对象在ui线程运行

class MainThreadImpl : MainThread {

    companion object {
        private var sMainThread: MainThread? = null

        fun getInstance() = 
                sMainThread ?: synchronized(this) {
                    sMainThread ?: MainThreadImpl().also { sMainThread = it }
                }
    }

    private val mHandler: Handler = Handler(Looper.getMainLooper())

    override fun post(runnable: Runnable) {
        mHandler.post(runnable)
    }
}

MainThreadImpl需要在主线程初始化

后台线程池:

interface Executor {
    fun execute(interactor: AbstractInteractor)
}
/**
 * This singleton class will make sure that each interactor operation gets a background thread.
 */
class ThreadExecutor : Executor {

    companion object {

        // This is a singleton
        private var sThreadExecutor: ThreadExecutor? = null

        private val CORE_POOL_SIZE = 3
        private val MAX_POOL_SIZE = 5
        private val KEEP_ALIVE_TIME = 120
        private val TIME_OUT = TimeUnit.SECONDS
        private val WORK_QUEUE = LinkedBlockingQueue<Runnable>()

        /**
         * Returns a singleton instance of this executor. If the executor is not initialized then it initializes it and returns
         * the instance.
         */
        fun getInstance() =
                sThreadExecutor ?: synchronized(this) {
                    sThreadExecutor ?: ThreadExecutor().also { sThreadExecutor = it }
                }
    }

    private var mThreadPoolExecutor: ThreadPoolExecutor

    init {
        val keepAlive = KEEP_ALIVE_TIME.toLong()
        mThreadPoolExecutor = ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                keepAlive,
                TIME_OUT,
                WORK_QUEUE)
    }

    override fun execute(interactor: AbstractInteractor) {
        mThreadPoolExecutor.submit {

            // run the main logic
            interactor.run()

            // mark it as finished
            interactor.onFinished()
        }
    }

}

编写内层Interactor

先写一个Interactor包含处理业务逻辑的代码。所有的Interactor都应该在后台运行,而不应影响UI展示。

interface WelcomingInteractor : Interactor {
    interface Callback {
        fun onSuccess(msg: String)
        fun onFailed(error: String)
    }
}

Callback负责与主线程的UI组件联通,放在WelcomingInteractor中可以避免给所有Callback接口起不同的名字而又能将它们有效区分。

实现获取消息的逻辑用于获取数据:

interface MessageRepository {
    fun getWelcomeMsg(): String
}

用业务逻辑代码来实现Interactor接口:

class WelcomingInteractorImpl constructor(threadExecutor: ThreadExecutor,
                                          mainThread: MainThread,
                                          val callback: WelcomingInteractor.Callback,
                                          val messageRepository: MessageRepository) : AbstractInteractor(threadExecutor, mainThread), WelcomingInteractor {

    private fun notifyError() {
        mainThread.post { callback.onFailed("nothing to welcome you") }
    }

    private fun postMessage(msg: String) {
        mainThread.post { callback.onSuccess(msg) }
    }


    override fun run() {
        val msg = messageRepository.getWelcomeMsg()

        if (msg == null || msg?.length == 0) {
            notifyError()
            return
        }

        postMessage(msg)
    }
}

这个Interactor获取数据并判断后向UI层发送数据或报错,是逻辑的核心。这里通过Callback向UI发送信息,这个Callback扮演的是presenter的角色。

注意:实现AbstractInteractor接口,代码就会在后台执行。

由于以上代码不依赖于android相关类库,所以可以直接进行Junit测试

编写presentation层

  • presentation包

Presentation层在架构中属于外层的范围,依赖于框架,包含了UI展示的代码。

创建初期基类:

interface BaseView {
    fun showProgress()
    fun hideProgress()
    fun showError()
}
interface BasePresenter {
    fun resume()
    fun pause()
    fun stop()
    fun destroy()
    fun onError(msg: String)
}
abstract class AbstractPresenter(val executor: Executor,
                                 val mainThread: MainThread) {
}

编写具体表现:

首先编写Presenter和View的接口:

interface MainPresenter : BasePresenter {
    interface View : BaseView {
        fun displayWelMsg(msg: String)
    }
}
class MainPresenterImpl(threadExecutor: Executor,
                        mainThread: MainThread,
                        val view: MainPresenter.View,
                        val messageRepository: MessageRepository) : AbstractPresenter(threadExecutor, mainThread), MainPresenter, WelcomingInteractor.Callback {
    override fun resume() {
        view.showProgress()

        val interactor = WelcomingInteractorImpl(
                executor,
                mainThread,
                this,
                messageRepository
        )

        interactor.execute()
    }

    override fun pause() {
    }

    override fun stop() {
    }

    override fun destroy() {
    }

    override fun onError(msg: String) {
        view.showError(msg)
    }


    override fun onSuccess(msg: String) {
        view.hideProgress()
        view.displayWelMsg(msg)
    }

    override fun onFailed(error: String) {
        view.hideProgress()
        onError(error)
    }
}

继承BasePresenter后重写onResume()方法启动Interactor,execute()方法会在后台线程中调用WelcomingInteractorImpl类的run()方法。

在上面的代码中给Interactor传入下列属性:
- ThreadExecutor:用于在后台线程运行Interactor,建议将这个类设计成单例。这个类属于domain包,不需要在外层实现。
- MainThreadImpl:用于在主线程中执行Interactor的Runnable对象。在依赖框架的外层代码中可以访问主线程,所以这个类要在外层实现。
- this:因为MainPresenter也实现了Callback接口,Interactor要通过Callback来更新UI。
- MessageRepository: 实现类WelcomMessageRepository让Interactor获取数据,下面会展示具体代码。

通过实现Callback接口监听Interactor事件:

    override fun onSuccess(msg: String) {
        view.hideProgress()
        view.displayWelMsg(msg)
    }

    override fun onFailed(error: String) {
        view.hideProgress()
        onError(error)
    }

view其实就是实现了MainPresenter.View接口的UI层对象,比如activity

class MainActivity : AppCompatActivity(), MainPresenter.View {

    companion object {
        private val TAG = this::class.java.simpleName
    }

    private lateinit var mPresenter: MainPresenter

    override fun showProgress() {
        Log.d(TAG, "show")
    }

    override fun hideProgress() {
        Log.d(TAG, "hide")
    }

    override fun showError(msg: String) {
        Log.d(TAG, "error")
    }

    override fun displayWelMsg(msg: String) {
        Log.d(TAG, msg)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        mPresenter = MainPresenterImpl(
                ThreadExecutor.getInstance(),
                MainThreadImpl.getInstance(),
                this,
                WelcomeMsgRepository())
    }

    override fun onResume() {
        super.onResume()
        mPresenter.resume()
    }
}

编写Storage层

  • storage包

repository中的接口就在storage层实现。所有与数据相关的代码都在这里。资源库模式下数据的来源是不确定的,不论是数据库、服务器还是文件。

class WelcomeMsgRepository : MessageRepository {
    override fun getWelcomeMsg(): String {
        val msg = "Hello World"

        try {
            Thread.sleep(2000) // 模拟网络请求或数据库延迟
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return msg
    }
}

这个就是上面的数据来源,实际应用中可以根据不同的数据来源进行封装或者加入一些缓存之类的技术

总结

可以发现MvpClean架构进一步把逻辑层剥离出来,相比较mvp解耦程度更高,但同时创建的类也更多了,所以比较适合大型项目,小项目用这种有点得不偿失,要注意选择。

官方demo