Android IOC——編譯時(shí)(Compile time)處理,擼一個(gè)簡易版的ButterKnife

前言

對于IOC的概念以及運(yùn)行時(shí)處理不了解的可以看下前面我寫的這篇文章:Android IOC——運(yùn)行時(shí)(Runtime)處理

Android開發(fā)大多是基于Java開發(fā)的罩锐,而Java后端的技術(shù)又沉淀了那么多年奉狈。于是乎,我們可以發(fā)現(xiàn)最近這幾年唯欣,Android開發(fā)借鑒了非常多的Java后端思想嘹吨,比如IOC、AOP境氢、組件化……

目前比較優(yōu)秀并且使用范圍較廣的Android IOC框架有ButterKnifeDagger2碰纬、Dagger等等萍聊。我們以ButterKnife為例,貼一段使用方式的代碼片:

class ExampleActivity extends Activity {
  @BindView(R.id.user) EditText username;
  @BindView(R.id.pass) EditText password;

  @BindString(R.string.login_error) String loginErrorMessage;

  @OnClick(R.id.submit) void submit() {
    // TODO call server...
  }

  @Override public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.bind(this);
    // TODO Use fields...
  }
}

為什么ButterKnife使用@BindView悦析、@BindString@OnClick注解修飾成員變量后寿桨,再調(diào)用ButterKnife.bind(this)后,就完成了對成員變量的賦值呢强戴?其實(shí)很簡單就是在編譯時(shí)(Compile time)亭螟,ButterKnife通過注解處理器(Annotation Processor)生成了這些編譯時(shí)注解對應(yīng)的依賴注入輔助類;然后在運(yùn)行時(shí)(*** Runtime***)骑歹,ButterKnife.bind(this)通過反射調(diào)用這些依賴注入輔助類完成對象的注入预烙。

概念

為了跟上時(shí)代,本文的關(guān)注重點(diǎn)是如何寫一個(gè)Android IOC基于注解實(shí)現(xiàn)——編譯時(shí)(Compile time)處理道媚,而其中的核心就是注解處理器(Annotation Processor)扁掸。

注解處理器是javac的一個(gè)工具,它用來在編譯時(shí)掃描和處理注解最域。一個(gè)注解的注解處理器谴分,以Java代碼(或者編譯過的字節(jié)碼)作為輸入,生成文件(通常是.java文件)作為輸出镀脂。這些生成的.java文件牺蹄,會同其他普通的手動(dòng)編寫的.java源文件一樣被javac編譯

簡單的說薄翅,通過注解處理器的這一特性沙兰,某些文件(通常是.java虑省,當(dāng)然也可以是.txt、.xml……)或者說代碼僧凰,我們可以不用手動(dòng)編寫了探颈,注冊的注解處理器會在編譯時(shí)自動(dòng)幫我們生成。

說了那么多的注解處理器训措,還是來看下處理器API吧伪节。在Java中,所有的注解處理器都實(shí)現(xiàn)自接口Processor绩鸣,通常都是使用抽象類AbstractProcessor怀大。只關(guān)注AbstractProcessor的重要代碼,如下所示:

public abstract class AbstractProcessor implements Processor {
    /**
     * 會被注解處理工具調(diào)用
     *
     * @param processingEnvironment 提供一些有用的工具類Elements呀闻、Messager化借、Filer……
     */
    public synchronized void init(ProcessingEnvironment processingEnvironment){ }

     /**
     * 當(dāng)前處理器支持的注解
     */
    public Set<String> getSupportedAnnotationTypes() { }

    /**
     * 當(dāng)前使用的Java版本號,通常這里返回值使用{#link SourceVersion#latestSupported()}
     */
    public SourceVersion getSupportedSourceVersion() { }

    /**
     * 負(fù)責(zé)注解的掃描捡多、解析蓖康、處理,以及生成相應(yīng)的文件
     *
     * @param set
     * @param roundEnvironment 可以查詢出包含特定注解的被注解元素
     * @return
     */
    public abstract boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment);
}

