自定義 Android IOC 框架

概述

什麼是 IOC

Inversion of Control,英文縮寫爲 IOC,意思爲控制反轉。java

具體什麼含義呢?android

假設一個類中有不少的成員變量,若是你須要用到裏面的成員變量,傳統作法是 new 出來進行使用,可是在 IOC 的原則中,咱們不要 new,由於這樣的耦合度過高,咱們能夠在須要注入(new)的成員變量上添加註解,等待加載這個類的時候,則進行注入。數組

那麼怎麼進行注入呢?bash

簡單的說,就是經過反射的方式,將字符串類路徑變爲類。app

什麼是反射

JAVA 並非一種動態變成語言,爲了使語言更加靈活,JAVA 引入了反射機制。JAVA 反射機制是在運行過程當中,對於任意一個類,都能知道這個類的全部屬性和方法;對於任意一個屬性,都可以調用它的任意一個方法;這種動態獲取信息以及動態調用對象方法的功能成爲 JAVA 語言的反射機制。框架

什麼是註解

JAVA 1.5 以後引入的註解和反射,註解的實現依賴於反射。JAVA 中的註解是一種繼承自接口 java.lang.annotation.Annotation 的特殊接口。
那麼接口怎麼可以設置屬性呢?
簡單來講就是 JAVA 經過動態代理的方式爲你生成了一個實現了接口 Annotation 的實例,而後對該代理實例的屬性賦值,這樣就能夠在程序運行時(若是將註解設置爲運行時可見的話)經過反射獲取到註解的配置信息。說的通俗一點,註解至關於一種標記,在程序中加了註解就等於爲程序打上了某種標記。程序能夠利用JAVA的反射機制來了解你的類及各類元素上有無何種標記,針對不一樣的標記,就去作相應的事件。標記能夠加在包,類,方法,方法的參數以及成員變量上。ide

實現

定義註解

  1. 佈局註解
/**
 * Created by Keven on 2019/6/3.
 *
 * 佈局註解
 */
//RUNTIME 運行時檢測,CLASS 編譯時檢測  SOURCE 源碼資源時檢測
@Retention(RetentionPolicy.RUNTIME)
//TYPE 用在類上  FIELD 註解只能放在屬性上  METHOD 用在方法上  CONSTRUCTOR 構造方法上
@Target(ElementType.TYPE)
public @interface KevenContentViewInject {
    int value();//表明能夠 Int 類型,取註解裏面的參數
}
複製代碼
  1. 屬性組件註解
/**
 * Created by Keven on 2019/6/3.
 *
 * 組件註解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)//用在屬性字段上
public @interface KevenViewInject {
    int value();
}
複製代碼
  1. 事件註解
/**
 * Created by zhengjian on 2019/6/3.
 *
 * 事件註解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)//使用在方法上
public @interface KevenOnClickInject {
    //會有不少個點擊事件,因此使用數組
    int[] value();
}
複製代碼

實現注入工具類

/**
 * Created by Keven on 2019/6/3.
 * <p>
 * InjectUtils 注入工具類
 */
public class InjectUtils {
    //注入方法 Activity
    public static void inject(Activity activity) {
        injectLayout(activity);
        injectViews(new ViewFinder(activity), activity);
        injectEvents(new ViewFinder(activity), activity);
    }

    //注入方法 View
    public static void inject(View view, Activity activity) {
        injectViews(new ViewFinder(view), activity);
        injectEvents(new ViewFinder(view), activity);

    }

    //注入方法 Fragment
    public static void inject(View view, Object object) {
        injectViews(new ViewFinder(view), object);
        injectEvents(new ViewFinder(view), object);
    }


    /**
     * 事件注入
     */
    private static void injectEvents(ViewFinder viewFinder, Object object) {
        // 1.獲取全部方法
        Class<?> clazz = object.getClass();
        Method[] methods = clazz.getDeclaredMethods();
        // 2.獲取方法上面的全部id
        for (Method method : methods) {
            KevenOnClickInject onClick = method.getAnnotation(KevenOnClickInject.class);
            if (onClick != null) {
                int[] viewIds = onClick.value();
                if (viewIds.length > 0) {
                    for (int viewId : viewIds) {
                        // 3.遍歷全部的id 先findViewById而後 setOnClickListener
                        View view = viewFinder.findViewById(viewId);
                        if (view != null) {
                            view.setOnClickListener(new DeclaredOnClickListener(method, object));
                        }
                    }
                }
            }
        }
    }


