IOC是什麼?
是面向對象編程中的一種設計原則,可以用來減低計算機代碼之間的耦合度,其中最常見的便是依賴注入。簡單點說就是如果你在一個類A中實例化一個對象B,那麼久需要在A類中使用new來進行實例化對象B,這樣A和B的耦合度就很高,而IOC是通過註解讓其進行動態生成,而不需要自己去實例化,以此來降低耦合度。(依賴注入核心)
需要用到的知識:
反射
註解
如果不熟悉上面兩個知識,可百度找一篇如果操作即可。
實現的最終結果:
注入佈局:即讓我們在代碼中不需要寫setContentView()方法,而是通過在類之上添加註解(MContentView)來動態生成:
注入控件:即在代碼中不需要寫findViewById方法,而是通過在屬性之上添加註解(MBIndView)來動態生成:
實現過程:
實現注入佈局過程:
思考1:要想實現在類上的註解,首先我們需要創建一個註解MContentView,而這個註解是作用在類之上,所以其作用域是類之上(ElementType.TYPE)並且我們的註解是在運行時使用的,所以聲明其是運行時使用(RetentionPolicy.RUNTIME)。因此創建一個註解如下:
@Target(ElementType.TYPE)//作用在類上
@Retention(RetentionPolicy.RUNTIME)//運行時
public @interface MContentView {
}
思考2:由最終效果可知,因爲我們需要代替setContentView(),而這個方法需要傳入layoutId(R.layout.activity_main),所以註解中還要添加一個int類型的方法,如下:
@Target(ElementType.TYPE)//作用在類上
@Retention(RetentionPolicy.RUNTIME)//運行時
public @interface MContentView {
int value() ;
}
至此註解MContentView已完成,在MainActivity中即可引用該註解並附上layoutId,如下圖:
當寫好註解後,我們便需要通過這個註解來處理我們需要的邏輯,因此寫一個工具類(InjectTool)來實現,並在onCreate方法中將MainActivity引入,如圖:
inject方法如圖:
由上圖可知injectContentView()方法便是實現注入佈局,因此我們在該方法中實現注入佈局,而實現該方法的思路如下:
1、通過MainActivity的class信息獲取我們自定義的MContentView的註解
2、如果找到註解則獲取註解上的layoutId值
3、通過反射,反射除setContentView方法
4、執行setContentView方法
具體代碼如下:
private static void injectContentView(Object object) {
//獲取對象的類
Class<?> activity = object.getClass();
//獲取類上的MContentView註解
MContentView contentView = activity.getAnnotation(MContentView.class);
//如果contentView等於空則代表沒有該註解,便直接返回
if (contentView==null){
Log.d(TAG, "injectContentView: 沒找到該註解");
return;
}
//如果由該註解,則獲得該註解上的layoutId
int value = contentView.value();
try {
//通過反射,反射除類activity裏的setContentView方法並執行該方法
Method method = activity.getMethod("setContentView",//該參數是反射的方法名稱,因爲我們要執行setContentView(),所以反射setContentView
int.class);//反射方法中需要攜帶的參數的類型
method.invoke(object,// 第一個參數是指該方法在哪個類中執行,因爲我們是在object對象中執行的setContentView方法,所以傳入object
value);//這個參數是指執行setContentView方法所需要的參數
} catch (Exception e) {
e.printStackTrace();
}
}
實現注入控件:
1:和注入佈局一樣首先創建一個註解mBindView:
@Target(ElementType.FIELD)//作用在屬性之上
@Retention(RetentionPolicy.RUNTIME)
public @interface MBindView {
int value() ;
}
2:在MainActivity中引用該註解:
3:在InjectTool中實現injectBindView(注入控件)方法,實現思路如下:
1、找到我們定義的MBindView註解:因爲其是作用在屬性之上的,所以我們需要過MainActivity的class信息先找到所有的屬性(getDeclaredFields)
2、獲取到屬性後,便獲取該屬性上的註解並判斷有無我們許喲啊的MBindView註解
3、獲取到MBindView註解後獲取其中的id值
4、通過反射反射出findViewById方法,並將id值傳入執行該方法
5、將執行後返回的對象複製給我們的屬性(即btn = findViewById(xxx)這個操作)
代碼如下:
private static void injectBindView(Object object) {
Class<?> activity = object.getClass();
//遍歷所有字段
Field[] declaredFields = activity.getDeclaredFields();
for (Field declaredField : declaredFields) {
//獲取字段上的註解
MBindView mBindView = declaredField.getAnnotation(MBindView.class);
if (mBindView==null){
Log.d(TAG, "injectBindView: 沒找到該註解");
continue;
}
int value = mBindView.value();
if (value==-1){
Log.d(TAG, "injectBindView: 請設置綁定id");
return;
}
try {
Method findViewById = activity.getMethod("findViewById", int.class);
Object invoke = findViewById.invoke(object, value);//執行findViewById方法(findViewById(xxx))
declaredField.set(object,invoke);//將已經執行的findViewById複製給擁有註解的declareField對象,即(btn = findViewById(xxx)的複製操作)
} catch (Exception e) {
e.printStackTrace();
}
}
}
MainActivity完整代碼如下:
@MContentView(R.layout.activity_main)
public class MainActivity extends AppCompatActivity {
@MBindView(R.id.btn1)
Button btn1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectTool.inject(this);
btn1.setText("HELLO YOYO");
}
}
效果圖如下:
至此注入佈局和注入控件已完成,源碼將在下節IOC實現ButterKnife(二)之注入事件 中給出