對于注解處理器的getSupportedAnnotationTypes()getSupportedSourceVersion()來說垒手,還可以用注解實(shí)現(xiàn)蒜焊,比如@SupportedAnnotationTypes@SupportedSourceVersion

IOC——編譯時(shí)(Compile time)處理

說明下本文IOC目標(biāo)——編譯時(shí)生成依賴注入的輔助類科贬,實(shí)現(xiàn)成員變量view和string的依賴注入泳梆。

先上個(gè)IOC工程結(jié)構(gòu)圖,后續(xù)以工程module結(jié)構(gòu)來逐步解釋:

LeeKnife工程結(jié)構(gòu).png

自定義注解module——lee-annotations

自定義兩個(gè)編譯時(shí)(Compile time)注解——InjectStringInjectView

/**
 * 用于字符串的編譯時(shí)注入
 * <ul>
 * <li>編譯完成后榜掌,注解失效</li>
 * </ul>
 *
 * @author xpleemoon
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface InjectString {
    String value() default "IOC——編譯時(shí)注解處理\n既要會用洋槍洋炮\n又要會造土槍土炮";
}

/**
 * 用于view的編譯時(shí)注入
 * <ul>
 * <li>編譯完成后优妙,注解失效</li>
 * </ul>
 *
 * @author xpleemoon
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface InjectView {
    int id() default 0;
}

@Target(ElementType.FIELD)限定了它們的使用范圍——成員變量,同時(shí)注解的屬性都有默認(rèn)值——InjectString的默認(rèn)值為“IOC——編譯時(shí)注解處理\n既要會用洋槍洋炮\n又要會造土槍土炮”憎账,InjectView的默認(rèn)id為0套硼。

@Retention(RetentionPolicy.CLASS)表明注解的保留策略為編譯時(shí),即注解的生命周期一直會被保持到編譯時(shí)鼠哥,在運(yùn)行時(shí)就無法獲取該注解的任何信息了熟菲。鑒于此,我們對編譯時(shí)的注解只能在編譯時(shí)進(jìn)行處理朴恳。

注解的編譯時(shí)處理module——lee-compiler

先上一段LeeProcessor的代碼片:

@AutoService(Processor.class)
public class LeeProcessor extends AbstractProcessor {
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        this.mMessager = processingEnvironment.getMessager();
        this.mElementUtils = processingEnvironment.getElementUtils();
        this.mFiler = processingEnvironment.getFiler();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        Set<String> annotations = new LinkedHashSet<>();
        annotations.add(InjectString.class.getCanonicalName());
        annotations.add(InjectView.class.getCanonicalName());
        return annotations;
    }

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

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        parseInjector(InjectString.class, roundEnvironment.getElementsAnnotatedWith(InjectString.class));
        parseInjector(InjectView.class, roundEnvironment.getElementsAnnotatedWith(InjectView.class));

        generateCode();
        return false;
    }
}
  • @AutoService(Processor.class)——auto-service是google提供的注解處理器注冊工具抄罕,通過使用注解@AutoService(Processor.class)來快速的完成注冊過程。當(dāng)然若不使用auto-service于颖,可以按照以下步驟呆贿,純手工注冊LeeProcessor
    1. 在lee-compiler module中創(chuàng)建一個(gè)文件夾services,該文件夾的全路徑為“l(fā)ee-compiler/src/main/resources/META_INF/services”
    2. 在第1步創(chuàng)建的services文件夾下創(chuàng)建一個(gè)文本文件,名字為“javax.annotation.processing.Processor”
    3. 在“javax.annotation.processing.Processor”文件中做入,寫入注解處理器LeeProcessor的類全限定名com.xpleemoon.compiler.LeeProcessor冒晰,完成對注解處理器LeeProcessor的注冊。若有多個(gè)注解處理器竟块,則每個(gè)注解處理器都必須要在該文件中注冊壶运,并且每個(gè)處理器單獨(dú)一行
  • init(processingEnvironment)——通過processingEnvironment獲取一系列工具類對象
    • mMessager,用于編譯時(shí)的信息打印
    • mElementUtils浪秘,Element的處理工具
    • mFiler蒋情,用于文件創(chuàng)建
  • getSupportedAnnotationTypes()——支持注解InjectStringInjectView
  • getSupportedSourceVersion()——當(dāng)前支持的最新Java版本號
  • process(set, roundEnvironment)——parseInjector(injectorClz, injectorElements):解析處理注解InjectStringInjectViewgenerateCode():生成.java輔助類

為了方便理解process(set, roundEnvironment)的執(zhí)行過程耸携,接下來將重點(diǎn)分析parseInjector(injectorClz, injectorElements)generateCode()棵癣。

分析parseInjector(injectorClz, injectorElements)

 /**
     * 解析注解
     *
     * @param injectorClz 注解的class對象
     * @param injectorElements 注解的{@link Element}集合
     */
    private void parseInjector(Class<?> injectorClz, Set<? extends Element> injectorElements) {
        if (injectorClz == null
                || injectorElements == null
                || injectorElements.size() <= 0) {
            return;
        }

        mMessager.printMessage(Diagnostic.Kind.NOTE, "解析注解:" + injectorClz.getSimpleName());

        Map<String, List<Element>> map = new HashMap<>(); // key為注解宿主類的全限定名,value為注解element列表
        for (Element element : injectorElements) {
            if (element.getKind() != ElementKind.FIELD) {
                String exceptionMsg = "Only fields can be annotated with " + injectorClz.getSimpleName();
                mMessager.printMessage(Diagnostic.Kind.ERROR, exceptionMsg, element);
                throw new IllegalStateException(exceptionMsg);
            }

            String fullClassName = TypeInfoUtils.getFullClzName(mElementUtils, element);
            List<Element> elementList = map.get(fullClassName);
            if (elementList == null) {
                elementList = new ArrayList<>();
                map.put(fullClassName, elementList);
            }
            elementList.add(element);
        }

        for (Map.Entry<String, List<Element>> entry : map.entrySet()) {
            String fullClassName = entry.getKey();
            Set<AbstractBinding> bindings = mBindingMap.get(fullClassName);
            if (bindings == null) {
                bindings = new HashSet<>();
                mBindingMap.put(fullClassName, bindings);
            }

            if (injectorClz == InjectString.class) {
                bindings.add(new StringBinding(InjectString.class, mElementUtils, mMessager, entry.getValue()));
            } else if (injectorClz == InjectView.class) {
                bindings.add(new ViewBinding(InjectView.class, mElementUtils, mMessager, entry.getValue()));
            }
        }
    }

