Android MVVM框架搭建MMKV + Room + RxJava2

Posted 初学者-Study

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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

Android MVVM框架搭建高德地图定位天气查询BottomSheetDialog

MMKV-Android中的存储框架