不詩意的女程序媛不是好廚師~
轉載請註明出處,From李詩雨—https://blog.csdn.net/cjm2484836553/article/details/104539874
在前面的文章中我們已經學習了 依賴注入與控制反轉的概念、註解、和反射 ,有了這些知識做鋪墊,我們就可以 更加深入的來學習一下 IOC注入技術了。
今天我們主要 來學習運行時注入,並親自擼代碼來一步一步的實現 佈局注入 和控件注入 。
文章的邏輯思路講的很細,也很好懂,沒有什麼難點,並且文章篇幅也不長,不妨一讀哦~
1.概念再理解
溫故而知新,上篇文章中跟大家提到了 控制反轉(IOC) 和 依賴注入的概念,可能大家還是有點 花非花霧非霧的 感覺,今天經過親自的擼代碼之後,我有了新的體會。在此與大家分享~
【控制反轉(IOC)】:是原來由程序代碼中主動獲取的資源,轉變由第三方獲取並使原來的代碼被動接收的方式,以達到解耦的效果。
按照上篇文章的內容,我們把它看成是一種控制權的反轉。
但其實,我們還可以把它看成是一種義務的轉交,即 把我們自己應該做的事轉交給別人來做,從而讓自己變得更輕鬆。
再舉個形象的栗子來說吧:
在一個月黑風高的寒冷的夜晚,你有事要出門,由於天氣太冷你要披肩大棉襖才能出去,於是你就自己乖乖的拿了棉襖再乖乖的穿好出門,消失在寒冷的黑夜中。
IOC就是你有了一個女朋友,你只告訴她你要出門,於是貼心的女朋友便給你拿來棉衣,幫你穿上,才放心讓你出門。於是你在愛的目光中出了門~
恩,女朋友就好比IOC,把你本來要拿衣服穿衣服的事情 轉交給了女朋友來做。
畫個圖來幫助大家理解:
好的,現在我們就開始擼代碼來學習 IOC注入技術吧~
2.佈局注入
我們都知道在Activity中我們 通過自己的 setContentView(R.layout.activity_main)
來加入、顯示佈局的。
那如果我現在採用ioc,不是自己來注入佈局,而是讓我的女朋友來注入佈局,該怎麼做呢?
- ①首先,我得造一個女朋友出來!她裏面有佈局注入的方法。
- ②其次,我們考慮到可能所有的Activity都要用到,所以,我們在BaseActivity的onCreat中完成注入。
- ③MainActivity繼承BaseActivity。並且把
setContentView(R.layout.activity_main)
這句代碼去掉!
//①造了一個女朋友
public class InjectUtils {
public static void inject(Object context) {
//佈局的注入
injectLayout(context);
}
private static void injectLayout(Object context) {
}
}
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
InjectUtils.inject(this);//②在這裏注入
}
}
//③繼承BaseActivity ,並去掉setContentView(R.layout.activity_main)這句代碼
public class MainActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_main);//把這行代碼去掉!讓女朋友來完成
}
}
好的,到這裏大家應該都沒有什麼問題吧。
現在大家想想,我們既然去掉了setContentView(R.layout.activity_main);
這句代碼,那此時我們的MainActivity是不知道需要哪個佈局的。
這該怎麼辦?怎麼才能知道MainActivity需要哪個佈局文件呢?
那我們就要標識出來我們所需要的佈局文件呀,那怎麼標識呢?
對!用註解。就像這樣:
那接下來我們就要來自定義這個註解啦~
- ④自定義註解MyContentView。
//④自定義註解MyContentView
@Target(ElementType.TYPE) //表明:註解將來是使用在類上面的
@Retention(RetentionPolicy.RUNTIME) //表明註解的存活週期,我們希望可以在運行時讀取到它的信息
public @interface MyContentView {
int value();
}
好了,到目前爲止,我們的主要邏輯就完成了。但是,此時運行還是不能加載出佈局的,因爲這還是個假貨,我們InjectUtils中的injectLayout()還是空的,裏面什麼都沒有做。
所以,接下來我們的重點就是實現injectLayout()方法了。
⑤實現injectLayout()方法:
我們先來分析一下,在該方法中我們要做什麼:
首先我們要明確的是,此處我們肯定要 運用反射 去獲取所需信息和執行對應方法了。
- 第一步 獲取activity對應的Class
- 第二步 拿到該Class上的MyContentView註解
- 第三步 取到註解括號後面的內容,即佈局id
再接下來 就要 反射在class上去執行setContentView了:
- 第四步 利用反射獲取setContentView()對應的method
- 第五步 反射執行setContentView()方法。
//⑤實現injectLayout()方法
private static void injectLayout(Object context) {
// a.獲取到Activity對應的Class
Class<?> clazz = context.getClass();
// b.拿到該Class上的MyContentView註解
MyContentView myContentView = clazz.getAnnotation(MyContentView.class);
if (myContentView != null) { //如果有MyContentView註解就執行以下操作
// c.取到註解括號後面的內容,即佈局id
int layoutId = myContentView.value();
//====== 接下來就要 反射去執行setContentView
try {
// d.利用反射獲取setContentView()對應的method
Method method = clazz.getMethod("setContentView", int.class);
// e.反射執行setContentView()方法。即相當於context.method(layoutId);
method.invoke(context, layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
好的,那現在我來運行程序,如果可以正常顯示出來佈局是不是就可以證明,我注入佈局成功啦!
先給大家看一下我的佈局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:orientation="vertical"
android:padding="10dp"
tools:context=".MainActivity">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:text="ioc注入技術,哈哈哈~" />
<Button
android:id="@+id/button1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按鈕1" />
<Button
android:id="@+id/button2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="按鈕2" />
</LinearLayout>
好的,下面就是見證奇蹟的時刻啦:
成功啦!我成功啦,啊哈哈哈哈~
完成了佈局注入,那我們下面繼續控件注入吧~
3.控件注入
上面我們的佈局已經注入成功,並且可以正常顯示了。
我們可以看到佈局中有2個按鈕,那如果我想把這兩個按鈕注入該怎麼辦呢?
即:我現在不想自己通過findViewById來注入按鈕,而是想讓我的【ioc女朋友】來幫我實現按鈕的注入~
我們先來看一下我的預期想達到的效果:
那要達到這種效果我們該怎麼實現呢?
有了佈局注入的經驗,相信對於 控件注入 大家還是會有大體的思路的:
我們還用之前的女朋友InjectUtils,還是在BaseActivity中進行注入。
那我們就要在InjectUtils裏添加一個控件注入的方法injectView():
現在我們既然不想自己使用findViewById來獲取控件,而是想用這種形式來注入控件:
那我們肯定還是要通過使用註解,並且在註解後面傳入對應控件的id。
所以第①步,我們要自定義一個BindView註解:
//① 自定義一個BindView註解
@Target(ElementType.FIELD) //說明該註解是用在屬性上的
@Retention(RetentionPolicy.RUNTIME)//該註解可以保留到程序運行的時候
public @interface BindView {
int value();
}
第②步,具體實現injectView()方法。
實現injectView()方法是重點,讓我們來仔細分析一下思路:
- 首先,我們肯定還是要通過反射,所以要先拿到Activity對應的Class.
- 拿到了clazz後,我們還要拿到clazz上的所有屬性字段(Fields)。▲▲▲
- 然後我們就要循環遍歷屬性,看屬性上是否有BindView註解。
- 如果屬性上確實拿到了BindView註解,那我們就要繼續拿到註解後面的viewId了。
- 再接着就是反射執行findViewById方法,得到對應的view.
- 最後要注意,對於私有屬性,無論是對它進行讀寫,都要調用field.setAccessible(true)。▲
private static void injectView(Object context) {
//獲取clazz
Class<?> clazz = context.getClass();
//獲取clazz上的所有屬性
Field[] fields = clazz.getDeclaredFields();
//循環遍歷每一個屬性
for (Field field : fields) {
//獲取屬性上的BindView註解
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {//如果該屬性上找到了BindView註解
//拿到註解後面的viewId
int viewId = bindView.value();
//運行到這裏,每個按鈕的ID已經取到了
//下面就是反射執行findViewById方法
try {
Method method = clazz.getMethod("findViewById", int.class);
View view = (View) method.invoke(context, viewId);
//對 field 做相關操作
//注意:如果獲取的字段是私有的,不管是讀還是寫,都要先 field.setAccessible(true);纔可以。否則會報:IllegalAccessException。
field.setAccessible(true);
field.set(context, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
好的,現在我們控件注入的相關操作就完成了,那讓我們來改個button的名稱測試一下吧:
@MyContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@BindView(R.id.button1)
Button btn1;
@BindView(R.id.button2)
Button btn2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//檢測 控件注入 是否成功
btn1.setText("我是注入的按鈕01");
btn2.setText("我是注入的按鈕02");
}
}
下面還是見證奇蹟的時刻:
怎麼樣是不是不擼不知道,一擼代碼才知道原來這就是IOC技術啊,也蠻容易的嘛~
是的,佈局注入和控件注入我們都輕鬆搞定啦。
還有一個事件注入我們沒有實現,這個事件注入就會有點小難度了喲。
害怕文檔太長,大家懶得看(PS:其實是因爲我懶),
那我們就在下篇繼續來擼代碼一步一步實現 事件注入 吧~~~
積累點滴,做好自己~