Android 应用程序架构 - MVVM 还是 MVC?
Posted
技术标签:
【中文标题】Android 应用程序架构 - MVVM 还是 MVC?【英文标题】:Android application architecture - MVVM or MVC? 【发布时间】:2012-01-20 14:14:51 【问题描述】:我有一个我要开始工作的 android 项目,我希望它的结构尽可能健壮。
我来自 WPF MVVM 背景,我已经阅读了一些关于 android 应用程序架构的内容,但我只是找不到关于我应该使用哪种架构的直接明确答案。
有人建议使用 MVVM - http://vladnevzorov.com/2011/04/30/android-application-architecture-part-ii-architectural-styles-and-patterns/
还有其他人建议使用 MVC,但没有具体说明应该如何实现。
正如我所说,我来自 WPF-MVVM 背景,因此我知道它严重依赖绑定,据我所知,Android 默认不支持这些绑定。
似乎有第 3 方解决方案 - http://code.google.com/p/android-binding/ 但我不知道我是否愿意依赖它。如果它的开发停止并且未来的 API 等将不再支持它会怎样。
基本上,我正在寻找的是一个详尽的教程,它将教我构建应用程序结构的最佳实践。文件夹和类结构等等。我只是找不到任何详尽的教程,我原以为谷歌会为其开发人员提供这样的教程。我只是认为这种文档在技术方面的处理不够好 - http://developer.android.com/guide/topics/fundamentals.html
我希望我已经足够清楚并且我没有要求太多,我只是想确定我的应用程序的结构,以免我的代码变成意大利面条怪物。
谢谢!
【问题讨论】:
很好的问题,你会认为谷歌或某人会有一些关于这个主题的综合教程或书籍。我在同一个地方;来自具有 MVC 框架的 Web 开发背景,是一个有凝聚力的应用程序的实际实现。 Android 中的架构仍然让我望而却步。 我启动了 android-binding 项目 (code.google.com/p/android-binding)。我想我正在努力继续开发它:) 现在我们有了 ICS 支持。 从这里开始查看我的博文Android Architecture: MV? 【参考方案1】:首先,Android 不会强迫您使用任何架构。不仅如此,它还使得试图遵循任何一个都变得有些困难。这将要求您成为一名聪明的开发人员,以避免创建意大利面条式代码库:)
您可以尝试适应任何您知道和喜欢的模式。我发现当你开发越来越多的应用程序时,最好的方法会以某种方式进入你的内心(很抱歉,但一如既往,在你开始做对之前,你必须犯很多错误)。
关于你知道的模式,让我做错事:我将混合三种不同的模式,以便你了解 android 中什么是做什么的。我相信 Presenter/ModelView 应该在 Fragment 或 Activity 中的某个位置。适配器有时可能会完成这项工作,因为它们会处理列表中的输入。可能活动也应该像控制器一样工作。模型应该是常规的 java 文件,而视图应该位于布局资源和一些您可能必须实现的自定义组件中。
我可以给你一些建议。 这是一个社区 wiki 答案,因此希望其他人可以提出其他建议。
文件组织
我认为主要有两种合理的可能性:
按类型组织所有内容 - 为所有活动创建一个文件夹,为所有适配器创建另一个文件夹,为所有片段创建另一个文件夹,等等 按域组织所有内容(也许不是最好的词)。这意味着与“ViewPost”相关的所有内容都将位于同一个文件夹中——活动、片段、适配器等。与“ViewPost”相关的所有内容都将位于另一个文件夹中。 “EditPost”等也是如此。我猜活动会要求您创建文件夹,然后会有一些更通用的用于基类的文件夹。就个人而言,我只参与过使用第一种方法的项目,但我真的很想尝试后一种方法,因为我相信它可以让事情更有条理。我认为拥有一个包含 30 个不相关文件的文件夹没有任何优势,但这就是我使用第一种方法得到的结果。
命名
在创建布局和样式时,始终使用它们所在的活动 (/fragment) 的前缀来命名(或标识它们)。因此,“ViewPost”上下文中使用的所有字符串、样式、ID 都应该以“@id/view_post_heading”(例如,对于 textview)、“@style/view_post_heading_style”、“@string/view_post_greeting”开头。
这将优化自动完成、组织、避免名称冲突等。
基类
我认为您几乎希望将基类用于您所做的所有事情:适配器、活动、片段、服务等。这些可能至少对调试有用,因此您可以了解所有活动中正在发生的事件.
一般
我从不使用匿名类 - 这些很丑陋,当您尝试阅读代码时会分散您的注意力 有时我更喜欢使用内部类(与创建专用类相比) - 如果一个类不会在其他任何地方使用(而且它很小),我认为这非常方便。 从一开始就考虑您的日志系统 - 您可以使用 android 的日志系统,但要好好利用它!【讨论】:
【参考方案2】:我认为通过一个例子来解释android中的MVVM会更有帮助。完整的文章连同 GitHub repo 信息是here 以获取更多信息。
让我们假设在本系列的第一部分中介绍了相同的基准电影应用程序示例。用户输入电影的搜索词并按下“查找”按钮,应用程序会根据该按钮搜索包含该搜索词的电影列表并显示它们。单击列表中的每部电影,然后显示其详细信息。
我现在将解释这个应用程序是如何在 MVVM 中实现的,然后是完整的 android 应用程序,它可以在 my GitHub page 上找到。
当用户单击视图上的“查找”按钮时,会从视图模型中调用一个方法,并将搜索词作为其参数:
main_activity_button.setOnClickListener(
showProgressBar()
mMainViewModel.findAddress(main_activity_editText.text.toString())
)
ViewModel 然后从 Model 中调用findAddress
方法来搜索电影名称:
fun findAddress(address: String)
val disposable: Disposable = mainModel.fetchAddress(address)!!.subscribeOn(schedulersWrapper.io()).observeOn(schedulersWrapper.main()).subscribeWith(object : DisposableSingleObserver<List<MainModel.ResultEntity>?>()
override fun onSuccess(t: List<MainModel.ResultEntity>)
entityList = t
resultListObservable.onNext(fetchItemTextFrom(t))
override fun onError(e: Throwable)
resultListErrorObservable.onNext(e as HttpException)
)
compositeDisposable.add(disposable)
当响应来自 Model 时,RxJava 观察者的 onSuccess 方法携带成功的结果,但由于 ViewModel 与 View 无关,它没有或使用任何 View 实例来传递结果进行显示。相反,它通过调用 resultListObservable.onNext(fetchItemTextFrom(t)) 来触发 resultListObservable 中的事件,View 会观察到该事件:
mMainViewModel.resultListObservable.subscribe(
hideProgressBar()
updateMovieList(it)
)
所以 observable 在 View 和 ViewModel 之间起到了中介的作用:
ViewModel 在其 observable 中触发一个事件 View 通过订阅 ViewModel 的 observable 来更新 UI这是视图的完整代码。在本例中,View 是一个 Activity 类,但 Fragment 也可以等同使用:
class MainActivity : AppCompatActivity()
private lateinit var mMainViewModel: MainViewModel
private lateinit var addressAdapter: AddressAdapter
override fun onCreate(savedInstanceState: Bundle?)
super.onCreate(savedInstanceState)
mMainViewModel = MainViewModel(MainModel())
loadView()
respondToClicks()
listenToObservables()
private fun listenToObservables()
mMainViewModel.itemObservable.subscribe(Consumer goToDetailActivity(it) )
mMainViewModel.resultListObservable.subscribe(Consumer
hideProgressBar()
updateMovieList(it)
)
mMainViewModel.resultListErrorObservable.subscribe(Consumer
hideProgressBar()
showErrorMessage(it.message())
)
private fun loadView()
setContentView(R.layout.activity_main)
addressAdapter = AddressAdapter()
main_activity_recyclerView.adapter = addressAdapter
private fun respondToClicks()
main_activity_button.setOnClickListener(
showProgressBar()
mMainViewModel.findAddress(main_activity_editText.text.toString())
)
addressAdapter setItemClickMethod
mMainViewModel.doOnItemClick(it)
fun showProgressBar()
main_activity_progress_bar.visibility = View.VISIBLE
fun hideProgressBar()
main_activity_progress_bar.visibility = View.GONE
fun showErrorMessage(errorMsg: String)
Toast.makeText(this, "Error retrieving data: $errorMsg", Toast.LENGTH_SHORT).show()
override fun onStop()
super.onStop()
mMainViewModel.cancelNetworkConnections()
fun updateMovieList(t: List<String>)
addressAdapter.updateList(t)
addressAdapter.notifyDataSetChanged()
fun goToDetailActivity(item: MainModel.ResultEntity)
var bundle = Bundle()
bundle.putString(DetailActivity.Constants.RATING, item.rating)
bundle.putString(DetailActivity.Constants.TITLE, item.title)
bundle.putString(DetailActivity.Constants.YEAR, item.year)
bundle.putString(DetailActivity.Constants.DATE, item.date)
var intent = Intent(this, DetailActivity::class.java)
intent.putExtras(bundle)
startActivity(intent)
class AddressAdapter : RecyclerView.Adapter<AddressAdapter.Holder>()
var mList: List<String> = arrayListOf()
private lateinit var mOnClick: (position: Int) -> Unit
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): Holder
val view = LayoutInflater.from(parent!!.context).inflate(R.layout.item, parent, false)
return Holder(view)
override fun onBindViewHolder(holder: Holder, position: Int)
holder.itemView.item_textView.text = mList[position]
holder.itemView.setOnClickListener mOnClick(position)
override fun getItemCount(): Int
return mList.size
infix fun setItemClickMethod(onClick: (position: Int) -> Unit)
this.mOnClick = onClick
fun updateList(list: List<String>)
mList = list
class Holder(itemView: View?) : RecyclerView.ViewHolder(itemView)
这是视图模型:
class MainViewModel()
lateinit var resultListObservable: PublishSubject<List<String>>
lateinit var resultListErrorObservable: PublishSubject<HttpException>
lateinit var itemObservable: PublishSubject<MainModel.ResultEntity>
private lateinit var entityList: List<MainModel.ResultEntity>
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
private lateinit var mainModel: MainModel
private val schedulersWrapper = SchedulersWrapper()
constructor(mMainModel: MainModel) : this()
mainModel = mMainModel
resultListObservable = PublishSubject.create()
resultListErrorObservable = PublishSubject.create()
itemObservable = PublishSubject.create()
fun findAddress(address: String)
val disposable: Disposable = mainModel.fetchAddress(address)!!.subscribeOn(schedulersWrapper.io()).observeOn(schedulersWrapper.main()).subscribeWith(object : DisposableSingleObserver<List<MainModel.ResultEntity>?>()
override fun onSuccess(t: List<MainModel.ResultEntity>)
entityList = t
resultListObservable.onNext(fetchItemTextFrom(t))
override fun onError(e: Throwable)
resultListErrorObservable.onNext(e as HttpException)
)
compositeDisposable.add(disposable)
fun cancelNetworkConnections()
compositeDisposable.clear()
private fun fetchItemTextFrom(it: List<MainModel.ResultEntity>): ArrayList<String>
val li = arrayListOf<String>()
for (resultEntity in it)
li.add("$resultEntity.year: $resultEntity.title")
return li
fun doOnItemClick(position: Int)
itemObservable.onNext(entityList[position])
最后是模型:
class MainModel
private var mRetrofit: Retrofit? = null
fun fetchAddress(address: String): Single<List<MainModel.ResultEntity>>?
return getRetrofit()?.create(MainModel.AddressService::class.java)?.fetchLocationFromServer(address)
private fun getRetrofit(): Retrofit?
if (mRetrofit == null)
val loggingInterceptor = HttpLoggingInterceptor()
loggingInterceptor.level = HttpLoggingInterceptor.Level.BODY
val client = OkHttpClient.Builder().addInterceptor(loggingInterceptor).build()
mRetrofit = Retrofit.Builder().baseUrl("http://bechdeltest.com/api/v1/").addConverterFactory(GsonConverterFactory.create()).addCallAdapterFactory(RxJava2CallAdapterFactory.create()).client(client).build()
return mRetrofit
class ResultEntity(val title: String, val rating: String, val date: String, val year: String)
interface AddressService
@GET("getMoviesByTitle")
fun fetchLocationFromServer(@Query("title") title: String): Single<List<ResultEntity>>
全文here
【讨论】:
以上是关于Android 应用程序架构 - MVVM 还是 MVC?的主要内容,如果未能解决你的问题,请参考以下文章
Jetpack Compose 架构如何选?MVP MVVM 还是 MVI?
Android Architecture(中文官方文档)——MVVM、DataBinding、Lifecycle、Room、LiveData