Android Architecture Components 是谷歌在Google I/O 2017發佈一套幫助開發者解決Android 架構設計的方案。裏面包含了兩大塊內容:
1.生命週期相關的 Lifecycle-aware Components
2.數據庫解決方案 Room
本文主要爲如何使用Lifecycle && LiveData && ViewModel方式搭建MVVM
一、Lifecycle
對整個Activity生命週期進行監聽需要繼承自LifecycleObserver接口
class MainLifecycleObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun onCreate(owner: LifecycleOwner){
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun onStart(owner: LifecycleOwner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
fun onResume(owner: LifecycleOwner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
fun onPause(owner: LifecycleOwner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun onStop(owner: LifecycleOwner) {
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
fun onDestroy(owner: LifecycleOwner) {
}
/**
* 所有生命週期
*/
@OnLifecycleEvent(Lifecycle.Event.ON_ANY)
fun onAny(owner: LifecycleOwner, event: Lifecycle.Event) {
}
}
在activity或者fragmetn中註冊
lifecycle.addObserver(MainLifecycleObserver())
二、LiveData+ViewModel
1.LiveData
LiveData 是一種持有可被觀察數據的類。和其他可被觀察的類不同的是,LiveData 是有生命週期感知能力的,這意味着它可以在 activities, fragments 或者 services 生命週期是活躍狀態時更新這些組件。那麼什麼是活躍狀態呢?lifecycle 中提到的 STARTED 和 RESUMED就是活躍狀態,只有在這兩個狀態下LiveData 是會通知數據變化的。
使用 LiveData,必須配合實現了 LifecycleOwner 的對象。當對應的生命週期對象 DESTORY 時,才能移除觀察者。對於 activities 和 fragments 非常重要,因爲他們可以在生命週期結束的時候立刻解除對數據的訂閱,從而避免內存泄漏等問題。
2.ViewModel
ViewModel設計的目的就是存放和處理和UI相關的數據,並且這些數據不受配置變化(Configuration Changes,例如:旋轉屏幕,組件被系統回收)的影響。
注意:ViewModel 絕對不可以引用 view、Lifycycle 或者任何持有 context 的類。
2.1viewModelScope
AndroidX Lifecycle v2.1.0 在 ViewModel 中引入 viewModelScope
,當 ViewModel 被銷燬時它會自動取消協程任務
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc02'
使用示例
class MainViewModel : ViewModel() {
fun launchDataLoad() {
viewModelScope.launch {
sortList()
}
}
suspend fun sortList() = withContext(Dispatchers.Default) {
}
}
3.示例
MutableLiveData + ViewModel基類
abstract class MutableViewModel<T> : ViewModel() {
private var bean = MutableLiveData<T>()
fun getLiveData(): MutableLiveData<T> {
return bean
}
fun setLiveData(t: T) {
bean.value = t
}
/**
* 異步使用
*/
fun postLiveData(t: T) {
bean.postValue(t)
}
}
單個數據ViewModel
class MainViewModel : BaseViewModel<String>() {}
使用ViewModelProvider(this).get(MainViewModel::class.java)需要引入implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-rc02',當然你也可以自定義Factory
class MainActivity : AppCompatActivity() {
private var i = 0
private lateinit var mainViewModel: MainViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_architecture)
//綁定組件的聲明週期
lifecycle.addObserver(MainLifecycleObserver())
//獲取ViewModel,implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0-rc02'
mainViewModel = ViewModelProvider(this).get(MainViewModel::class.java)
//監聽
mainViewModel.getLiveData().observe(this, Observer {
textView.text = it
})
button.setOnClickListener {
mainViewModel.setLiveData("${i++}")
}
}
}
多個數據ViewModel
data class MainBean(var msg:String,var frequency:Int) {}
class MainViewModel : BaseViewModel<MainBean>() {}
使用MutableLiveData,那麼整個實體類或者數據類型變化後才通知更新,不會因爲某個字段變化而更新
//監聽
mainViewModel.getLiveData().observe(this, Observer {
textView.text = it.msg
})
button.setOnClickListener {
mainViewModel.setLiveData(MainBean("第${i++}",i++))
}
三、MVVM
上面簡單介紹了一下Architecture Components模式的MVVM,接下來是登陸的使用示例
class LoginActivity : BaseActivity<LoginViewModel>(){
override fun getLayoutResID(): Int {
return R.layout.activity_login
}
override fun addObserver(): LifecycleObserver? {
return null
}
override fun getViewModel(): Class<LoginViewModel> {
return LoginViewModel::class.java
}
override fun initView() {
viewModel.userAccount.observe(this, Observer {
viewModel.getCode()
})
viewModel.code.observe(this, Observer {
if (viewModel.login()){
startActivity(PermissionsActivity::class.java)
}
})
}
override fun initEvent() {
setEditTextMutableLiveData(et_userAccount,viewModel.userAccount)
setEditTextMutableLiveData(et_code,viewModel.code)
}
}
爲了簡化代碼封裝了一個MVVM之BaseActivity,使用方式很簡單,傳入一個佈局id和一個ViewModel,LifecycleObserver是爲了監聽組件的生命週期,可以傳null
setEditTextMutableLiveData是封裝的一個方法,目的是EditText的值動態賦予MutableLiveData
/**
* EditText的值動態賦予MutableLiveData
*/
fun setEditTextMutableLiveData(et: EditText, liveData: MutableLiveData<String>) {
et.addTextChangedListener(object : TextWatcher {
override fun afterTextChanged(s: Editable?) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
liveData.value = s.toString()
}
})
}
接下來看ViewModel的代碼
class LoginViewModel : ViewModel() {
//用戶賬號
var userAccount = MutableLiveData<String>()
//驗證碼
var code = MutableLiveData<String>()
init {
userAccount.value = ""
code.value = ""
}
/**
* 獲取驗證碼
*/
fun getCode(){
if (userAccount.value.equals("13800000001")){
showLongToast("獲取到驗證碼")
}
}
/**
* 登陸
*/
fun login():Boolean{
if (userAccount.value.equals("13800000001") && code.value.equals("123456")){
return true
}
return false
}
}
最後是測試代碼
class LoginActivityTest{
private lateinit var activity: LoginActivity
@get:Rule
var mActivityRule = ActivityScenarioRule(LoginActivity::class.java)
//在Test前初始化
@Before
fun init() {
mActivityRule.scenario.onActivity {
activity = it
}
}
@Test
fun login(){
val et_userAccount = Espresso.onView(ViewMatchers.withId(R.id.et_userAccount))
val et_code = Espresso.onView(ViewMatchers.withId(R.id.et_code))
et_userAccount.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
et_code.check(ViewAssertions.matches(ViewMatchers.isDisplayed()))
//typeText是在原先的基礎上輸入文本 ,replaceText是替換文本
et_userAccount.perform(ViewActions.typeText("1"), ViewActions.closeSoftKeyboard())
Thread.sleep(1000)
et_userAccount.perform(ViewActions.typeText("3"), ViewActions.closeSoftKeyboard())
Thread.sleep(1000)
et_userAccount.perform(ViewActions.typeText("13800000001"), ViewActions.closeSoftKeyboard())
Thread.sleep(1000)
et_userAccount.perform(ViewActions.replaceText("13800000001"), ViewActions.closeSoftKeyboard())
Thread.sleep(1000)
et_code.perform(ViewActions.replaceText("12345789"), ViewActions.closeSoftKeyboard())
Thread.sleep(1000)
et_code.perform(ViewActions.replaceText("123456"), ViewActions.closeSoftKeyboard())
Thread.sleep(1000)
}
}