玩安卓从 0 到 1 之总体概览

前言

其实写MVVM?瞎搞一波?和MVVM?继续搞一波这两篇文章的时候没觉得水,但是后来自己看了一遍,感觉除了截了几张图之外并没说什么关于技术的东西,这就很扯淡了,技术文章没说技术。。。

接下来的一番话可能会得罪包括我在内的非常多的作者。。。

其实好多人写安卓是想当然的写,没错,就是想当然的写,也有可能只是我这样。

网上说的什么MVC、MVP、MVVM等等,说的都天花乱坠,也包括我之前发的两篇文章,说的挺好但是就没有例子,也没有思想,不对,有时候有思想,但是例子写的太简单根本没用,比如我之前看过好多作者写的MVP的文章,都是说一通MVP的意思是啥就占了很多的篇幅,再说下和MVC相比较有什么优势又写了不少,一大堆理论,实践的时候就写了一个登录页面,是,思想领悟到了,到底怎么写啊,哪家公司的安卓项目是光写一个登录页面,况且一个登录页面用MVP干啥,为了多写代码吗?增加代码的数量?照着写一个登录页面还好,整个项目怎么搭建?碰到特殊情况怎么处理?完完全全是摸石头过河!

所以准备写一个系列的文章,从最开始的项目搭建开始,一步一步地把写一个小项目的过程和思想尽力说明白,这就是文章诞生的原因。

项目呢就是之前写的MVVM版本的玩安卓,接口是泓洋大神现成的,大家可以下载apk先体验下:www.pgyer.com/llj2

再放下项目的Github地址:github.com/zhujiang521…

正文

也许好多人看见我上面写的那段话就不想看后面了。。。。那还写什么正文啊。。但,,应该还有人没骂我,那就能继续写了。。

如果让你重新从头到尾写一个项目,你第一步会干什么?

是不是把你问住了?有多少人一直在维护公司的一些项目,很久没这么干过了。来吧,今天开始在干一次吧!

MVC、MVP虽然不能说过时了,但毕竟是新项目,肯定要用最新的MVVM,又到了老生常谈的问题,什么是MVVM?前两篇文章其实解释的已经够多了,在这里就不赘述了,再说本篇文章的重点是怎么用!

第一步——写基类

我不知道大家写代码的习惯是什么,我个人习惯是新项目先搭建基类,然后再下手,因为不写好基类之后再抽取的话会很麻烦,那么安卓的基类是什么呢?肯定是 BaseActivity 和 BaseFragment。

说起 BaseActivity 和 BaseFragment,这里要写的东西一定要考虑好,因为这里的东西一定要是绝大多数类都能用到的方法,还有一些是要留给子类实现的。说到这里就需要想一下什么是绝大多数类都能用到的方法,看过项目介绍的应该知道项目实现了五种不同的状态:正常显示内容、加载中、没有网络、没有内容、加载错误,很显然,这些内容都应该写在 BaseActivity 和 BaseFragment 中,那么接下来就到了激动人心的码代码环节!

1、1 BaseActivity
abstract class BaseActivity : AppCompatActivity(){/*** Activity中显示加载等待的控件。*/private var loading: ProgressBar? = null/*** Activity中由于服务器异常导致加载失败显示的布局。*/private var loadErrorView: View? = null/*** Activity中由于网络异常导致加载失败显示的布局。*/private var badNetworkView: View? = null/*** Activity中当界面上没有任何内容时展示的布局。*/private var noContentView: View? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setupViews()}protected open fun setupViews() {loading = findViewById(R.id.loading)noContentView = findViewById(R.id.noContentView)badNetworkView = findViewById(R.id.badNetworkView)loadErrorView = findViewById(R.id.loadErrorView)if (loading == null) {Log.e(TAG, "loading is null")}if (badNetworkView == null) {Log.e(TAG, "badNetworkView is null")}if (loadErrorView == null) {Log.e(TAG, "loadErrorView is null")}}companion object {private const val TAG = "BaseActivity"}
}

好了,先放这么多,放太多会懵逼的。。。来看下代码吧:首先设置为抽象类是为啥就不说了,这个不知道的话该去学习 Java 基础了,然后把需要的 View 都找到,接下来就需要一个接口了,需要把在 Activity 或 Fragment 中进行数据请求所需要经历的生命周期函数抽出来,这样 BaseActivity 和 BaseFragment 就可以重复利用了,说干就干:

