常见架构模式
-
MVC:
- 在2011 2012时期广泛使用
-
为什么是MVC模式
-
由工程结构决定:
- res:作View 层,
- src中的Activity作控制层,所以是MVC架构
-
-
缺点:
- 承担View,控制,模型
- 过于臃肿
- 没有单一职责
- 耦合度高
- 扩展性不好
- 管理混乱
-
MVP:Activity只做View层,引入P层(Presenter)
-
特点:分层清晰
-
实现过程:View->P->View:
-
实例:
- 做登录功能,用户点击按钮后,Activity将信息提交给P层,P层在提交给服务器,然后将服务器返回的信息回馈给View层(Activity)接口回调
-
-
弊端:
- 接口地狱:凡是P层的模型数据需要返回给View层均通过接口回调
-
-
MVVM:Activity:View层;P层换成VM层(ViewModel层):VM层实现ViewModel会更好用,不实现也不存在
-
引用DataBinding实现V层与VM层的双向绑定
- 这是两条单向的路:点击V层会触发VM层;修改VM层会影响V层
-
问题:DataBinding属于MVVM上?
- DataBinding是一个工具集,独立于架构模式;
- 只不过大部分情况下,MVVM都会使用DataBinding;小部分情况下MVP也可以使用DataBinding;但是MVC基本就不用
- DataBinding比MVVM出现得早
-
JetPack组件库中的ViewModel和MVVM中的VM层是两回事
- 前者是组件库,是工具集;
- 后者是架构之一
-
那个设计模式最好?
-
没有最好,只有适合;
-
MVC:适合与小项目,一两个人的,在Activity中能处理好就行
-
MVP:项目太大(编译就有8分钟),需要分层非常清晰(开发的人太多了)
-
MVVM:界面更新频繁(网易云音乐,腾讯视频)
- 可以启用DataBinding+ViewModel+LiveData+LifeCycle实现数据驱动开发
-
-
DataBinding_Java版本实现数据驱动UI
-
设计思路:实现数据驱动UI
-
分层:
- Activity作为控制层
- JavaBean作为Model层
- 布局文件作为View层
-
实现效果:在Activity中开启对JavaBean属性修改,同时界面更新
-
-
环境准备:
-
打开dataBinding开关:两种打开方式
-
-
工程结构:
-
编写Model层(JavaBean):User类
-
继承BaseObservable类:这个类就拥有DataBinding的能力了
public class User extends BaseObservable
-
编写字段:待会就更新这个
private String name; private String pwd;
-
添加对应的get/set方法
-
在get方法上添加注解:@Bindable(这样子要规范一点)
-
在BR文件中生成对应属性的数值标记:用于一个整数值指定
-
在User中进行BR文件的引用:
-
-
在set方法内添加:notifyPropertyChanged
- 会自动生成BR文件(但是要等编译了才能用)
- APT技术:字节码插桩
-
-
完整代码:User
package com.derry.databinding_java; import androidx.databinding.BaseObservable; import androidx.databinding.Bindable; // Model层 public class User extends BaseObservable { private String name; private String pwd; public User(String name, String pwd) { this.name = name; this.pwd = pwd; } @Bindable // BR里面标记生成 name数值标记 public String getName() { return name; } public void setName(String name) { this.name = name; notifyPropertyChanged(BR.name); // APT又是主接处理器技术 BR文件,数据驱动UI } @Bindable // BR里面标记生成 pwd数值标记 public String getPwd() { return pwd; } public void setPwd(String pwd) { this.pwd = pwd; notifyPropertyChanged(BR.pwd); // APT又是主接处理器技术 BR文件 } }
-
-
使用DataBinding管理布局:管理布局中所有的控件
-
使用:将鼠标悬停在activity_main.xml文件的最外标签上,第一个提示
-
将布局文件分为两半:
- 不拆,会崩掉的
- Android系统是不认识的;
-
拆开后会增加一个tag的
-
上面一半是拿给DataBinding使用的,Android是无法解析的
- 动态注入了Tag:寻找控件
- 其实上面由activity_main.xml上一半生成的,但是也会有xml下面的
-
下面一半,用于Android解析,绘制View使用的
-
-
activity_main.xml布局拆分后的位置
-
上面一半:交给DataBinding进行布局管理的,这部分Android是不认识的
-
-
下面一半在这里:
-
-
布局文件代码展示:拆分后的activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <!-- layout是DataBinding 管理了 我们整个布局了 --> <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"> <data> <!-- 不要被迷惑了,不是反射 --> <!-- setUser函数 setAa --> <variable name="user" type="com.derry.databinding_java.User" /> </data> <!-- 上面的是 DataBinding 内部用的, 注意:Android View体系 不认识 --> <!-- Android View体系的 下面的所有内容, 拿给Android绘制 --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <!-- @= View -> Model 一向 --> <!-- EditText --> <EditText android:id="@+id/tv1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@={user.name}" android:textSize="50sp" /> <EditText android:id="@+id/tv2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@={user.pwd}" android:textSize="50sp" /> </LinearLayout> </layout>
-
-
编写控制层:MainActivity(使用DataBinding技术奖Model层与View层进行绑定)
-
实现效果:在子线程中,更新JavaBean(Model层)的属性,在View层(界面上可以看到)
-
实现细节:
-
加载布局文件activity_main.xml
final ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
-
跟ViewModel类似:
- 以布局为对象,自动生成一个类:ActivityMainBinding
- 点击这个类(CTRL+鼠标左键)就会跳转到对应的布局文件:activity_main.xml
-
-
建立绑定关系:
binding.setUser(user); // 必须要建立绑定关系,否则没有任何效果
-
子线程更新数据
new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); user.setName(user.getName() + "第"+i+"次"); // view.setText(text); user.setPwd(user.getPwd() + "第"+i+"次"); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start();
-
-
完整代码:MainActivity
package com.derry.databinding_java; import androidx.appcompat.app.AppCompatActivity; import androidx.databinding.DataBindingUtil; import android.os.Bundle; import com.derry.databinding_java.databinding.ActivityMainBinding; //Activity作控制 public class MainActivity extends AppCompatActivity { User user; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // setContentView(R.layout.activity_main); final ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main); user = new User("用户名更新:", "密码更新:"); binding.setUser(user); // 必须要建立绑定关系,否则没有任何效果 // Model(修改Model的数据) ---> View(UI的控件就DataBinding自动刷新) 一向 更新数据驱动UI new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(1000); //Model--->View:数据驱动UI user.setName(user.getName() + "第"+i+"次"); // view.setText(text); user.setPwd(user.getPwd() + "第"+i+"次"); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); // user.setName("888"); } }
-
-
运行效果:每间隔一秒,界面刷新一次
-
错误排查:
-
查布局:
- DataBinding使用APT技术会对布局进行全盘扫描,布局出错,处处报错
-
全盘加载
- 这个就很坑,有些时候DataBinding就是加载不出来,但是重启AS就好了
-
需要编译一下才行
-
-
DataBinding的弊端
-
之前是很卡,并且项目布局越大,越卡(宁愿用MVP陷入接口地狱,也不用DataBinding)
-
DataBinding实现双向绑定(Kotlin 版本)先实现数据驱动UI
-
分层:
- model层:StudentInfo
- 控制层:MainActivity
- View层:activity_main.xml
-
工程结构:
-
编写model层:StudentInfo
-
仿照java版本出错
-
继承BaseObservable类
-
为get添加注解
-
Kotlin中为每个属性都是内置了get/set方法的,因此重写get,然后添加注解,发现报错:注解是给函数和方法的,此时的get就只是一个表达式而已,这个注解就失效了,无法生成BR中的字段
-
-
尝试向set方法内添加:notifyPropertyChanged(BR.name)
- 还是不行的,注解都加不上去,BR文件都无法生成
-
-
Kotlin版本中的model层的正确编写
package com.derry.databinding_kt.model import androidx.databinding.BaseObservable import androidx.databinding.Bindable import androidx.databinding.ObservableField // DataBinding BindingAdapter 涉及到判空 后面看 class StudentInfo /*: BaseObservable()*/ { // 第一种方式 未修复 KT放弃第一种方式 var name: String? = null //@Bindable//注解是给函数和方法的,此时的get就只是一个表达式而已,这个注解就失效了,无法生成BR中的字段 get() { return field } set(value) { field = value // notifyPropertyChanged(BR.name) } var pwd : String? = null get() { return field } set(value) { field = value } // 第二种方式 已修复 懒加载 val nameF : ObservableField<String> by lazy { ObservableField<String>() } val pwdF : ObservableField<String> by lazy { ObservableField<String>() } }
-
-
View 层编写:将布局交由DataBinding进行管理
-
代码实现:
<?xml version="1.0" encoding="utf-8"?> <!-- DataBinding的编译标准 --> <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"> <!-- 定义该View(布局)需要绑定的数据来源,拿给下面的那个用 --> <data> <variable name="studentInfo" type="com.derry.databinding_kt.model.StudentInfo" /> </data> <!-- 安卓认识的 常规布局 --> <!-- 布局常规代码如下 --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical"> <!-- 未修复 --> <EditText android:id="@+id/et_name1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{studentInfo.name}" /> <EditText android:id="@+id/et_pwd1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{studentInfo.pwd}" android:layout_marginBottom="60dp" /> <!-- 修复后 --> <EditText android:id="@+id/et_name2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{studentInfo.nameF}" /> <EditText android:id="@+id/et_pwd2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{studentInfo.pwdF}" android:layout_marginBottom="60dp" /> </layout>
-
-
编写控制层:MainActivity
-
加载布局文件
val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
-
细节一:DataBindingUtil
- 这个DataBindingUtils是系统自动生成的(但是要编译一下才行,因为其背后使用了APT技术)
-
细节二:指定了泛型< ActivityMainBinding >
-
查看源码:
-
限定泛型类型:ViewDataBinding的子类(可以是ActivityMainBinding)
-
为什么java版本中,在加载布局文件时,不需要指定泛型?
-
因为java没有类型推导机制,java在声明变量的时候就已经决定了变量的类型了
-
kotlin中变量的类型时依托于其内在的类型推导机制,从而确定的;如果不指定泛型,那么在kotlin中这个变量是什么都不知道
-
-
-
-
子线程更新model层中的数据:三秒后更新
Log.d(TAG, "name:${studentInfo.name}, pwd:${studentInfo.pwd}") Handler().postDelayed({ studentInfo.name = "第一种更新方式,更新用户名" studentInfo.pwd = "第一种更新方式,更新密码" }, 3000) // 修复后: Model ---> View 一向 Log.d(TAG, "name:${studentInfo.nameF.get()}, pwd:${studentInfo.pwdF.get()}") Handler().postDelayed({ studentInfo.nameF.set("第二种更新方式,更新用户名") studentInfo.pwdF.set("第二种更新方式,更新密码") }, 3000)
-
数据传递流程:
-
在控制层(Activity)修改Model层(StudentInfo)数据
-
通过Model层
-
反馈到View层(activity_main.xml)中的由DataBinding管理的(activity_main.xml的上一半)中的data标签中
-
View层再从这个data标签里面去拿数据,实际上拿的就是Model层中的数据
-
-
完整代码:
package com.derry.databinding_kt import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Handler import android.util.Log import androidx.databinding.DataBindingUtil import com.derry.databinding_kt.databinding.ActivityMainBinding import com.derry.databinding_kt.model.StudentInfo class MainActivity : AppCompatActivity() { private val TAG = MainActivity::class.java.simpleName private val studentInfo = StudentInfo()//拿到model数据了 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main) val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) // TODO 单向绑定第一种方式:<Model ---> View> studentInfo.name = "初始化用户名" studentInfo.pwd = "初始化密码" binding.studentInfo = studentInfo // 数据的刷新 发现界面没有效果,目前:界面不会根据Model的变化 而 变化,没有加注解的,根本就无法工作的 // 未修复: Log.d(TAG, "name:${studentInfo.name}, pwd:${studentInfo.pwd}") Handler().postDelayed({ studentInfo.name = "第一种更新方式,更新用户名" studentInfo.pwd = "第一种更新方式,更新密码" }, 3000) // 修复后: Model ---> View 一向 Log.d(TAG, "name:${studentInfo.nameF.get()}, pwd:${studentInfo.pwdF.get()}") Handler().postDelayed({ studentInfo.nameF.set("第二种更新方式,更新用户名") studentInfo.pwdF.set("第二种更新方式,更新密码") }, 3000) } }
-
-
运行截图:
-
初始状态:
-
等待三秒更新UI
-
实现双向绑定
-
概述:
-
Model--->View:数据驱动UI
- 在控制层(Activity)中更新Model层(StudentInfo)View层(界面)显示更新后的数据
-
View--->Model:UI驱动数据
- 在View层(界面)更新Model层(StudnetInfo)的数据,在控制层(Activity)中通过打印日志进行显示
-
-
工程结构:
-
Model层:StudentInfo
package com.derry.databinding_kt.model import androidx.databinding.BaseObservable import androidx.databinding.Bindable import androidx.databinding.ObservableField // DataBinding BindingAdapter 涉及到判空 后面看 class StudentInfo /*: BaseObservable()*/ { // 第一种方式 未修复 KT放弃第一种方式 var name: String? = null //@Bindable//注解是给函数和方法的,此时的get就只是一个表达式而已,这个注解就失效了,无法生成BR中的字段 get() { return field } set(value) { field = value // notifyPropertyChanged(BR.name) } var pwd : String? = null get() { return field } set(value) { field = value } // 第二种方式 已修复 懒加载 val nameF : ObservableField<String> by lazy { ObservableField<String>() } val pwdF : ObservableField<String> by lazy { ObservableField<String>() } }
-
控制层:Activity
package com.derry.databinding_kt import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.os.Handler import android.util.Log import androidx.databinding.DataBindingUtil import com.derry.databinding_kt.databinding.ActivityMainBinding import com.derry.databinding_kt.model.StudentInfo class MainActivity : AppCompatActivity() { private val TAG = MainActivity::class.java.simpleName private val studentInfo = StudentInfo()//拿到model数据了 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // setContentView(R.layout.activity_main) val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main) // TODO 单向绑定第一种方式:<Model ---> View> studentInfo.name = "初始化用户名" studentInfo.pwd = "初始化密码" binding.studentInfo = studentInfo // 数据的刷新 发现界面没有效果,目前:界面不会根据Model的变化 而 变化,没有加注解的,根本就无法工作的 // 未修复: Log.d(TAG, "name:${studentInfo.name}, pwd:${studentInfo.pwd}") Handler().postDelayed({ studentInfo.name = "第一种更新方式,更新用户名" studentInfo.pwd = "第一种更新方式,更新密码" }, 3000) // TODO 双向绑定(Model ---> View 一向 and View ---> Model 一向 ) == 双向 // Model ---> View 一向 studentInfo.nameF.set("用户名初始化") studentInfo.pwdF.set("密码初始化") // 修改Model --> View binding.studentInfo = studentInfo Log.d(TAG, "name:${studentInfo.nameF.get()}, pwd:${studentInfo.pwdF.get()}") Handler().postDelayed({ studentInfo.nameF.set("Model--->View") studentInfo.pwdF.set("实现数据驱动UI") }, 3000) Handler().postDelayed({ // 10秒后,测试 View ---> Model Model是否被修改了 Log.d(TAG, "name:${studentInfo.nameF.get()}, pwd:${studentInfo.pwdF.get()}") }, 10000) // 我们开发用的多的是, Model(修改888) ---> View(888) 一向 } }
-
View层:activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <!-- DataBinding的编译标准 --> <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"> <!-- 定义该View(布局)需要绑定的数据来源,拿给下面的那个用 --> <data> <variable name="studentInfo" type="com.derry.databinding_kt.model.StudentInfo" /> </data> <!-- 安卓认识的 常规布局 --> <!-- 布局常规代码如下 --> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" android:orientation="vertical"> <!-- 未修复 --> <EditText android:id="@+id/et_name1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{studentInfo.name}" /> <EditText android:id="@+id/et_pwd1" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{studentInfo.pwd}" android:layout_marginBottom="60dp" /> <!-- 修复后 --> <EditText android:id="@+id/et_name2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{studentInfo.nameF}" /> <EditText android:id="@+id/et_pwd2" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@{studentInfo.pwdF}" android:layout_marginBottom="60dp" /> <!-- 双向绑定 = 就代表着View可以驱动Model @= View -> Model View -> Model 一向 --> <EditText android:id="@+id/et_name3" android:layout_width="match_parent" android:layout_height="wrap_content" <!--这个等号就是 实现UI驱动数据,在界面上面修改数据后,在Model层上就看到了--> android:text="@={studentInfo.nameF}" /> <EditText android:id="@+id/et_pwd3" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@={studentInfo.pwdF}" android:layout_marginBottom="60dp" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@={studentInfo.nameF}" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@={studentInfo.pwdF}" /> </LinearLayout> </layout>
-
运行截图:
-
界面初始化
-
等待三秒,UI更新:数据驱动UI
-
改变View层值
-
在控制层中打印日志信息:View--->Model
-
-
总结:
-
在开发的时候一般都是单向的(View--->Model),因为双向绑定非常耗费性能
-
Databinding还有很多的好东西
-
BindingAdapter等
-
Model--->View:搞个事件就行了
-
-
问题
-
Android Studio无法输入中文
- 关掉,然后重启,就行了
-
Android Studio格式化代码:代码是一行显示的
- CTRL+ALT+L
-
xml引用判空:DataBinding中的BindingAdapter