article_detail_activity.xml: 定义页面的UI
ArticleDetailViewModel.kt: 为UI准备数据的类
ArtcileDetailActivity.kt: 显示ViewModel中的数据与响应用户交互的控制器
下面开始实现(为了简单,只显示了主要部分):
<?xml version="1.0" encoding="utf-8"?><android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.AppBarLayout>
<android.support.design.widget.CollapsingToolbarLayout>
<android.support.v7.widget.Toolbar
app:title=“@{vm.title}”/>
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
<android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>
/**
- 页面描述:ArticleDetailViewModel
- @param repo 数据源Model(MVVM 中的M),负责提供ViewModel中需要处理的数据
- Created by ditclear on 2017/11/17.
*/
class ArticleDetailViewModel @Inject constructor(val repo: ArticleRepository) {
//data//
lateinit var articleId:Int
val loading=ObservableBoolean(false)
val content = ObservableField()
val title = ObservableField()
//binding//
fun loadArticle():Single
repo.getArticleDetail(articleId)
.async()
.doOnSuccess { t: Article? ->
t?.let {
title.set(it.title)
content.set(it.content)
}
}
.doOnSubscribe { startLoad()}
.doAfterTerminate { stopLoad() }
fun startLoad()=loading.set(true)
fun stopLoad()=loading.set(false)
}
/**
- 页面描述:ArticleDetailActivity,处理和用户的交互(点击事件),以及处理
- viewModel层回调的数据,附加一些显示Loading,空状态和绑定生命周期等等的操作
- Created by ditclear on 2017/11/17.
*/
class ArticleDetailActivity : BaseActivity() {
override fun getLayoutId(): Int = R.layout.article_detail_activity
@Inject
lateinit var viewModel: ArticleDetailViewModel
//init
override fun initView() {
//统一都是KEY_DATA,别自己瞎命名
val articleID: Int? = intent?.extras?.getInt(Constants.KEY_DATA)
if (articleID == null) {
toast(“文章不存在”, ToastType.WARNING)
finish()
}
getComponent().inject(this)
mBinding.vm = viewModel.apply {
this.articleID = articleID
}
}
//加载数据
override fun loadData() {
viewModel.loadData()
.compose(bindToLifecycle())
// .doOnSubcribe{ showLoadingDialog() }
// .doAfterTerminate{ hideLoadingDialog() }
.subscribe({},{ dispatchFailure(it) })
}
}
他们是如何工作的呢?
在进入到ArticleDetailActivity
页面之后
- init()方法->先进行数据的初始化,将viewModel和xml文件进行绑定
- loadData()方法->调用viewModel的方法
进入ArticleDetailViewModel
- 调用Model层获取详情方法获取数据源
- 根据需要使用RxJava操作符对数据进行转换,通过DataBinding更新UI
- 返回可观测的Single对象给View
回到ArticleDetailActivity
页面
- 绑定生命周期,避免内存泄漏
- 对返回的可观测对象进行订阅
- 处理成功和失败的情况
至此,V-VM之间如何协作就清楚了。
M—VM
现在我们把View和ViewModel联系了起来,但是ViewModel该如何获取数据呢?
我们使用Retrofit来从后端获取网络数据。
interface ArticleService{
//文章详情
@GET(“article_detail.php”)
fun getArticleDetail(@Query(“id”) id: Int): Single
}
使用Room数据库来进行持久化
@Dao
interface ArticleDao{
@Query(“SELECT * FROM Articles WHERE articleid= :id”)
fun getArticleById(id:Int):Single
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertArticle(article :Article)
}
然后使用ArticleRepository.kt对网络和本地操作进行一层封装
/**
- 页面描述:ArticleRepository
- 提供数据给ViewModel层 , 处理网络数据和本地缓存之间的关系
- Created by ditclear on 2017/11/17.
*/
class ArticleRepository @Inject constructor
(private val remote: ArticleService, private val local: ArticleDao) {
/* 文章详情
- 先查看本地是否有缓存,如果没有那么再去请求网络,成功后更新本地缓存
*/
fun getArticle(articleId: Int): Single=
local.getArticleById(articleId).onErrorResumeNext {
if (it is EmptyResultSetException) {
remote.getArticleDetail(articleId).doOnSuccess { t -> t?.let { local.insertArticle(it) } }
} else throw it
}
}
先查看本地是否有缓存,如果没有那么再去请求网络,成功后更新本地缓存。
封装成Repository的原因是ViewModel不需要知道它的数据具体是从哪来的,这不是ViewModel这一层需要关心的事情。
即使你的项目没有进行数据缓存,总是从网络拉取数据,也建议封装成Repository,这意味着你的网络层是可以替换的,意义有点类似于封装一个ImageLoadUtil。
总体的流程就这么多,其实弄懂就很简单了。关键点是各层之间职责明确,以及解耦(Dagger2)和使用DataBinding时需要一个统一的规范。
而再细分,优化,也就是进行模块化、组件化的工作,深入些的插件化、热修复等等。不过万丈高楼平地起,我们的地基打的严实,以后的工作才会相对容易。
本文的代码都可以在github.com/ditclear/Pa…中找到
一些建议
建议一:在Activity或Fragment里处理点击事件
使用Presenter来继承View.OnClickListener
interface Presenter:View.OnClickListener{
override fun onClick(v: View?)
}
然后在BaseActivity/BaseFragment里实现它
abstract class BaseActivity : RxAppCompatActivity(),Presenter{
}
这样当我们要设置点击事件时,只需要
class ArticleDetailActivity : BaseActivity() {
//…
//init
override fun initView() {
mBinding.let{
it.vm=mViewModel
it.presenter=this
}
}
}
在xml中使用时,则统一使用presenter.onClick(view)
方法
<android.support.design.widget.CoordinatorLayout>
<android.support.design.widget.FloatingActionButton
android:id=“@+id/stow_fab”
android:onClick=“@{(v)->presenter.onClick(v)}”
/>
</android.support.design.widget.CoordinatorLayout>
真正处理则放在相应的Activity/Fragment里
class ArticleDetailActivity : BaseActivity() {
//…
@SingleClick
override fun onClick(v: View?) {
when (v?.id) {
R.id.stow_fab -> stow()
//more …
R.id.other_action -> other()
}
}
//其它
private fun stow() {
总结
首先是感觉自己的基础还是不够吧,大厂好像都喜欢问这些底层原理。
另外一部分原因在于资料也还没有看完,一面时凭借那份资料考前突击恶补个几天居然也能轻松应对(在这里还是要感谢那份资料,真的牛),于是自我感觉良好,资料就没有怎么深究下去了。
之前的准备只涉及了Java、Android、计网、数据结构与算法这些方面,面对面试官对其他基础课程的考察显得捉襟见肘。
下一步还是要查漏补缺,进行针对性复习。
最后的最后,那套资料这次一定要全部看完,是真的太全面了,各个知识点都涵盖了,几乎我面试遇到的所有问题的知识点这里面都有!希望大家不要犯和我一样的错误呀!!!一定要看完!
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
T1-1714564086492)]
[外链图片转存中…(img-PKeJu16E-1714564086493)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!