MVVM模式是指Model-View-ViewModel。关于MVP架构,无论如何抽象化,在我们的View层中是无法避免的要处理一部分逻辑的。而MVVM模式中的View是将View的状态和行为完全抽象化,把逻辑与界面的控制完全交给ViewModel处理。
MVVM由下面三个核心组件组成:
1、打开软件,进入登录页面,检查用户数据库中是否有数据,如果有则自动加载用户名密码到输入栏。
2、用户输入用户名密码。
3、点击登录,比对用户输入用户名密码与数据库中密码是否一致,并进行登录结果提示。
这一层主要是获取数据库中的业务数据,本demo使用Room数据库,相比于GreenDao,Room更简洁高效,想深入了解Room搜索其他相关文章。
implementation 'android.arch.persistence.room:runtime:2.2.0'
annotationProcessor 'android.arch.persistence.room:compiler:2.2.0'
@Entity
public class User{
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "uid")
private int id;
private String name;
private String password;
public User(String name,String password){
this.name = name;
this.password = password;
}
//...省略get set
}
用了@Entity标注的类,表示当前类的类名作为表名,这个类里面的所有属性,作为表里的字段。
Dao为数据操作类,包含用于访问数据库的方法。
@Dao
public interface UserDao {
//查询所有数据
@Query("Select * from User")
List<User> getAll();
//删除全部数据
@Query("DELETE FROM User")
void deleteAll();
//一次插入单条数据 或 多条
// @Insert(onConflict = OnConflictStrategy.REPLACE),这个是干嘛的呢,下面有详细教程
@Insert
void insert(User... users);
//一次删除单条数据 或 多条
@Delete
void delete(User... users);
//一次更新单条数据 或 多条
@Update
void update(User... users);
//根据字段去查找数据
@Query("SELECT * FROM User WHERE id= :uid")
Person getUserByUid(int uid);
//一次查找多个数据
@Query("SELECT * FROM UserWHERE id IN (:userIds)")
List<Person> loadAllByIds(List<Integer> userIds);
//多个条件查找
@Query("SELECT * FROM UserWHERE name = :name AND password= :password")
Person getUserByNameage(String name, int password);
}
这里唯一特殊的就是@Insert。其有一段介绍:对数据库设计时,不允许重复数据的出现。否则,必然造成大量的冗余数据。实际上,难免会碰到这个问题:冲突。当我们像数据库插入数据时,该数据已经存在了,必然造成了冲突。该冲突该怎么处理呢?在@Insert注解中有conflict用于解决插入数据冲突的问题,其默认值为OnConflictStrategy.ABORT。对于OnConflictStrategy而言,它封装了Room解决冲突的相关策略。
OnConflictStrategy.REPLACE:冲突策略是取代旧数据同时继续事务
OnConflictStrategy.ROLLBACK:冲突策略是回滚事务
OnConflictStrategy.ABORT:冲突策略是终止事务
OnConflictStrategy.FAIL:冲突策略是事务失败
OnConflictStrategy.IGNORE:冲突策略是忽略冲突
此类继承RoomDataBase,用于指定database的表映射实体数据以及版本等信息。
@Database(entities = {User.class}, version = 1,exportSchema = false)
public abstract class BaseAppData extends RoomDatabase {
public abstract UserDao getUserDao();
}
public class DBInstance {
//private static final String DB_NAME = "/sdcard/LianSou/room_test.db";
private static final String DB_NAME = "room_test";
public static AppDataBase appDataBase;
public static AppDataBase getInstance(){
if(appDataBase==null){
synchronized (DBInstance.class){
if(appDataBase==null){
appDataBase = Room.databaseBuilder(MyApplication.getInstance(),AppDataBase.class, DB_NAME)
//下面注释表示允许主线程进行数据库操作,但是不推荐这样做。
//我这里是为了Demo展示,稍后会结束和LiveData和RxJava的使用
.allowMainThreadQueries()
.build();
}
}
}
return appDataBase;
}
}
需要使用时直接如下调用:
DBInstance.getInstance().getUserDao().insert(user);
在ViewModel层处理业务逻辑。需要用到LiveData
public class LoginViewModel extends AndroidViewModel {
private Context context;
/**
* 线程池
*/
final ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
public LoginViewModel(@NonNull Application application) {
super(application);
}
/**
* 登录
*
* @param context
* @param user
*/
public void doLogin(Context context, User user) {
this.context = context;
Runnable runnable = () -> {
if (DBInstance.getInstance().getUserDao().getByUserNameAndPassword(user.getUserName(), user.getPassword()) != null) {
//登录成功
handler.sendEmptyMessage(0x56);
}else {
//登录失败
handler.sendEmptyMessage(0x01);
}
};
cachedThreadPool.execute(runnable);
}
/**
* 获取用户账号信息
*
* @return
*/
public MutableLiveData<User> getLastLoginAccount() {
//因为用到LiveData,我觉得都不需要切换到主线程了。LiveData可以帮我们做
//调用接口,返回我们的MutableLiveData>
final MutableLiveData<User> liveData = new MutableLiveData<>();
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
User user = DBInstance.getInstance().getUserDao().getByUid(1);
if(user == null){
user = new User();
user.setUserName("admin");
user.setPassword("123456");
getUserDao().insert(user);
}
liveData.postValue(user);
}
});
return liveData;
}
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == 0x56) {
Toast.makeText(context, "登录成功", Toast.LENGTH_SHORT).show();
}else if (msg.what==0x01){
Toast.makeText(context, "登录失败", Toast.LENGTH_SHORT).show();
}
}
};
}
在View层我们主要采用DataBinding将数据和view绑定,其中的优点和详细使用请自行百度,这一层主要是显示布局和外观等。
需要用到 Lifecycle
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="User"
type="com.zhd.db.room.entities.User" />
<variable
name="onLoginClickListener"
type="android.view.View.OnClickListener" />
data>
<LinearLayout
android:orientation="vertical"
android:gravity="center_horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:layout_width="match_parent"
android:layout_height="60dp"
android:hint="请输入您的账号"
android:id="@+id/login_account"
android:singleLine="true"
android:text="@={User.name}"/>
<EditText
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="60dp"
android:hint="请输入您的密码"
android:inputType="textPassword"
android:id="@+id/login_password"
android:singleLine="true"
android:text="@={User.password}"/>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="@{onLoginClickListener}"
android:text="登录"/>
LinearLayout>
layout>
public abstract class BaseActivity<VM extends BaseViewModel, VDB extends ViewDataBinding> extends AppCompatActivity {
/**
* 获取当前activity布局文件,并初始化binding
* @return
*/
protected abstract int getContentViewId();
/**
* 处理逻辑业务
*/
protected abstract void processLogic();
protected VM mViewModel;
protected VDB binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(getContentViewId());
//初始化binging
binding = DataBindingUtil.setContentView(this, getContentViewId());
//给binding加上感知生命周期,AppCompatActivity就是lifeOwner
binding.setLifecycleOwner(this);
//创建我们的ViewModel。
createViewModel();
processLogic();
}
public void createViewModel() {
if (mViewModel == null) {
Class modelClass;
Type type = getClass().getGenericSuperclass();
if (type instanceof ParameterizedType) {
modelClass = (Class) ((ParameterizedType) type).getActualTypeArguments()[0];
} else {
//如果没有指定泛型参数,则默认使用BaseViewModel
modelClass = BaseViewModel.class;
}
mViewModel = (VM) ViewModelProviders.of(this).get(modelClass);
}
}
/**
* ActivityLoginBinding 是DataBinding在xml页面绑定后自动生成的,【文件名】为:xml文件(名去掉_,且首字母大写)+ Binding,
* 在build文件夹内,路径为 包名.+ databinding. + 【文件名】
*/
public class LoginActivity extends BaseActivity<LoginViewModel, ActivityLoginBinding> {
@Override
protected int getContentViewId() {
return R.layout.activity_login;
}
@Override
protected void processLogic() {
//自动填充上次登录过的用户
mViewModel.getLastLoginAccount().observe(LoginActivity.this, user ->
binding.setUser(user)
);
binding.setOnLoginClickListener(v -> {
//登录
mViewModel.doLogin(LoginActivity.this,binding.getUser());
});
}
}
至此,MVVM框架已经简单地搭建起来了,能够实现真正的解耦,ViewModel层可以抽出来做单元测试,界面与数据的绑定也变得非常简单,代码量少了很多,结构清晰明了,便于开发与维护。