背景
SharedPreferences常用来存储一些轻量级的数据,SharedPreferences存储的就是一个key-value(键值对)。Sharedpreferences在日常的android开发中使用的应该算是挺频繁的,通常我们开发者为了存储一个key,都会在一个类里写好对应的getter和setter方法,而且还要手动写key,也就是说写了方法还要定义key。项目写的多了以后明显会感觉到这样写很麻烦。这时候XPrefs就出现了,并很好的解决了这个问题。
XPrefs可以直接保存和读取实例对象,是SharedPreferences中的ORM,还可以通过接口来做文章,这种方式用起来是最方便的,具体介绍接着往下看。
用法
初始化
XPrefs.bind(this);
初始化是绑定了contenxt,之后的操作就不需要传入context参数了,如果你传入activity,会自动转成application context,这样就避免了内存泄漏的问题,当然你也可以在application的类里进行绑定操作。
整存整取
/** * 整存整取,把javabean类中的所有有效的字段都写入sharedpreferences中 */
private void saveAll() {
//如果想切换写入的sharedpreferences文件,可以调用
// XPrefs.changeFileName("your custom sp file's name");
//如果设置Mode,如果调用了changeFileName方法,则必须在changeFileName之后调用
// XPrefs.changeFileMode(Context.MODE_PRIVATE);
//或者直接调用
// XPrefs.changeFileNameAndMode("your custom sp file's name", Context.MODE_PRIVATE);
XPrefs.changeFileName(spFile1);
UserBean userBean = new UserBean();
userBean.setAge(21);
userBean.setFuns(1000);
userBean.setMoney(100);
userBean.setName("韩梅梅");
userBean.setVIP(true);
XPrefs.saveAll(userBean);
//读取,查看存入的数据
userBean = XPrefs.get(UserBean.class);
LogUtils.i("saveAll " + userBean);
}
上面代码中的changeFileName和changeFileMode方法分别是设置Sharedpreferences文件的name和mode的,要注意的是,如果调用了changeFileName,那么需要调用changeFileMode的话就必须要在changeFileName之后调用。如果不设置name,默认操作的文件名是XPrefs,默认的mode是Context.MODE_PRIVATE。
接着new了一个UserBean的实例,给需要保存的属性设置了对应的值,然后调用saveAll就把UserBean中所有的属性都保存进了SharedPreferences文件里,其中key是属性名,value是属性的值。让我们接着看看UserBean这个类,
/** * final 修饰的字段不会被存储 */
private final String InvalidField="InvalidField";
/** * 添加了XIgnore注解的字段会被忽视,也不会被存储 */
@XIgnore
private String IgnoreField="IgnoreField";
//目前支持以下几种类型的数据存储
private String name;
private float money;
private int age;
private boolean isVIP;
private long funs;
...省略一些getter、setter方法和toString()...
用final修饰的属性和加上@XIgnore注解的属性都是被忽略的,不会被存储的。存储的时候,key是属性名(如”name”,”money”,”age”…),value是属性的值。最后通过下面这行代码读取所有存入的数据,直接调用userbean的getter方法,就可以使用这些数据。
userBean = XPrefs.get(UserBean.class);
整存整取的好处除了方便以外,就是效率高,无论存储了多少属性,都只操作了一次文件,有点类似于数据库中的事物。
既然可以保存和读取整个javaBean,那么也应该可以对javaBean中的单个属性进行存储和读取。
单个字段的存储和读取
/** * 单个字段的存储和读取 */
public void save() {
//修改存储的sharedpreferences文件,mode默认为Context.MODE_PRIVATE
XPrefs.changeFileName(spFile2);
UserBean userBean = new UserBean();
userBean.setAge(42);
userBean.setFuns(2000);
userBean.setMoney(200);
userBean.setName("李雷");
userBean.setVIP(false);
XPrefs.save(userBean, "name");
XPrefs.save(userBean, "age");
XPrefs.save(userBean, "funs");
XPrefs.save(userBean, "money");
XPrefs.save(userBean, "isVIP");
//读取,查看存入的数据
Class cls = UserBean.class;
boolean isVIP = XPrefs.getBoolean(cls, "isVIP");
String name = XPrefs.getString(cls, "name");
int age = XPrefs.getInt(cls, "age");
long funs = XPrefs.getLong(cls, "funs");
float money = XPrefs.getFloat(cls, "money");
LogUtils.i("save name=" + name + ";money=" + money + ";age=" + age + ";funs=" + funs + ";isVIP=" + isVIP);
//整取
userBean = XPrefs.get(UserBean.class);
LogUtils.i("save " + userBean.toString());
}
上面的代码演示了对UserBean中的字段单独进行读写操作。
XPrefs.save(userBean, "name");
String name = XPrefs.getString(cls, "name");
要注意的save和getString方法传入的第二个参数得和属性名相同,才能达到想要的效果。也就是如果定义了一个属性private String name;那么传入的就得是“name”。看上去有点麻烦,确实挺麻烦的,但这只是一种用法,更方便的用法后面会介绍。
使用接口和注解
private void saveAllAndFollowYourHeart() {
IUser user = XPrefs.getObject(IUser.class);
user.setName("Tom");
user.setAge(18);
user.setFuns(4000);
user.setMoney(40000);
user.setVip(true);
LogUtils.i("IUser name=" + user.getName() + ";money=" + user.getMoney() + ";age=" + user.getAge()+ ";funs=" + user.getFuns() + ";isVIP=" + user.getVip());
}
首先,调用了XPrefs.getObject(IUser.class)拿到了接口IUser的一个实例对象user,接着分别调用了user的set和get方法,看上去没什么,但是,在执行set方法的时候就已经把数据 存了起来,执行get方法就是把数据读取出来。
具体让我们看看IUser接口,
@XSPFile(fileName = "IUser", fileMode = Context.MODE_PRIVATE) public interface IUser {
@XSet(key = "name", fileName = "IUser", fileMode = Context.MODE_PRIVATE) void setName(String name);
@XGet(key = "name") String getName();
@XSet(key = "age") void setAge(int age);
@XGet(key = "age") int getAge();
@XSet(key = "funs") void setFuns(int funs);
@XGet(key = "funs") int getFuns();
@XSet(key = "vip") void setVip(boolean vip);
@XGet(key = "vip") boolean getVip();
@XSet(key = "money") void setMoney(float money);
@XGet(key = "money") float getMoney();
}
有三个注解XSPFile,XSet和XGet,这三个注解都不是必需的。其中XSPFile是用来指定file name和mode的,用来标记接口,作用域是类。
@XSPFile(fileName = "IUser", fileMode = Context.MODE_PRIVATE)
XSet和XGet则是来标记方法的作用的,一个方法用XSet标记了,那么这个方法的作用就是写入数据到指定的SharedPreferences文件中的,其中key就是写入时的key。XSet也能指定写入的文件 name和mode,如果XSPFile和XSet同时指定了文件的name和mode,那么以XSet指定的为准。
@XSet(key = "name", fileName = "IUser", fileMode = Context.MODE_PRIVATE) void setName(String name);
如果一个方法用XGet标记了,那么这个方法的作用就是从SharedPreferences文件中读取数据,XGet中的key就是读取时的key,其他的和XSet一样。
@XGet(key = "name") String getName();
这样也不是很好,每写一个方法就得加一个注解,好麻烦的说,所以还可以更简单点。
使用接口不用注解
private void saveAllAndFollowYourHeartToo() {
IEmployee employee = XPrefs.getObject(IEmployee.class);
employee.setName("员工");
employee.setAge(22);
employee.setSalary(3000);
LogUtils.i("IEmployee name=" + employee.getName() + ";age=" + employee.getAge() + ";salary=" + employee.getSalary());
}
这里的用法和用注解的是一样的,主要还是IEmployee接口上的区别,
@XSPFile(fileName = "IEmployee", fileMode = Context.MODE_PRIVATE)
public interface IEmployee {
/** * 存储字段 name * * @param name value */
void setName(String name);
/** * 读取字段 name * * @return */
String getName();
void setAge(int age);
int getAge();
void setSalary(float salary);
float getSalary();
}
可以看到,不用注解以后,代码少了将近一半。
这个时候key主要是通过解析方法名来得到的,所以一个方法以set开头的方法的作用就是写入数据到指定的SharedPreferences文件中,其中key就是方法名除去set的后半部分,且首字母小写,举个例子,方法setName的key就是name;
那么一个以get开头的方法的作用也是很明显了,就是从SharedPreferences文件中读取数据,其中key就是方法名出去get的后半部分,首字母也是小写,举个例子,方法getName的key就是name。看上去已经很方便了,但是这都不算什么,更骚的在后面。
最后一种用法
private void saveAllAndFollowYourHeartThree() {
IStudent student = XPrefs.getObject(IStudent.class);
student.name("学生3号");
student.score(100);
student.sex("女");
LogUtils.i("IStudent name=" + student.name() + ";score=" + student.score() + ";sex=" + student.sex());
}
这里的用法和上面两种依然没有什么区别,主要的区别还是在IStudent接口上,
@XSPFile(fileName = "IStudent")
public interface IStudent {
/** * 存储字段 name * * @param name value */
void name(String name);
/** * 读取字段 name * * @return */
String name();
void score(int score);
int score();
void sex(String sex);
String sex();
}
和上种用法相比,区别就是方法名中没有了set和get,那么这是怎么判断方法的作用和要存入数据的key的呢?首先,key就是方法名,完完全全的懒人的写法;其次,方法的作用是通过方法参数和方法返回值来判断的,
- 如果一个方法有一个参数,那么这个方法的作用就是写入数据到指定的SharedPreferences文件中,key是方法名,值是传入的参数;
- 如果一个方法没有参数并且方法的返回值不是void,那么这个方法的作用就是从指定的SharedPreferences文件中读取数据,key是方法名。
这几种通过接口来做文章的写法,XPrefs内部是通过动态代理来做的,java对动态代理的支持仅限于接口,所以用的是接口,如果不用接口的话就得引入第三方的库,这种我也做过,但是项目的体积就会变得更大,最终我放弃了。
混淆注意事项
- 不要混淆XPrefs中的注解类型,添加混淆配置:
-keep class * extends java.lang.annotation.Annotation { *; }
- 对于用于持久化的实体类不要混淆,包含javaBean和接口,在demo中是这样的:
-keep interface com.huxq17.xprefs.example.interfaces.** { *; }
-keepclasseswithmembers class com.huxq17.xprefs.example.UserBean {
<fields>;
<methods>;
}
具体根据自己项目情况而定。
最后
XPrefs的地址:点击我,这次这个项目的灵感来源于github上的一个项目,当时我就在思考怎么做出一个简单易用的SharedPreferences工具类,为了寻找灵感我就在github上面乱逛,一次偶然的机会我看到了这个项目,阅读了项目里的代码,思路立马就清晰了,项目地址:androidInject,非常感谢这个项目的作者。