第一次知道MvpClean还是在搜寻架构组件的博客时,出于好奇便学习了下。MvpClean给我的第一感觉是烦,写一个功能要创建n个文件,但不可否认对于大型项目和多人合作开发有很大的优势。
在Clean架构中,代码被分层成洋葱形,层层包裹,其中有一个依赖性规则:内层不能依赖外层,即内层不知道有关外层的任何事情,所以这个架构是向内依赖的。看个图感受一下:
Clean架构可以使代码有如下特性:
1. 独立于架构
2. 易于测试
3. 独立于UI
4. 独立于数据库
5. 独立于任何外部类库
普通android应用一般需要如下三层:
外层:实现层——接口实现层是体现架构细节的地方。实现架构的代码是所有不用来解决问题的代码,这包括所有与安卓相关的东西,比如创建Activity和Fragment,发送Intent以及其他联网与数据库的架构相关的代码。
中层:接口适配层——接口适配层的目的就是桥接逻辑层和架构层的代码。
内层:逻辑层——逻辑层包含了真正解决问题的代码。这一层不包含任何实现架构的代码,不用模拟器也应能运行这里的代码。这样一来逻辑代码就有了易于测试、开发和维护的优点。
每一个位于核心层外部的层都应能将外部模型转成可以被内层处理的内部模型。内层不能持有属于外层的模型类的引用。举个例子,当逻辑层的模型不能直接优雅地展现给用户,或是需要同时展示多个逻辑层的模型时,最好创建一个ViewModel类来更好的进行UI展示。这样一来,就需要一个属于外层的Converter类来将逻辑层模型转换成合适的ViewModel。
项目结构
一般来说一个安卓应用的结构如下:
外层项目包:UI,Storage,Network等等。
中层项目包:Presenter,Converter。
内层项目包:Interactor,Model,Repository,Executor。
外层
外层体现了框架的细节:
中层
桥接实现代码与逻辑代码的Glue Code:
内层
内层包含了最高级的代码,里面都是POJO类,这一层的类和对象不知道外层的任何信息,且应能在任何JVM下运行:
use case个人感觉可以理解为一次事务
网上Clean代码
从逻辑层开始编写,有利于提前测试
创建初期基类:
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()
/**
* 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层在架构中属于外层的范围,依赖于框架,包含了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层
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