    private static class DeclaredOnClickListener implements View.OnClickListener {
        private Method mMethod;
        private Object mHandlerType;

        public DeclaredOnClickListener(Method method, Object handlerType) {
            mMethod = method;
            mHandlerType = handlerType;
        }

        @Override
        public void onClick(View v) {
            // 4.反射執行方法
            mMethod.setAccessible(true);
            try {
                mMethod.invoke(mHandlerType, v);
            } catch (Exception e) {
                e.printStackTrace();
                try {
                    mMethod.invoke(mHandlerType, null);
                } catch (Exception e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    //控件注入
    private static void injectViews(ViewFinder viewFinder, Object object) {
        //獲取每個屬性上的註解
        Class<?> myClass = object.getClass();
        Field[] myFields = myClass.getDeclaredFields();//先拿到全部的成員變量
        for (Field field : myFields) {
            KevenViewInject myView = field.getAnnotation(KevenViewInject.class);
            if (myView != null) {
                int value = myView.value();//拿到屬性id
                View view = viewFinder.findViewById(value);
                //將view 賦值給類裏面的屬性
                try {
                    field.setAccessible(true);//爲了防止其是私有的,設置容許訪問
                    field.set(object, view);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private static void injectLayout(Activity activity) {
        //獲取咱們自定義類KevenContentViewInject 上面的註解
        Class<?> myClass = activity.getClass();
        KevenContentViewInject myContentView = myClass.getAnnotation(KevenContentViewInject.class);
        if (myContentView!=null){
            int myLayoutResId = myContentView.value();
            activity.setContentView(myLayoutResId);
        }

    }

}
複製代碼

定義 ViewFinder 類

用於注入工具類中的 findViewById工具

/**
 * Created by Keven on 2019/6/3.
 */
final class ViewFinder {

    private View view;
    private Activity activity;

    public ViewFinder(View view) {
        this.view = view;
    }

    public ViewFinder(Activity activity) {
        this.activity = activity;
    }

    public View findViewById(int id) {
        if (view != null) return view.findViewById(id);
        if (activity != null) return activity.findViewById(id);
        return null;
    }


    public View findViewById(int id, int pid) {
        View pView = null;
        if (pid > 0) {
            pView = this.findViewById(pid);
        }

        View view = null;
        if (pView != null) {
            view = pView.findViewById(id);
        } else {
            view = this.findViewById(id);
        }
        return view;
    }

    /*public Context getContext() {
        if (view != null) return view.getContext();
        if (activity != null) return activity;
        return null;
    }*/
}
複製代碼

使用 IOC 框架

佈局文件

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ioc.IocActivity">
    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/dimen_35dp"
        android:text="你好,IOC"
        android:textSize="25sp"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"/>
    <Button
        android:id="@+id/bt_pop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/dimen_35dp"
        android:text="彈窗"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/tv_title"/>

</android.support.constraint.ConstraintLayout>
複製代碼

Activity 代碼

//佈局文件注入
@KevenContentViewInject(R.layout.activity_ioc)
public class IocActivity extends AppCompatActivity {

    //屬性控件注入
    @KevenViewInject(R.id.tv_title)
    private TextView tv_title;
    @KevenViewInject(R.id.bt_pop)
    private Button bt_pop;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //注入工具綁定
        InjectUtils.inject(this);
    }
    
    //點擊事件注入
    @KevenOnClickInject(R.id.bt_pop)
    public void change(){
        tv_title.setText("hello IOC");
        Toast.makeText(this,"Hello IOC",Toast.LENGTH_SHORT).show();
    }
}
複製代碼

當咱們點擊彈窗按鈕時,上方 TextView 內容會改變,而且有 Toast 彈出。佈局

相關文章
相關標籤/搜索