一種簡單優雅的方法來使用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 * { *; }
複製代碼