interface RequestLifecycle {fun startLoading()fun loadFinished()fun loadFailed(msg: String?)}

那么接下来接该改造下 BaseActivity 了:

abstract class BaseActivity : AppCompatActivity(), RequestLifecycle {/*** 当Activity中的加载内容服务器返回失败,通过此方法显示提示界面给用户。** @param tip* 界面中的提示信息*/protected fun showLoadErrorView(tip: String = "加载数据失败") {loadFinished()if (loadErrorView != null) {val loadErrorText = loadErrorView?.findViewById<TextView>(R.id.loadErrorText)loadErrorText?.text = tiploadErrorView?.visibility = View.VISIBLEreturn}}/*** 当Activity中的内容因为网络原因无法显示的时候,通过此方法显示提示界面给用户。** @param listener* 重新加载点击事件回调*/protected fun showBadNetworkView(listener: View.OnClickListener) {loadFinished()if (badNetworkView != null) {badNetworkView?.visibility = View.VISIBLEbadNetworkView?.setOnClickListener(listener)return}}/*** 当Activity中没有任何内容的时候,通过此方法显示提示界面给用户。* @param tip* 界面中的提示信息*/protected fun showNoContentView(tip: String) {loadFinished()val noContentText = noContentView?.findViewById<TextView>(R.id.noContentText)noContentText?.text = tipnoContentView?.visibility = View.VISIBLE}/*** 将load error view进行隐藏。*/private fun hideLoadErrorView() {loadErrorView?.visibility = View.GONE}/*** 将no content view进行隐藏。*/private fun hideNoContentView() {noContentView?.visibility = View.GONE}/*** 将bad network view进行隐藏。*/private fun hideBadNetworkView() {badNetworkView?.visibility = View.GONE}@CallSuperoverride fun startLoading() {hideBadNetworkView()hideNoContentView()hideLoadErrorView()loading?.visibility = View.VISIBLE}@CallSuperoverride fun loadFinished() {loading?.visibility = View.GONEhideBadNetworkView()hideNoContentView()hideLoadErrorView()}@CallSuperoverride fun loadFailed(msg: String?) {loading?.visibility = View.GONEhideBadNetworkView()hideNoContentView()hideLoadErrorView()}
}

写到这里已经有大概的样子了,这里解释下 @CallSuper 这个注解:表示任何重写方法都应该调用此方法。接下来该干什么呢?刚才说过,有些很多类能用到,并且可以父类实现的咱们已经实现了,还有一种就是需要子类来实现的,比如:加载布局、加载页面、加载具体数据等等,不管是 Activity 或者是 Fragment 都需要,但是都必须是子类来实现的,那么也可以写一个接口来抽出来:

interface BaseInit {fun initData()fun initView()fun getLayoutId(): Int}

很清晰吧,加载数据、加载View、获取布局,那就可以继续完善下 BaseActivity 了:

abstract class BaseActivity : AppCompatActivity(), RequestLifecycle, BaseInit {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)transparentStatusBar()setContentView(getLayoutId())initView()initData()}override fun setContentView(layoutResID: Int) {super.setContentView(layoutResID)setupViews()}/*** 将状态栏设置成透明。只适配Android 5.0以上系统的手机。*/private fun transparentStatusBar() {if (AndroidVersion.hasLollipop()) {val decorView = window.decorViewdecorView.systemUiVisibility =View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLEwindow.statusBarColor = Color.TRANSPARENT}}}

这块来干了两件事:1、实现了初始化的接口并调用;2、将状态栏设置为透明,因为目前所有应用都实现了沉浸式。

不知道大家注意到没有,在父类中直接能实现的接口都实现了,但是需要在子类中实现的接口都没有实现,这就相当于在父类中直接写抽象方法,为了能和 BaseFragment 复用,所以提取成了接口,但并不在父类中进行实现从而交给了子类来实现。

最后再给 BaseActivity 加一个功能就差不多了:Activity 控制器,有很多情况下我们想把之前打开的 Activity 给一次性关闭,但是很麻烦,所以要实现一个 Activity 的控制器,每次 Activity 在执行 onCreate 方法的时候加入到控制器中,onDestroy 方法的时候从控制器中移除掉,来吧,写一个吧:

