美文网首页Android
Andorid利用编译时注解,使用LiveData+DataBi

Andorid利用编译时注解,使用LiveData+DataBi

作者: When_Young | 来源:发表于2020-05-26 17:05 被阅读0次

未经本人同意不得转载

前言

最近有空研究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源码
欢迎大家指出错误和改进

另外此项目我会持续更新

项目地址

谢谢大家观看

相关文章

  • Andorid利用编译时注解,使用LiveData+DataBi

    未经本人同意不得转载 前言 最近有空研究google发布的jectPack,发现自己所有的MVP架构有点跟不上时代...

  • andorid注解使用

    最近使用retrofit时,发现其使用了大量的注解,联想以前的butterknife等,发现注解也是很好用与有意思...

  • java注解

    使用 定义注解 使用注解 反编译 反编译MyAnnotation.class 反编译注解MyAnnotation$...

  • java注解

    内容: 注解的定义 注解的语法 源码级别的注解的使用 运行时注解的使用 编译时注解的使用 Android 预置的注...

  • Android编译时注解

    Android编译时注解 [TOC] 前言 相信大家经常都使用到注解,如果使用过AndroidAnnotation...

  • android源码中编译APK

    andorid源码中编译apk,代码中引入新的包编译不过,需要在andorid.mk中添加相对应的包使用过程参考源...

  • Android笔记-注解

    一、注解 1.1注解分类 1.2、示例 1、定义注解 2、使用注解 3、定义运行时注解 4、定义编译时注解 @Re...

  • 自定义注解

    注解分类 1、代码注解2、编译时注解3、运行时注解 注解范例 使用注解的类 注解解析类 注解实战 需求1、有一张用...

  • 注解学习笔记

    什么是注解注解分类注解作用分类 元注解 Java内置注解 自定义注解自定义注解实现及使用编译时注解注解处理器注解处...

  • Android使用AnnotationProcessor处理编译

    Android中处理编译时注解使用AnnotationProcessor,下面我们来看下如何使用Annotatio...

网友评论

    本文标题:Andorid利用编译时注解,使用LiveData+DataBi

    本文链接:https://www.haomeiwen.com/subject/xlpmahtx.html