Dagger2入門系列一:基礎使用

0、相關文章:

文章1:Android 神兵利器Dagger2使用詳解(一)基礎使用(系列文章 四篇,第一篇剛開始如果@Inject不能用,就清除緩存重啓一下AS,在第三四篇裏面還有dagger-android的文章鏈接)

Android:dagger2讓你愛不釋手-基礎依賴注入框架篇

深入淺出,一篇文章讓你學會Dagger2使用

Dagger2的輕鬆愉悅解析

Dagger2 入門,以初學者角度

備註:此文主要參考文章1

1、概念

1.1、什麼是依賴注入

依賴注入是一種面向對象的編程模式,它的出現是爲了降低耦合性,所謂耦合就是類之間依賴關係,所謂降低耦合就是降低類和類之間依賴關係。通過註解的方式注入,可以很好地降低耦合性,Dagger2就是通過註解的方式完成依賴注入的。

1.2、添加依賴

如下:

    implementation 'com.google.dagger:dagger:2.17'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.17'

完整文件:項目的build.gradle


buildscript {
    
    repositories {
        google()
        jcenter()
        
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.6.3'


        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
        
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

app的build.gradle

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.gs.dagtest1"
        minSdkVersion 24
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'


    implementation 'com.google.dagger:dagger:2.17'
    annotationProcessor 'com.google.dagger:dagger-compiler:2.17'

//    implementation 'com.google.dagger:dagger-android:2.17'
//    implementation 'com.google.dagger:dagger-android-support:2.17'
//    annotationProcessor 'com.google.dagger:dagger-android-processor:2.17'

    //butterknife
    implementation 'com.jakewharton:butterknife:10.2.1'
    annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1'

}

2、代碼實戰

2.1、創建一個Student類,並添加Inject註解

import javax.inject.Inject;


public class Student {
    @Inject
    public Student() {
    }
}

備註1:添加@Inject後,使用Ctrl+F9(或者rebuild)進行一次編譯。

備註2:javax.inject包的目錄如下所示:

2.2、編譯後項目的變化

在目錄Dagger2Test1\app\build\generated\ap_generated_sources\debug\out\com\gs\dagtest1\bean可以看到一個Student_Factory的類。

具體內容:

// Generated by Dagger (https://google.github.io/dagger).
package com.gs.dagtest1.bean;

import dagger.internal.Factory;

public final class Student_Factory implements Factory<Student> {
  private static final Student_Factory INSTANCE = new Student_Factory();

  @Override
  public Student get() {
    return provideInstance();
  }

  public static Student provideInstance() {
    return new Student();
  }

  public static Student_Factory create() {
    return INSTANCE;
  }

  public static Student newStudent() {
    return new Student();
  }
}

可以看到這是一個工廠類,而我們可以通過三種方式獲取到Student對象,如下:

        Student student1 = Student_Factory.create().get();
        LogUtils.e(student1.toString());

        Student student2 = Student_Factory.newStudent();
        LogUtils.e(student2.toString());

        Student student3 = Student_Factory.provideInstance();
        LogUtils.e(student3.toString());

打印結果如下:

綜上,我們通過@Inject註解了一個Student的構造方法後,可以讓編譯器幫助我們生成一個對應的工廠類Student_Factory,通過該工廠類,我們可以通過三種方法獲取到Student對象。

當然,通過這三種方式獲取到Student對象我們自己就可以實現,這裏只是讓大家更方便地理解編譯類。

2.3、獲取Student對象

我們創建一個Activity來調用Student

public class Test1Activity extends AppCompatActivity {

    @Inject
    Student student;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test1);
        ButterKnife.bind(this);
    }

    @OnClick(R.id.btn1)
    public void onViewClicked() {
        Toast.makeText(this, student.toString(), Toast.LENGTH_SHORT).show();
    }
}

接下來我們創建一個Activity類,在這個類中創建一個成員變量Student,按照Dagger2給我們的指示,當我們需要一個Student,我們只需要在這個成員變量上方加一個@Inject註解,編譯器會自動幫我們產生對應的代碼,我們就可以直接使用這個Student對象了!

本案例中我們設置一個Button,點擊Button後我們打印出這個Student對象。

事實真的如此嗎?我們直接運行代碼,並點擊Button,很遺憾,直接報空指針異常:

顯然,和平常使用的結果一樣,@Inject並沒有幫助我們初始化對應的Student對象,或者說,我們的Activity並沒有使用剛纔我們看到的Student_Factory類,不過也可以理解,我們並沒有建立Activity和Student_Factory類之間的關係嘛。

2.4、創建Module類和Component接口

import dagger.Module;

@Module
public class Test1Module {
    private Test1Activity activity;

    Test1Module(Test1Activity activity) {
        this.activity = activity;
    }
}
import dagger.Component;

@Component(modules = Test1Module.class)
public interface Test1Component {
    void inject(Test1Activity activity);
}

請注意,Module類上方的@Module註解意味着這是一個提供數據的【模塊】,而Component接口上方的@Component(modules = A01SimpleModule.class)說明這是一個【組件】(我更喜歡稱呼它爲注射器)。

突然出現的這兩個類可以稱得上是莫名其妙,因爲我們從代碼上來看並不知道這對於Student和Activity之間關係有什麼實質性的進展,但假如我們這時在Activty中添加這一段代碼:

public class Test1Activity extends AppCompatActivity {

    @Inject
    Student student;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test1);
        ButterKnife.bind(this);
        initDatas();
    }

    private void initDatas() {
        DaggerTest1Component.builder()
                .test1Module(new Test1Module(this))
                .build()
                .inject(this);
    }

    @OnClick(R.id.btn1)
    public void onViewClicked() {
        Toast.makeText(this, student.toString(), Toast.LENGTH_SHORT).show();
    }
}

然後運行代碼點擊Button,神奇的事情發生了:

顯然,添加了兩個看起來莫名其妙的Module和Component類,然後在Activity中添加一段代碼,被@Inject的Student類被成功依賴注入到了Activity中,我們接下來就可以肆無忌憚使用這個Student對象了!

3、我們爲什麼使用依賴注入

這時候不可避免的,有些同學會有些疑問,我們爲什麼要花費這麼大的力氣(時間成本)去學習這樣一個看起來很雞肋的Dagger呢?我們需要一個Student對象,完全可以直接通過new的方式創建一個嘛!

當然是有必要的,因爲通常簡單的代碼具有耦合性,而要想降低這樣的耦合就需要其他的輔助代碼,其實少代碼量和低耦合這兩者並不能同時兼顧。

試想,我們如果通過這樣的方式,在其他的文件中創建了這樣若干個(心大一些,我們是一個大項目的唯一負責人),不,1000個文件中使用到了Student對象,我們至少要new 1000個新的Student類對象,這時,新的需求到了,我們的Student需要添加一個String類型的參數name。

what the fuck? 這意味我需要分別跑這1000個文件中逐個修改new Student()的那行代碼嗎?

如果是Dagger2,當然不需要,我們只需要在Student類中做出簡單的修改即可,這在後文中將會提到(因爲涉及參數問題),我們只需要知道只需輕鬆幾步即可完成Student的構造修改問題,達到低耦合的效果即可。

4、Module和Component作用詳解

現在我們具體的思考以下一個場景:

我們假設案例中的Activity代表家庭住址,Student代表某個商品,現在我們需要在家(Activity)中使用商品(Student),我們網購下單,商家(代表着案例中自動生成的Student_Factory工廠類)將商品出廠,這時我們能夠在家直接獲得並使用商品嗎?

當然不可能,雖然商品(Student)已經從工廠(Factory)生產出來,但是並沒有和家(Activity)建立連接,我們還需要一個新的對象將商品送貨上門,這種英雄級的人物叫做——快遞員(Component,注入器)。

沒錯,我們需要這樣的一種注入器,將已經生產的Student對象傳遞到需要使用該Student的容器Activity中,於是我們需要在Activity中增加這樣幾行代碼:

DaggerTest1Component.builder()
                .test1Module(new Test1Module(this))
                .build()
                .inject(this);

這就說明快遞員Component已經將對象Inject(注入)到了this(Activity)中了,既然快遞到家,我們當然可以直接使用Student啦!

這時有朋友可能會問,原來Component起的作用是這樣,那麼Module是幹嘛的呢?

事實上,在這個案例中,我們將這行代碼進行註釋後運行,發現我們依然可以使用Student對象:

DaggerTest1Component.builder()
//                .test1Module(new Test1Module(this))
                .build()
                .inject(this);

我們可以暫時這樣理解Module,它的作用就好像是快遞的箱子,裏面裝載的是我們想要的商品,我們在Module中放入什麼商品,快遞員(Component)將箱子送到我們家(Activity容器),我們就可以直接使用裏面的商品啦!

爲了展示Module的作用,我們重寫Student類,取消了@Inject註解

public class Student {
    
    public Student() {
    }

}

Module中添加這樣一段代碼:

@Module
public class Test1Module {
    private Test1Activity activity;

    Test1Module(Test1Activity activity) {
        this.activity = activity;
    }

    @Provides
    Student provideStudent() {
        return new Student();
    }
}

 然後Component不變,Activity中仍然是這樣:

DaggerTest1Component.builder()
                .test1Module(new Test1Module(this))
                .build()
                .inject(this);

然後運行,我們發現,仍然可以點擊使用Student對象!

原因很簡單,雖然@Inject註解取消了,但是我們已經在快遞箱子(Module)中通過@Providers放入了一個Student對象,然後讓快遞員(Component)送到了家中(Activity),我們當然可以使用Student對象了!

5、小結

經過簡單的使用Dagger2,我們已經可以基本有了以下了解:

@Inject : 注入,被註解的構造方法會自動編譯生成一個Factory工廠類提供該類對象。

@Component: 注入器,類似快遞員,作用是將產生的對象注入到需要對象的容器中,供容器使用。

@Module: 模塊,類似快遞箱子,在Component接口中通過@Component(modules =
xxxx.class),將容器需要的商品封裝起來,統一交給快遞員(Component),讓快遞員統一送到目標容器中。

現在看回來,我們僅僅通過幾個註解就能使用對象,還是很方便的,我會在接下來的文章中對@Component和@Module註解之後,編譯期生成的代碼進行解析,看看到底這兩個註解到底起着什麼樣的作用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章