Map<String, List<Element>> map = new HashMap<>()夺衍,構(gòu)造一個(gè)map(key為注的宿主類的全限定名狈谊,value為注解element列表)用于緩存。

第一個(gè)for循環(huán)遍歷注解的Element集合injectorElements

  1. String fullClassName = TypeInfoUtils.getFullClzName(mElementUtils, element)沟沙,獲取類的全限定名
  2. List<Element> elementList = map.get(fullClassName)河劝,通過類的全限定名fullClassName作為key,獲取對應(yīng)的value——elementList
  3. elementList.add(element)尝胆,把注解的element添加到上面第2步獲取到的列表丧裁,也就是間接的把element存到了map

簡單的說,第一個(gè)for循環(huán)干的事情就是把注解的宿主類找出來含衔,然后把宿主類的所有注解緩存到一個(gè)列表中,從而形成注解的宿主類(Key)對應(yīng)注解列表(Value)的結(jié)構(gòu)

第二個(gè)for循環(huán)遍歷map(由上面第一個(gè)for循環(huán)生成):

  1. String fullClassName = entry.getKey()二庵,獲取注解的宿主類全限定名
  2. Set<AbstractBinding> bindings = mBindingMap.get(fullClassName)贪染,通過第1步拿到的注解的宿主類全限定名獲取AbstractBinding的集合binds
  • mBindingMap,它是一個(gè)成員變量Map<String, Set<AbstractBinding>> mBindingMap = new HashMap<>()催享。key:注解的宿主類的全限定名杭隙;value:對應(yīng)宿主類中所有注解的集合
  • AbstractBinding的主要作用就是生成注解對應(yīng)的注入方法
  1. if條件判斷injectorClz的注解類型,然后new出injectorClz對應(yīng)的AbstractBinding對象因妙,最后添加到第2步的集合binds痰憎。這樣也就間接的緩存到了成員變量mBindingMap

