Android APT 實例講解

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() 方法了

MethodSpecJavaPoet 提供的一個概念笔时,用于抽象出生成一個函數(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() 方法

  1. MainActivity 方法中直接調(diào)用 MainActivityViewBindingbind() 方法
  2. 因為 MainActivityViewBinding 的包名路徑和 Activity 是相同的谦纱,所以也可以通過反射來觸發(fā) MainActivityViewBindingbind() 方法
/**
 * 作者: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;
    }

}

ApplicationonCreate() 方法中傳入具體的實現(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 類生成一個子類 UserPreferencesUserPreferences 包含一個 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)中宠漩,L、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)的 getset 方法生成規(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ā)效率的

有興趣看具體實現(xiàn)的可以點傳送門:Android_APT

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末肤寝,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子抖僵,更是在濱河造成了極大的恐慌醒陆,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,248評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件裆针,死亡現(xiàn)場離奇詭異刨摩,居然都是意外死亡,警方通過查閱死者的電腦和手機世吨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評論 2 381
  • 文/潘曉璐 我一進店門澡刹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人耘婚,你說我怎么就攤上這事罢浇。” “怎么了沐祷?”我有些...
    開封第一講書人閱讀 153,443評論 0 344
  • 文/不壞的土叔 我叫張陵嚷闭,是天一觀的道長。 經(jīng)常有香客問我赖临,道長胞锰,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,475評論 1 279
  • 正文 為了忘掉前任兢榨,我火速辦了婚禮嗅榕,結(jié)果婚禮上顺饮,老公的妹妹穿的比我還像新娘。我一直安慰自己凌那,他們只是感情好兼雄,可當(dāng)我...
    茶點故事閱讀 64,458評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著帽蝶,像睡著了一般赦肋。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上励稳,一...
    開封第一講書人閱讀 49,185評論 1 284
  • 那天金砍,我揣著相機與錄音,去河邊找鬼麦锯。 笑死恕稠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的扶欣。 我是一名探鬼主播鹅巍,決...
    沈念sama閱讀 38,451評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼料祠!你這毒婦竟也來了骆捧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,112評論 0 261
  • 序言:老撾萬榮一對情侶失蹤髓绽,失蹤者是張志新(化名)和其女友劉穎敛苇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體顺呕,經(jīng)...
    沈念sama閱讀 43,609評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡枫攀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,083評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了株茶。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片来涨。...
    茶點故事閱讀 38,163評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖启盛,靈堂內(nèi)的尸體忽然破棺而出蹦掐,到底是詐尸還是另有隱情,我是刑警寧澤僵闯,帶...
    沈念sama閱讀 33,803評論 4 323
  • 正文 年R本政府宣布嘹吨,位于F島的核電站捡絮,受9級特大地震影響蠢络,放射性物質(zhì)發(fā)生泄漏弹砚。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,357評論 3 307
  • 文/蒙蒙 一牺弹、第九天 我趴在偏房一處隱蔽的房頂上張望浦马。 院中可真熱鬧时呀,春花似錦张漂、人聲如沸晶默。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽磺陡。三九已至,卻和暖如春漠畜,著一層夾襖步出監(jiān)牢的瞬間币他,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評論 1 261
  • 我被黑心中介騙來泰國打工憔狞, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蝴悉,地道東北人。 一個月前我還...
    沈念sama閱讀 45,636評論 2 355
  • 正文 我出身青樓瘾敢,卻偏偏與公主長得像拍冠,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子簇抵,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,925評論 2 344

推薦閱讀更多精彩內(nèi)容

  • 序言 注解是Java程序和Android程序中常見的語法庆杜,之前雖然知道有這么個東西,但并沒有深入了解注解碟摆。寫Eve...
    左大人閱讀 4,653評論 3 15
  • 概念 APT(Annotation Processing Tool)編譯時注解晃财,是javac的一個工具,在java...
    耳_總閱讀 2,182評論 0 3
  • Android APT快速教程 簡介 APT(Annotation Processing Tool)即注解處理器典蜕,...
    33ae5f9d4c24閱讀 1,991評論 1 23
  • 應(yīng)該要走了吧断盛。 二月午后,稀黃的陽光大大咧咧地鉆進窗戶愉舔,撫上我的脖子郑临,肆意地索吻。我拉開簾子屑宠,索性乘著這股百無聊賴...
    劉和碩閱讀 325評論 0 1
  • 調(diào)入期刊部已有1個月有余厢洞,其間瑣碎與陌生在此不提,從未想到典奉,有一日躺翻,竟與花花綠綠,千姿百態(tài)的期刊親密接觸卫玖。內(nèi)心...
    Danny小奇閱讀 126評論 0 0