0、相關文章:
文章1:Android 神兵利器Dagger2使用詳解(一)基礎使用(系列文章 四篇,第一篇剛開始如果@Inject不能用,就清除緩存重啓一下AS,在第三四篇裏面還有dagger-android的文章鏈接)
Android: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註解之後,編譯期生成的代碼進行解析,看看到底這兩個註解到底起着什麼樣的作用。