object ActivityCollector {private const val TAG = "ActivityCollector"private val activityList = ArrayList<WeakReference<Activity>?>()fun size(): Int {return activityList.size}fun add(weakRefActivity: WeakReference<Activity>?) {activityList.add(weakRefActivity)}fun remove(weakRefActivity: WeakReference<Activity>?) {val result = activityList.remove(weakRefActivity)Log.d(TAG, "remove activity reference $result")}fun finishAll() {if (activityList.isNotEmpty()) {for (activityWeakReference in activityList) {val activity = activityWeakReference?.get()if (activity != null && !activity.isFinishing) {activity.finish()}}activityList.clear()}}}

上面这个类很简单,只是一个 ArrayList ,进行添加和移除操作,这里需要注意的是为了防止内存泄露使用到了弱引用。

接下来就该把这个控制器添加到 BaseActivity 中了:

    private var weakRefActivity: WeakReference<Activity>? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)ActivityCollector.add(WeakReference(this))weakRefActivity = WeakReference(this)}override fun onDestroy() {super.onDestroy()ActivityCollector.remove(weakRefActivity)}

好了,BaseActivity 到这里就差不多了,放一个完整版的吧:

abstract class BaseActivity : AppCompatActivity(), RequestLifecycle, BaseInit {/*** Activity中显示加载等待的控件。*/private var loading: ProgressBar? = null/*** Activity中由于服务器异常导致加载失败显示的布局。*/private var loadErrorView: View? = null/*** Activity中由于网络异常导致加载失败显示的布局。*/private var badNetworkView: View? = null/*** Activity中当界面上没有任何内容时展示的布局。*/private var noContentView: View? = nullprivate var weakRefActivity: WeakReference<Activity>? = nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)transparentStatusBar()setContentView(getLayoutId())ActivityCollector.add(WeakReference(this))weakRefActivity = WeakReference(this)initView()initData()}override fun onDestroy() {super.onDestroy()ActivityCollector.remove(weakRefActivity)}override fun setContentView(layoutResID: Int) {super.setContentView(layoutResID)setupViews()}protected open fun setupViews() {loading = findViewById(R.id.loading)noContentView = findViewById(R.id.noContentView)badNetworkView = findViewById(R.id.badNetworkView)loadErrorView = findViewById(R.id.loadErrorView)if (loading == null) {Log.e(TAG, "loading is null")}if (badNetworkView == null) {Log.e(TAG, "badNetworkView is null")}if (loadErrorView == null) {Log.e(TAG, "loadErrorView is null")}}/*** 将状态栏设置成透明。只适配Android 5.0以上系统的手机。*/private fun transparentStatusBar() {if (AndroidVersion.hasLollipop()) {val decorView = window.decorViewdecorView.systemUiVisibility =View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLEwindow.statusBarColor = Color.TRANSPARENT}}/*** 当Activity中的加载内容服务器返回失败,通过此方法显示提示界面给用户。** @param tip* 界面中的提示信息*/protected fun showLoadErrorView(tip: String = "加载数据失败") {loadFinished()if (loadErrorView != null) {val loadErrorText = loadErrorView?.findViewById<TextView>(R.id.loadErrorText)loadErrorText?.text = tiploadErrorView?.visibility = View.VISIBLEreturn}}/*** 当Activity中的内容因为网络原因无法显示的时候,通过此方法显示提示界面给用户。** @param listener* 重新加载点击事件回调*/protected fun showBadNetworkView(listener: View.OnClickListener) {loadFinished()if (badNetworkView != null) {badNetworkView?.visibility = View.VISIBLEbadNetworkView?.setOnClickListener(listener)return}}/*** 当Activity中没有任何内容的时候,通过此方法显示提示界面给用户。* @param tip* 界面中的提示信息*/protected fun showNoContentView(tip: String) {loadFinished()val noContentText = noContentView?.findViewById<TextView>(R.id.noContentText)noContentText?.text = tipnoContentView?.visibility = View.VISIBLE}/*** 将load error view进行隐藏。*/private fun hideLoadErrorView() {loadErrorView?.visibility = View.GONE}/*** 将no content view进行隐藏。*/private fun hideNoContentView() {noContentView?.visibility = View.GONE}/*** 将bad network view进行隐藏。*/private fun hideBadNetworkView() {badNetworkView?.visibility = View.GONE}@CallSuperoverride fun startLoading() {hideBadNetworkView()hideNoContentView()hideLoadErrorView()loading?.visibility = View.VISIBLE}@CallSuperoverride fun loadFinished() {loading?.visibility = View.GONEhideBadNetworkView()hideNoContentView()hideLoadErrorView()}@CallSuperoverride fun loadFailed(msg: String?) {loading?.visibility = View.GONEhideBadNetworkView()hideNoContentView()hideLoadErrorView()}companion object {private const val TAG = "BaseActivity"}
}
BaseFragment