簡單的說,第二個(gè)循環(huán)就是把注解的宿主類找出來攀涵,然后把宿主類的所有注解緩存到一個(gè)列表中铣耘,從而形成注解的宿主類(Key)對應(yīng)AbstractBinding(生成注解的注入方法)列表(Value)的結(jié)構(gòu)

分析generateCode()

/**
     * 生成代碼
     */
    private void generateCode() {
        for (Map.Entry<String, Set<AbstractBinding>> entry : mBindingMap.entrySet()) {
            Set<AbstractBinding> bindings = entry.getValue();
            if (bindings == null || bindings.size() <= 0) {
                continue;
            }
            List<Element> elementList = bindings.iterator().next().mElementList;
            if (elementList == null || elementList.size() <= 0) {
                continue;
            }

            Element element = elementList.get(0);
            String simpleClzName = TypeInfoUtils.getSimpleClzName(element) + SUFFIX; // 生成java類的simple名
            TypeName typeName = TypeInfoUtils.getEnclosingTypeName(element); // 注解的宿主類

            // 1. 創(chuàng)建一系列方法
            mMessager.printMessage(Diagnostic.Kind.NOTE, "創(chuàng)建入口方法:" + METHOD);
            MethodSpec.Builder bindsMethodBuilder = MethodSpec.methodBuilder(METHOD)
                    .addModifiers(Modifier.PUBLIC, Modifier.STATIC)
                    .returns(void.class)
                    .addParameter(typeName, PARAMETER)
                    .addJavadoc("數(shù)據(jù)綁定的入口\n為{@code $N}中{@link $T}和{@link $T}注解的變量綁定數(shù)據(jù)", PARAMETER, InjectString.class, InjectView.class);
            List<MethodSpec> methodSpecs = new ArrayList<>(); // 方法列表,用于后續(xù)創(chuàng)建類時(shí)使用
            for (AbstractBinding binding : bindings) {
                methodSpecs.add(binding.generateMethod());
                bindsMethodBuilder.addStatement(binding.getMethodName() + "($N)", PARAMETER);
            }
            methodSpecs.add(bindsMethodBuilder.build());

            // 2. 創(chuàng)建java類
            mMessager.printMessage(Diagnostic.Kind.NOTE, "創(chuàng)建類:" + simpleClzName);
            TypeSpec.Builder injectorClzBuilder = TypeSpec.classBuilder(simpleClzName)
                    .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
                    .addJavadoc("{@link $T}的leeknife注解字段注入器\n" +
                            "<ul><li>通過編譯器掛載的注解處理器自動(dòng)生成java源文件后以故,編譯器會再將該java源文文件同其它java源文件一起編譯成class文件</li></ul>\n\n" +
                            "@author $N", typeName, LeeProcessor.class.getSimpleName());
            for (MethodSpec methodSpec : methodSpecs) {
                injectorClzBuilder.addMethod(methodSpec);
            }
            TypeSpec injectorClz = injectorClzBuilder.build();

            // 3. 生成java源文件
            mMessager.printMessage(Diagnostic.Kind.NOTE, "創(chuàng)建java源文件:" + simpleClzName);
            JavaFile javaFile = JavaFile.builder(TypeInfoUtils.getPkgName(mElementUtils, element), injectorClz)
                    .addFileComment("This codes are generated automatically. Do not modify!")
                    .build();
            try {
                javaFile.writeTo(mFiler);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

循環(huán)遍歷mBindingMap蜗细,依次執(zhí)行如下步驟(注:這些步驟已經(jīng)通過mMessager手動(dòng)調(diào)用進(jìn)行打印):

  1. 創(chuàng)建一系列私有的靜態(tài)綁定方法,暴露binds方法作為那些似有方法的訪問入口
  2. 創(chuàng)建java輔助類XXX$$Injector
  3. 生成java源文件

生成代碼使用到的MethodSpec以及TypeSpec都是square開源的javapoet炉媒,提供了一系列非常方便實(shí)用的API踪区,用于生成.java源碼。javapoet把這些代碼的中間生成過程都當(dāng)作對象來處理吊骤,更符合面向?qū)ο缶幊潭懈凇.?dāng)然,你如果一定不想用javapoet白粉,就想找麻煩传泊,那也行就用idk提供的Writer

為了便于理解,現(xiàn)在Terminal通過命令./gradlew clean assembleDebug編譯LeeKnife-IOC這個(gè)工程(注:注解已經(jīng)在CompileIOCActivity中使用蜗元,若注解沒有使用或渤,那么注解處理器就不會做任何處理),編譯過程中奕扣,我們將會看到如下信息:

注解處理器LeeProcessor處理過程.png

ViewBinding為例薪鹦,我們看下它到底生成了什么樣的綁定方法:

final class ViewBinding extends AbstractBinding<InjectView> {

    ViewBinding(Class<InjectView> clz, Elements elementUtils, Messager messager, List<Element> elementList) {
        super(clz, elementUtils, messager, elementList);
    }

    @Override
    String getMethodName() {
        return "bindView";
    }

    @Override
    MethodSpec generateMethod() {
        super.generateMethod();

        TypeName enclosingTypeName = TypeInfoUtils.getEnclosingTypeName(mElementList.get(0)); // 獲取注解宿主類
        MethodSpec.Builder builder = MethodSpec.methodBuilder(getMethodName()) // 方法名 bindView
                .addModifiers(Modifier.PRIVATE, Modifier.STATIC) // 方法修飾 private static
                .returns(void.class) // 方法返回值 無
                .addParameter(enclosingTypeName, "target") // 參數(shù)名 target
                .addJavadoc("為{@code $N}中{@link $T}注解的view變量綁定數(shù)據(jù)", "target", mClz); // 方法注釋
        for (Element viewInjectorElement : mElementList) { // 循環(huán)添加語句
            TypeName typeName = TypeInfoUtils.getTypeName(viewInjectorElement); // 獲取注解修飾的字段類型
            int viewId = viewInjectorElement.getAnnotation(mClz).id(); // 獲取注解指定的id值
            builder.addStatement("target.$N = ($T) target.findViewById($L)", viewInjectorElement.getSimpleName(), typeName, viewId); // 構(gòu)建賦值語句
        }
        return builder.build();
    }
}

ViewBindinggenerateMethod()方法注視很詳細(xì),就不細(xì)講了惯豆。調(diào)用ViewBinding.generateMethod()池磁,它最終生成的代碼將會是如下形式:

/**
   * 為{@code target}中{@link com.xpleemoon.annotations.InjectView}注解的view變量綁定數(shù)據(jù) */
  private static void bindView(XXX target) {
    target.mText = (TextView) target.findViewById(2131427415);
    target.mBtn = (Button) target.findViewById(2131427416);
  }

IOC使用module——app

效果圖鎮(zhèn)樓:

Compile time-IOC.gif
public class CompileIOCActivity extends AppCompatActivity {
    /**
     * 使用默認(rèn)值注入
     */
    @InjectString
    String mTextStr;
    @InjectString("編譯時(shí)注解處理,IOC就這么簡單")
    String mToastStr;
    @InjectView(id = R.id.compile_inject_text)
    TextView mText;
    @InjectView(id = R.id.compile_inject_button)
    Button mBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_compile_ioc);

        LeeKnife.inject(this);
        mText.setText(mTextStr);
        mBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(getApplicationContext(), mToastStr, Toast.LENGTH_LONG).show();
            }
        });
    }

}

