首先說說java反射機制
JAVA反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法;這種動態獲取的以及動態調用對象的方法的功能稱爲Java的反射機制。
使用場景
- 逆向代碼 ,例如反編譯
- 與註解相結合的框架 例如Retrofit
- 單純的反射機制應用框架 例如EventBus 2.x
- 動態生成類框架 例如Gson
大牛框架
Annotations、ButterKnife、eventBus、afinal、ThinkAndroid還有國內的xutils等
ViewUtils介紹
主要說明xUtils裏面的ViewUtils
在BaseActivity中初始化 init
在子集的Activity中賦值
@Inject(R.id.text)
TextView text;
反射實踐
首先定義註解User類
public class User {
private String name;
private int age;
private String eat(String eat){
System.out.println("eat = "+eat);
return eat+"真好喫";
}
}
定義註解對象
@Target(ElementType.FIELD)//用於限制當前自定義註解類作用的對象
//@Retention(RetentionPolicy.SOURCE)//用於源碼階段 該註解類只會在源碼出現,當將源碼編譯成字節碼的時候註解信息就被清除
//@Retention(RetentionPolicy.CLASS) //該註解類會被編譯到字節碼中,但是當虛擬機去加載這個字節碼的時候,註解信息就被清除
@Retention(RetentionPolicy.RUNTIME)//該註解類永遠保留到被加載到虛擬機中
public @interface ViewInject {
int age();
String name();
}
然後將註解對象放到對應的註解類上
@ViewInject(name="張三",age = 12)
private String name;
這裏如果將註解放到eat方法上面就會報錯,因爲定義的註解是FIELD類型
然後開始反射賦值,在字段上面賦值
@ViewInject(name="張三",age = 12)
private String name;
最後在開始通過Java反射賦值
//需求:獲取User類中name字段上的自定義註解的值,然後將該值的age通過反射設置給User屬性,將name 設置給user的name屬性
User user = new User();
第一步 獲取user類的字節碼
// user.getClass();
// User.class;
// Class.forName("xxxxx")
Class clazz = User.class;
第二步 將字節碼裏面的name字段獲取到,其中getField的方法只能獲取public方法
// clazz.getField(name) 這個方法只能獲取聲明爲public的方法
Field declaredField = clazz.getDeclaredField("name");
Field declaredField2 = clazz.getDeclaredField("age");
第三步 獲取字段上面的註解對象
ViewInject annotation = declaredField.getAnnotation(ViewInject.class);
if(annotation!=null){
int age = annotation.age();
String name = annotation.name();
printlns("age = "+ age + ",name = "+ name);
/**
* 通過反射將這個兩個值設置給user對象
*/
declaredField.setAccessible(true);//設置允許反射,其實就是允許暴力反射
declaredField2.setAccessible(true);
declaredField.set(user,name);
declaredField2.set(user,age);
printlns(user.toString());
}else {
printlns("字段上面沒有定義註解");
}
//通過反射調用eat的方法
Method declaredMethod = clazz.getDeclaredMethod("eat",String.class);
declaredMethod.setAccessible(true);
Object object = declaredMethod.invoke(user, "牛肉拉麪");
printlns((String) object);
這裏注意幾點:(乾貨)
- getDeclaredField和getField區別:第一個是能獲取所有字段,第二個是隻能獲取定義了public的字段,所有如果要獲取所有的字段就要用到getDeclaredField
- setAccessible作用:默認是false,設置是否能夠獲取私用字段,方法同理。
- 命名value 和 id 區別:value是抽象方法,在使用時候不需要寫,下面就會將到。
自己編寫ioc框架
首先我們新建 ViewInject註解類,ViewUtils實現類
在ViewInject類中定義註解的抽象方法名,這裏說一下,定義value 和 其他名字比如 id
如果定義了id方法,在使用的時候,需要出現 id 名稱
@ViewInject(id = R.id.text)
TextView text;
而如果註解抽象方法明是 value 的話,則不需要
@ViewInject(R.id.text)
TextView text;
然後再ViewUtils類裏面定義 init()方法,這裏需要訂單形參,網上說的好多方法參數大部分使用的是Activity,因爲這樣是面向父類編程,但是我不這麼認爲,既然面向父類編程,爲啥,不徹底恩,直接用Context 上下文 做爲形參,當然也不是說用Activity不對。
寫了這些,可以在MainActivity中先定義上
public class MainActivity extends AppCompatActivity {
@ViewInject(R.id.text)
TextView text;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewUtils.init(this);
if(text != null){
text.setText("這是成功後賦值");
}
}
}
最後我們開始編寫最核心的邏輯 ViewUtils 類
第一:先編寫字段View的綁定
- 先獲取字節碼
- 獲取所有的字段
- 遍歷字段查找還有註解字段
- 獲取註解字段的值
- 通過反射,獲取類的對應方法,將值賦值給字段
代碼實現如下
/**
* 這是綁定視圖View
* @param context
* @throws NoSuchMethodException
* @throws InvocationTargetException
* @throws IllegalAccessException
*/
private static void bindView(Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
/**
* 1 獲取字節碼
*/
Class clazz = context.getClass();
/**
* 2 獲取所以的字段
*/
Field[] fields = clazz.getDeclaredFields();
if(fields != null && fields.length > 0){
for (int i =0 ;i<fields.length;i++){
Field field = fields[i];
/**
* 3.遍歷字段查找還有註解字段
*/
ViewInject viewInject = field.getAnnotation(ViewInject.class);
if(viewInject!=null){
/**
* 4.獲取註解字段的值
*/
int valueId = viewInject.value();
if(valueId>0){
/**
* 通過反射,獲取類的對應方法,將值賦值給字段
*/
Method findByIdM = clazz.getMethod("findViewById",int.class);
findByIdM.setAccessible(true);
Object findByIdObj = findByIdM.invoke(context,valueId);
field.setAccessible(true);
field.set(context,findByIdObj);
}
}
}
}
}
下面開始實現綁定點擊事件
- 先獲取字節碼
- 獲取所有的方法
- 遍歷方法,獲取方法上所有的註解
- 遍歷註解,獲取註解的類型,獲取註解的註解,判斷是否存
- 通註解類型獲取反射註解的方法,獲取所有的字段
- 利用動態代理模式將註解的onclickListener進行代理設置,這裏需要新建代理類
- 獲取註解信息,並且實例化
- 將實例化的對象進行事件綁定
代理類
public class DayHandler implements InvocationHandler{
private Map<String,Method> mtdMap;
private WeakReference<Object> object;
public DayHandler(Object object){
this.object = new WeakReference<Object>(object);
}
public void addMethod(String name,Method method){
if(mtdMap == null){
mtdMap = new HashMap<>();
}
mtdMap.put(name,method);
}
@Override
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
Object handler = object.get();
if(handler!=null){
String name = method.getName();
method = mtdMap.get(name);
if(method!=null){
return method.invoke(handler,objects);
}
}
return null;
}
}
註解類
@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface EventBus {
String listenerSetter();
Class listenerType();
String listenerBackName();
}
這個
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@EventBus(listenerType = View.OnClickListener.class, listenerSetter = "setOnClickListener", listenerBackName = "onClick")
public @interface OnClick {
int[] value();
}
註解實現
private static void bindOnClick(Context context) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Class<? extends Context> clazz = context.getClass();
Method[] methods = clazz.getDeclaredMethods();
//遍歷所有的方法
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
//拿到方法上的所有的註解
for (Annotation annotation : annotations) {
Class<? extends Annotation> annotationType = annotation.annotationType();
//拿到註解上的註解
EventBus eventBus = annotationType.getAnnotation(EventBus.class);
//如果設置爲EventBase
if (eventBus != null) {
//取出設置監聽器的名稱,監聽器的類型,調用的方法名
String listenerSetter = eventBus
.listenerSetter();
Class<?> listenerType = eventBus.listenerType();
String methodName = eventBus.listenerBackName();
try {
//拿到Onclick註解中的value方法
Method aMethod = annotationType.getDeclaredMethod("value");
//取出所有的viewId
int[] viewIds = (int[]) aMethod.invoke(annotation, null);
//通過InvocationHandler設置代理
DayHandler handler = new DayHandler(context);
handler.addMethod(methodName, method);
Object listener = Proxy.newProxyInstance(
listenerType.getClassLoader(),
new Class<?>[] { listenerType }, handler);
//遍歷所有的View,設置事件
for (int viewId : viewIds) {
Method findViewById = clazz.getMethod("findViewById", int.class);
Object view = findViewById.invoke(context, viewId);
Method setEventListenerMethod = view.getClass().getMethod(listenerSetter, listenerType);
setEventListenerMethod.invoke(view, listener);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}