美文网首页
从Room到SharedPreference

从Room到SharedPreference

作者: lvsecoto | 来源:发表于2019-03-15 19:03 被阅读0次

    在众多的 Android app 持久化方案挑选较合适的一种尤为重要,框架的体量要合适需求的体量,前者过大会拖累开发进度,后者过大则缺乏灵活性

    关于Room

    Room是android官方开发的一个操作Sqlite的框架,它可以像Retroift一样通过注解和接口方便地对数据库增删查改

    public interface UserDao {
        @Query("SELECT * FROM user")
        LiveData<User> findUser();
        
        @Update()
        void updateUser(User user)
    }
    

    有趣的是,如果将数据库查询结果以LiveData或者Flowable形式返回时。后续如果对查询对应的表进行编辑时,订阅LiveData或Flowable的观察者可以收到更新后的内容

    public class MainActivity extends AppCompatActivity {
        
        @Override
        public void onCreate() {
            super.onCreate();
            //... Dao的初始化等等...
    
            // 订阅user
            userDao.findUser().observe(this, new Observer<User>() {
                @Override
                public void onChanged(String s) {
                    ((TextView)findViewByid(R.id.user_name)).setText(user.name)
                }
            });
                
            findViewById(R.id.insert).setOnClickListener() {
                // 更新User,然后UserName的TextView会显示为Mike
                userDao.updateUser(new User("Mike"))
            }
        }
    }
    

    这个特性可非常强大,想想下面情况:

    UserDetail页面显示用户信息,UserEdit页面编辑用户信息。当UserDetail页面切换到UserEdit页面后,在上面修改了用户信息,返回UserDetail页面要将修改后的用户信息显示出来

    以前的我们的做法是通过startActivityForResult将修改后的User返回给前一个页面,这就意味着当前页面要知道前一个页面是什么,耦合性非常强。再如UserDetail页面和UserEdit页面之间隔了好几个页面的话,UserEdit页面就要一个一个页面往回传给UserDetail页面

    而现在,只要UserDetail页面订阅了userDao.findUser(),UserEdit页面调用userDao.updateUser(),就能完成两个页面User内容的同步,无论之间隔了多少个页面

    Room之所以能实现如此方便的功能,因为它实现了两个要求:

    1. 是一个被观察者,LiveData或者Flowable/Observable
    2. 一旦数据变化,被观察者就会通知观察者更新

    但是,有些数据没必要用如此繁琐的Room框架

    对于有大量数据需要增删查改兼排列的,用Room比较方便。但是我们只是想存储简单的用户信息,或者10来条历史记录,就没必要了,你要建立表的关联,你要写SQL,还要处理Room编译时报的一堆蛋疼的错误(貌似Room是一个印度妹子开发的)!

    其实我们可以在项目中同时存在几种存储方案,以适应不同的需求。

    • 对于大量的数据,而且实体间是有关联的,用Room性能会好点,Room还支持外键约束
    • 对于小量的数据,十来条或者甚至只有一条的,用下面的SharedPreference合适

    让SharedPreference实现Room一样的功能!

    将对象存储在SharedPreference

    把要存储对象(比如这里的User)转换为json字符串,然后存储在相应的键的值上

    用LiveData观察SharedPreference某个键的值

    技术的关键点是SharedPreference可以通过注册监听器订阅和解除订阅

    /**
     * 创建一个可以观察SharedPreference Key 对应的数据的变化的LiveData
     *
     * @param <T>
     */
    abstract static class PrefLiveData<T> extends LiveData<T> {
    
        private SharedPreferences mPref;
    
        private String mKey;
    
        private T mDefValue;
    
        private SharedPreferences.OnSharedPreferenceChangeListener mListener =
            (pref, key) -> {
                if (Objects.equals(key, mKey)) {
                    setValue(getPrefValue(mPref, mKey, mDefValue));
                }
        };
    
        /**
         * @param sharedPreferences SharedPreference对象
         * @param key               对应的SharedPreference Key
         * @param defValue          默认指
         */
        PrefLiveData(SharedPreferences sharedPreferences, String key, T defValue) {
            mPref = sharedPreferences;
            mKey = key;
            mDefValue = defValue;
        }
    
        @Override
        protected void onActive() {
            super.onActive();
            
            // 先立即将值发送给观察者
            setValue(getPrefValue(mPref, mKey, mDefValue));
            
            // 注册监听器,当值修改时通知观察者
            mPref.registerOnSharedPreferenceChangeListener(mListener);
        }
    
        @Override
        protected void onInactive() {
            super.onInactive();
            
            // 解除监听器,防止内存泄漏
            mPref.unregisterOnSharedPreferenceChangeListener(mListener);
        }
    
        /**
          * 如何从SharedPreference获取数据
          */
        protected abstract T getPrefValue(SharedPreferences pref, String key, T defValue);
    }
    

    这里我们定义了一个类,用LiveData观察某个键的值,但是没有定义如何去读取这个值,因为getPrefValue是抽象的

    接下来实现这个抽象类,用Gson去获取这个值,将其转换为我们想要的对象T

    @SuppressWarnings("SpellCheckingInspection")
    private static final Gson mGSON = new Gson();
    
    /**
     * 创建LiveData用于观察对象
     */
    @NonNull
    public static <T> LiveData<T> objectLiveData(
        SharedPreferences pref, String key, Class<T> tClass) {
        return new PrefLiveData<T>(pref, key, null) {
            @Override
            protected T getPrefValue(
                SharedPreferences pref, String key, T defValue) {
                return getObject(pref, key, tClass);
            }
        };
    }
    
    /**
     * 直接获取Pref中的对象
     */
    @Nullable
    public static <T> T getObject(SharedPreferences pref, String key, Class<T> tClass) {
        String json = pref.getString(key, null);
        if (json == null) {
            return null;
        }
    
        try {
            return mGSON.fromJson(json, tClass);
        } catch (JsonSyntaxException ignored) {
            return null;
        }
    
    

    或列表

    列表需要用TypeToken处理列表范型

    /**
      * 创建LiveData用于观察对象列表
      */
    @NonNull
    public static <T> LiveData<List<T>> objectListLiveData(
        SharedPreferences pref, 
        String key, 
        Class<T> type) {
        return new PrefLiveData<List<T>>(pref, key, null) {
            @Override
            protected List<T> getPrefValue(
                SharedPreferences pref, 
                String key, 
                List<T> defValue) {
                return getObjectList(pref, key, type);
            }
        };
    }
    
    /**
      * 帮助GSON解析列表范型数据
      */
    static class GenericOf<T> implements ParameterizedType {
    
        private final Class<T> type;
    
        GenericOf(Class<T> type) {
            this.type = type;
        }
    
        @Override
        @NonNull
        public Type[] getActualTypeArguments() {
            return new Type[]{type};
        }
    
        @Override
        @NonNull
        public Type getRawType() {
            return List.class;
        }
    
        @Override
        public Type getOwnerType() {
            return null;
        }
    }
    
    /**
      * 直接获取Pref中的对象列表
      */
    @SuppressWarnings("unchecked")
    @Nullable
    public static <T> List<T> getObjectList(
        SharedPreferences pref, 
        String key, 
        Class<T> type) {
        String json = pref.getString(key, null);
        if (json == null) {
            return null;
        }
    
        try {
            return mGSON.fromJson(json, new GenericOf(type));
        } catch (JsonSyntaxException ignored) {
            return null;
        }
    }
    
    

    支持增删改SharedPreference

    之所以要使用数据库,其中之一的原因是它可以增删查改。

    下面来设计SharedPreference增删查改接口,具体实现限于篇幅就不在这里列出了,大家想去了解的话可以去我的GitHub仓库查看

    /**
      * 添加{@code object}, 到列表位置{@code index},如果位置为负数,则表示倒数第几位,
      * -1代表倒数第一位,-2代表倒数第二位
      *
      * @return 如果索引不在范围,或者{@code key}对应的位置没有列表存在,返回false,否则为true
      */
    @WorkerThread
    public static <T> boolean insert(
        SharedPreferences pref, 
        String key, Class<T> type, 
        T object, 
        int index)
        
    // 同时还有insertFirst,insertLast队头插入,队尾插入
    

    这里我把它设计得尽量和SQL语法一样,用了接口Where<T>

    /**
      * where条件测试
      */
    public interface Where<T> {
        boolean where(T object);
    }
    

    继续

    /**
      * 删除符合条件{@code where}的条目{@code count}个
      * 
      * @return 删除的数量
      */
    @WorkerThread
    public static <T> int delete(
        SharedPreferences pref, 
        String key, 
        Class<T> type,
        Where<T> where,
        int count
        )
     // 同时还有deleteOnce,碰到where返回为True的就不再继续查找下去了,达到优化性能的效果
     // deleteAll删除key下面的所有数据
    

    这里我把它设计得尽量和SQL语法一样,用了接口Update<T>

    /**
    * 更新操作
    */
    public interface Update<T> {
        T update(T object);
    }
    

    继续

    /**
      * 当条件符合{@code where}, 则执行{@code update}
      *
      * @param count 指定要更新的数目
      * @return 多少行更新了
      */
      @WorkerThread
      public static <T> int update(
          SharedPreferences pref,
          String key,
          Class<T> type,
          Update<T> update,
          Where<T> where,
          int count)
     // 和delete一样,有updateOnce和updateAny
    

    以上内容就此告一段落,LiveData依旧不如RxJava灵活,但是比RxJava方便,拿起就用,自动和生命周期组件解除订阅

    但今后有时间还会发布RxJava版本,还有基于Kotlin扩展函数的版本,将大大减低代码量,加快开发速度

    完整代码可到我的Github查看

    https://gist.github.com/lvsecoto/be5aea0d89beb10b8946dbdacac0ee4e

    单元测试

    什么?你对我写出来的代码不放心?没问题,在此奉上以上代码的单元测试

    https://gist.github.com/lvsecoto/be5aea0d89beb10b8946dbdacac0ee4e#file-sharedpreferencedaotest

    相关文章

      网友评论

          本文标题:从Room到SharedPreference

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