其实 BaseFragment 和 BaseActivity 基本一样,只是加载布局的地方有所不同,大家都是老司机,应该都懂:

    /*** Fragment中inflate出来的布局。*/private var rootView: View? = nulloverride fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,savedInstanceState: Bundle?): View? {val view = inflater.inflate(getLayoutId(), container, false)onCreateView(view)return view}/*** 在Fragment基类中获取通用的控件,会将传入的View实例原封不动返回。* @param view* Fragment中inflate出来的View实例。* @return  Fragment中inflate出来的View实例原封不动返回。*/private fun onCreateView(view: View): View {rootView = viewloading = view.findViewById(R.id.loading)noContentView = view.findViewById(R.id.noContentView)badNetworkView = view.findViewById(R.id.badNetworkView)loadErrorView = view.findViewById(R.id.loadErrorView)if (loading == null) {throw NullPointerException("loading is null")}if (badNetworkView == null) {throw NullPointerException("badNetworkView is null")}if (loadErrorView == null) {throw NullPointerException("loadErrorView is null")}return view}

细心的大家应该都看出来了,在 BaseActivity 中如果 View 为空我只打印了 log 值,但在 BaseFragment 中却抛了异常!这里其实看需求来写,如果你认为你的实现都必须要实现 LCE ,那么就直接抛出,这样运行的时候就可以直接看出问题了,如果没必要的话打印个 log 值知道就可以了,没什么特别的深意。

LCE 布局

上面 BaseActivity 和 BaseFragment 中都提到的布局还没写呢!接下来写下布局吧:

一个一个来吧,先来没有内容的布局吧!

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/wall"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerInParent="true"android:orientation="vertical"><ImageViewandroid:layout_width="@dimen/dp_80"android:layout_height="@dimen/dp_80"android:layout_gravity="center_horizontal"android:src="@drawable/no_content_image" /><TextViewandroid:id="@+id/noContentText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginTop="@dimen/dp_20"android:layout_marginBottom="@dimen/dp_20"android:textSize="@dimen/sp_13"android:textColor="@color/secondary_text"tools:text="没有更多内容了"/></LinearLayout></RelativeLayout>

再来没有网络的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/badNetworkRootView"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/wall"android:focusable="true"android:foreground="?android:selectableItemBackground"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerInParent="true"android:orientation="vertical"><ImageViewandroid:layout_width="@dimen/dp_74"android:layout_height="@dimen/dp_88"android:layout_gravity="center_horizontal"android:src="@drawable/bad_network_image" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginTop="@dimen/dp_20"android:layout_marginBottom="@dimen/dp_20"android:text="@string/bad_network_view_tip"android:textColor="@color/secondary_text"android:textSize="@dimen/sp_13" /></LinearLayout></RelativeLayout>

接下来是加载错误的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:background="@color/wall"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerInParent="true"android:orientation="vertical"><ImageViewandroid:layout_width="@dimen/dp_74"android:layout_height="@dimen/dp_88"android:layout_gravity="center_horizontal"android:src="@drawable/bad_network_image" /><TextViewandroid:id="@+id/loadErrorText"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:layout_marginTop="@dimen/dp_20"android:layout_marginBottom="@dimen/dp_20"android:textColor="@color/secondary_text"android:textSize="@dimen/sp_13"tools:text="加载失败了" /></LinearLayout></RelativeLayout>

还有加载中的布局:

<?xml version="1.0" encoding="utf-8"?>
<ProgressBarxmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/loading"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_margin="@dimen/dp_64"android:layout_gravity="center"android:indeterminate="true" />

