Butterknife深入剖析,自己實現(xiàn)Butterknife

前言

Butterknife我相信,對大部分做Android開發(fā)的人都不陌生,這個是供職于Square公司的JakeWharton大神開發(fā)的,目前github的star為 12449 。使用這個庫,在AS中搭配Android ButterKnife Zelezny插件,簡直是開發(fā)神器,從此擺脫繁瑣的findViewById(int id),也不用自己手動@bind(int id) , 直接用插件生成即可。這種采用注解DI組件的方式,在Spring中很常見,起初也是在Spring中興起的 。今天我們就一探究竟,自己實現(xiàn)一個butterknife (有不會用的,請自行Google)。

項目地址: JakeWharton/butterknife

butterknife

實現(xiàn)原理 (假定你對注解有一定的了解)

注解

對ButterKnife有過了解人 , 注入字段的方式是使用注解@Bind(R.id.tv_account_name),但首先我們需要在Activity聲明注入ButterKnife.bind(Activity activity) 。我們知道,注解分為好幾類, 有在源碼生效的注解,有在類文件生成時生效的注解,有在運(yùn)行時生效的注解。分別為RetentionPolicy.SOURCE,RetentionPolicy.CLASS,RetentionPolicy.RUNTIME ,其中以RetentionPolicy.RUNTIME最為消耗性能。而ButterKnife使用的則是編譯器時期注入,在使用的時候,需要配置classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' , 這個配置說明,在編譯的時候,進(jìn)行注解處理。要對注解進(jìn)行處理,則需要繼承AbstractProcessor , 在boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)中進(jìn)行注解處理。

實現(xiàn)方式

知曉了注解可以在編譯的時候進(jìn)行處理,那么,我們就可以得到注解的字段屬性與所在類 , 進(jìn)而生成注入文件,生成一個注入類的內(nèi)部類,再進(jìn)行字段處理 , 編譯之后就會合并到注入類中,達(dá)到植入新代碼段的目的。例如:我們注入@VInjector(R.id.tv_show) TextView tvShow;我們就可以得到tvShow這個變量與R.id.tv_show這個id的值,然后進(jìn)行模式化處理injectObject.tvShow = injectObject.findViewById(R.id.tv_show); ,再將代碼以內(nèi)部類的心事加入到組件所在的類中 , 完成一次DI(注入) 。

實現(xiàn)流程圖

view_injector_流程圖

① 首先創(chuàng)建一個視圖注解
② 創(chuàng)建一個注解處理器,用來得到注解的屬性與所屬類
③ 解析注解,分離組合Class與屬性
④ 組合Class與屬性,生成新的Java File

APT生成的Java File , 以及模式代碼

generator java file

使用Javac , 編譯時期生成注入類的子類

項目UML圖

ViewInject UML

簡要說明:

主要類:
VInjectProcessor ----> 注解處理器 , 需要配置注解處理器

resources
        - META-INF
              - services
                    - javax.annotation.processing.Processor

Processor內(nèi)容:

com.zeno.viewinject.apt.VInjectProcessor   # 指定處理器全類名

圖示:

processor config

VInjectHandler ----> 注解處理類 , 主要進(jìn)行注入類與注解字段進(jìn)行解析與封裝,將同類的字段使用map集合進(jìn)行映射。exp: Map<Class,List<Attr>> 。

ViewGenerateAdapter -----> Java File 生成器,將注入的類與屬性,重新生成一個Java File,是其注入類的內(nèi)部類 。

具體實現(xiàn)

一 , 創(chuàng)建注解 , 對視圖進(jìn)行注解,R.id.xxx , 所以注解類型是int類型

/**
 * Created by Zeno on 2016/10/21.
 *
 * View inject
 * 字段注入注解,可以新建多個注解,再通過AnnotationProcessor進(jìn)行注解處理
 * RetentionPolicy.CLASS ,在編譯的時候進(jìn)行注解 。我們需要在生成.class文件的時候需要進(jìn)行處理
 */

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.FIELD)
public @interface VInjector {
    int value();
}

二, 注解處理器 關(guān)于注解處理器配置,上面已經(jīng)做了說明

/**
 * Created by Zeno on 2016/10/21.
 *
 * Inject in View annotation processor
 *
 * 需要在配置文件中指定處理類 resources/META-INF/services/javax.annotation.processing.Processor
 * com.zeno.viewinject.apt.VInjectProcessor
 */

@SupportedAnnotationTypes("com.zeno.viewinject.annotation.VInjector")
@SupportedSourceVersion(SourceVersion.RELEASE_6)
public class VInjectProcessor extends AbstractProcessor {

    List<IAnnotationHandler> mAnnotationHandler = new ArrayList<>();
    Map<String,List<VariableElement>> mHandleAnnotationMap = new HashMap<>();
    private IGenerateAdapter mGenerateAdapter;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        // init annotation handler , add handler
        registerHandler(new VInjectHandler());

        // init generate adapter
        mGenerateAdapter = new ViewGenerateAdapter(processingEnv);

    }

    /*可以有多個處理*/
    protected void registerHandler(IAnnotationHandler handler) {
        mAnnotationHandler.add(handler);
    }

    // annotation into process run
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {

        for (IAnnotationHandler handler : mAnnotationHandler) {
            // attach environment , 關(guān)聯(lián)環(huán)境
            handler.attachProcessingEnvironment(processingEnv);
            // handle annotation 處理注解 ,得到注解類的屬性列表
            mHandleAnnotationMap.putAll(handler.handleAnnotation(roundEnv));
        }
        // 生成輔助類
        mGenerateAdapter.generate(mHandleAnnotationMap);
        // 表示處理
        return true;
    }
}

