未经本人同意不得转载
前言
最近有空研究google发布的jectPack,发现自己所有的MVP架构有点跟不上时代了,就封装了一套极简的MVVM框架。其中使用 LiveData,DataBinding,ViewModel,LifeCycle,并且使用编译时注解,封装了一套仿Butterknife初始化框架,废话不多说,上代码。
怎么使用
在项目build.gradle文件中添加
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
依赖所有模块
如果使用了kotlin项目,必须添加kapt 'com.github.WenYong1994.WenYApp:wheny_compiler_lib:1.1.4'
dependencies {
...
implementation 'com.github.WenYong1994:WenYApp:1.1.4'
annotationProcessor 'com.github.WenYong1994.WenYApp:wheny_compiler_lib:1.1.4'
kapt 'com.github.WenYong1994.WenYApp:wheny_compiler_lib:1.1.4'
...
//需要正常使用的第三方依赖
//添加lifeCycler
implementation group: 'androidx.lifecycle', name: 'lifecycle-common-java8', version: '2.2.0'
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
//添加ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
//添加recyclerView
implementation "com.android.support:recyclerview-v7:${recyclerview_libary_version}"
//添加网络okhttp3 和RxJava
implementation "com.squareup.okhttp3:okhttp:4.2.0"
implementation "io.reactivex.rxjava2:rxjava:2.1.0" // 必要rxjava2依赖
implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
implementation "io.reactivex.rxjava2:rxandroid:2.0.1" // 必要rxandrroid依赖,切线程时需要用到
implementation 'com.squareup.retrofit2:retrofit:2.3.0' // 必要retrofit依赖
implementation 'com.squareup.retrofit2:converter-gson:2.3.0' // 必要依赖,解析json字符所用
implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1' //非必要依赖, log依赖,如果需要打印OkHttpLog需要添加
//
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' // 必要依赖,和Rxjava结合必须用到,下面会提到
}
注意
如果想使用某一个部分功能请看下面的注释
{
implementation 'com.github.WenYong1994:WenYApp:1.1.4'//依赖所有模块
// implementation 'com.github.WenYong1994.WenYApp:whenynetlibrary:1.1.4'//网络相关模块
//
// implementation 'com.github.WenYong1994.WenYApp:wheny_annotation_apilib:1.1.4'//MVVM相关注解初始化
// implementation 'com.github.WenYong1994.WenYApp:wheny_annotation_lib:1.1.4'//MVVM相关注解初始化
//
// implementation 'com.github.WenYong1994.WenYApp:whenydblibary:1.1.4'//数据库相关模块
//
// implementation 'com.github.WenYong1994.WenYApp:whenylibrary:1.1.4'//工具类相关模块
annotationProcessor 'com.github.WenYong1994.WenYApp:wheny_compiler_lib:1.1.0'//如果使用到MVVM注解初始化 必须添加
}
更多使用方式请在文章结尾的传送门去GitHub项目源码查看示例
先来简单介绍一下本框架的每个组件
MVVM
MVVM是一套MVP的升级,将MVP里面的P层替换为VM层,其他层所负责的功能不变化。这里的VM主要是使用了DataBinding来进行数据绑定。并在VM里面处理交互逻辑
LiveData
LiveData是一个可以感知生命周期变化,并且可监听的数据
简单示例
class LoginActivity : AppCompatActivity() {
var testLive = MutableLiveData<String>("")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var layoutId= R.layout.activity_login
setContentView(layoutId)
testLive.observe(this, Observer<String> {
Log.e("testLive",it)
})
test_2.setOnClickListener {
testLive.postValue("test")
}
}
override fun onDestroy() {
super.onDestroy()
}
}
这里testLive调用observe方法时会传入一个LifeCycle进去。只有当LifeCycle处于可见状态时Observer才会被调用,并且会储存最后一次数据变化,直到LifeCycle变为可见时Observer会被调用
DataBinding
DataBinding主要是负责数据绑定(缺点,由于使用了伪JAVA代码来绑定数据,出错就很难定位错误的具体位置,但是熟悉之后还是比较好定位问题)就不一 一赘述了
例子:
使用在项目的build.gradle文件里开启 DataBinding
android{
...
dataBinding {
enabled = true
}
...
}
新建VM类,TestVm,这里继承BaseObservable是让数据驱动UI更新
import android.view.View
import androidx.databinding.BaseObservable
import androidx.databinding.Bindable
import androidx.databinding.library.baseAdapters.BR
class TestVm : BaseObservable() {
@Bindable
var txt1 = "1"
set(value) {
field = value
notifyPropertyChanged(BR.txt1)
}
fun onClickTest(view: View){
txt1 = "test"
}
}
xml中编写,dataBinding的双向绑定使用 android:text="@={testVm.txt1}"
<?xml version="1.0" encoding="utf-8"?>
<layout 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"
tools:context=".mvvm.view.databinding.TestDataBindingActivity">
<data>
<variable
name="testVm"
type="com.example.wenyapplication.mvvm.view.databinding.TestVm" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{testVm.txt1}"
tools:text="2"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="测试"
android:onClick="@{(view)->testVm.onClickTest(view)}"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={testVm.txt1}"
/>
</LinearLayout>
</layout>
Activity中
class TestDataBindingActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_test_data_binding)
val contentView = DataBindingUtil.setContentView<ActivityTestDataBindingBinding>(this, R.layout.activity_test_data_binding)
contentView.testVm = TestVm()
}
}
更多的自定义双向绑定就请同学自行度娘
ViewModel
ViewModel是google推出的一款为规划MVVM框架所提出的。他主要功能是既充当了MVVM中VM的角色,也有其本来的功能,如生命周期会比Activity长(所以Google不建议 ViewModel持有任何Activity,Fragment,避免内存泄漏,只有纯业务代码,也方便单元测试),当activity被系统销毁时,例如旋转屏幕时,ViewModel会储存数据,数据不会丢失。(有一些极端情况下,ViewModel也会被销毁,这就需要在OnSaveInstance里面来进行存储关键数据),在同一个Activity里面不同地方获取的一个类的ViewModel是单列的。所以可以用于fragment,dialog之间的通信。配合LiveData还可以同步数据变化(并且不用考虑activity的生命周期,LiveData的观察者会自动处理)
简单例子
var mLoginVm: LoginVm?=null
mLoginVm = ViewModelProviders.of(this).get(LoginVm::class.java)
LifeCycle
LifeCycle是一个让其他组件可以轻松的监听activity生命周期
lifecycle.addObserver(object : LifecycleEventObserver{
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
when (event){
Lifecycle.Event.ON_START->{
}
Lifecycle.Event.ON_DESTROY->{
}
}
}
})
重点来了
编译时注解
编译时注解是只会存在于项目编译时,并且项目在编译时会调用自定义Provessor来进行生成中间JAVA类,不清楚的同学可以在网上看看资料
本篇文章的灵魂来了
本着懒得习惯,能少一句代码是一句代码的宗旨,所以使用编译时注解模仿butterKnife的方式对ViewModel和DataBinding的初始化,并且在BaseViewModel中实现了LifeCycle的监听(注意这里不能让ViewModel持有lifecycle,而是让ViewModel实现DefaultLifecycleObserver,在Activity中调用lifecycle.addObserver)。
使用方式
加载wheny_annotation_lib、wheny_annotation_apilib、wheny_compiler_lib,
这里 wheny_annotation_lib 和 wheny_annotation_apilib 是javaLib
这三个model的依赖方式是
wheny_annotation_apilib 依赖 wheny_annotation_lib
wheny_compiler_lib 依赖 wheny_annotation_lib
需要使用的model 依赖 wheny_annotation_lib和wheny_annotation_apilib
具体使用方式,使用注解的方式进行对ViewModel的初始化
class LoginActivity : AppCompatActivity() {
@InjectViewModel(dataBindFieldName = "LoginVm")
var mLoginVm: LoginVm?=null
@InjectViewModel(dataBindFieldName = "LoginVm2",needFactory = true)
var mLoginVm2: LoginVm2?=null
@InjectViewModel(needFactory = true)
var mLoginVm3: LoginVm2?=null
// var testLive = MutableLiveData<String>("")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
var layoutId= R.layout.activity_login
// setContentView(layoutId)
val loginBinding = ViewModelInjector.inject<ActivityLoginBinding>(this, layoutId)
ViewModelInjector.injectByFactory(this, LoginVm2Factory("ssss", App.getApplication()),"mLoginVm2",loginBinding)
ViewModelInjector.injectByFactory(this, LoginVm2Factory("33333", App.getApplication()),"mLoginVm3",loginBinding)
mLoginVm = ViewModelProviders.of(this).get(LoginVm::class.java)
// testLive.observe(this, Observer<String> {
// Log.e("testLive",it)
// })
// test_2.setOnClickListener {
// testLive.postValue("test")
// }
// lifecycle.addObserver(object : LifecycleEventObserver{
// override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
// when (event){
// Lifecycle.Event.ON_START->{
//
// }
// Lifecycle.Event.ON_DESTROY->{
//
// }
// }
// }
// })
}
override fun onDestroy() {
super.onDestroy()
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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"
tools:context=".mvvm.view.login.LoginActivity">
<data>
<variable
name="LoginVm"
type="com.example.wenyapplication.mvvm.view.login.LoginVm" />
<variable
name="LoginVm2"
type="com.example.wenyapplication.mvvm.view.login.LoginVm2" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="账号"
android:text="@={LoginVm2.account}"
/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="密码"
android:text="@={LoginVm2.pwd}"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{()->LoginVm2.doLogin()}"
android:text="登录"
/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{(view) -> LoginVm2.test()}"
android:text="test"
/>
<Button
android:id="@+id/test_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="test2"
/>
<TextView
android:id="@+id/tvMsg"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{LoginVm2.loginBean.message}"
/>
<com.example.wenyapplication.view.InputTextLine
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:single_input_title="sassss"
app:single_input_text="@={LoginVm2.loginBean.message}"
/>
</LinearLayout>
</layout>
这里LoginVm没有实际意义只是表明 一个Activity可以有多个VM,处理不同的业务逻辑
LoginVm2是带有构造参数的ViewModel。初始化的时候必须使用Factory的方式
mLoginVm2 = LoginVm2Factory("ssss", App.getApplication()).create(LoginVm2::class.java)
loginBinding.loginVm2=mLoginVm2
LoginVm2
class LoginVm2(string:String,app: Application) : BaseAndroidViewModel(app){
var loginBean = MutableLiveData<LoginBean>()
var account:String = ""
var pwd : String =""
var msg :String = string
set(value) {
field = value
}
fun doLogin() {
var json = "{\"channel\":\"android\",\"data\":\"{\\\"password\\\":\\\"123456\\\",\\\"os\\\":\\\"android\\\",\\\"uid\\\":0,\\\"username\\\":\\\"kred001\\\"}\",\"salt\":\"064797\",\"service\":\"functionaryLoginService\",\"test\":true,\"time\":1590024509,\"version\":\"1.0\"}"
val requestBody = json.toRequestBody("application/json; charset=utf-8;".toMediaTypeOrNull())
val compose = RetrofitServiceManager.getInstance().create(ApiService::class.java)
.login(requestBody)
.flatMap {
SystemClock.sleep(2000)
Flowable.just(it.data.toString())
}
.compose(RxSchedulers.io_main())
.subscribe({
Log.e("doLogin","doLogindoLogindoLogindoLogindoLogindoLogindoLogin");
loginBean.value= LoginBean(0, it)
},{
loginBean.value=LoginBean(0,it.message.toString())
})
addDisposable(compose)
}
fun test(){
Handler().postDelayed({ loginBean.value?.message?.postValue(loginBean.value?.message?.value+"1") }, 2000)
}
}
class LoginVm2Factory(private val string:String,private val app: Application) : ViewModelProvider.NewInstanceFactory(){
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return LoginVm2(string, app) as T
}
}
这里的ViewModelInjector还兼容了在Fragment中的初始化
class TestFragment : Fragment() {
@InjectViewModel(dataBindFieldName = "testFraVm")
var testFraVm:TestFraVm?=null
@InjectViewModel
var mainVm: MainVm? = null
var rootView :View?= null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
var dt = ViewModelInjector.inject<FragmentTestBinding>(this,activity as AppCompatActivity,inflater,R.layout.fragment_test,container)
rootView = dt.root
testFraVm?.test?.postValue("212112124")
return rootView
}
override fun onPause() {
super.onPause()
}
}
fragment的xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".mvvm.view.main.TestFragment">
<data>
<variable
name="testFraVm"
type="com.example.wenyapplication.mvvm.view.main.TestFraVm" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#fa1aaf"
>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@{testFraVm.test}"
tools:text="ahhhahhahah"
/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="sfadfsafsafss"
/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="@{()-> testFraVm.testCl()}"
/>
</LinearLayout>
</layout>
MainActivity
public class MainActivity extends AppCompatActivity {
@InjectViewModel(dataBindFieldName = "mainVm")
MainVm mainVm;
@InjectViewModel(dataBindFieldName = "testFraVm")
TestFraVm testFraVm;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewModelInjector.inject(this, R.layout.activity_main);
getSupportFragmentManager().beginTransaction().add(R.id.main_fra,new TestFragment()).commit();
getSupportFragmentManager().beginTransaction().add(R.id.main_fra_2,new TestFragment2()).commit();
}
public void setMainVm(MainVm mainVm) {
this.mainVm = mainVm;
}
public void setTestFraVm(TestFraVm testFraVm) {
this.testFraVm = testFraVm;
}
}
MainActivity的xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".mvvm.view.main.MainActivity"
>
<data>
<variable
name="mainVm"
type="com.example.wenyapplication.mvvm.view.main.MainVm" />
<variable
name="testFraVm"
type="com.example.wenyapplication.mvvm.view.main.TestFraVm" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<Button
android:id="@+id/btn_databinding"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{mainVm.dataBindingBtnTxt}"
android:onClick="@{(view) -> mainVm.testDataBinding(view)}"
/>
<Button
android:id="@+id/btn_mvvm"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{mainVm.loginVmTxt}"
android:onClick="@{(view) -> mainVm.testLogin(view)}"
/>
<Button
android:id="@+id/test_net"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{mainVm.testNetTxt}"
/>
<Button
android:id="@+id/test_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{mainVm.testListTxt}"
android:onClick="@{(view) -> mainVm.testList(view)}"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{testFraVm.test}"
/>
<FrameLayout
android:id="@+id/main_fra"
android:layout_width="match_parent"
android:layout_height="200dp"/>
<FrameLayout
android:id="@+id/main_fra_2"
android:layout_width="match_parent"
android:layout_height="200dp"/>
</LinearLayout>
</layout>
重点 这里activity中的mainVm和fragment的mainVm是同一个对象,所以轻松实现fragment和activity的通信
这里在Activity中使用方式是
这里使用注解来注解变量,
dataBindFieldName 指的是在layout里面 variable的name标签,如果不填写就不会将ViewModel绑定到ViewDataBinding中
needFactory指的 是否需要使用Factory来创建ViewModel
@InjectViewModel(dataBindFieldName = "mainVm")
MainVm mainVm;
注意这里如果使用Factory来创建ViewModel,在同一个activity中同一个ViewModel类就不会是同一个对象
@InjectViewModel(dataBindFieldName = "LoginVm")
var mLoginVm: LoginVm?=null
@InjectViewModel(dataBindFieldName = "LoginVm2",needFactory = true)
var mLoginVm2: LoginVm2?=null
@InjectViewModel(needFactory = true)
var mLoginVm3: LoginVm2?=null
ViewModelInjector.injectByFactory(this, LoginVm2Factory("ssss", App.getApplication()),"mLoginVm2",loginBinding)
ViewModelInjector.injectByFactory(this, LoginVm2Factory("33333", App.getApplication()),"mLoginVm3",loginBinding)
这里是使用Factory来初始化ViewModel,注意这里的mLoginVm2,和mLoginVm3 是被注解的成员变量的变量名
否则成员变量在执行之后不会被赋值
如果是java语法,必须实现这个被注解的变量的set方法
如
public void setMainVm(MainVm mainVm) {
this.mainVm = mainVm;
}
最后大家如果想看看源码的可以直接去github源码
欢迎大家指出错误和改进
另外此项目我会持续更新
谢谢大家观看