APT(Annotation Processing Tool) 即注解處理器,是一種注解處理工具,用來在編譯期掃描和處理注解,通過注解來生成 Java 文件吞琐。即以注解作為橋梁,通過預(yù)先規(guī)定好的代碼生成規(guī)則來自動生成 Java 文件然爆。此類注解框架的代表有 ButterKnife站粟、Dragger2、EventBus 等
Java API 已經(jīng)提供了掃描源碼并解析注解的框架施蜜,開發(fā)者可以通過繼承 AbstractProcessor 類來實現(xiàn)自己的注解解析邏輯卒蘸。APT 的原理就是在注解了某些代碼元素(如字段、函數(shù)、類等)后缸沃,在編譯時編譯器會檢查 AbstractProcessor 的子類恰起,并且自動調(diào)用其 process() 方法,然后將添加了指定注解的所有代碼元素作為參數(shù)傳遞給該方法趾牧,開發(fā)者再根據(jù)注解元素在編譯期輸出對應(yīng)的 Java 代碼
一检盼、實現(xiàn)一個輕量的 “ButterKnife”
這里以 ButterKnife 為實現(xiàn)目標(biāo),在講解 Android APT 的內(nèi)容的同時翘单,逐步實現(xiàn)一個輕量的控件綁定框架吨枉,即通過注解來自動生成如下所示的 findViewById() 代碼
package hello.leavesc.apt;
public class MainActivityViewBinding {
public static void bind(MainActivity _mainActivity) {
_mainActivity.btn_serializeSingle = (android.widget.Button) (_mainActivity.findViewById(2131165221));
_mainActivity.tv_hint = (android.widget.TextView) (_mainActivity.findViewById(2131165333));
_mainActivity.btn_serializeAll = (android.widget.Button) (_mainActivity.findViewById(2131165220));
_mainActivity.btn_remove = (android.widget.Button) (_mainActivity.findViewById(2131165219));
_mainActivity.btn_print = (android.widget.Button) (_mainActivity.findViewById(2131165218));
_mainActivity.et_userName = (android.widget.EditText) (_mainActivity.findViewById(2131165246));
_mainActivity.et_userAge = (android.widget.EditText) (_mainActivity.findViewById(2131165245));
_mainActivity.et_singleUserName = (android.widget.EditText) (_mainActivity.findViewById(2131165244));
_mainActivity.et_bookName = (android.widget.EditText) (_mainActivity.findViewById(2131165243));
}
}
控件綁定的方式如下所示
@BindView(R.id.et_userName)
EditText et_userName;
@BindView(R.id.et_userAge)
EditText et_userAge;
@BindView(R.id.et_bookName)
EditText et_bookName;
1.1、建立 Module
首先在工程中新建一個 Java Library哄芜,命名為 apt_processor貌亭,用于存放 AbstractProcessor 的實現(xiàn)類。再新建一個 Java Library认臊,命名為 apt_annotation 圃庭,用于存放各類注解
當(dāng)中,apt_processor 需要導(dǎo)入如下依賴
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.google.auto.service:auto-service:1.0-rc2'
implementation 'com.squareup:javapoet:1.10.0'
implementation project(':apt_annotation')
}
當(dāng)中失晴,JavaPoet 是 square 開源的 Java 代碼生成框架剧腻,可以很方便地通過其提供的 API 來生成指定格式(修飾符、返回值涂屁、參數(shù)书在、函數(shù)體等)的代碼。auto-service 是由 Google 開源的注解注冊處理器
實際上拆又,上面兩個依賴庫并不是必須的儒旬,可以通過硬編碼代碼生成規(guī)則來替代,但還是建議使用這兩個庫遏乔,因為這樣代碼的可讀性會更高义矛,且能提高開發(fā)效率
app Module 需要依賴這兩個 Java Library
implementation project(':apt_annotation')
annotationProcessor project(':apt_processor')
這樣子发笔,我們需要的所有基礎(chǔ)依賴關(guān)系就搭建好了
1.2盟萨、編寫代碼生成規(guī)則
首先觀察自動生成的代碼,可以歸納出幾點需要實現(xiàn)的地方:
1了讨、文件和源 Activity 處在同個包名下
2捻激、類名以 Activity名 + ViewBinding 組成
3、bind() 方法通過傳入 Activity 對象來獲取其聲明的控件對象來對其進行實例化前计,這也是 ButterKnife 要求需要綁定的控件變量不能聲明為 private 的原因
package hello.leavesc.apt;
public class MainActivityViewBinding {
public static void bind(MainActivity _mainActivity) {
_mainActivity.btn_serializeSingle = (android.widget.Button) (_mainActivity.findViewById(2131165221));
_mainActivity.tv_hint = (android.widget.TextView) (_mainActivity.findViewById(2131165333));
...
}
}
在 apt_processor Module 中創(chuàng)建 BindViewProcessor 類并繼承 AbstractProcessor 抽象類胞谭,該抽象類含有一個抽象方法 process() 以及一個非抽象方法 getSupportedAnnotationTypes() 需要由我們來實現(xiàn)
/**
* 作者:leavesC
* 時間:2019/1/3 14:32
* 描述:
* GitHub:https://github.com/leavesC
* Blog:http://www.reibang.com/u/9df45b87cfdf
*/
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> hashSet = new HashSet<>();
hashSet.add(BindView.class.getCanonicalName());
return hashSet;
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
return false;
}
}
getSupportedAnnotationTypes() 方法用于指定該 AbstractProcessor 的目標(biāo)注解對象,process() 方法則用于處理包含指定注解對象的代碼元素
BindView 注解的聲明如下所示男杈,放在 apt_annotation 中丈屹,注解值 value 用于聲明 viewId
@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface BindView {
int value();
}
要自動生成 findViewById() 方法,則需要獲取到控件變量的引用以及對應(yīng)的 viewid,所以需要先遍歷出每個 Activity 包含的所有注解對象
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//獲取所有包含 BindView 注解的元素
Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Map<TypeElement, Map<Integer, VariableElement>> typeElementMapHashMap = new HashMap<>();
for (Element element : elementSet) {
//因為 BindView 的作用對象是 FIELD旺垒,因此 element 可以直接轉(zhuǎn)化為 VariableElement
VariableElement variableElement = (VariableElement) element;
//getEnclosingElement 方法返回封裝此 Element 的最里層元素
//如果 Element 直接封裝在另一個元素的聲明中彩库,則返回該封裝元素
//此處表示的即 Activity 類對象
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
Map<Integer, VariableElement> variableElementMap = typeElementMapHashMap.get(typeElement);
if (variableElementMap == null) {
variableElementMap = new HashMap<>();
typeElementMapHashMap.put(typeElement, variableElementMap);
}
//獲取注解值,即 ViewId
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int viewId = bindAnnotation.value();
//將每個包含了 BindView 注解的字段對象以及其注解值保存起來
variableElementMap.put(viewId, variableElement);
}
...
return true;
}
當(dāng)中先蒋,Element 用于代表程序的一個元素骇钦,這個元素可以是:包、類竞漾、接口眯搭、變量、方法等多種概念业岁。這里以 Activity 對象作為 Key 鳞仙,通過 map 來存儲不同 Activity 下的所有注解對象
獲取到所有的注解對象后,就可以來構(gòu)造 bind() 方法了
MethodSpec 是 JavaPoet 提供的一個概念笔时,用于抽象出生成一個函數(shù)時需要的基礎(chǔ)元素繁扎,直接看以下方法應(yīng)該就可以很容易理解其含義了
通過 addCode() 方法把需要的參數(shù)元素填充進去,循環(huán)生成每一行 findView 方法
/**
* 生成方法
*
* @param typeElement 注解對象上層元素對象糊闽,即 Activity 對象
* @param variableElementMap Activity 包含的注解對象以及注解的目標(biāo)對象
* @return
*/
private MethodSpec generateMethodByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString());
//方法參數(shù)名
String parameter = "_" + StringUtils.toLowerCaseFirstChar(className.simpleName());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(className, parameter);
for (int viewId : variableElementMap.keySet()) {
VariableElement element = variableElementMap.get(viewId);
//被注解的字段名
String name = element.getSimpleName().toString();
//被注解的字段的對象類型的全名稱
String type = element.asType().toString();
String text = "{0}.{1}=({2})({3}.findViewById({4}));";
methodBuilder.addCode(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(viewId)));
}
return methodBuilder.build();
}
完整的代碼聲明如下所示
/**
* 作者:leavesC
* 時間:2019/1/3 14:32
* 描述:
* GitHub:https://github.com/leavesC
* Blog:http://www.reibang.com/u/9df45b87cfdf
*/
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Elements elementUtils;
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> hashSet = new HashSet<>();
hashSet.add(BindView.class.getCanonicalName());
return hashSet;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//獲取所有包含 BindView 注解的元素
Set<? extends Element> elementSet = roundEnvironment.getElementsAnnotatedWith(BindView.class);
Map<TypeElement, Map<Integer, VariableElement>> typeElementMapHashMap = new HashMap<>();
for (Element element : elementSet) {
//因為 BindView 的作用對象是 FIELD梳玫,因此 element 可以直接轉(zhuǎn)化為 VariableElement
VariableElement variableElement = (VariableElement) element;
//getEnclosingElement 方法返回封裝此 Element 的最里層元素
//如果 Element 直接封裝在另一個元素的聲明中,則返回該封裝元素
//此處表示的即 Activity 類對象
TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
Map<Integer, VariableElement> variableElementMap = typeElementMapHashMap.get(typeElement);
if (variableElementMap == null) {
variableElementMap = new HashMap<>();
typeElementMapHashMap.put(typeElement, variableElementMap);
}
//獲取注解值右犹,即 ViewId
BindView bindAnnotation = variableElement.getAnnotation(BindView.class);
int viewId = bindAnnotation.value();
//將每個包含了 BindView 注解的字段對象以及其注解值保存起來
variableElementMap.put(viewId, variableElement);
}
for (TypeElement key : typeElementMapHashMap.keySet()) {
Map<Integer, VariableElement> elementMap = typeElementMapHashMap.get(key);
String packageName = ElementUtils.getPackageName(elementUtils, key);
JavaFile javaFile = JavaFile.builder(packageName, generateCodeByPoet(key, elementMap)).build();
try {
javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
/**
* 生成 Java 類
*
* @param typeElement 注解對象上層元素對象提澎,即 Activity 對象
* @param variableElementMap Activity 包含的注解對象以及注解的目標(biāo)對象
* @return
*/
private TypeSpec generateCodeByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
//自動生成的文件以 Activity名 + ViewBinding 進行命名
return TypeSpec.classBuilder(ElementUtils.getEnclosingClassName(typeElement) + "ViewBinding")
.addModifiers(Modifier.PUBLIC)
.addMethod(generateMethodByPoet(typeElement, variableElementMap))
.build();
}
/**
* 生成方法
*
* @param typeElement 注解對象上層元素對象,即 Activity 對象
* @param variableElementMap Activity 包含的注解對象以及注解的目標(biāo)對象
* @return
*/
private MethodSpec generateMethodByPoet(TypeElement typeElement, Map<Integer, VariableElement> variableElementMap) {
ClassName className = ClassName.bestGuess(typeElement.getQualifiedName().toString());
//方法參數(shù)名
String parameter = "_" + StringUtils.toLowerCaseFirstChar(className.simpleName());
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(void.class)
.addParameter(className, parameter);
for (int viewId : variableElementMap.keySet()) {
VariableElement element = variableElementMap.get(viewId);
//被注解的字段名
String name = element.getSimpleName().toString();
//被注解的字段的對象類型的全名稱
String type = element.asType().toString();
String text = "{0}.{1}=({2})({3}.findViewById({4}));";
methodBuilder.addCode(MessageFormat.format(text, parameter, name, type, parameter, String.valueOf(viewId)));
}
return methodBuilder.build();
}
}
1.3念链、注解綁定效果
首先在 MainActivity 中聲明兩個 BindView 注解盼忌,然后 Rebuild Project,使編譯器根據(jù) BindViewProcessor 生成我們需要的代碼
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv_hint)
TextView tv_hint;
@BindView(R.id.btn_hint)
Button btn_hint;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
rebuild 結(jié)束后掂墓,就可以在 generatedJava 文件夾下看到 MainActivityViewBinding 類自動生成了
此時有兩種方式可以用來觸發(fā) bind() 方法
- 在 MainActivity 方法中直接調(diào)用 MainActivityViewBinding 的 bind() 方法
- 因為 MainActivityViewBinding 的包名路徑和 Activity 是相同的谦纱,所以也可以通過反射來觸發(fā) MainActivityViewBinding 的 bind() 方法
/**
* 作者:leavesC
* 時間:2019/1/3 14:34
* 描述:
* GitHub:https://github.com/leavesC
* Blog:http://www.reibang.com/u/9df45b87cfdf
*/
public class ButterKnife {
public static void bind(Activity activity) {
Class clazz = activity.getClass();
try {
Class bindViewClass = Class.forName(clazz.getName() + "ViewBinding");
Method method = bindViewClass.getMethod("bind", activity.getClass());
method.invoke(bindViewClass.newInstance(), activity);
} catch (Exception e) {
e.printStackTrace();
}
}
}
兩種方式各有優(yōu)缺點。第一種方式在每次 build project 后才會生成代碼君编,在這之前無法引用到對應(yīng)的 ViewBinding 類跨嘉。第二種方式可以用固定的方法調(diào)用方式,但是相比方式一吃嘿,反射會略微多消耗一些性能
但這兩種方式的運行結(jié)果是完全相同的
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MainActivityViewBinding.bind(this);
tv_hint.setText("leavesC Success");
btn_hint.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "Hello", Toast.LENGTH_SHORT).show();
}
});
}
二祠乃、對象 持久化+序列化+反序列化 框架
通過第一節(jié)的內(nèi)容,讀者應(yīng)該了解到了 APT 其強大的功能了 兑燥。這一節(jié)再來實現(xiàn)一個可以方便地將 對象進行持久化+序列化+反序列 的框架
2.1亮瓷、確定目標(biāo)
通常,我們的應(yīng)用都會有很多配置項需要進行緩存降瞳,比如用戶信息嘱支、設(shè)置項開關(guān)、服務(wù)器IP地址等。如果采用原生的 SharedPreferences 來實現(xiàn)的話除师,則很容易就寫出如下丑陋的代碼赢织,不僅需要維護多個數(shù)據(jù)項的 key 值,而且每次存入和取出數(shù)據(jù)時都會有一大片重復(fù)的代碼馍盟,不易維護
SharedPreferences sharedPreferences = getSharedPreferences("SharedPreferencesName", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("IP", "192.168.0.1");
editor.commit();
String userName = sharedPreferences.getString("userName", "");
String ip = sharedPreferences.getString("IP", "");
因此于置,這里就來通過 APT 來實現(xiàn)一個可以方便地對數(shù)據(jù)進行 持久化+序列化+反序列化 的框架,具體的目標(biāo)有以下幾點:
1贞岭、可以將 Object 進行序列化八毯,并且提供反序列化為 Object 的方法
2、Object 的序列化結(jié)果可以持久化保存到本地
3瞄桨、持久化數(shù)據(jù)時需要的唯一 key 值由框架內(nèi)部自動進行維護
4话速、序列化、反序列化芯侥、持久化的具體過程由框架外部實現(xiàn)泊交,框架只負責(zé)搭建操作邏輯
目標(biāo)1可以通過 Gson 來實現(xiàn),目標(biāo)2則可以通過使用騰訊開源的 MMKV 框架來實現(xiàn)柱查,需要導(dǎo)入以下兩個依賴
implementation 'com.google.code.gson:gson:2.8.5'
implementation 'com.tencent:mmkv:1.0.16'
2.2廓俭、效果預(yù)覽
這里先預(yù)先看下框架的使用方式。新的注解以 Preferences 命名唉工,假設(shè) User 類中有三個字段值需要進行本地緩存研乒,因此都為其加上 Preferences 注解
public class User {
@Preferences
private String name;
@Preferences
private int age;
@Preferences
private Book book;
...
}
而我們要做的,就是通過 APT 自動為 User 類來生成一個 UserPreferences 子類淋硝,之后的數(shù)據(jù)緩存操作都是通過 UserPreferences 來進行
緩存整個對象
User user = new User();
UserPreferences.get().setUser(user);
緩存單個屬性值
String userName = et_singleUserName.getText().toString();
UserPreferences.get().setName(userName);
獲取緩存的對象
User user = UserPreferences.get().getUser();
移除緩存的對象
UserPreferences.get().remove();
可以看到雹熬,整個操作都是十分的簡潔,之后就來開工吧
2.3谣膳、實現(xiàn)操作接口
為了實現(xiàn)目標(biāo)4竿报,需要先定義好操作接口,并由外部傳入具體的實現(xiàn)
public interface IPreferencesHolder {
//序列化
String serialize(String key, Object src);
//反序列化
<T> T deserialize(String key, Class<T> classOfT);
//移除指定對象
void remove(String key);
}
以上三個操作對于框架內(nèi)部來說應(yīng)該是唯一的继谚,因此可以通過單例模式來全局維護烈菌。APT 生成的代碼就通過此入口來調(diào)用 持久化+序列化+反序列化 方法
public class PreferencesManager {
private IPreferencesHolder preferencesHolder;
private PreferencesManager() {
}
public static PreferencesManager getInstance() {
return PreferencesManagerHolder.INSTANCE;
}
private static class PreferencesManagerHolder {
private static PreferencesManager INSTANCE = new PreferencesManager();
}
public void setPreferencesHolder(IPreferencesHolder preferencesHolder) {
this.preferencesHolder = preferencesHolder;
}
public IPreferencesHolder getPreferencesHolder() {
return preferencesHolder;
}
}
在 Application 的 onCreate() 方法中傳入具體的實現(xiàn)
PreferencesManager.getInstance().setPreferencesHolder(new PreferencesMMKVHolder());
public class PreferencesMMKVHolder implements IPreferencesHolder {
@Override
public String serialize(String key, Object src) {
String json = new Gson().toJson(src);
MMKV kv = MMKV.defaultMMKV();
kv.putString(key, json);
return json;
}
@Override
public <T> T deserialize(String key, Class<T> classOfT) {
MMKV kv = MMKV.defaultMMKV();
String json = kv.decodeString(key, "");
if (!TextUtils.isEmpty(json)) {
return new Gson().fromJson(json, classOfT);
}
return null;
}
@Override
public void remove(String key) {
MMKV kv = MMKV.defaultMMKV();
kv.remove(key);
}
}
2.4、編寫代碼生成規(guī)則
一樣是需要繼承 AbstractProcessor 類犬庇,子類命名為 PreferencesProcessor
首先僧界,PreferencesProcessor 類需要生成一個序列化整個對象的方法侨嘀。例如臭挽,需要為 User 類生成一個子類 UserPreferences ,UserPreferences 包含一個 setUser(User instance) 方法
public String setUser(User instance) {
if (instance == null) {
PreferencesManager.getInstance().getPreferencesHolder().remove(KEY);
return "";
}
return PreferencesManager.getInstance().getPreferencesHolder().serialize(KEY, instance);
}
對應(yīng)的方法生成規(guī)則如下所示咬腕』斗澹可以看出來,大體規(guī)則還是和第一節(jié)類似,一樣是需要通過字符串來拼接出完整的代碼纽帖。當(dāng)中宠漩,T 都是替代符懊直,作用類似于 MessageFormat
/**
* 構(gòu)造用于序列化整個對象的方法
*
* @param typeElement 注解對象上層元素對象扒吁,即 Java 對象
* @return
*/
private MethodSpec generateSetInstanceMethod(TypeElement typeElement) {
//頂層類類名
String enclosingClassName = ElementUtils.getEnclosingClassName(typeElement);
//方法名
String methodName = "set" + StringUtils.toUpperCaseFirstChar(enclosingClassName);
//方法參數(shù)名
String fieldName = "instance";
MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName)
.addModifiers(Modifier.PUBLIC)
.returns(String.class)
.addParameter(ClassName.get(typeElement.asType()), fieldName);
builder.addStatement("if ($L == null) { $T.getInstance().getPreferencesHolder().remove(KEY); return \"\"; }", fieldName, serializeManagerClass);
builder.addStatement("return $T.getInstance().getPreferencesHolder().serialize(KEY, $L)", serializeManagerClass, fieldName);
return builder.build();
}
此外,還需要一個用于反序列化本地緩存的數(shù)據(jù)的方法
public User getUser() {
return PreferencesManager.getInstance().getPreferencesHolder().deserialize(KEY, User.class);
}
對應(yīng)的方法生成規(guī)則如下所示
/**
* 構(gòu)造用于獲取整個序列化對象的方法
*
* @param typeElement 注解對象上層元素對象室囊,即 Java 對象
* @return
*/
private MethodSpec generateGetInstanceMethod(TypeElement typeElement) {
//頂層類類名
String enclosingClassName = ElementUtils.getEnclosingClassName(typeElement);
//方法名
String methodName = "get" + StringUtils.toUpperCaseFirstChar(enclosingClassName);
MethodSpec.Builder builder = MethodSpec.methodBuilder(methodName)
.addModifiers(Modifier.PUBLIC)
.returns(ClassName.get(typeElement.asType()));
builder.addStatement("return $T.getInstance().getPreferencesHolder().deserialize(KEY, $L.class)", serializeManagerClass, enclosingClassName);
return builder.build();
}
為了實現(xiàn)目標(biāo)三(持久化數(shù)據(jù)時需要的唯一 key 值由框架內(nèi)部自動進行維護)雕崩,在持久化時使用的 key 值由當(dāng)前的 包名路徑+類名 來決定,由此保證 key 值的唯一性
例如融撞,UserPreferences 類緩存數(shù)據(jù)使用的 key 值是
private static final String KEY = "leavesc.hello.apt.model.UserPreferences";
對應(yīng)的方法生成規(guī)則如下所示
/**
* 定義該注解類在序列化時使用的 Key
*
* @param typeElement 注解對象上層元素對象盼铁,即 Java 對象
* @return
*/
private FieldSpec generateKeyField(TypeElement typeElement) {
return FieldSpec.builder(String.class, "KEY")
.addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
.initializer("\"" + typeElement.getQualifiedName().toString() + SUFFIX + "\"")
.build();
}
其他相應(yīng)的 get 和 set 方法生成規(guī)則就不再贅述了,有興趣研究的同學(xué)可以下載源碼閱讀
2.5尝偎、實際體驗
修改 MainActivity 的布局
public class MainActivity extends AppCompatActivity {
@BindView(R.id.et_userName)
EditText et_userName;
@BindView(R.id.et_userAge)
EditText et_userAge;
···
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ButterKnife.bind(this);
MainActivityViewBinding.bind(this);
btn_serializeAll.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String userName = et_userName.getText().toString();
String ageStr = et_userAge.getText().toString();
int age = 0;
if (!TextUtils.isEmpty(ageStr)) {
age = Integer.parseInt(ageStr);
}
String bookName = et_bookName.getText().toString();
User user = new User();
user.setAge(age);
user.setName(userName);
Book book = new Book();
book.setName(bookName);
user.setBook(book);
UserPreferences.get().setUser(user);
}
});
btn_serializeSingle.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String userName = et_singleUserName.getText().toString();
UserPreferences.get().setName(userName);
}
});
btn_remove.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
UserPreferences.get().remove();
}
});
btn_print.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
User user = UserPreferences.get().getUser();
if (user == null) {
tv_hint.setText("null");
} else {
tv_hint.setText(user.toString());
}
}
});
}
}
數(shù)據(jù)的整個存取過程自我感覺還是十分的簡單的饶火,不用再自己去維護臃腫的 key 表,且可以做到存取路徑的唯一性致扯,還是可以提高一些開發(fā)效率的