可以看到對CompileIOCActivity類中類型為ViewString的成員變量使用注解@InjectView@InjectString楷兽,同時(shí)還發(fā)現(xiàn)這里注解修飾的成員變量的訪問權(quán)限使用包可訪問的地熄,那這是為什么呢?先留下這個(gè)疑問芯杀,本小節(jié)最后來解答端考。

前面說過編譯時(shí)注解處理器會生成.java源文件,這個(gè)文件在下面這個(gè)目錄:

注解處理器LeeProcessor生成的CompileIOCActivity$$Injector.png

通過觀察發(fā)現(xiàn)他的類名就是"CompileIOCActivity"+"$$Injector"揭厚,并且所在的包名和CompileIOCActivity一致却特。那這里包名為什么要一致呢?其實(shí)很簡單筛圆,就是為了調(diào)用LeeKnife.inject(target)時(shí)裂明,方便LeeKnife獲取輔助類XXX$$Injector:

// SUFFIX的值為$$Injector
Class injectorClz = Class.forName(target.getClass().getName() + SUFFIX);

再來看下生成的IOC輔助類CompileIOCActivity$$Injector源代碼:

// This codes are generated automatically. Do not modify!
package com.xpleemoon.annotations.demo.annotationprocess.compile;

import android.widget.Button;
import android.widget.TextView;

