Android ioc架構編寫

首先說說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的綁定

  1. 先獲取字節碼
  2. 獲取所有的字段
  3. 遍歷字段查找還有註解字段
  4. 獲取註解字段的值
  5. 通過反射,獲取類的對應方法,將值賦值給字段

代碼實現如下

/**
 * 這是綁定視圖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);
                }
            }

        }
    }

}

下面開始實現綁定點擊事件

  1. 先獲取字節碼
  2. 獲取所有的方法
  3. 遍歷方法,獲取方法上所有的註解
  4. 遍歷註解,獲取註解的類型,獲取註解的註解,判斷是否存
  5. 通註解類型獲取反射註解的方法,獲取所有的字段
  6. 利用動態代理模式將註解的onclickListener進行代理設置,這裏需要新建代理類
  7. 獲取註解信息,並且實例化
  8. 將實例化的對象進行事件綁定

代理類

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();
                }
            }

        }
    }

}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章