對得到的注解進(jìn)行處理 , 主要是進(jìn)行注解類型與屬性進(jìn)行分離合并處理,因為一個類有多個屬性,所以采用map集合,進(jìn)行存儲,數(shù)據(jù)結(jié)構(gòu)為:Map<String:className , List<VariableElement:element>>

/**
 * Created by Zeno on 2016/10/21.
 *
 * 注解處理實現(xiàn) , 解析VInjector注解屬性
 */
public class VInjectHandler implements IAnnotationHandler {


    private ProcessingEnvironment mProcessingEnvironment;

    @Override
    public void attachProcessingEnvironment(ProcessingEnvironment environment) {
            this.mProcessingEnvironment = environment;
    }

    @Override
    public Map<String, List<VariableElement>> handleAnnotation(RoundEnvironment roundEnvironment) {
        Map<String,List<VariableElement>> map = new HashMap<>();
        /*獲取一個類中帶有VInjector注解的屬性列表*/
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(VInjector.class);
        for (Element element : elements) {
            VariableElement variableElement = (VariableElement) element;
            /*獲取類名 ,將類目與屬性配對,一個類,對于他的屬性列表*/
            String className = getFullClassName(variableElement);
            List<VariableElement> cacheElements = map.get(className);
            if (cacheElements == null) {
                cacheElements = new ArrayList<>();
                map.put(className,cacheElements);
            }
            cacheElements.add(variableElement);
        }

        return map;
    }

    /**
     * 獲取注解屬性的完整類名
     * @param variableElement
     */
    private String getFullClassName(VariableElement variableElement) {
        TypeElement typeElement = (TypeElement) variableElement.getEnclosingElement();
        String packageName = AnnotationUtils.getPackageName(mProcessingEnvironment,typeElement);
        return packageName+"."+typeElement.getSimpleName().toString();
    }
}

生成Java File , 根據(jù)獲取的屬性與類,創(chuàng)建一個注入類的內(nèi)部類

/**
 * Created by Zeno on 2016/10/21.
 *
 * 生成View注解輔助類
 */
public class ViewGenerateAdapter extends AbstractGenerateAdapter {

    public ViewGenerateAdapter(ProcessingEnvironment processingEnvironment) {
        super(processingEnvironment);
    }

    @Override
    protected void generateImport(Writer writer, InjectInfo injectInfo) throws IOException {
        writer.write("package "+injectInfo.packageName+";");
        writer.write("\n\n");
        writer.write("import  com.zeno.viewinject.adapter.IVInjectorAdapter;");
        writer.write("\n\n");
        writer.write("import  com.zeno.viewinject.utils.ViewFinder;");
        writer.write("\n\n\n");
        writer.write("/* This class file is generated by ViewInject , do not modify */");
        writer.write("\n");
        writer.write("public class "+injectInfo.newClassName+" implements IVInjectorAdapter<"+injectInfo.className+"> {");
        writer.write("\n\n");
        writer.write("public void injects("+injectInfo.className+" target) {");
        writer.write("\n");
    }

    @Override
    protected void generateField(Writer writer, VariableElement variableElement, InjectInfo injectInfo) throws IOException {
        VInjector vInjector = variableElement.getAnnotation(VInjector.class);
        int resId = vInjector.value();
        String fieldName = variableElement.getSimpleName().toString();
        writer.write("\t\ttarget."+fieldName+" = ViewFinder.findViewById(target,"+resId+");");
        writer.write("\n");
    }

    @Override
    protected void generateFooter(Writer writer) throws IOException {
        writer.write(" \t}");
        writer.write("\n\n");
        writer.write("}");
    }
}

結(jié)語

ButterKnife類型的注解框架,其主要核心就是編譯時期注入, 如果是采用運(yùn)行時注解的話,那性能肯定影響很大,國內(nèi)有些DI框架就是采用的運(yùn)行時注解,所以性能上會有所損傷 。原以為很高深的東西,其實剖析過原理之后,也就漸漸明白了,不再視其為高深莫測,我們自己也可以實現(xiàn)同等的功能。

程序員最好的學(xué)習(xí)方式就是,學(xué)習(xí)別人的代碼,特別是像jakeWharton這樣的大神的代碼,值得研究與學(xué)習(xí) , 然后模仿之。

源碼

ViewInjectDemo UML圖與流程圖都會放在github上

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,109評論 25 709
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,586評論 19 139
  • 上周四去了根大生態(tài)園。因為那里要舉行2016年江蘇省鄉(xiāng)村美食大賽。有十六個城市參賽,共160位選手,他們每人做一道...
    玖月琉璃閱讀 2,042評論 3 1
  • 1.30字自我介紹 親愛的你好,很高興認(rèn)識你,我叫陳芝,是一名85后的三寶寶媽,來自美麗的煙花之鄉(xiāng)湖南瀏陽。 2....
    cz陳芝閱讀 454評論 0 0
  • 如何治療多囊卵巢綜合征?這道題拖了太久。妹子們看過來吧,幾句話很難講清楚,先前的評論和私信就不一一回復(fù)咯。 一、關(guān)...
    女性健康咨詢_7945閱讀 822評論 0 1

友情鏈接更多精彩內(nèi)容