一种简单优雅的方法来使用Android SharePreference,基于编译时注解自动生成恶心的代码。 githubgit
若是您的项目使用 Gradle 构建, 只须要在您的build.gradle文件添加以下到 dependencies
:github
compile 'com.kevin:hannibai:0.5.1'
annotationProcessor 'com.kevin:hannibai-compiler:0.5.1'
复制代码
引入JSON序列化json
因为能够保存对象甚至集合到SharePreference,根据项目使用引入具体转换器。bash
Gsonide
compile 'com.kevin:hannibai-converter-gson:0.2.6'
复制代码
Jackson工具
compile 'com.kevin:hannibai-converter-jackson:0.2.6'
复制代码
FastJsongradle
compile 'com.kevin:hannibai-converter-fastjson:0.2.6'
复制代码
LoganSquareui
compile 'com.kevin:hannibai-converter-logansquare:0.2.6'
复制代码
这里仅仅实现了Gson、Jackson、FastJson及LoganSquare的实现,聪明的你确定会本身扩展,或者通知我去扩展。this
在Application 中初始化加密
Hannibai.init(this);
if (debug) {
Hannibai.setDebug(true);
}
Hannibai.setConverterFactory(GsonConverterFactory.create());
复制代码
建立一个类,使用SharePreference
进行注解
@SharePreference
public class AppPreference {
}
复制代码
加入一个成员变量
这里使用
public
修饰,固然你也可使用private
、protected
或者不写,任何一种姿式均可以。
@SharePreference
public class AppPreference {
public String name;
}
复制代码
Build —> Rebuild Project
这个过程会自动生成一堆你以为写起来很恶心的东西。
在代码中使用
// 获取AppPreference操做类
AppPreferenceHandle preferenceHandle = Hannibai.create(AppPreferenceHandle.class);
// 设置name到SharePreference
preferenceHandle.setName("Kevin");
// 从SharePreference中获取name
String name = preferenceHandle.getName();
Toast.makeText(this, "name = " + name, Toast.LENGTH_SHORT).show();
复制代码
是否是跟简单的就完成了保存数据到SharePreference
,已经从SharePreference
读取数据,以前恶心的一堆东西已经替你偷偷生成并且藏起来啦~
初始化
在进行初始化的时候有以下两个方法
Hannibai.init(this);
复制代码
Hannibai.init(this, false);
复制代码
这两个方法的区别就是是否对数据进行加密,若是第二个参数为true
则表明要进行加密存储。默认为true
,这样能够减少文件的大小。
获取操做类
有两种方式,一种是获取通用的,另外一种是能够传入ID参数,为不一样用户创建不一样
SharePreference
文件。
假设类为AppPreference
:
通用
AppPreferenceHandle preferenceHandle = Hannibai.create(AppPreferenceHandle.class);
复制代码
区分用户
AppPreferenceHandle preferenceHandle = Hannibai.create(AppPreferenceHandle.class, "ID_123");
复制代码
生成的方法
假设你的成员变量是
name
,那么会生成如下方法:
判断SharePreference
中是否包含name
@DefString("")
public boolean containsName() {
return Hannibai.contains1(mSharedPreferencesName, mId, "name", "");
}
复制代码
在SharePreference
中获取name
值
@DefString("")
public String getName() {
return Hannibai.get1(mSharedPreferencesName, mId, "name", "");
}
复制代码
设置name
值到SharePreference
@Apply
public void setName(final String name) {
Hannibai.set1(mSharedPreferencesName, mId, "name", -1L, false, name);
}
复制代码
在SharePreference
中移除name
@Apply
public void removeName() {
Hannibai.remove1(mSharedPreferencesName, mId, "name");
}
复制代码
在SharePreference
中移除全部数据
@Apply
public void removeAll() {
Hannibai.clear(mSharedPreferencesName, mId);
}
复制代码
支持的类型
类型 | sample |
---|---|
String |
String name; |
int |
int age; |
Integer |
Integer age; |
long |
long timestamp; |
Long |
Long timestamp; |
float |
float salary; |
Float |
Float salary; |
double |
double salary; |
Double |
Double salary; |
User |
User user; |
List<xxx> |
List userList; |
Map<xxx, xxx> |
Map<String, User> userMap; |
Set<xxx> |
Set userSet; |
XXX<xxx> |
只支持一级泛型,List<List<String>> 这种是不支持的。 |
设置默认值
@DefString("zwenkai")
public String name;
复制代码
支持
类型 | sample |
---|---|
DefString | @DefString("zwenkai") |
DefInt | @DefInt(18) |
DefBoolean | @DefBoolean(true) |
DefLong | @DefLong(123456789) |
DefFloat | @DefFloat(123.45F) |
设置过时时间
默认不会过时
@Expire(value = 3, unit = Expire.Unit.MINUTES)
public long salary;
复制代码
能够设置更新数据时从新倒计时:
@Expire(value = 5, unit = Expire.Unit.MINUTES, update = true)
public long salary;
复制代码
支持
单位 | sample |
---|---|
毫秒 | @Expire(value = 1, unit = Expire.Unit.MILLISECONDS) |
秒 | @Expire(value = 1, unit = Expire.Unit.SECONDS) |
分 | @Expire(value = 1, unit = Expire.Unit.MINUTES) |
小时 | @Expire(value = 1, unit = Expire.Unit.HOURS) |
天 | @Expire(value = 1, unit = Expire.Unit.DAYS) |
设置提交类型
提交类型有
Commit
和Apply
两种,默认为Apply
。
Commit
@Commit
public String userName;
复制代码
Apply
@Apply
public String userName;
复制代码
支持RxJava
有些时候,
Observable
对象更好操做,那么你只须要一个注解@RxJava
就能搞定。
使用以下:
@RxJava
public String name;
复制代码
生成方法:
@DefString("")
public Observable<String> getName1() {
return Observable.create(
new ObservableOnSubscribe<String>() {
@Override
public void subscribe(ObservableEmitter<String> e) throws Exception {
e.onNext(getName());
e.onComplete();
}
}
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
复制代码
使用:
preferenceHandle.getName1().subscribe(new Consumer<String>() {
@Override
public void accept(String name) throws Exception {
Toast.makeText(MainActivity.this, "name = " + name, Toast.LENGTH_SHORT).show();
}
});
复制代码
有人说这生成的是RxJava2啊,我还在用RxJava1咋办?别着急大兄弟,添给注解添加version
属性配置便可。
生成方法:
@DefString("")
public Observable<String> getName1() {
return Observable.create(
new Observable.OnSubscribe<String>() {
@Override
public void call(Subscriber<? super String> subscriber) {
subscriber.onNext(getName());
subscriber.onCompleted();
}
}
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread());
}
复制代码
有人又说了,你这个getName1()
感受不爽,我想换个名字。添加注解属性就能够啦。
@RxJava(suffix = "牛")
public String name;
复制代码
那么在使用的时候就是这样的:
preferenceHandle.getName牛().subscribe(new Consumer<String>() {
@Override
public void accept(String name) throws Exception {
Toast.makeText(MainActivity.this, "name = " + name, Toast.LENGTH_SHORT).show();
}
});
复制代码
原理比较简单,相信聪明的你早就想到啦。不就是编译时注解搞的鬼嘛,恭喜你答对了。
全部的数据都封装到BaseModel
中而后转换为JSON存储字符串到SharePreference
final class BaseModel<T> {
public long createTime;
public long updateTime;
public long expireTime;
public long expire;
public T data;
// ... ...
}
复制代码
经过几个时间字段来标记过时信息。
仿照Retrofit
定义的JSON转换接口
因为你们项目中使用JSON转换工具存在差别,有人喜欢使用
GSON
,有同窗以为FastJson
是速度最快的,也有同窗感受Jackson
最优。这里均可以根据本身的状况灵活配置,固然若是使用其余的你也能够本身去写转换器,或者告诉我我去添加支持。
public interface Converter<F, T> {
T convert(F value) throws Exception;
interface Factory {
<F> Converter<F, String> fromType(Type fromType);
<T> Converter<String, T> toType(Type toType);
}
}
复制代码
初始化依旧模仿:
Hannibai.setConverterFactory(GsonConverterFactory.create());
复制代码
生成接口
首先生成IHandle
接口
只有一个
removeAll()
方法,其实主要是为了混淆好配置而进行的抽取。
public interface IHandle {
@Apply
void removeAll();
}
复制代码
而后根据AppPreference
生成AppPreferenceHandle
接口
这里为对
AppPreference
变量生成操做方法。
public interface AppPreferenceHandle extends IHandle {
@DefString("zwenkai")
boolean containsName();
@DefString("zwenkai")
String getName();
@Apply
void setName(final String name);
@Apply
void removeName();
}
复制代码
最后根据AppPreference
生成AppPreferenceHandleImpl
类
该类为
AppPreference
接口的实现以及单例模式的封装。
final class AppPreferenceHandleImpl implements AppPreferenceHandle, IHandle {
private final String mSharedPreferencesName = "com.haha.hannibaitest.AppPreference";
private final String mId;
private AppPreferenceHandleImpl() {
this.mId = "";
}
public AppPreferenceHandleImpl(String id) {
this.mId = id;
}
public static AppPreferenceHandleImpl getInstance() {
return Holder.INSTANCE;
}
@DefString("zwenkai")
public boolean containsName() {
return Hannibai.contains1(mSharedPreferencesName, mId, "name", "zwenkai");
}
@DefString("zwenkai")
public String getName() {
return Hannibai.get1(mSharedPreferencesName, mId, "name", "zwenkai");
}
@Apply
public void setName(final String name) {
Hannibai.set1(mSharedPreferencesName, mId, "name", -1L, false, name);
}
@Apply
public void removeName() {
Hannibai.remove1(mSharedPreferencesName, mId, "name");
}
@Apply
public void removeAll() {
Hannibai.clear(mSharedPreferencesName, mId);
}
private static class Holder {
private static final AppPreferenceHandleImpl INSTANCE = new AppPreferenceHandleImpl();
}
}
复制代码
数据加密
数据加密比较简单,就是对
SharePreference
中Key
对应的Value
进行亦或运算。加密解密的方法为同一个,很巧妙,有兴趣的同窗能够研究下。
static final String endecode(String input) {
char[] key = "Hannibai".toCharArray();
char[] inChars = input.toCharArray();
for (int i = 0; i < inChars.length; i++) {
inChars[i] = (char) (inChars[i] ^ key[i % key.length]);
}
return new String(inChars);
}
复制代码
为何以前说这样加密能够减少存储文件的大小呢?
未加密:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="name">{"data":"Kevin","createTime":1509687733860,"expire":-1,"expireTime":0,"updateTime":1509946500232}</string>
</map>
复制代码
加密:
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<string name="name">3C CSj* CEj =! LSSTYqWVY^QRQ~QBL :LTDSMK-5%LTYNC8 -CT_\RXP|WYV]VPQ5</string>
</map>
复制代码
其实主要是把"
的转义字符"
给替换掉了,压缩点在这里。
具体操做类
在生成的操做类中,能够看到都是调用了
Hannibai
类的方法,那么这里面是怎么封装的呢?
public final class Hannibai {
static boolean debug = false;
public static final void init(Context context) {
RealHannibai.getInstance().init(context, true);
}
public static final void init(Context context, boolean encrypt) {
RealHannibai.getInstance().init(context, encrypt);
}
public static final void setDebug(boolean debug) {
Hannibai.debug = debug;
}
public static final <T> T create(final Class<T> preference) {
return RealHannibai.getInstance().create(preference);
}
public static final <T> T create(final Class<T> preference, String id) {
return RealHannibai.getInstance().create(preference, id);
}
public static final void setConverterFactory(Converter.Factory factory) {
RealHannibai.getInstance().setConverterFactory(factory);
}
public static final <T> boolean contains1(String name, String id, String key, T defValue) {
return RealHannibai.getInstance().contains(name, id, key, defValue.getClass());
}
public static final <T> boolean contains2(String name, String id, String key, Type type) {
return RealHannibai.getInstance().contains(name, id, key, type);
}
public static final <T> T get1(String name, String id, String key, T defValue) {
return RealHannibai.getInstance().get(name, id, key, defValue, defValue.getClass());
}
public static final <T> T get2(String name, String id, String key, Type type) {
return RealHannibai.getInstance().get(name, id, key, null, type);
}
public static final <T> void set1(String name, String id, String key, long expire, boolean updateExpire, T newValue) {
RealHannibai.getInstance().set1(name, id, key, expire, updateExpire, newValue);
}
public static final <T> boolean set2(String name, String id, String key, long expire, boolean updateExpire, T newValue) {
return RealHannibai.getInstance().set2(name, id, key, expire, updateExpire, newValue);
}
public static final void remove1(String name, String id, String key) {
RealHannibai.getInstance().remove1(name, id, key);
}
public static final boolean remove2(String name, String id, String key) {
return RealHannibai.getInstance().remove2(name, id, key);
}
public static final void clear(String name, String id) {
RealHannibai.getInstance().clear(name, id);
}
}
复制代码
好吧,Hannibai
类能够说啥事没干,大部分工做是对RealHannibai
的封装。
不骗你,真的操做类
如何获取定义变量操做类的?
经过以前的介绍,定义的
AppPreference
首先生成AppPreferenceHandle
接口,而后生成AppPreferenceHandle
接口的实现类AppPreferenceHandleImpl
。
在使用AppPreference
的时候是这样的:
AppPreferenceHandle preferenceHandle = Hannibai.create(AppPreferenceHandle.class);
复制代码
那是怎么经过接口获取的实现类呢?在RealHannibai
中:
final public <T> T create(final Class<T> preference) {
Utils.validateHandleInterface(preference);
try {
return (T) Class.forName(preference.getName() + "Impl")
.getMethod("getInstance")
.invoke(null);
} catch (Exception e) {
Log.e(TAG, "Something went wrong!");
throw new RuntimeException(e);
}
}
复制代码
跟简单,就是根据AppPreferenceHandle
拼接Impl
找到AppPreferenceHandleImpl
类,而后反射调用它的getInstance()
静态方法。
获取带id
的实现类:
final public <T> T create(final Class<T> preference, String id) {
Utils.validateHandleInterface(preference);
try {
return (T) Class.forName(preference.getName() + "Impl")
.getConstructor(String.class)
.newInstance(id);
} catch (Exception e) {
Log.e(TAG, "Something went wrong!");
throw new RuntimeException(e);
}
}
复制代码
这个也是拼接到类名,而后反射它的构造方法获取实例。
判断是否包含Key
首先获取
Key
对应的Value
,若是Value
为空则不包含Key
,若是Value
不为空则将数据转换为BaseModel实体看是否过时,过时也为不包含,不过时则为包含该Key
。
final <T> boolean contains(String name, String id, String key, Type type) {
String value = getSharedPreferences(name, id).getString(key, null);
if (value == null || value.length() == 0) {
if (Hannibai.debug)
Log.d(TAG, String.format("Value of %s is empty.", key));
return false;
} else {
ParameterizedType parameterizedType = type(BaseModel.class, type);
BaseModel<T> model = null;
try {
model = (BaseModel<T>) getConverterFactory().toType(parameterizedType).convert(mEncrypt ? Utils.endecode(value) : value);
} catch (Exception e) {
if (mEncrypt) {
Log.e(TAG, "Convert JSON to Model failed,will use unencrypted retry again.");
} else {
Log.e(TAG, "Convert JSON to Model failed,will use encrypted retry again.");
}
if (Hannibai.debug) {
e.printStackTrace();
}
try {
model = (BaseModel<T>) getConverterFactory().toType(type).convert(mEncrypt ? value : Utils.endecode(value));
} catch (Exception e1) {
Log.e(TAG, "Convert JSON to Model complete failure.");
if (Hannibai.debug) {
e1.printStackTrace();
}
}
}
if (null == model) {
return false;
}
if (model.dataExpired()) {
if (Hannibai.debug)
Log.d(TAG, String.format("Value of %s is %s expired, return false.", key, model.data));
return false;
} else {
return true;
}
}
}
复制代码
这里进行了解密的尝试,好比以前配置的为加密,存储了数据,而后又配置了未加密,这时按照配置读取是错误的,配置说未加密其实是加密的,这里进行了容错处理。
获取Key
对应Value
首先获取
Key
对应的Value
,若是Value
为空则返回默认值,若是Value
不为空则将数据转换为BaseModel实体看是否过时,过时也返回默认值,不过时则返回BaseModel
中对应数据。
final <T> T get(String name, String id, String key, T defValue, Type type) {
if (Hannibai.debug) Log.d(TAG, String.format("Retrieve the %s from the preferences.", key));
String value = getSharedPreferences(name, id).getString(key, null);
if (value == null || value.length() == 0) {
if (Hannibai.debug)
Log.d(TAG, String.format("Value of %s is empty, return the default %s.", key, defValue));
return defValue;
} else {
ParameterizedType parameterizedType = type(BaseModel.class, type);
BaseModel<T> model = null;
try {
model = (BaseModel<T>) getConverterFactory().toType(parameterizedType).convert(mEncrypt ? Utils.endecode(value) : value);
} catch (Exception e) {
if (mEncrypt) {
Log.e(TAG, "Convert JSON to Model failed,will use unencrypted retry again.");
} else {
Log.e(TAG, "Convert JSON to Model failed,will use encrypted retry again.");
}
if (Hannibai.debug) {
e.printStackTrace();
}
try {
model = (BaseModel<T>) getConverterFactory().toType(type).convert(mEncrypt ? value : Utils.endecode(value));
} catch (Exception e1) {
Log.e(TAG, String.format("Convert JSON to Model complete failure, will return the default %s.", defValue));
if (Hannibai.debug) {
e1.printStackTrace();
}
}
}
if (null == model) {
return defValue;
}
if (Hannibai.debug) {
Log.d(TAG, String.format("Value of %s is %s, create at %s, update at %s.", key, model.data, model.createTime, model.updateTime));
if (!model.dataExpired()) {
if (model.expire > 0) {
Log.d(TAG, String.format("Value of %s is %s, Will expire after %s seconds.", key, model.data, (model.expireTime - System.currentTimeMillis()) / 1000));
} else {
Log.d(TAG, String.format("Value of %s is %s.", key, model.data));
}
}
}
if (model.dataExpired()) {
if (Hannibai.debug)
Log.d(TAG, String.format("Value of %s is %s expired, return the default %s.", key, model.data, defValue));
return defValue;
} else {
return model.data;
}
}
}
复制代码
设置Key
对应值
首先获取
Key
对应的Value
,若是Value
不为空,转换为BaseModel
实体,更新对应数据,过时时间信息,而后转化为JSON字符串存储,若是Value
为空则建立BaseModel
并转化为JSON字符串存储。
private final <T> SharedPreferences.Editor set(String name, String id, String key, long expire, boolean updateExpire, T newValue) throws Exception {
if (Hannibai.debug) Log.d(TAG, String.format("Set the %s value to the preferences.", key));
BaseModel<T> model = null;
ParameterizedType type = type(BaseModel.class, newValue.getClass());
SharedPreferences sharedPreferences = getSharedPreferences(name, id);
String value = sharedPreferences.getString(key, null);
if (value != null && value.length() != 0) {
try {
model = (BaseModel<T>) getConverterFactory().toType(type).convert(mEncrypt ? Utils.endecode(value) : value);
} catch (Exception e) {
if (mEncrypt) {
Log.e(TAG, "Convert JSON to Model failed,will use unencrypted retry again.");
} else {
Log.e(TAG, "Convert JSON to Model failed,will use encrypted retry again.");
}
if (Hannibai.debug) {
e.printStackTrace();
}
try {
model = (BaseModel<T>) getConverterFactory().toType(type).convert(mEncrypt ? value : Utils.endecode(value));
} catch (Exception e1) {
Log.e(TAG, "Convert JSON to Model complete failure.");
if (Hannibai.debug) {
e1.printStackTrace();
}
}
}
if (null == model) {
model = new BaseModel<>(newValue, expire);
} else {
if (model.dataExpired()) {
model = new BaseModel<>(newValue, expire);
if (Hannibai.debug)
Log.d(TAG, String.format("Value of %s is %s expired", key, model.data));
} else {
model.update(newValue, updateExpire);
}
}
} else {
model = new BaseModel<>(newValue, expire);
}
String modelJson = getConverterFactory().fromType(type).convert(model);
return sharedPreferences.edit().putString(key, mEncrypt ? Utils.endecode(modelJson) : modelJson);
}
复制代码
若是使用了混淆,添加以下到混淆配置文件
-dontwarn com.kevin.hannibai.**
-keep class com.kevin.hannibai.** { *; }
-keep class * implements com.kevin.hannibai.IHandle { *; }
-keep @com.kevin.hannibai.annotation.SharePreference class * { *; }
复制代码