Android MVVM框架搭建MMKV + Room + RxJava2
Posted 初学者-Study
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android MVVM框架搭建MMKV + Room + RxJava2相关的知识,希望对你有一定的参考价值。
android MVVM框架搭建(三)MMKV + Room + RxJava2
前言
在上一篇文章中,我讲述了怎么在MVVM框架中搭建网络访问框架,并通过一个必应的每日壁纸做了一次请求接口的访问演示,这篇文章就需要来讲述Android端的本地数据库的使用和在MVVM中使用方式了。
正文
本文说的是数据库,为什么要讲这个呢,因为在实际开发中,有一些数据并不需要实时更新,我们只需要在第一次打开应用的时候获取到,然后保存到手机本地数据库中即可,需要的时候从数据库中获取。当数据要更新是再从服务器获取,这样可以减少请求次数。
而我所讲的是JetPack中的一个组件,Room,这是一个数据库组件,实际上也是对Sqlite的上层封装,在没有Room之前我们也会使用一些第三方的开源库,比如GreenDao、LitePal、ORMLite等。当然了,你现在依然可以使用这些开源库,毕竟你养成了使用习惯了。但是本着技多不压身的原则,我们还是可以了解一下的,你说呢?
一、添加依赖
在创建的项目里,默认是没有Room的依赖的,因此需要手动去添加,添加在app的build.gradle中的dependencies{}闭包下,代码如下:
//Room数据库
implementation 'androidx.room:room-runtime:2.3.0'
annotationProcessor 'androidx.room:room-compiler:2.3.0'
//Room 支持RxJava2
implementation 'androidx.room:room-rxjava2:2.3.0'
//腾讯MMKV
implementation 'com.tencent:mmkv:1.2.11'
然后点击 Sync Now进行项目的同步,同步之后就可以开始使用了。
二、MMKV
在Android系统中使用了多年的SharedPreferences ,终于被Google给放弃了,在JetPack的新组件中新增了一个DataStore,其实在DataStore出现之前已经有一些第三方的本地缓存处理库了,例如腾讯的MMKV库,比较的好用,在我以往的博客中也没有使用过MMKV,就在本文中使用吧,其实JetPack中也有一个组件是用来解决SharedPreferences的,就是DataStore,但是我发现它的使用群体还没有上去,因此就先不做介绍,改成了MMKV库,这个库个人感觉使用起来比DataStore要简单一些,这同样是一个很实用的库。
在上面的build.gradle配置中我已经添加了目前最新的依赖库了,下面使用它吧。其实很简单的。
1. 初始化
第一步就是在自定义的Application中进行初始化,在onCreate方法中增加如下代码:
//MMKV初始化
MMKV.initialize(this);
当然你也可以这样写。用于查看你的缓存文件存在哪里
String initialize = MMKV.initialize(this);
System.out.println("MMKV INIT " + initialize);
2. 数据存取
下面我会写一个工具类用来处理缓存数据的存取,在com.llw.mvvm包下新增一个utils包,包下新建一个MVUtils类,里面的代码如下:
public class MVUtils {
private static MVUtils mInstance;
private static MMKV mmkv;
public MVUtils() {
mmkv = MMKV.defaultMMKV();
}
public static MVUtils getInstance() {
if (mInstance == null) {
synchronized (MVUtils.class) {
if (mInstance == null) {
mInstance = new MVUtils();
}
}
}
return mInstance;
}
/**
* 写入基本数据类型缓存
*
* @param key 键
* @param object 值
*/
public static void put(String key, Object object) {
if (object instanceof String) {
mmkv.encode(key, (String) object);
} else if (object instanceof Integer) {
mmkv.encode(key, (Integer) object);
} else if (object instanceof Boolean) {
mmkv.encode(key, (Boolean) object);
} else if (object instanceof Float) {
mmkv.encode(key, (Float) object);
} else if (object instanceof Long) {
mmkv.encode(key, (Long) object);
} else if (object instanceof Double) {
mmkv.encode(key, (Double) object);
} else if (object instanceof byte[]) {
mmkv.encode(key, (byte[]) object);
} else {
mmkv.encode(key, object.toString());
}
}
public static void putSet(String key, Set<String> sets) {
mmkv.encode(key, sets);
}
public static void putParcelable(String key, Parcelable obj) {
mmkv.encode(key, obj);
}
public static Integer getInt(String key) {
return mmkv.decodeInt(key, 0);
}
public static Integer getInt(String key, int defaultValue) {
return mmkv.decodeInt(key, defaultValue);
}
public static Double getDouble(String key) {
return mmkv.decodeDouble(key, 0.00);
}
public static Double getDouble(String key, double defaultValue) {
return mmkv.decodeDouble(key, defaultValue);
}
public static Long getLong(String key) {
return mmkv.decodeLong(key, 0L);
}
public static Long getLong(String key, long defaultValue) {
return mmkv.decodeLong(key, defaultValue);
}
public static Boolean getBoolean(String key) {
return mmkv.decodeBool(key, false);
}
public static Boolean getBoolean(String key, boolean defaultValue) {
return mmkv.decodeBool(key, defaultValue);
}
public static Float getFloat(String key) {
return mmkv.decodeFloat(key, 0F);
}
public static Float getFloat(String key, float defaultValue) {
return mmkv.decodeFloat(key, defaultValue);
}
public static byte[] getBytes(String key) {
return mmkv.decodeBytes(key);
}
public static byte[] getBytes(String key, byte[] defaultValue) {
return mmkv.decodeBytes(key, defaultValue);
}
public static String getString(String key) {
return mmkv.decodeString(key, "");
}
public static String getString(String key, String defaultValue) {
return mmkv.decodeString(key, defaultValue);
}
public static Set<String> getStringSet(String key) {
return mmkv.decodeStringSet(key, Collections.<String>emptySet());
}
public static Parcelable getParcelable(String key) {
return mmkv.decodeParcelable(key, null);
}
/**
* 移除某个key对
*
* @param key
*/
public static void removeKey(String key) {
mmkv.removeValueForKey(key);
}
/**
* 清除所有key
*/
public static void clearAll() {
mmkv.clearAll();
}
}
这里的代码很简单,就是数据的存和取,下面我们来使用它,就在LoginActivity中做一个测试吧,在测试之前还需要在Application中对这个MVUtils类进行一个初始化。
//工具类初始化
MVUtils.getInstance();
截图如下:
3. 使用
在LoginActivity中的onCreate方法中写入如下代码:
//存
Log.d("TAG", "onCreate: 存");
MVUtils.put("age",24);
//取
int age = MVUtils.getInt("age",0);
Log.d("TAG", "onCreate: 取 :" + age);
很简单的代码就是存一个int类型的值,然后取一个int类的值。
下面运行一下,只要进入到LoginActivity即可:
是不是可以呢?可以的话就进行下一步了,Room的使用了。记得把测试的代码给删掉啊。
三、Room
Room 在开发阶段通过注解的方式标记相关功能,编译时自动生成响应的 impl 实现类。而下面关于创建数据库、创建表、创建Dao类,都与注解有关系。
1. @Entity
下面我们来进行创建,在此之前我现在com.llw.mvvm包下新建一个db包。db包下新建一个AppDatabase类,空类就好。然后在db包下新建一个bean包,bean包下新建一个Image类,我们可以分析一下需要存到数据库中的值,是否所有数据都要存入,不要做没必要的事情,那是给自己找事。
从网络返回的数据可以得知,我使用的是其实就只有一小部分,那么我把这一小部分抽离出来做一个bean,Image类的代码如下:
@Entity
public class Image {
@PrimaryKey
private int uid;
private String url;
private String urlbase;
private String copyright;
private String copyrightlink;
private String title;
public int getUid() {
return uid;
}
public void setUid(int uid) {
this.uid = uid;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUrlbase() {
return urlbase;
}
public void setUrlbase(String urlbase) {
this.urlbase = urlbase;
}
public String getCopyright() {
return copyright;
}
public void setCopyright(String copyright) {
this.copyright = copyright;
}
public String getCopyrightlink() {
return copyrightlink;
}
public void setCopyrightlink(String copyrightlink) {
this.copyrightlink = copyrightlink;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public Image(){}
@Ignore
public Image(int uid, String url, String urlbase, String copyright, String copyrightlink, String title) {
this.uid = uid;
this.url = url;
this.urlbase = urlbase;
this.copyright = copyright;
this.copyrightlink = copyrightlink;
this.title = title;
}
}
这里用到了@Entity和@PrimaryKey一个表示数据库中的表名,一个是主键名,这里你也可以设置主键自增,我这里不设置是因为我永远只有一条数据,因此就没有必要。而这里还有一个构造方法,为了写数据方便一些,这个方法我们并不需要写入到数据库中,因此一旦我们写了一个有参数的构造方法则需要通过@Ignore将这个构造方法忽略掉,同时也要增加一个无参的构造方法,当然了@Ignore也可以用在别的参数上,除了主键,其他的无用变量都可以加@Ignore,加了就不会在表中出现。
下面来看看操作数据的类
2. @Dao
在db包下新建一个dao包,dao包下新建一个ImageDao接口,里面的代码如下:
@Dao
public interface ImageDao {
@Query("SELECT * FROM image")
List<Image> getAll();
@Query("SELECT * FROM image WHERE uid LIKE :uid LIMIT 1")
Image queryById(int uid);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(Image... images);
@Delete
void delete(Image image);
}
现在可以更改这个AppDatabase类了。
3. @Database
这里会用到第三个注解,修改后的AppDatabase代码如下:
@Database(entities = {Image.class},version = 1,exportSchema = false)
public abstract class AppDatabase extends RoomDatabase {
public abstract ImageDao imageDao();
}
这里通过注解的方式创建了一个数据库,同时在里面建了一个表,设置了当前的数据库版本,并且不允许导出。里面定了一个抽象方法imageDao()。Room库会采用编译时技术对这个ImageDao 进行实现。
4. 初始化
Room数据库的初始化依然要放在BaseApplication当中,增加一个变量。
//数据库
public static AppDatabase db;
然后在onCreate中进行数据库的创建,代码如下:
//创建本地数据库
db = Room.databaseBuilder(getApplicationContext(),AppDatabase.class, "mvvm_demo").build();
这里创建了名为mvvm_demo的本地数据库。
然后再在BaseApplication中增加一个getDb方法。
public static AppDatabase getDb(){
return db;
}
5. 使用
在上一篇文章中,我将数据请求的代码放在MainRepository中,而使用Room数据库的代码也是在这个MainRepository里面,这里面的代码会做改动,而且改动很大。首先说一下改动思路吧,首先必应每日的壁纸是一样的,因此无论你是请求一次还是多次得到的值都是一样的,因此可以通过一个缓存再来确定设置今天是否有请求过网络接口,有的话再根据一个缓存值判断当前时间是否超过了今天的24点。没有超过就去本地数据库数据,超过了就请求网络。那么这个编码思路就很明确了。
首先是保存到本地数据库的方法,我们将在网络请求之后调用这个方法,代码如下:
private static final String TAG = MainRepository.class.getSimpleName();
final MutableLiveData<BiYingResponse> biyingImage = new MutableLiveData<>();
/**
* 保存数据
*/
private void saveImageData(BiYingResponse biYingImgResponse) {
//记录今日已请求
MVUtils.put(Constant.IS_TODAY_REQUEST,true);
//记录此次请求的时最晚有效时间戳
MVUtils.put(Constant.REQUEST_TIMESTAMP,DateUtil.getMillisNextEarlyMorning());
BiYingResponse.ImagesBean bean = biYingImgResponse.getImages().get(0);
//保存到数据库
new Thread(() -> BaseApplication.getDb().imageDao().insertAll(
new Image(1,bean.getUrl(),bean.getUrlbase(),bean.getCopyright(),
bean.getCopyrightlink(), bean.getTitle()))).start();
}
然后我们新增一个网络请求的方法。
/**
* 从网络上请求数据
*/
@SuppressLint("CheckResult")
private void requestNetworkApi() {
Log.d(TAG, "requestNetworkApi: 从网络获取");
ApiService apiService = NetworkApi.createService(ApiService.class);
apiService.biying().compose(NetworkApi.applySchedulers(new BaseObserver<BiYingResponse>() {
@Override
public void onSuccess(BiYingResponse biYingImgResponse) {
//存储到本地数据库中,并记录今日已请求了数据
saveImageData(biYingImgResponse);
biyingImage.setValue(biYingImgResponse);
}
@Override
public void onFailure(Throwable e) {
KLog.e("BiYing Error: " + e.toString());
}
}));
}
最后是一个从本地获取数据的方法
/**
* 从本地数据库获取
*/
private void getLocalDB() {
Log.d(TAG, "getLocalDB: 从本地数据库获取");
BiYingResponse biYingImgResponse = Android MVVM框架搭建OKHttp + Retrofit + RxJava
Android MVVM框架搭建OKHttp + Retrofit + RxJava
Android MVVM框架搭建ViewModel + LiveData + DataBinding
Android MVVM框架搭建ViewModel + LiveData + DataBinding