框架手寫系列---apt註解處理器方式實現ButterKnife框架

一、ButterKnife

ButterKnife作爲常用框架之一,主要用於簡化代碼,減少重複代碼。

這裏主要注重原理與核心,將分步驟手寫它的核心代碼。

ButterKnife最常用的是去除代碼中的findViewById,以註解的方式代替原有的代碼,這裏也從這裏入手。

Android之註解的使用——綁定android控件 這是前文中通過反射方式實現,可對比查看

二、原理說明

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.hello)
    TextView hello;

    @BindView(R.id.btn)
    Button btn;

    ...

核心原理:

1、ButterKnife的用法,如上圖所示,以註解的方式標識控件。

2、通過apt註解處理器,處理該註解BindView,將註解上的參數傳入到處理器中,生成代碼。

3、通過調用生成的代碼,實現findviewById等。

三、手寫實現

1、定義註解BindView

//編譯時起效
@Retention(RetentionPolicy.CLASS)
//針對的是屬性
@Target(ElementType.FIELD)
public @interface BindView {
    int value();
}

2、註解處理器的編寫

//註解處理器的依賴,此處有注意點:
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    //依賴註解
    implementation project(path: ':bind-annotation')
    //如果是3.6+的android studio,auto-service需要按如下依賴
    compileOnly'com.google.auto.service:auto-service:1.0-rc4'
    annotationProcessor'com.google.auto.service:auto-service:1.0-rc4'
}
//註冊編譯處理器到系統
@AutoService(Processor.class)
public class BindProcessor extends AbstractProcessor {

    private Filer filer;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //filter 用於後續寫文件
        filer = processingEnvironment.getFiler();

    }

    
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        //roundEnvironment中根據annotation獲取節點
        Set<? extends Element> elementsAnnotatedWith = roundEnvironment.getElementsAnnotatedWith(BindView.class);
        //區分上面獲取到的節點,獲取類名和該類下的BindView節點====> 形成一個Map<類名,BindView標記的View集合>這樣的結構。
        //後續根據這個結構生成一個或者多個java文件
        Iterator<? extends Element> iterator = elementsAnnotatedWith.iterator();
        Map<String,List<VariableElement>> map = new HashMap<>();
        List<VariableElement> variableElements;
        while (iterator.hasNext()){
            //節點集合
            VariableElement variableElement = (VariableElement)iterator.next();

            //類名
            String className = variableElement.getEnclosingElement().getSimpleName().toString();
            variableElements = map.get(className);
            if(map.get(className) == null){
                variableElements = new ArrayList<>();
                map.put(className,variableElements);
            }
            variableElements.add(variableElement);
        }


        //寫文件
        Writer writer = null;
        Iterator<String> iteratorNames = map.keySet().iterator();
        while (iteratorNames.hasNext()){
            String currentClassName = iteratorNames.next();

            List<VariableElement> currentVariableElements = map.get(currentClassName);
            //包名
            String packageName = processingEnv.getElementUtils().getPackageOf(currentVariableElements.get(0)).toString();

            try {
                JavaFileObject sourceFile = filer.createSourceFile(packageName + "." + currentClassName + "$$ViewBind");
                writer = sourceFile.openWriter();

                StringBuilder stringBuilder = new StringBuilder();

                stringBuilder.append("package "+packageName+";\n");
                stringBuilder.append("import com.sunny.bind_api.IBindView;\n");
                stringBuilder.append("public class "+currentClassName + "$$ViewBind implements IBindView<"+packageName+"."+currentClassName+">{"+"\n");
                stringBuilder.append("public void bind("+packageName+"."+currentClassName+" target){\n");

                for(VariableElement currentElement :currentVariableElements){
                    //控件名字
                    String filedName = currentElement.getSimpleName().toString();
                    //控件類型
                    TypeMirror typeMirror = currentElement.asType();
                    //控件resId
                    int resourceId = currentElement.getAnnotation(BindView.class).value();

                    stringBuilder.append("target."+filedName +" = ("+typeMirror+")target.findViewById("+resourceId+");\n");
                }
                stringBuilder.append("}\n}\n");

                writer.write(stringBuilder.toString());
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                if(writer != null){
                    try {
                        writer.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return false;
    }


    @Override
    public SourceVersion getSupportedSourceVersion() {
        return processingEnv.getSourceVersion();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> types = new HashSet<>();
        types.add(BindView.class.getCanonicalName());
        return types;
    }
}

這裏的核心在於:如何獲取到Field類型的註解控件,然後 獲取該控件的類型,resourceId,並把以上控件信息,放置到一個集合中,後續遍歷並生成文件。

1、variableElement代表屬性節點

2、ExecutableElement代表方法節點

3、TypeElement代表最外層的類節點

此處在生成代碼時,實現了一個接口:IBindView,主要是爲了後續在調用代碼時更有指向性與範圍。

public interface IBindView<T> {
    void bind(T t);
}

3、調用代碼的編寫

public class ButterKnife {

    public static void bind(Activity activity){
        String className = activity.getClass().getName()+"$$ViewBind";
        try {
            Class<?> aClass = Class.forName(className);
            //IBindView的使用
            IBindView o = (IBindView)aClass.newInstance();
            //具體調用
            o.bind(activity);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

目前在用的ButterKnife,複雜性與完整性,比以上的核心簡寫複雜很多,但本質原理就是上面描述的這樣:用apt的方式,簡化代碼與去除重複代碼。

apt對於去重代碼、生成代碼、切面編程等,十分有效,多數涉及到框架的,都會用到該用法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章