每次寫代碼的時候,看到一大堆
FindViewById()
都挺煩的,沒有沒什么方法能幫我擺脫這種煩惱呢?當然是有的,目前比較流行的時注解框架是Xutils3
和ButterKnife
,接下來我們來分析一下它們是如何實現(xiàn)的.
Xutils3注解實現(xiàn)原理
- 首先看一下
Xutils3
的簡單用法
@ViewInject(R.id.tv_test)
private TextView mTvTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
x.view().inject(this);
mTvTest.setText("hello ioc");
}
用法是相當?shù)暮唵?通過@ViewInject
綁定id,然后通過inject(this)
注入當前對象,就完成了findViewById
的過程.
- 查看Xutils原碼,看看
inject(this)
做了什么
// inject view
Field[] fields = handlerType.getDeclaredFields();
if (fields != null && fields.length > 0) {
for (Field field : fields) {
Class<?> fieldType = field.getType();
if (
/* 不注入靜態(tài)字段 */ Modifier.isStatic(field.getModifiers()) ||
/* 不注入final字段 */ Modifier.isFinal(field.getModifiers()) ||
/* 不注入基本類型字段 */ fieldType.isPrimitive() ||
/* 不注入數(shù)組類型字段 */ fieldType.isArray()) {
continue;
}
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if (viewInject != null) {
try {
View view = finder.findViewById(viewInject.value(), viewInject.parentId());
if (view != null) {
field.setAccessible(true);
field.set(handler, view);
} else {
throw new RuntimeException("Invalid @ViewInject for "
+ handlerType.getSimpleName() + "." + field.getName());
}
} catch (Throwable ex) {
LogUtil.e(ex.getMessage(), ex);
}
}
}
} // end inject view
我們發(fā)現(xiàn)這里主要是通過反射,先獲取該類的所有的屬性,然后遍歷,找到ViewInject
注解,然后獲取value值,即對應(yīng)的viewId
,然后再調(diào)用FindViewById
找到對應(yīng)的View
,
field.setAccessible(true);
field.set(handler, view);
然后通過上述代碼注入屬性,就完成了FindViewById
的全過程.仔細一分析,是不是也不是那么難.主要就是利用了java的反射機制,通過動態(tài)獲取Annotation
,然后實現(xiàn)View
的注入.
屬性注入 : 利用反射去 獲取Annotation --> value --> findViewById --> 反射注入屬性
事件注入 :利用反射去 獲取Annotation --> value --> findViewById --> setOnclickListener --> 動態(tài)代理反射執(zhí)行方法
ButterKnife注解實現(xiàn)原理
- 同樣的,我們來看一下ButterKnife的簡單用法
@Bind(R.id.tv_test)
private TextView mTvTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
mTvTest.setText("hello ioc");
}
也非常簡潔,兩行代碼完成屬性注入.(這里使用的7.0.1所以是@Bind
而不是@BindView
);
- 查看源碼
ButterKnife
的源碼看起來就比較費勁了,主要是通過ButterKnifeProcessor
這個類動態(tài)解析Annotation注解,在編譯時生成xxxxx$$ViewBinder
這個類,然后實現(xiàn)FindViewById
過程.
public class MainActivity$$ViewBinder<T extends com.example.wenjian.eassyjoke.MainActivity> implements ViewBinder<T> {
@Override public void bind(final Finder finder, final T target, Object source) {
View view;
view = finder.findRequiredView(source, 2131427415, "field 'mTvTest'");
target.mTvTest = finder.castView(view, 2131427415, "field 'mTvTest'");
}
@Override public void unbind(T target) {
target.mTvTest = null;
}
}
在這里我們也可以看到為什么聲明屬性的時候不能private
了
兩者實現(xiàn)的方式有差異,
ButterKnife
基于編譯時注解,相對較為高效
打造自己的注解框架
看了別人的代碼,自己也可以動手擼一下了
- 首先我們得了解java的
Annatation
,在這里我們先參考一下java的@Override
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
這里我們可以知道如何自定義注解
@Target
指定注解作用目標,以下是ElementType
的幾種常用類型
Class, interface (including annotation type), or enum declaration /
TYPE,類
/* Field declaration (includes enum constants) /
FIELD,屬性
/* Method declaration /
METHOD,方法
/* Formal parameter declaration /
PARAMETER,參數(shù)
/* Constructor declaration /
CONSTRUCTOR,構(gòu)造方法
/* Local variable declaration /
LOCAL_VARIABLE,本地變量
/* Annotation type declaration /
ANNOTATION_TYPE,注解類型
/* Package declaration */
PACKAGE,包
@Retention
指定何時生效,有以下三種場景
* Annotations are to be discarded by the compiler.
*/
SOURCE,源碼
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,編譯時
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME,運行時
2.開始碼自己的注解框架
自定義@ViewById
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME) //什么時候生效:運行時
public @interface ViewById {
int value();
}
實現(xiàn)ViewUtils
兼容各種場景
public class ViewUtils {
public static void inject(Activity activity) {
inject(new ViewFinder(activity),activity);
}
public static void inject(View view) {
inject(new ViewFinder(view),view);
}
public static void inject(View view, Object object) {
inject(new ViewFinder(view),object);
}
private static void inject(ViewFinder finder, Object object) {
injectField(finder, object);
injectEvent(finder, object);
}
通過反射注入屬性
/**
* 注入屬性
* @param finder
* @param object
*/
private static void injectField(ViewFinder finder, Object object) {
//1.獲取class
Class<?> clazz = object.getClass();
//2.獲取所有的屬性
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
ViewById viewById = field.getAnnotation(ViewById.class);
if (viewById != null) {
int viewId = viewById.value();
View view = finder.findViewById(viewId);
if (view != null) {
//設(shè)置可以訪問所有修飾符 包括private
field.setAccessible(true);
try {
//注入屬性
field.set(object, view);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
}
到此已經(jīng)完成了,我們來驗證一下吧
@ViewById(R.id.tv_test)
private TextView mTvTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.inject(this);
mTvTest.setText("hello ioc");
}
和Xutils
的實現(xiàn)方式差不多,同樣是基于反射,重要的是不必依賴第三方庫了.你也自己動手實現(xiàn)setOnclickListener
,原理是一樣的.