/**
 * {@link CompileIOCActivity}的leeknife注解字段注入器
 * <ul><li>通過編譯器掛載的注解處理器自動(dòng)生成java源文件后,編譯器會再將該java源文文件同其它java源文件一起編譯成class文件</li></ul>
 *
 * @author LeeProcessor */
public final class CompileIOCActivity$$Injector {
  /**
   * 為{@code target}中{@link com.xpleemoon.annotations.InjectView}注解的view變量綁定數(shù)據(jù) */
  private static void bindView(CompileIOCActivity target) {
    target.mText = (TextView) target.findViewById(2131427415);
    target.mBtn = (Button) target.findViewById(2131427416);
  }

  /**
   * 為{@code target}中{@link com.xpleemoon.annotations.InjectString}注解的字符串變量綁定數(shù)據(jù) */
  private static void bindString(CompileIOCActivity target) {
    target.mTextStr = "IOC——編譯時(shí)注解處理\n"
            + "既要會用洋槍洋炮\n"
            + "又要會造土槍土炮";
    target.mToastStr = "編譯時(shí)注解處理太援,IOC就這么簡單";
  }

  /**
   * 數(shù)據(jù)綁定的入口
   * 為{@code target}中{@link com.xpleemoon.annotations.InjectString}和{@link com.xpleemoon.annotations.InjectView}注解的變量綁定數(shù)據(jù) */
  public static void binds(CompileIOCActivity target) {
    bindView(target);
    bindString(target);
  }
}

CompileIOCActivity調(diào)用的LeeKnife.inject(this)進(jìn)行IOC數(shù)據(jù)注入闽晦,其實(shí)就是反射調(diào)用輔助類CompileIOCActivity$$Injector.binds(target)CompileIOCActivity$$Injector的具體生成過程提岔,可見下一節(jié)仙蛉。

分析到了這里,現(xiàn)在可以回答之前的問題——為什么注解修飾的成員變量的訪問權(quán)限使用包可訪問的唧垦?既然注解處理器LeeProcessor生成的輔助類CompileIOCActivity$$Injector與注入目標(biāo)CompileIOCActivity是在同一個(gè)包名下的捅儒,那么為了不影響代碼執(zhí)行效率,我們在CompileIOCActivity$$Injector的bind方法中不使用反射進(jìn)行數(shù)據(jù)的注入,而是直接通過target.xxx = yyy進(jìn)行賦值巧还。

IOC注入module——lee-knife

public final class LeeKnife {
    /**
     * 編譯時(shí)處理生成的java文件后綴
     */
    private static final String SUFFIX = "$$Injector";
    /**
     * 入口方法名
     */
    private static final String METHOD = "binds";

    private static void check(Activity activity) {
        if (activity == null) {
            throw new IllegalStateException("依賴注入的activity不能為null");
        }

        Window window = activity.getWindow();
        if (window == null || window.getDecorView() == null) {
            throw new IllegalStateException("依賴注入的activity未建立視圖");
        }
    }

