前言
對于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框架有ButterKnife、Dagger2碰纬、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)來逐步解釋:
自定義注解module——lee-annotations
自定義兩個(gè)編譯時(shí)(Compile time)注解——InjectString
和InjectView
:
/**
* 用于字符串的編譯時(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
:- 在lee-compiler module中創(chuàng)建一個(gè)文件夾services,該文件夾的全路徑為“l(fā)ee-compiler/src/main/resources/META_INF/services”
- 在第1步創(chuàng)建的services文件夾下創(chuàng)建一個(gè)文本文件,名字為“javax.annotation.processing.Processor”
- 在“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()
——支持注解InjectString
和InjectView
-
getSupportedSourceVersion()
——當(dāng)前支持的最新Java版本號 -
process(set, roundEnvironment)
——parseInjector(injectorClz, injectorElements)
:解析處理注解InjectString
和InjectView
,generateCode()
:生成.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
:
-
String fullClassName = TypeInfoUtils.getFullClzName(mElementUtils, element)
沟沙,獲取類的全限定名 -
List<Element> elementList = map.get(fullClassName)
河劝,通過類的全限定名fullClassName
作為key,獲取對應(yīng)的value——elementList
-
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)生成):
-
String fullClassName = entry.getKey()
二庵,獲取注解的宿主類全限定名 -
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)的注入方法
- 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)行打印):
- 創(chuàng)建一系列私有的靜態(tài)綁定方法,暴露
binds
方法作為那些似有方法的訪問入口 - 創(chuàng)建java輔助類
XXX$$Injector
- 生成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中使用蜗元,若注解沒有使用或渤,那么注解處理器就不會做任何處理),編譯過程中奕扣,我們將會看到如下信息:
以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();
}
}
ViewBinding
的generateMethod()
方法注視很詳細(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)樓:
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
類中類型為View
和String
的成員變量使用注解@InjectView
和@InjectString
楷兽,同時(shí)還發(fā)現(xiàn)這里注解修飾的成員變量的訪問權(quán)限使用包可訪問的地熄,那這是為什么呢?先留下這個(gè)疑問芯杀,本小節(jié)最后來解答端考。
前面說過編譯時(shí)注解處理器會生成.java源文件,這個(gè)文件在下面這個(gè)目錄:
通過觀察發(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源碼