Android進(jìn)階篇- IOC注入架構(gòu)

前言

在平時(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

  1. 一般如果需要在運(yùn)行時(shí)去動(dòng)態(tài)獲取注解信息,用RUNTIME注解
  2. 要在編譯時(shí)進(jìn)行一些預(yù)處理操作,如ButterKnife,用CLASS注解。注解會(huì)在class文件中存在,但是在運(yùn)行時(shí)會(huì)被丟棄
  3. 做一些檢查性的操作,如@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)容的吐司~


image.png

目前呢, 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)講吧..

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 2.1 我們的理念是:讓別人為你服務(wù) IoC是隨著近年來(lái)輕量級(jí)容器(Lightweight Container)的...
    好好學(xué)習(xí)Sun閱讀 2,738評(píng)論 0 11
  • 本來(lái)是準(zhǔn)備看一看Spring源碼的。然后在知乎上看到來(lái)一個(gè)帖子,說(shuō)有一群**自己連Spring官方文檔都沒(méi)有完全讀...
    此魚(yú)不得水閱讀 6,952評(píng)論 4 21
  • 原文地址,此處只為學(xué)習(xí)先來(lái)講一講,一個(gè)簡(jiǎn)單的依賴注入例子。 依賴 如果在 Class A 中,有 Class B ...
    lltree閱讀 1,854評(píng)論 2 8
  • 怎么讓人發(fā)笑? 讓他意外/讓他優(yōu)越/讓他宣泄,笑的原因千差萬(wàn)別,但都基于這三種原理。 呀,好像幽默有章可循,這真是...
    大嘴8閱讀 296評(píng)論 0 1
  • 在計(jì)算機(jī)中是采用二進(jìn)制,這樣造成在操作系統(tǒng)中對(duì)容量的計(jì)算是以每1024為一進(jìn)制的,每1024字節(jié)為1KB,每102...
    HelloWorld_26閱讀 225評(píng)論 0 0