    /**
     * IOC注入
     *
     * @param target
     */
    public static void inject(@NonNull Activity target) {
        check(target);

        try {
            Class injectorClz = Class.forName(target.getClass().getName() + SUFFIX);
            Method bindsMethod = injectorClz.getMethod(METHOD, target.getClass());
            bindsMethod.invoke(null, target);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

inject(target)

  • 獲取參數(shù)target對應(yīng)的輔助類XXX$$Injector
  • 反射調(diào)用輔助類XXX$$Injector中的binds方法鞭莽,完成IOC的數(shù)據(jù)注入target過程

結(jié)束

經(jīng)過上面的分析,應(yīng)該也了解了如何寫一個(gè)Android IOC框架(Demo級別)麸祷。既然會寫了的話澎怒,那么對于閱讀ButterKnife這一類的Android IOC開源庫來說也不是什么難事了。

另外阶牍,目前Android App開發(fā)上還有一個(gè)比較熱的東西——AOP喷面,其實(shí)也不是什么新東西或者新概念(做過后端或者了解spring的一定知道)。其實(shí)要想實(shí)現(xiàn)一個(gè)AOP也不是什么難事:在IOC的基礎(chǔ)上走孽,加上動(dòng)態(tài)代理也是可以實(shí)現(xiàn)AOP的惧辈。

奉上LeeKnife-IOC源碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市磕瓷,隨后出現(xiàn)的幾起案子盒齿,更是在濱河造成了極大的恐慌,老刑警劉巖困食,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件边翁,死亡現(xiàn)場離奇詭異,居然都是意外死亡硕盹,警方通過查閱死者的電腦和手機(jī)符匾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瘩例,“玉大人啊胶,你說我怎么就攤上這事《庀停” “怎么了创淡?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長南吮。 經(jīng)常有香客問我,道長誊酌,這世上最難降的妖魔是什么部凑? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮碧浊,結(jié)果婚禮上涂邀,老公的妹妹穿的比我還像新娘。我一直安慰自己箱锐,他們只是感情好比勉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般浩聋。 火紅的嫁衣襯著肌膚如雪观蜗。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天衣洁,我揣著相機(jī)與錄音墓捻,去河邊找鬼。 笑死坊夫,一個(gè)胖子當(dāng)著我的面吹牛砖第,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播环凿,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼梧兼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了智听?” 一聲冷哼從身側(cè)響起羽杰,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎瞭稼,沒想到半個(gè)月后忽洛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡环肘,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年欲虚,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悔雹。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡复哆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出腌零,到底是詐尸還是另有隱情梯找,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布益涧,位于F島的核電站锈锤,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闲询。R本人自食惡果不足惜久免,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望扭弧。 院中可真熱鬧阎姥,春花似錦、人聲如沸鸽捻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至衣赶,卻和暖如春诊赊,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背屑埋。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工豪筝, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人摘能。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓续崖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親团搞。 傳聞我的和親對象是個(gè)殘疾皇子严望,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

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

  • 此文為本人學(xué)習(xí)guice的過程中,翻譯的官方文檔逻恐,如有不對的地方像吻,歡迎指出。另外還有一些附件說明复隆、吐槽拨匆、疑問點(diǎn),持...
    李眼鏡閱讀 3,468評論 2 5
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,498評論 25 707
  • 什么是注解注解分類注解作用分類 元注解 Java內(nèi)置注解 自定義注解自定義注解實(shí)現(xiàn)及使用編譯時(shí)注解注解處理器注解處...
    Mr槑閱讀 1,070評論 0 3
  • 早上起來挽拂,兒子自己穿好上衣去刷牙洗臉了惭每,回來后我發(fā)現(xiàn)他衣服已經(jīng)濕了,我問他:你想換一件還是就穿這件濕的衣服亏栈?...
    劉小摳的腳印閱讀 305評論 0 0
  • 箭劃過 將空氣一并撕扯 塵埃一顆顆炸破 我無處可躲 又何必躲呢 我未嘗不懼怕傷痛 自然會心情沉重 飄遠(yuǎn)了弦歌聲 身...
    晴柒陌沫閱讀 278評論 2 3