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

概述

第一次知道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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章