前言
在平時(shí)寫(xiě)代碼的過(guò)程中都會(huì)出現(xiàn)很多方法中出現(xiàn)@Override
@hide
這樣的注解,還有在比如我們經(jīng)常用到的EventBus、ButterKnife、Retrofit、Dagger等都會(huì)用到。它們有什么作用?以及怎么使用的?改文章也會(huì)對(duì)上一篇反射的技術(shù)進(jìn)行進(jìn)一步的加深使用和理解。如果沒(méi)有使用反射技術(shù)童鞋請(qǐng)先閱讀上一篇 [Android進(jìn)階篇-反射機(jī)制ReFlect]
IOC
-
初始 DIP、Ioc、DI、Ioc容器
依賴倒置原則 (DIP, Dependency Inverse Principle)
強(qiáng)調(diào)系統(tǒng)的“高層組件”不應(yīng)當(dāng)依賴于“底層組件”, 并且不論是“高層組件”還是“底層組件”都應(yīng)當(dāng)依賴于抽象。抽象不應(yīng)當(dāng)依賴于實(shí)現(xiàn),實(shí)現(xiàn)應(yīng)該依賴于抽象(軟件設(shè)計(jì)原則)
控制反轉(zhuǎn)(Ioc,Inverse of Control)
一種反轉(zhuǎn)、依賴和接口的方式。就是將控制權(quán)“往高處/上層”轉(zhuǎn)移,控制反轉(zhuǎn)是實(shí)現(xiàn)依賴倒置的一種方法(DIP的具體實(shí)現(xiàn)方式)
依賴注入 (DI,Dependency Injection)
組件通過(guò)構(gòu)造函數(shù)或者setter方法,將其依賴暴露給上層,上層要設(shè)法取得組件的依賴,并將其傳遞給組件。依賴注入是實(shí)現(xiàn)控制反轉(zhuǎn)的一種手段(Ioc的具體實(shí)現(xiàn)方式)
Ioc容器
依賴注入的框架,用來(lái)映射依賴,管理對(duì)象創(chuàng)建和生存周期(DI框架)
-
作用以及優(yōu)缺點(diǎn)
Ioc的核心是解耦,簡(jiǎn)化我們的工作量。
而解耦的目的:修改耦合對(duì)象時(shí)不影響另外一個(gè)對(duì)象,降低模塊之間的關(guān)聯(lián)。
在Spring中IOC更多是依靠xml的配置,而在Android中的IOC框架可以不使用xml配置。
優(yōu)點(diǎn):代碼量減少,代碼閱讀性好
缺點(diǎn):會(huì)產(chǎn)生一定的性能消耗 (現(xiàn)今的手機(jī)上是可以忽略的)
-
元注解
定義定義注解時(shí),會(huì)需要一些元注解(meta-annotation)
,如@Target
和@Retention
。@Target
用來(lái)定義你的注解將應(yīng)用于什么地方(可能是一個(gè)類或者是一個(gè)方法),@Retention
用來(lái)定義注解在哪一個(gè)級(jí)別可用(是在源碼中SOURCE,還是在類文件中CLASS,又或者是在運(yùn)行時(shí)RUNTIME)
定義注解時(shí)很像是在定義一個(gè)接口,所以一般在注解中都會(huì)包含一些元素以表示某些值,當(dāng)處理注解時(shí),就可以根據(jù)這個(gè)值來(lái)做一些判斷。
@Target
Type:類/接口
FIELD:屬性
METHOD:方法
PARAMETER:參數(shù)
CONSTRUCTOR:構(gòu)造方法
LOCAL_VARIABLE:局部變量
ANNOTATION_TYPE:該注解使用在另一個(gè)注解上
PACKAGE:包
@Retention
注解會(huì)在class字節(jié)碼文件中存在, JWM加載時(shí)可以通過(guò)反射獲取到該注解的內(nèi)容
SOURCE:源碼級(jí)操作(檢查、檢測(cè))
CLASS:在編譯時(shí) 進(jìn)行一些預(yù)操作
RUNTIME:運(yùn)行時(shí)編譯
生命周期:SOURCE < CLASS < RUNTIME
- 一般如果需要在運(yùn)行時(shí)去動(dòng)態(tài)獲取注解信息,用RUNTIME注解
- 要在編譯時(shí)進(jìn)行一些預(yù)處理操作,如ButterKnife,用CLASS注解。注解會(huì)在class文件中存在,但是在運(yùn)行時(shí)會(huì)被丟棄
- 做一些檢查性的操作,如@Override,用SOURCE源碼注解。注解僅存在源碼級(jí)別,在編譯的時(shí)候丟棄該注解
-
注解元素
在定義注解時(shí),我們同樣可以為注解定義元素,例如:
@Target(ElementType.TYPE) //作用在類之上
@Retention(RetentionPolicy.RUNTIME) //運(yùn)行時(shí)編譯
public @interface User {
int age(); //返回int類型數(shù)據(jù)
String name() default "daxu"; //返回String類型數(shù)據(jù)
}
標(biāo)簽@User是由User.class定義,其中包含了int元素的age,以及一個(gè)String元素的name,如果name不傳值則默認(rèn)取值為“daxu”。在注解元素中,可用的類型如下:
所有的基本類型(int, float, boolean 等)
String
Class
enum
Annotaion
以上類型的數(shù)組
如果使用了其他類型則會(huì)編譯報(bào)錯(cuò)。注意,也不允許使用任何包裝類型。
-
使用
在Andorid開(kāi)發(fā)中我們經(jīng)常在Activity的onCraete中寫(xiě)setContentView(R.layout.xxx),最煩的是每個(gè)view的申明都需要些TextView tv = findviewById(R.id.tv), 所以有大神推出了ButterKnife使用注解來(lái)解決。今天我們就來(lái)自己手動(dòng)寫(xiě)一下。
打開(kāi)AS創(chuàng)建一個(gè)工程,并且創(chuàng)建一個(gè)Java的Library,在該lib中創(chuàng)建一個(gè)Kind為Annotation,Name為ContentView的注解類
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentView {
int value();
}
使用該注解時(shí)在Activity之上申明該注解,并傳入該Activity的layout
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
}
到此,注解已經(jīng)定義完成。那么怎么去完成把layout和Activity進(jìn)行綁定呢?下面我們?cè)趌ib中創(chuàng)建一個(gè)InjectManager的類。
public class InjectManager {
public static void inject(Activity activity) {
// 布局的注入
injectLayout(activity);
}
private static void injectLayout(Activity activity) {
//根據(jù)傳遞的Activity對(duì)象獲取類
Class<? extends Activity> clazz = activity.getClass();
//獲取類之上的注解
ContentView contentView = clazz.getAnnotation(ContentView.class);
if (contentView != null) {
//獲取注解的值,也就是傳遞的R.layout.activity_main
int layoutId = contentView.value();
//第一種方式 (這種方式比較low)
//activity. setContentView(layoutId);
//第二種方式:反射方法
try {
Method method = clazz.getMethod("setContentView", int.class);
//執(zhí)行方法
method.invoke(activity, layoutId);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
并且在BaseActivity的onCreate()方法中進(jìn)行注冊(cè)
public class BaseActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//注冊(cè)
InjectManager.inject(this);
}
}
開(kāi)始run一下項(xiàng)目,發(fā)現(xiàn)該MainActivity正常打開(kāi)。
按照此種方式我們繼續(xù)完成View的申明。比較findviewbyid才是最讓人寫(xiě)吐的...
在lib中創(chuàng)建一個(gè)Kind為Annotation,Name為BindView的注解類
@Target(ElementType.FIELD) //作用在屬性之上
@Retention(RetentionPolicy.RUNTIME)
public @interface BindView {
int value();
}
@BindView和@ContentView基本一致,其差別就是這次作用在屬性之上,而ContentView則作用在類之上。
定義好注解后,我們繼續(xù)在InjectManager中開(kāi)始編寫(xiě)代碼, 在InjectManager中定義一個(gè)方法injectView(Activity activity); 在inject()進(jìn)行調(diào)用
private static void injectView(Activity activity) {
//獲取類
Class<? extends Activity> clazz = activity.getClass();
//拿到每個(gè)屬性 (因?yàn)椴淮_定是什么修飾 所以使用getDeclaredFields)
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
//獲取每個(gè)屬性上的注解
BindView bindView = field.getAnnotation(BindView.class);
if (bindView != null) {
//拿到控件ID
int viewId = bindView.value();
try {
Method method = clazz.getMethod("findViewById", int.class);
Object view = method.invoke(activity, viewId);
//設(shè)置private的訪問(wèn)權(quán)限
field.setAccessible(true);
//將方法執(zhí)行的返回值賦值給全局的某屬性
field.set(activity, view);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
完成后在MainActivity中測(cè)試.. 為了測(cè)試view被正常申明,我們?cè)趏nResume()方法中彈出吐司,吐司內(nèi)容為該Button按鈕的內(nèi)容來(lái)測(cè)試一下。
@ContentView(R.layout.activity_main)
public class MainActivity extends BaseActivity {
@BindView(R.id.btn)
public Button btn;
@Override
protected void onResume() {
super.onResume();
Toast.makeText(this, btn.getText().toString(), Toast.LENGTH_LONG).show();
}
}
run項(xiàng)目發(fā)現(xiàn)項(xiàng)目運(yùn)行正常, 并且成功彈出Button內(nèi)容的吐司~
目前呢, MainActivity中的setContentView和findViewById已經(jīng)使用了注解的方式進(jìn)行了簡(jiǎn)化,還能有其他的地方可以使用注解來(lái)進(jìn)行快速開(kāi)發(fā)嗎?答案是當(dāng)然有!在Android開(kāi)發(fā)中還有很多地方都可以,比如:OnClickListener,onLongClickListener等點(diǎn)擊事件,像這種點(diǎn)擊事件它又和setContentView和findViewById完全不一樣,因?yàn)檫@種事件都帶有各自回調(diào)方法.. 而我們所有的操作也都寫(xiě)在了onClick的回調(diào)方法里面
這種情況下要怎么處理呢?
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
btn.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View v) {
return false;
}
});
看到上面的兩個(gè)點(diǎn)擊事件,我們發(fā)現(xiàn)了三個(gè)共同點(diǎn)(事件三部曲)
① 都有監(jiān)聽(tīng)的方法名
setxxxListener
② 都有監(jiān)聽(tīng)的對(duì)象
new View.OnxxxListener
③ 都有回調(diào)方法名
onxxx(View v)
找到了規(guī)律,我們是否可以想一個(gè)辦法,把這三個(gè)規(guī)律整合在一起并且“委托一個(gè)人”幫忙把回調(diào)里的事情給辦了呢? 委托這兩個(gè)字在我們開(kāi)發(fā)人員眼中很容易就會(huì)想到一個(gè)詞“代理”。代理模式不就是這樣的嗎? 簡(jiǎn)單點(diǎn)來(lái)講,就是能不能把事件三部曲
打包成一個(gè)對(duì)象
,用代理去完成這件事..
現(xiàn)在點(diǎn)擊事件的規(guī)律找到了,怎么去委托也知道了,但是怎么讓代理完成的事情和我們定義的方法綁定到一起呢? 這時(shí)就得需要用到另一個(gè)知識(shí)點(diǎn) AOP面向切面編程。
AOP面向切面的知識(shí)點(diǎn)下次再來(lái)講吧..