最后需要把这几个都合成到一个布局中:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><include layout="@layout/loading"android:visibility="gone"/><includeandroid:id="@+id/noContentView"layout="@layout/no_content_view"android:layout_width="match_parent"android:layout_height="match_parent"android:visibility="gone"/><includeandroid:id="@+id/badNetworkView"layout="@layout/bad_network_view"android:layout_width="match_parent"android:layout_height="match_parent"android:visibility="gone"/><includeandroid:id="@+id/loadErrorView"layout="@layout/load_error_view"android:layout_width="match_parent"android:layout_height="match_parent"android:visibility="gone"/></FrameLayout>

第二步——使用BaseActivity

这一块本来想写下首页来着,但是想了想东西太多了,所以挑选了一个不需要联网的一个页面——浏览历史,这一个页面既继承了 BaseActivity,又有无内容、加载中、有内容等状态的切换,所以比较合适。

先来看一下页面的布局吧:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".view.profile.history.BrowseHistoryActivity"><com.zj.core.util.TitleBarandroid:layout_width="match_parent"android:layout_height="wrap_content"app:backImageVisiable="true"app:titleName="浏览历史" /><FrameLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"><com.scwang.smartrefresh.layout.SmartRefreshLayoutandroid:id="@+id/historySmartRefreshLayout"android:layout_width="match_parent"android:layout_height="match_parent"><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/historyRecycleView"android:layout_width="match_parent"android:layout_height="match_parent" /></com.scwang.smartrefresh.layout.SmartRefreshLayout><include layout="@layout/layout_lce" /></FrameLayout></LinearLayout>

布局需要注意的是要把 layout_lce 写进去,layout_lce 就是咱们刚才编写的状态的布局,TitleBar 是我自定义的一个头布局,可设置标题、左边按钮、右边按钮,按钮的点击事件、图片或者问题都可以直接进行设置,大家可以进入 Github 中自行下载进行使用。

由于这个页面横竖屏无需做处理,所以只写一个页面即可。

布局写完了,下面就可以开始正式使用 BaseActivity 了:

class BrowseHistoryActivity : ArticleCollectBaseActivity() {private lateinit var articleAdapter: ArticleAdapterprivate var page = 1override fun getLayoutId(): Int {return R.layout.activity_browse_history}override fun initView() {historyRecycleView.layoutManager = LinearLayoutManager(this)articleAdapter = ArticleAdapter(this,R.layout.adapter_article,// viewModel.articleList, //数据源false)articleAdapter.setHasStableIds(true)historyRecycleView.adapter = articleAdapterhistorySmartRefreshLayout.apply {setOnRefreshListener { reLayout ->reLayout.finishRefresh(measureTimeMillis {page = 1// getArticleList() //加载数据}.toInt())}setOnLoadMoreListener { reLayout ->val time = measureTimeMillis {page++// getArticleList() //加载数据}.toInt()reLayout.finishLoadMore(if (time > 1000) time else 1000)}}}override fun initData() {// getArticleList() //加载数据}companion object {fun actionStart(context: Context) {val intent = Intent(context, BrowseHistoryActivity::class.java)context.startActivity(intent)}}}

上面的代码就是使用BaseActivity,大家也可以看到,和正常使用 Activity 基本一致,只不过更加简洁了而已,最下面的伴生方法是给了其他类跳转到当前类的一个入口,这里看不出优势,但如果需要传其他参数的话效果就很好了,可以有效避免传错参数。

上面类还有一些内容没写完,剩下的是 MVVM 的内容,在下一个模块说。

第三步——使用MVVM

相信看过我之前两篇文章的老司机们已经会使用了,再来回顾一下吧!

VM 之前也说过,不是 ViewModel 但也是,不懂的可以去看下之前的文章。来看下 ViewModel 吧:

class BrowseHistoryViewModel(application: Application) : AndroidViewModel(application) {private val pageLiveData = MutableLiveData<Int>()val articleList = ArrayList<Article>()val articleLiveData = Transformations.switchMap(pageLiveData) { page ->BrowseHistoryRepository(application).getBrowseHistory(page)}fun getArticleList(page: Int) {pageLiveData.value = page}}

是不是很简单,ViewModel + LiveData,就是这样,很简单是不是!

这里需要注意下使用到了 AndroidViewModel 。咱们平时使用的都是 ViewModel,有时候为了获取 Context 还需要单独传下参数,而 ViewModel 传参数又很麻烦,还需要使用 Factory 来传递,这种情况就可以使用 AndroidViewModel 了,可以直接继承进行使用,用的时候和之前一样就可以:

private val viewModel by lazy { ViewModelProvider(this).get(BrowseHistoryViewModel::class.java) }

是不是又 Get 到一个知识点,快记下来!

刚才的代码中在获取数据的地方都注释了,现在来看下吧!

    private fun getArticleList() {if (viewModel.articleList.size <= 0) {startLoading()}viewModel.getArticleList(page)}override fun initData() {viewModel.articleLiveData.observe(this, {if (it.isSuccess) {val articleList = it.getOrNull()if (articleList != null) {loadFinished()if (page == 1 && viewModel.articleList.size > 0) {viewModel.articleList.clear()}viewModel.articleList.addAll(articleList)articleAdapter.notifyDataSetChanged()} else {showLoadErrorView()}} else {if (viewModel.articleList.size <= 0) {showNoContentView("当前无历史浏览记录")} else {showToast("没有更多数据")loadFinished()}}})getArticleList()}

这段代码信息量就比较大了,老司机们应该看到了刚才 BaseActivity 的方法:startLoading()、loadFinished()、showLoadErrorView()、showNoContentView("")等,其实原理很简单,根据数据的状态进行显示不同的页面即可。

再来看看 BrowseHistoryRepository 的代码吧:

class BrowseHistoryRepository(context: Context) {private val browseHistoryDao = PlayDatabase.getDatabase(context).browseHistoryDao()/*** 获取历史记录列表*/fun getBrowseHistory(page: Int) = fire {val projectClassifyLists = browseHistoryDao.getHistoryArticleList((page - 1) * 20,HISTORY)if (projectClassifyLists.isNotEmpty()) {Result.success(projectClassifyLists)} else {Result.failure(RuntimeException("response status is "))}}}

到这里就很清晰了,Activity 用来展示页面,Repository 用来获取数据,ViewModel 用来处理数据和暂时保存数据以供 Activity 使用。

数据库肯定使用的是 Room ,这里要提一下,没用过 Room 的一定要使用下,如果你使用的是 Kotlin 的话更要使用了,Room 搭配上协程后简直不要太香!像上面的代码一样一行代码直接出结果,也无需进行线程的切换,因为这本来就是协程的擅长之处嘛!

总结

本来想写的东西很多,但是下笔却不知道如何进行描述,也怪自己,之前每个月能写几篇文章锻炼一下,现在一个月也就写一篇左右,是自己太懒了,忙不是借口,就是懒。。。

这一篇文章只是一个概览,告诉大家一些看着神秘的东西到底是啥,该怎样使用,下一篇文章带大家看一看项目的首页是怎样一步一步搭建起来的。

MVVM 我感觉其实没有那么神秘,只是在工作中没有合适的项目用来练手,理解起来有些生疏,其实相对于 MVP 和 MVC 来说逻辑上更加清晰也更加方便了,毕竟有官方的 JetPack 来加持,肯定很香!文章虽然不长但也不短,还有好多细节没写到,大家如果有任何疑问,可以在评论区告诉我,或者是想让我写哪方面的内容也可以在评论区告诉我,以后我要坚持一个月最少两篇博客的更新!!!如果对大家有帮助的话别忘记三连,感谢

玩安卓从 0 到 1 之总体概览相关推荐

  1. 玩安卓从 0 到 1 之架构思考

    前言 这篇文章是这个系列的第四篇文章了,下面是前三篇文章: 1.玩安卓从 0 到 1 之总体概览 2.玩安卓从 0 到 1 之项目首页 3.玩安卓从 0 到 1 之首页框架搭建. 按照惯例,放一下 G ...

  2. 玩安卓从 0 到 1 之项目总结

    玩安卓从 0 到 1 之项目总结 前言 这篇文章是这个系列的第七篇文章了,也是我准备写的这个系列的最后一篇文章.下面是前六篇文章: 1.玩安卓从 0 到 1 之总体概览 2.玩安卓从 0 到 1 之项 ...

  3. 玩安卓从 0 到 1 之列表一键置顶

    前言 系列文章 这篇文章是这个系列的第六篇文章了,下面是前五篇文章: 1.玩安卓从 0 到 1 之总体概览 2.玩安卓从 0 到 1 之项目首页 3.玩安卓从 0 到 1 之首页框架搭建. 4.玩安卓 ...

  4. 玩安卓从 0 到 1 之首页框架搭建

    前言 这篇文章是这个系列的第三篇文章了,前两篇文章分别是玩安卓从 0 到 1 之总体概览和玩安卓从 0 到 1 之项目首页. 一开始想的是一篇文章搞定,从项目的搭建到完成把所有的知识点写一遍,努力不做 ...

  5. 荣耀7x Android8,荣耀8/畅玩7X确认升级安卓8.0

    原标题:荣耀8/畅玩7X确认升级安卓8.0 IT之家12月11日消息 据外媒报道,日前,荣耀总裁赵明在接受塞尔维亚一家在线媒体采访时透露,适配荣耀8.荣耀8 Pro(荣耀V9国际版)的安卓8.0系统目 ...

  6. 荣耀畅玩7X安装鸿蒙系统,华为荣耀畅玩7X EMUI8.0回退到EMUI5.0教程(安卓8.0降级7.0)...

    华为荣耀畅玩7X EMUI8.0回退到EMUI5.0教程(安卓8.0降级7.0).现在有一部分华为荣耀畅玩7X手机用户通过官方申请升级到EMUI8.0版本了,但是新版本在尝鲜之后,大家会觉得手机部分功 ...

  7. 《云阅2.0》一款同时看玩安卓和干货集中营资讯的App

    一.云阅2.0 <云阅>一个仿网易云音乐UI,使用Gank.Io及豆瓣Api开发的开源项目 在云阅发布第一版之后,大约经过了近两年的时间,不断的更新迭代,现在已经完成了2.0,相比第一版它 ...

  8. 荣耀畅玩4x android 6,华为荣耀4X EMUI4.0回退EMUI3.1教程 安卓6.0降级5.1方法

    华为荣耀4X EMUI4.0降级教程不知道有没有机友需要的,而这个教程是很多新手在寻找的吧,升级到华为荣耀4X EMUI4.0之后,如果想降级到EMUI3.1稳定版,看本教程还是挺有必要的,而具体的操 ...

  9. android创建房间界面,自由之战3月25日安卓1.0.5更新_开房间玩法开启_蚕豆网新闻...

    自由之战在3月25日将要迎来新一轮的更新,在更新之后,玩家将可以游戏中体验到全新的开房间约战模式.此次模式开启后,玩家终于可以在游戏中邀请到自己的好基友PK一番了,下面就让我们来围观下3月25日更新内 ...

最新文章

  1. 2020-08-24绘制ROC   PR曲线 核心方法总结 ,计算AUC核心方法
  2. 中国首份自动驾驶路测报告:自主车企全面落后
  3. MySQL 创建用户与修改密码
  4. Node.js包管理器Yarn的入门介绍与安装
  5. python搭建django
  6. 美团点评容器平台HULK的调度系统
  7. 【报告分享】2020年金融科技十大关键词.pdf(附下载链接)
  8. 索爱确认2月13日发布Xperia Play
  9. Python+tkinter应用程序设置背景图片
  10. 猜名人读心术作业C语言答案,谁能说说这个读心术的原理?太他妈邪乎了。。。...
  11. linux oracle 失败怎么办,Linux开机报错unable to load selinux policy怎么办?
  12. HTML(hiden控件 readonly disabled)(maxlength属性)(id属性重点)
  13. UWB定位系统会存在定位误差吗?
  14. ubuntu命令行查看dns_linux命令,查看dns服务器的状态,查看dhcp服务器的状态
  15. 计算机应用计术,计算机应用技术.ppt
  16. android 自定义抽屉,android – 动作栏抽屉切换自定义图标
  17. 遗传算法优化BP神经网络的实例
  18. 基于相移法的结构光三维测量技术
  19. crypto-CSTPC(羊城杯 2020)
  20. Flutter 遇到的问题整理

热门文章

  1. 抽奖随机滚动_原来抽奖不是凭运气!两个技巧,让你在抽奖环节独占鳌头
  2. jQuery第二章选择器
  3. 知网的caj怎么保存成pdf
  4. 订单和订单详情的一对一 ,一对多映射
  5. Citrix ADC 13.0 下载 百度网盘 按您的方式进行应用交付
  6. Outlook Express 收发邮件出现0x800CCC0F错误代码解决方法
  7. 0.OpenCV可视化(Viz)——Viz环境的配置
  8. windows 介绍
  9. sd卡格式化后还能恢复吗?恢复小技巧分享!
  10. python text函数_python可视化text()函数使用详解